diff --git a/Editor/IncrementalBuildPipeline/PlayerBuildProgramLibrary.Data/Data.cs b/Editor/IncrementalBuildPipeline/PlayerBuildProgramLibrary.Data/Data.cs index 4f6311e389..7ddc326039 100644 --- a/Editor/IncrementalBuildPipeline/PlayerBuildProgramLibrary.Data/Data.cs +++ b/Editor/IncrementalBuildPipeline/PlayerBuildProgramLibrary.Data/Data.cs @@ -48,6 +48,7 @@ public class PlayerBuildConfig public string Architecture; public bool UseIl2Cpp; public bool UseCoreCLR; + public bool NoGUID; public bool InstallIntoBuildsFolder; public bool GenerateIdeProject; public bool Development; @@ -61,6 +62,7 @@ public class PlayerBuildConfig public class BuiltFilesOutput { public string[] Files = new string[0]; + public string BootConfigArtifact; } public class LinkerConfig @@ -91,12 +93,18 @@ public class Il2CppConfig public string[] AdditionalCppFiles = new string[0]; public string[] AdditionalArgs = new string[0]; public string CompilerFlags; + public string[] AdditionalLibraries; + public string[] AdditionalDefines; + public string[] AdditionalIncludeDirectories; + public string[] AdditionalLinkDirectories; public string LinkerFlags; + public string LinkerFlagsFile; public string ExtraTypes; public bool CreateSymbolFiles; public bool AllowDebugging; public string SysRootPath; public string ToolChainPath; + public string RelativeDataPath; } public class Services diff --git a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporter.bindings.cs b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporter.bindings.cs index d174d50f38..2dc62dd976 100644 --- a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporter.bindings.cs +++ b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporter.bindings.cs @@ -15,7 +15,8 @@ namespace UnityEditor.U2D { // SpriteAtlas Importer lets you modify [[SpriteAtlas]] - [NativeHeader("Editor/Src/2D/SpriteAtlas/SpriteAtlasImporter.h")] + [HelpURL("https://docs.unity3d.com/2022.3/Documentation/Manual/sprite/atlas/v2/sprite-atlas-v2.html")] + [NativeHeader("Editor/Src/2D/SpriteAtlas/SpriteAtlasImporter.h")] public sealed partial class SpriteAtlasImporter : AssetImporter { extern internal static void MigrateAllSpriteAtlases(); diff --git a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporterInspector.cs b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporterInspector.cs index ee8e7ea0b2..d8ed43bc71 100644 --- a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporterInspector.cs +++ b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporterInspector.cs @@ -58,6 +58,7 @@ class Styles public readonly GUIContent paddingLabel = EditorGUIUtility.TrTextContent("Padding", "The amount of extra padding between packed sprites."); public readonly GUIContent generateMipMapLabel = EditorGUIUtility.TrTextContent("Generate Mip Maps"); + public readonly GUIContent packPreviewLabel = EditorGUIUtility.TrTextContent("Pack Preview", "Save and preview packed Sprite Atlas textures."); public readonly GUIContent sRGBLabel = EditorGUIUtility.TrTextContent("sRGB", "Texture content is stored in gamma space."); public readonly GUIContent readWrite = EditorGUIUtility.TrTextContent("Read/Write", "Enable to be able to access the raw pixel data from code."); public readonly GUIContent variantMultiplierLabel = EditorGUIUtility.TrTextContent("Scale", "Down scale ratio."); @@ -152,6 +153,7 @@ private enum AtlasType { Undefined = -1, Master = 0, Variant = 1 } private float m_MipLevel = 0; private bool m_ShowAlpha; + private bool m_Discard = false; private List m_PlatformSettingsOptions; private int m_SelectedPlatformSettings = 0; @@ -406,23 +408,29 @@ protected override void Apply() if (HasModified()) { if (spriteAtlasAsset) + { SpriteAtlasAsset.Save(spriteAtlasAsset, m_AssetPath); + AssetDatabase.ImportAsset(m_AssetPath); + } + m_ContentHash = GetInspectorHash(); } base.Apply(); } + protected override bool useAssetDrawPreview { get { return false; } } + protected void PackPreviewGUI() { EditorGUILayout.Space(); using (new GUILayout.HorizontalScope()) { - using (new EditorGUI.DisabledScope(!HasModified() || !IsValidAtlas())) + using (new EditorGUI.DisabledScope(!HasModified() || !IsValidAtlas() || Application.isPlaying)) { GUILayout.FlexibleSpace(); - if (GUILayout.Button("Pack Preview")) + if (GUILayout.Button(styles.packPreviewLabel)) { GUI.FocusControl(null); SpriteAtlasUtility.EnableV2Import(true); @@ -443,7 +451,7 @@ private bool IsValidAtlas() public override bool HasModified() { - return base.HasModified() || m_ContentHash != GetInspectorHash(); + return !m_Discard && (base.HasModified() || m_ContentHash != GetInspectorHash()); } private void ValidateMasterAtlas() @@ -696,7 +704,7 @@ private void HandlePlatformSettingUI(string secondaryTextureName) ITexturePlatformSettingsView view = isSecondary ? m_SecondaryTexturePlatformSettingsView : m_TexturePlatformSettingsView; if (shownTextureFormatPage == -1) { - if (m_TexturePlatformSettingsController.HandleDefaultSettings(defaultPlatformSettings, m_TexturePlatformSettingsView, m_TexturePlatformSettingTextureHelper)) + if (m_TexturePlatformSettingsController.HandleDefaultSettings(defaultPlatformSettings, view, m_TexturePlatformSettingTextureHelper)) { for (var i = 0; i < defaultPlatformSettings.Count; ++i) { @@ -734,7 +742,7 @@ private void HandlePlatformSettingUI(string secondaryTextureName) } m_TexturePlatformSettingsView.buildPlatformTitle = buildPlatform.title.text; - if (m_TexturePlatformSettingsController.HandlePlatformSettings(buildPlatform.defaultTarget, platformSettings, m_TexturePlatformSettingsView, m_TexturePlatformSettingTextureHelper)) + if (m_TexturePlatformSettingsController.HandlePlatformSettings(buildPlatform.defaultTarget, platformSettings, view, m_TexturePlatformSettingTextureHelper)) { for (var i = 0; i < platformSettings.Count; ++i) { @@ -827,6 +835,20 @@ private void HandlePackableListUI() } } + public override void SaveChanges() + { + if (!m_Discard) + base.SaveChanges(); + m_ContentHash = GetInspectorHash(); + } + + public override void DiscardChanges() + { + m_Discard = true; + base.DiscardChanges(); + m_ContentHash = GetInspectorHash(); + } + void CachePreviewTexture() { var spriteAtlas = AssetDatabase.LoadAssetAtPath(m_AssetPath); @@ -859,18 +881,18 @@ void CachePreviewTexture() // sactx-2-128x128-Uncompressed-My Sprite Atlas-0fe925a#_Glow-var-0.5... string texName = m_PreviewTextures[i].name; string pageNum = texName.Split('-')[1]; - int hashTag = texName.IndexOf('#'); + int hashTag = texName.IndexOf('?'); int dashAfterHashTag = hashTag != -1 ? texName.IndexOf('-', hashTag) : -1; string secondaryName; if (hashTag == -1) secondaryName = ""; else if (dashAfterHashTag == -1) - secondaryName = "-" + texName.Substring(hashTag + 1); + secondaryName = texName.Substring(hashTag + 1); else - secondaryName = "-" + texName.Substring(hashTag + 1, dashAfterHashTag - hashTag - 1); + secondaryName = texName.Substring(hashTag + 1, dashAfterHashTag - hashTag - 1); - m_OptionDisplays[i] = string.Format("#{0}{1}", pageNum, secondaryName); + m_OptionDisplays[i] = secondaryName == "" ? string.Format("MainTex - Page ({0})", pageNum) : string.Format("{0} - Page ({1})", secondaryName, pageNum); m_OptionValues[i] = i; } } diff --git a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasInspector.cs b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasInspector.cs index c263cb4d27..03df8bc520 100644 --- a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasInspector.cs +++ b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasInspector.cs @@ -376,7 +376,7 @@ public override void OnInspectorGUI() bool spriteAtlasPackignEnabled = (EditorSettings.spritePackerMode == SpritePackerMode.BuildTimeOnlyAtlas || EditorSettings.spritePackerMode == SpritePackerMode.AlwaysOnAtlas || EditorSettings.spritePackerMode == SpritePackerMode.SpriteAtlasV2); - if (spriteAtlasPackignEnabled) + if (spriteAtlasPackignEnabled && !Application.isPlaying) { if (GUILayout.Button(styles.packButton, GUILayout.ExpandWidth(false))) { @@ -754,18 +754,18 @@ void CachePreviewTexture() // sactx-2-128x128-Uncompressed-My Sprite Atlas-0fe925a#_Glow-var-0.5... string texName = m_PreviewTextures[i].name; string pageNum = texName.Split('-')[1]; - int hashTag = texName.IndexOf('#'); + int hashTag = texName.IndexOf('?'); int dashAfterHashTag = hashTag != -1 ? texName.IndexOf('-', hashTag) : -1; string secondaryName; if (hashTag == -1) secondaryName = ""; else if (dashAfterHashTag == -1) - secondaryName = "-" + texName.Substring(hashTag + 1); + secondaryName = texName.Substring(hashTag + 1); else - secondaryName = "-" + texName.Substring(hashTag + 1, dashAfterHashTag - hashTag - 1); + secondaryName = texName.Substring(hashTag + 1, dashAfterHashTag - hashTag - 1); - m_OptionDisplays[i] = string.Format("#{0}{1}", pageNum, secondaryName); + m_OptionDisplays[i] = secondaryName == "" ? string.Format("MainTex - Page ({0})", pageNum) : string.Format("{0} - Page ({1})", secondaryName, pageNum); m_OptionValues[i] = i; } } diff --git a/Editor/Mono/ActiveEditorTracker.bindings.cs b/Editor/Mono/ActiveEditorTracker.bindings.cs index 631817a74f..a797bef7bc 100644 --- a/Editor/Mono/ActiveEditorTracker.bindings.cs +++ b/Editor/Mono/ActiveEditorTracker.bindings.cs @@ -59,7 +59,11 @@ public override int GetHashCode() public Editor[] activeEditors { get { return (Editor[])Internal_GetActiveEditors(this); } } [FreeFunction] - internal static extern void Internal_GetActiveEditorsNonAlloc(ActiveEditorTracker self, Editor[] editors); + static extern Editor[] Internal_GetActiveEditorsNonAllocInternal(ActiveEditorTracker self, [Unmarshalled] Editor[] editors); + internal static void Internal_GetActiveEditorsNonAlloc(ActiveEditorTracker self, ref Editor[] editors) + { + editors = Internal_GetActiveEditorsNonAllocInternal(self, editors); + } // List version internal void GetObjectsLockedByThisTracker(List lockedObjects) @@ -204,9 +208,24 @@ public static ActiveEditorTracker sharedTracker } } + // Only valid and rebuilds when sharedTracker is locked + internal static ActiveEditorTracker fallbackTracker + { + get + { + var tracker = new ActiveEditorTracker(); + SetupFallbackTracker(tracker); + return tracker; + } + } + [FreeFunction("Internal_SetupSharedTracker")] static extern void SetupSharedTracker(ActiveEditorTracker sharedTracker); + [FreeFunction("Internal_SetupFallbackTracker")] + static extern void SetupFallbackTracker(ActiveEditorTracker fallbackTracker); + + [RequiredByNativeCode] static void Internal_OnTrackerRebuild() { if (editorTrackerRebuilt != null) diff --git a/Editor/Mono/Animation/AnimationUtility.bindings.cs b/Editor/Mono/Animation/AnimationUtility.bindings.cs index c8b1bdcd94..1353b5e314 100644 --- a/Editor/Mono/Animation/AnimationUtility.bindings.cs +++ b/Editor/Mono/Animation/AnimationUtility.bindings.cs @@ -78,6 +78,15 @@ internal enum PolynomialValid TooManySegments = 3 } + internal enum DiscreteBindingResult + { + Valid = 0, + InvalidScript = 1, + MissingField = 2, + IncompatibleFieldType = 3, + MissingDiscreteAttribute = 4 + } + public delegate void OnCurveWasModified(AnimationClip clip, EditorCurveBinding binding, CurveModifiedType type); public static OnCurveWasModified onCurveWasModified; @@ -118,7 +127,12 @@ public static AnimationClip[] GetAnimationClips(GameObject gameObject) var extraClips = new List(); clipSources[i].GetAnimationClips(extraClips); - allClips.AddRange(extraClips); + allClips.Capacity = allClips.Count + extraClips.Count; + foreach (var clip in extraClips) + { + if (clip != null) + allClips.Add(clip); + } } return allClips.ToArray(); @@ -131,7 +145,7 @@ public static AnimationClip[] GetAnimationClips(GameObject gameObject) extern internal static AnimationClip[] GetAnimationClipsInAnimationPlayer([NotNull] GameObject gameObject); // Sets the array of AnimationClips to be referenced in the Animation component - extern public static void SetAnimationClips([NotNull] Animation animation, AnimationClip[] clips); + extern public static void SetAnimationClips([NotNull] Animation animation, [Unmarshalled] AnimationClip[] clips); public static EditorCurveBinding[] GetAnimatableBindings(GameObject targetObject, GameObject root) { @@ -198,7 +212,7 @@ public static Type PropertyModificationToEditorCurveBinding(PropertyModification extern public static ObjectReferenceKeyframe[] GetObjectReferenceCurve([NotNull] AnimationClip clip, EditorCurveBinding binding); - public static void SetObjectReferenceCurve(AnimationClip clip, EditorCurveBinding binding, ObjectReferenceKeyframe[] keyframes) + public static void SetObjectReferenceCurve(AnimationClip clip, EditorCurveBinding binding, [Unmarshalled]ObjectReferenceKeyframe[] keyframes) { Internal_SetObjectReferenceCurve(clip, binding, keyframes, true); Internal_InvokeOnCurveWasModified(clip, binding, keyframes != null ? CurveModifiedType.CurveModified : CurveModifiedType.CurveDeleted); @@ -229,7 +243,7 @@ internal static void SetObjectReferenceCurveNoSync(AnimationClip clip, EditorCur } [NativeThrows] - extern private static void Internal_SetObjectReferenceCurve([NotNull] AnimationClip clip, EditorCurveBinding binding, ObjectReferenceKeyframe[] keyframes, bool updateMuscleClip); + extern private static void Internal_SetObjectReferenceCurve([NotNull] AnimationClip clip, EditorCurveBinding binding, [Unmarshalled] ObjectReferenceKeyframe[] keyframes, bool updateMuscleClip); extern public static AnimationCurve GetEditorCurve([NotNull] AnimationClip clip, EditorCurveBinding binding); @@ -267,6 +281,8 @@ internal static void SetEditorCurveNoSync(AnimationClip clip, EditorCurveBinding [NativeThrows] extern private static void Internal_SetEditorCurve([NotNull] AnimationClip clip, EditorCurveBinding binding, AnimationCurve curve, bool syncEditorCurves); + extern internal static DiscreteBindingResult IsDiscreteIntBinding(EditorCurveBinding binding); + extern internal static void SyncEditorCurves([NotNull] AnimationClip clip); private static void Internal_InvokeOnCurveWasModified(AnimationClip clip, EditorCurveBinding binding, CurveModifiedType type) @@ -384,7 +400,7 @@ public static AnimationCurve GetEditorCurve(AnimationClip clip, string relativeP } extern public static AnimationEvent[] GetAnimationEvents([NotNull] AnimationClip clip); - [NativeThrows] extern public static void SetAnimationEvents([NotNull] AnimationClip clip, [NotNull] AnimationEvent[] events); + [NativeThrows] extern public static void SetAnimationEvents([NotNull] AnimationClip clip, [NotNull][Unmarshalled] AnimationEvent[] events); extern public static string CalculateTransformPath([NotNull] Transform targetTransform, Transform root); diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowControl.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowControl.cs index 9deb61f26e..6dc2cb6663 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowControl.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowControl.cs @@ -131,7 +131,6 @@ private static bool HasFlag(ResampleFlags flags, ResampleFlags flag) private AnimationClipPlayable m_CandidateClipPlayable; private AnimationClipPlayable m_DefaultPosePlayable; private bool m_UsesPostProcessComponents = false; - HashSet m_ObjectsModifiedDuringAnimationMode = new HashSet(); private static ProfilerMarker s_ResampleAnimationMarker = new ProfilerMarker("AnimationWindowControl.ResampleAnimation"); @@ -775,42 +774,30 @@ private bool AllowRecordingPrefabPropertyOverridesFor(UnityEngine.Object compone if (componentOrGameObject == null) throw new ArgumentNullException(nameof(componentOrGameObject)); - GameObject inputGameObject = null; - if (componentOrGameObject is Component) - { - inputGameObject = ((Component)componentOrGameObject).gameObject; - } - else if (componentOrGameObject is GameObject) - { - inputGameObject = (GameObject)componentOrGameObject; - } - else - { + if (componentOrGameObject is not Component && componentOrGameObject is not GameObject) return true; - } var rootOfAnimation = state.activeRootGameObject; if (rootOfAnimation == null) return true; - // If the input object is a child of the current root of animation then disallow recording of prefab property overrides - // since the input object is currently being setup for animation recording - return inputGameObject.transform.IsChildOf(rootOfAnimation.transform) == false; + return false; } void OnExitingAnimationMode() { Undo.postprocessModifications -= PostprocessAnimationRecordingModifications; PrefabUtility.allowRecordingPrefabPropertyOverridesFor -= AllowRecordingPrefabPropertyOverridesFor; + } - // Ensures Prefab instance overrides are recorded for properties that was changed while in AnimationMode - foreach (var obj in m_ObjectsModifiedDuringAnimationMode) + void RecordPropertyOverridesForNonAnimatedProperties(UndoPropertyModification[] modifications) + { + PrefabUtility.allowRecordingPrefabPropertyOverridesFor -= AllowRecordingPrefabPropertyOverridesFor; + foreach (var mod in modifications) { - if (obj != null) - EditorUtility.SetDirty(obj); + PrefabUtility.RecordPrefabInstancePropertyModifications(mod.currentValue.target); } - - m_ObjectsModifiedDuringAnimationMode.Clear(); + PrefabUtility.allowRecordingPrefabPropertyOverridesFor += AllowRecordingPrefabPropertyOverridesFor; } private UndoPropertyModification[] PostprocessAnimationRecordingModifications(UndoPropertyModification[] modifications) @@ -827,17 +814,15 @@ private UndoPropertyModification[] PostprocessAnimationRecordingModifications(Un else if (previewing) modifications = RegisterCandidates(modifications); + // Fix for UUM-61742: Unrecorded Prefab overloads should be recorded immediately + RecordPropertyOverridesForNonAnimatedProperties(modifications); + RefreshDisplayNamesOnArrayTopologicalChange(modifications); // Only resample when playable graph has been customized with post process nodes. if (m_UsesPostProcessComponents) ResampleAnimation(ResampleFlags.None); - foreach (var mod in modifications) - { - m_ObjectsModifiedDuringAnimationMode.Add(mod.currentValue.target); - } - return modifications; } @@ -978,7 +963,7 @@ private List GetKeys(PropertyModification[] modificatio int keyIndex = curve.GetKeyframeIndex(state.time); if (keyIndex >= 0) { - keys.Add(curve.m_Keyframes[keyIndex]); + keys.Add(curve.keyframes[keyIndex]); } } } diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowCurve.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowCurve.cs index 966a51b453..c7d0cf939e 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowCurve.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowCurve.cs @@ -15,7 +15,7 @@ internal class AnimationWindowCurve : IComparable, IEquata { public const float timeEpsilon = 0.00001f; - public List m_Keyframes; + private List m_Keyframes; private EditorCurveBinding m_Binding; private int m_BindingHashCode; @@ -46,6 +46,8 @@ internal class AnimationWindowCurve : IComparable, IEquata public bool animationIsEditable { get { return m_SelectionBinding != null ? m_SelectionBinding.animationIsEditable : true; } } public int selectionID { get { return m_SelectionBinding != null ? m_SelectionBinding.id : 0; } } + public IReadOnlyList keyframes => m_Keyframes; + private object defaultValue { get @@ -190,17 +192,10 @@ public AnimationCurve ToAnimationCurve() AnimationCurve animationCurve = new AnimationCurve(); List keys = new List(); - float lastFrameTime = float.MinValue; - for (int i = 0; i < length; i++) { - // Make sure we don't get two keyframes in an exactly the same time. We just ignore those. - if (Mathf.Abs(m_Keyframes[i].time - lastFrameTime) > AnimationWindowCurve.timeEpsilon) - { - Keyframe newKeyframe = m_Keyframes[i].ToKeyframe(); - keys.Add(newKeyframe); - lastFrameTime = m_Keyframes[i].time; - } + Keyframe newKeyframe = m_Keyframes[i].ToKeyframe(); + keys.Add(newKeyframe); } animationCurve.keys = keys.ToArray(); @@ -212,17 +207,10 @@ public ObjectReferenceKeyframe[] ToObjectCurve() int length = m_Keyframes.Count; List keys = new List(); - float lastFrameTime = float.MinValue; - for (int i = 0; i < length; i++) { - // Make sure we don't get two keyframes in an exactly the same time. We just ignore those. - if (Mathf.Abs(m_Keyframes[i].time - lastFrameTime) > AnimationWindowCurve.timeEpsilon) - { - ObjectReferenceKeyframe newKeyframe = m_Keyframes[i].ToObjectReferenceKeyframe(); - lastFrameTime = newKeyframe.time; - keys.Add(newKeyframe); - } + ObjectReferenceKeyframe newKeyframe = m_Keyframes[i].ToObjectReferenceKeyframe(); + keys.Add(newKeyframe); } keys.Sort((a, b) => a.time.CompareTo(b.time)); @@ -301,6 +289,11 @@ public void RemoveKeyframe(AnimationKeyTime time) } } + public void RemoveKeyframe(AnimationWindowKeyframe keyframe) + { + m_Keyframes.Remove(keyframe); + } + public bool HasKeyframe(AnimationKeyTime time) { return GetKeyframeIndex(time) != -1; diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowEvent.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowEvent.cs index 613cfa17ff..4b3cce3606 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowEvent.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowEvent.cs @@ -13,10 +13,46 @@ namespace UnityEditor { - internal struct AnimationWindowEventMethod + /// + /// Holds the context for AnimationEvent editing. + /// + class AnimationEventEditorState { - public string name; - public Type parameterType; + static bool s_ShowOverloadedFunctionsDetails = true; + static bool s_ShowDuplicatedFunctionsDetails = true; + + bool m_ShowOverloadedFunctionsDetails = s_ShowOverloadedFunctionsDetails; + bool m_ShowDuplicatedFunctionsDetails = s_ShowDuplicatedFunctionsDetails; + + /// + /// Used to track whether or not to show extra details about duplicated function names found in among the potential supported functions + /// + public bool ShowOverloadedFunctionsDetails + { + get => m_ShowOverloadedFunctionsDetails; + set + { + m_ShowOverloadedFunctionsDetails = s_ShowOverloadedFunctionsDetails = value; + } + } + + /// + /// Used to track whether or not to show extra details about overloaded function names found in among the potential supported functions + /// + public bool ShowDuplicatedFunctionsDetails + { + get => m_ShowDuplicatedFunctionsDetails; + set + { + m_ShowDuplicatedFunctionsDetails = s_ShowDuplicatedFunctionsDetails = value; + } + } + + public AnimationEventEditorState() + { + m_ShowOverloadedFunctionsDetails = s_ShowOverloadedFunctionsDetails; + m_ShowDuplicatedFunctionsDetails = s_ShowDuplicatedFunctionsDetails; + } } internal class AnimationWindowEvent : ScriptableObject diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowEventInspector.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowEventInspector.cs index 126b3879a1..584f590753 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowEventInspector.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowEventInspector.cs @@ -5,7 +5,6 @@ using System.Linq; using UnityEngine; using UnityEditor; -using System.Collections; using System.Collections.Generic; using System.Reflection; using System; @@ -17,15 +16,18 @@ namespace UnityEditor [CanEditMultipleObjects] internal class AnimationWindowEventInspector : Editor { + public static GUIContent s_OverloadWarning = EditorGUIUtility.TrTextContent("Some functions were overloaded in MonoBehaviour components and may not work as intended if used with Animation Events!"); + public static GUIContent s_DuplicatesWarning = EditorGUIUtility.TrTextContent("Some functions have the same name across several Monobehaviour components and may not work as intended if used with Animation Events!"); + const string kNotSupportedPostFix = " (Function Not Supported)"; const string kNoneSelected = "(No Function Selected)"; - public static GUIContent s_OverloadWarning = EditorGUIUtility.TrTextContent("Some functions were overloaded in MonoBehaviour components and may not work as intended if used with Animation Events!"); + AnimationEventEditorState m_State = new(); public override void OnInspectorGUI() { var awes = targets.Select(o => o as AnimationWindowEvent).ToArray(); - OnEditAnimationEvents(awes); + OnEditAnimationEvents(awes, m_State); } protected override void OnHeaderGUI() @@ -34,12 +36,17 @@ protected override void OnHeaderGUI() DrawHeaderGUI(this, targetTitle); } - public static void OnEditAnimationEvent(AnimationWindowEvent awe) + public static void OnEditAnimationEvent(AnimationWindowEvent awe, AnimationEventEditorState state) { - OnEditAnimationEvents(new AnimationWindowEvent[] {awe}); + OnEditAnimationEvents(new AnimationWindowEvent[] {awe}, state); } - public static void OnEditAnimationEvents(AnimationWindowEvent[] awEvents) + // These are used so we don't alloc new lists on every call + static List supportedMethods; + static List overloads; + static List duplicates; + + public static void OnEditAnimationEvents(AnimationWindowEvent[] awEvents, AnimationEventEditorState state) { AnimationWindowEventData data = GetData(awEvents); if (data.events == null || data.selectedEvents == null || data.selectedEvents.Length == 0) @@ -53,64 +60,60 @@ public static void OnEditAnimationEvents(AnimationWindowEvent[] awEvents) if (data.root != null) { - List methods = new List(); - HashSet overloads = new HashSet(); - CollectSupportedMethods(data.root, methods, overloads); + supportedMethods ??= new List(); + overloads ??= new List(); + duplicates ??= new List(); - var methodsFormatted = new List(methods.Count); + supportedMethods.Clear(); + overloads.Clear(); + duplicates.Clear(); + CollectSupportedMethods(data.root, supportedMethods, overloads, duplicates); - for (int i = 0; i < methods.Count; ++i) - { - AnimationWindowEventMethod method = methods[i]; + int selected = supportedMethods.FindIndex(method => method.Name == firstEvent.functionName); - string postFix = " ( )"; - if (method.parameterType != null) - { - if (method.parameterType == typeof(float)) - postFix = " ( float )"; - else if (method.parameterType == typeof(int)) - postFix = " ( int )"; - else - postFix = string.Format(" ( {0} )", method.parameterType.Name); - } + // A non-empty array used for rendering the contents of the popup + // It is of size 1 greater than the list of supported methods to account for the "None" option + string[] methodsFormatted = new string[supportedMethods.Count + 1]; - methodsFormatted.Add(method.name + postFix); + for (int i = 0; i < supportedMethods.Count; ++i) + { + AnimationMethodMap methodMap = supportedMethods[i]; + string menuPath = methodMap.methodMenuPath; + methodsFormatted[i] = menuPath; } - int notSupportedIndex = methods.Count; - int selected = methods.FindIndex(method => method.name == firstEvent.functionName); + // Add a final option to set the function to no selected function + int notSupportedIndex = supportedMethods.Count; if (selected == -1) { - selected = methods.Count; - - AnimationWindowEventMethod newMethod = new AnimationWindowEventMethod(); - newMethod.name = firstEvent.functionName; - newMethod.parameterType = null; - - methods.Add(newMethod); + selected = notSupportedIndex; + // Display that the current function is not supported if applicable if (string.IsNullOrEmpty(firstEvent.functionName)) - methodsFormatted.Add(kNoneSelected); + methodsFormatted[notSupportedIndex] = kNoneSelected; else - methodsFormatted.Add(firstEvent.functionName + kNotSupportedPostFix); + methodsFormatted[notSupportedIndex] = firstEvent.functionName + kNotSupportedPostFix; + + var emptyMethodMap = new AnimationMethodMap(); + supportedMethods.Add(emptyMethodMap); } EditorGUIUtility.labelWidth = 130; EditorGUI.showMixedValue = !singleFunctionName; int wasSelected = singleFunctionName ? selected : -1; - selected = EditorGUILayout.Popup("Function: ", selected, methodsFormatted.ToArray()); + selected = EditorGUILayout.Popup("Function: ", selected, methodsFormatted); if (wasSelected != selected && selected != -1 && selected != notSupportedIndex) { foreach (var evt in data.selectedEvents) { - evt.functionName = methods[selected].name; + evt.functionName = supportedMethods[selected].Name; evt.stringParameter = string.Empty; } } EditorGUI.showMixedValue = false; - var selectedParameter = methods[selected].parameterType; + var selectedParameter = supportedMethods[selected].parameterType; if (singleFunctionName && selectedParameter != null) { @@ -127,6 +130,24 @@ public static void OnEditAnimationEvents(AnimationWindowEvent[] awEvents) { EditorGUILayout.Space(); EditorGUILayout.HelpBox(s_OverloadWarning.text, MessageType.Warning, true); + state.ShowOverloadedFunctionsDetails = EditorGUILayout.Foldout(state.ShowOverloadedFunctionsDetails, "Show Details"); + if (state.ShowOverloadedFunctionsDetails) + { + string overloadedFunctionDetails = "Overloaded Functions: \n" + GetFormattedMethodsText(overloads); + GUILayout.Label(overloadedFunctionDetails, EditorStyles.helpBox); + } + } + + if (duplicates.Count > 0) + { + EditorGUILayout.Space(); + EditorGUILayout.HelpBox(s_DuplicatesWarning.text, MessageType.Warning, true); + state.ShowDuplicatedFunctionsDetails = EditorGUILayout.Foldout(state.ShowDuplicatedFunctionsDetails, "Show Details"); + if (state.ShowDuplicatedFunctionsDetails) + { + string duplicatedFunctionDetails = "Duplicated Functions: \n" + GetFormattedMethodsText(duplicates); + GUILayout.Label(duplicatedFunctionDetails, EditorStyles.helpBox); + } } } else @@ -152,7 +173,7 @@ public static void OnEditAnimationEvents(AnimationWindowEvent[] awEvents) using (new EditorGUI.DisabledScope(true)) { AnimationEvent dummyEvent = new AnimationEvent(); - DoEditRegularParameters(new AnimationEvent[] {dummyEvent}, typeof(AnimationEvent)); + DoEditRegularParameters(new AnimationEvent[] { dummyEvent }, typeof(AnimationEvent)); } } } @@ -161,6 +182,44 @@ public static void OnEditAnimationEvents(AnimationWindowEvent[] awEvents) SetData(awEvents, data); } + static string GetFormattedMethodsText(List methods) + { + string text = ""; + foreach (AnimationMethodMap methodMap in methods) + { + text += string.Format("{0}.{1} ( {2} )\n", methodMap.sourceBehaviour.GetType().Name, methodMap.Name, GetTypeName(methodMap.parameterType)); + } + text = text.Trim(); + return text; + } + + static string GetTypeName(Type t) + { + if (t == null) + return ""; + if (t == typeof(int)) + return "int"; + if (t == typeof(float)) + return "float"; + if (t == typeof(string)) + return "string"; + if (t == typeof(bool)) + return "bool"; + return t.Name; + } + + static string GetFormattedMethodName(AnimationMethodMap methodMap) + { + string targetName = methodMap.sourceBehaviour.GetType().Name; + string methodName = methodMap.Name; + string args = GetTypeName(methodMap.parameterType); + + if (methodName.StartsWith("set_") || methodName.StartsWith("get_")) + return string.Format("{0}/Properties/{1} ( {2} )", targetName, methodName, args); + else + return string.Format("{0}/Methods/{1} ( {2} )", targetName, methodName, args); + } + public static void OnDisabledAnimationEvent() { AnimationEvent dummyEvent = new AnimationEvent(); @@ -168,11 +227,13 @@ public static void OnDisabledAnimationEvent() using (new EditorGUI.DisabledScope(true)) { dummyEvent.functionName = EditorGUILayout.TextField(EditorGUIUtility.TrTextContent("Function"), dummyEvent.functionName); - DoEditRegularParameters(new AnimationEvent[] {dummyEvent}, typeof(AnimationEvent)); + DoEditRegularParameters(new AnimationEvent[] { dummyEvent }, typeof(AnimationEvent)); } } - public static void CollectSupportedMethods(GameObject gameObject, List supportedMethods, HashSet overloadedMethods) + static Dictionary> s_TypeAnimationMethodMapCache = new Dictionary>(); + + static void CollectSupportedMethods(GameObject gameObject, List supportedMethods, List overloadedMethods, List duplicatedMethods) { if (gameObject == null) return; @@ -187,59 +248,99 @@ public static void CollectSupportedMethods(GameObject gameObject, List validMethods)) { - MethodInfo method = methods[i]; - string name = method.Name; + var pendingValidMethods = new List(); + MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly ); + for (int i = 0; i < methods.Length; i++) + { + MethodInfo method = methods[i]; + string name = method.Name; - if (!IsSupportedMethodName(name)) - continue; + if (!IsSupportedMethodName(name)) + continue; - ParameterInfo[] parameters = method.GetParameters(); - if (parameters.Length > 1) - continue; + ParameterInfo[] parameters = method.GetParameters(); + if (parameters.Length > 1) + continue; - Type parameterType = null; + Type parameterType = null; - if (parameters.Length == 1) - { - parameterType = parameters[0].ParameterType; - if (!(parameterType == typeof(string) || - parameterType == typeof(float) || - parameterType == typeof(int) || - parameterType == typeof(AnimationEvent) || - parameterType == typeof(UnityEngine.Object) || - parameterType.IsSubclassOf(typeof(UnityEngine.Object)) || - parameterType.IsEnum)) - continue; + if (parameters.Length == 1) + { + parameterType = parameters[0].ParameterType; + if (!(parameterType == typeof(string) || + parameterType == typeof(float) || + parameterType == typeof(int) || + parameterType == typeof(AnimationEvent) || + parameterType == typeof(UnityEngine.Object) || + parameterType.IsSubclassOf(typeof(UnityEngine.Object)) || + parameterType.IsEnum)) + continue; + } + + AnimationMethodMap newMethodMap = new AnimationMethodMap + { + sourceBehaviour = behaviour, + methodInfo = method, + parameterType = parameterType + }; + + newMethodMap.methodMenuPath = GetFormattedMethodName(newMethodMap); + + pendingValidMethods.Add(newMethodMap); } - AnimationWindowEventMethod newMethod = new AnimationWindowEventMethod(); - newMethod.name = method.Name; - newMethod.parameterType = parameterType; + validMethods = pendingValidMethods.AsReadOnly(); + s_TypeAnimationMethodMapCache.Add(type, validMethods); + } + foreach (var method in validMethods) + { // Since AnimationEvents only stores method name, it can't handle functions with multiple overloads. - // Only retrieve first found function, but discard overloads. - int existingMethodIndex = supportedMethods.FindIndex(m => m.name == name); + // or functions with the same name across multiple monobehaviours + // Only retrieve first found method, and discard overloads and duplicate names. + int existingMethodIndex = supportedMethods.FindIndex(m => m.Name == method.Name); if (existingMethodIndex != -1) { // The method is only ambiguous if it has a different signature to the one we saw before - if (supportedMethods[existingMethodIndex].parameterType != parameterType) + if (supportedMethods[existingMethodIndex].parameterType != method.parameterType) + { + overloadedMethods.Add(method); + } + // Otherwise, there is another monobehaviour with the same method name. + else { - overloadedMethods.Add(name); + duplicatedMethods.Add(method); } } else { - supportedMethods.Add(newMethod); + supportedMethods.Add(method); } } + type = type.BaseType; } } } + /// + /// Maps the methodInfo and paramter type of a considered animation method to a source monobeheaviour. + /// Mimics the structure of + /// + struct AnimationMethodMap + { + public Object sourceBehaviour; + public MethodInfo methodInfo; + public Type parameterType; + + // Used for caching + public string methodMenuPath; + + public string Name => methodInfo?.Name ?? ""; + } + public static string FormatEvent(GameObject root, AnimationEvent evt) { if (string.IsNullOrEmpty(evt.functionName)) @@ -272,7 +373,7 @@ public static string FormatEvent(GameObject root, AnimationEvent evt) if (method == null) continue; - var parameterTypes = method.GetParameters().Select(p => p.ParameterType); + var parameterTypes = method.GetParameters(); return evt.functionName + FormatEventArguments(parameterTypes, evt); } @@ -381,15 +482,15 @@ private static bool IsSupportedMethodName(string name) return name != "Main" && name != "Start" && name != "Awake" && name != "Update"; } - private static string FormatEventArguments(IEnumerable paramTypes, AnimationEvent evt) + private static string FormatEventArguments(ParameterInfo[] paramTypes, AnimationEvent evt) { - if (!paramTypes.Any()) + if (paramTypes.Length == 0) return " ( )"; - if (paramTypes.Count() > 1) + if (paramTypes.Length > 1) return kNotSupportedPostFix; - var paramType = paramTypes.First(); + var paramType = paramTypes[0].ParameterType; if (paramType == typeof(string)) return " ( \"" + evt.stringParameter + "\" )"; @@ -426,6 +527,9 @@ private struct AnimationWindowEventData public AnimationEvent[] selectedEvents; } + + // this are used so we don't alloc new lists on every call + static List getDataSelectedEvents; private static AnimationWindowEventData GetData(AnimationWindowEvent[] awEvents) { var data = new AnimationWindowEventData(); @@ -444,14 +548,15 @@ private static AnimationWindowEventData GetData(AnimationWindowEvent[] awEvents) if (data.events != null) { - List selectedEvents = new List(); + getDataSelectedEvents ??= new List(); + getDataSelectedEvents.Clear(); foreach (var awEvent in awEvents) { if (awEvent.eventIndex >= 0 && awEvent.eventIndex < data.events.Length) - selectedEvents.Add(data.events[awEvent.eventIndex]); + getDataSelectedEvents.Add(data.events[awEvent.eventIndex]); } - data.selectedEvents = selectedEvents.ToArray(); + data.selectedEvents = getDataSelectedEvents.ToArray(); } return data; @@ -477,11 +582,11 @@ private static void SetData(AnimationWindowEvent[] awEvents, AnimationWindowEven } } - [MenuItem("CONTEXT/AnimationWindowEvent/Reset")] + [MenuItem("CONTEXT/AnimationWindowEvent/Reset", secondaryPriority = 7)] static void ResetValues(MenuCommand command) { AnimationWindowEvent awEvent = command.context as AnimationWindowEvent; - AnimationWindowEvent[] awEvents = new AnimationWindowEvent[] {awEvent}; + AnimationWindowEvent[] awEvents = new AnimationWindowEvent[] { awEvent }; AnimationWindowEventData data = GetData(awEvents); if (data.events == null || data.selectedEvents == null || data.selectedEvents.Length == 0) diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyGUI.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyGUI.cs index 0fbdbf0583..f231fe167f 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyGUI.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyGUI.cs @@ -344,7 +344,7 @@ private void DoValueField(Rect rect, AnimationWindowHierarchyNode node, int row) if (curve.valueType == typeof(bool)) { - value = GUI.Toggle(valueFieldRect, m_HierarchyItemValueControlIDs[row], (float)value != 0, GUIContent.none, EditorStyles.toggle) ? 1f : 0f; + value = GUI.Toggle(valueFieldRect, m_HierarchyItemValueControlIDs[row], Convert.ToSingle(value) != 0f, GUIContent.none, EditorStyles.toggle) ? 1f : 0f; } else { @@ -367,7 +367,7 @@ private void DoValueField(Rect rect, AnimationWindowHierarchyNode node, int row) valueFieldRect, valueFieldDragRect, id, - (int)value, + Convert.ToInt32(value), EditorGUI.kIntFieldFormatString, m_AnimationSelectionTextField, true, @@ -384,7 +384,7 @@ private void DoValueField(Rect rect, AnimationWindowHierarchyNode node, int row) valueFieldRect, valueFieldDragRect, id, - (float)value, + Convert.ToSingle(value), "g5", m_AnimationSelectionTextField, true); @@ -394,7 +394,8 @@ private void DoValueField(Rect rect, AnimationWindowHierarchyNode node, int row) Event.current.Use(); } - if (float.IsInfinity((float)value) || float.IsNaN((float)value)) + var floatValue = Convert.ToSingle(value); + if (float.IsInfinity(floatValue) || float.IsNaN(floatValue)) value = 0f; } } @@ -493,7 +494,7 @@ private void DoCurveColorIndicator(Rect rect, AnimationWindowHierarchyNode node) { foreach (var curve in node.curves) { - if (curve.m_Keyframes.Any(key => state.time.ContainsTime(key.time))) + if (curve.keyframes.Any(key => state.time.ContainsTime(key.time))) { hasKey = true; } diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowKeyframe.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowKeyframe.cs index cbdfa58b5e..e9fa64f7fa 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowKeyframe.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowKeyframe.cs @@ -149,9 +149,9 @@ public int GetHash() public int GetIndex() { - for (int i = 0; i < curve.m_Keyframes.Count; i++) + for (int i = 0; i < curve.keyframes.Count; i++) { - if (curve.m_Keyframes[i] == this) + if (curve.keyframes[i] == this) { return i; } @@ -167,11 +167,11 @@ public Keyframe ToKeyframe() // case 1395978 // Negative int values converted to float create NaN values. Limiting discrete int values to only positive values // until we rewrite the animation backend with dedicated int curves. - floatValue = UnityEngine.Animations.DiscreteEvaluationAttributeUtilities.ConvertDiscreteIntToFloat(Math.Max((int)value, 0)); + floatValue = UnityEngine.Animations.DiscreteEvaluationAttributeUtilities.ConvertDiscreteIntToFloat(Math.Max(Convert.ToInt32(value), 0)); } else { - floatValue = (float)value; + floatValue = Convert.ToSingle(value); } var keyframe = new Keyframe(time, floatValue, inTangent, outTangent); diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowState.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowState.cs index 1511a2bf16..ff269084a7 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowState.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowState.cs @@ -532,7 +532,7 @@ private void SaveSelectedKeys(string undoLabel) List toBeDeleted = new List(); // If selected keys are dragged over non-selected keyframe at exact same time, then delete the unselected ones underneath - foreach (AnimationWindowKeyframe other in snapshot.curve.m_Keyframes) + foreach (AnimationWindowKeyframe other in snapshot.curve.keyframes) { // Keyframe is in selection, skip. if (snapshot.selectedKeys.Exists(liveEditKey => liveEditKey.key == other)) @@ -547,7 +547,7 @@ private void SaveSelectedKeys(string undoLabel) foreach (AnimationWindowKeyframe deletedKey in toBeDeleted) { - snapshot.curve.m_Keyframes.Remove(deletedKey); + snapshot.curve.RemoveKeyframe(deletedKey); } } @@ -806,7 +806,7 @@ public AnimationWindowKeyframe activeKeyframe { foreach (AnimationWindowCurve curve in allCurves) { - foreach (AnimationWindowKeyframe keyframe in curve.m_Keyframes) + foreach (AnimationWindowKeyframe keyframe in curve.keyframes) { if (keyframe.GetHash() == m_ActiveKeyframeHash) m_ActiveKeyframeCache = keyframe; @@ -831,7 +831,7 @@ public List selectedKeys m_SelectedKeysCache = new List(); foreach (AnimationWindowCurve curve in allCurves) { - foreach (AnimationWindowKeyframe keyframe in curve.m_Keyframes) + foreach (AnimationWindowKeyframe keyframe in curve.keyframes) { if (KeyIsSelected(keyframe)) { @@ -961,7 +961,7 @@ public void DeleteKeys(List keys) curves.Add(keyframe.curve); UnselectKey(keyframe); - keyframe.curve.m_Keyframes.Remove(keyframe); + keyframe.curve.RemoveKeyframe(keyframe); } SaveCurves(activeAnimationClip, curves, kEditCurveUndoLabel); @@ -984,7 +984,7 @@ public void StartLiveEdit() { LiveEditCurve snapshot = new LiveEditCurve(); snapshot.curve = selectedKey.curve; - foreach (AnimationWindowKeyframe key in selectedKey.curve.m_Keyframes) + foreach (AnimationWindowKeyframe key in selectedKey.curve.keyframes) { LiveEditKeyframe liveEditKey = new LiveEditKeyframe(); liveEditKey.keySnapshot = new AnimationWindowKeyframe(key); @@ -1222,7 +1222,7 @@ public void CopyAllActiveCurves() { foreach (AnimationWindowCurve curve in activeCurves) { - foreach (AnimationWindowKeyframe keyframe in curve.m_Keyframes) + foreach (AnimationWindowKeyframe keyframe in curve.keyframes) { s_KeyframeClipboard.Add(new AnimationWindowKeyframe(keyframe)); } @@ -1298,14 +1298,16 @@ public void PasteKeys() // Only allow pasting of key frame from numerical curves to numerical curves or from pptr curves to pptr curves. if ((newKeyframe.time >= 0.0f) && (newKeyframe.curve != null) && (newKeyframe.curve.isPPtrCurve == keyframe.curve.isPPtrCurve)) { - if (newKeyframe.curve.HasKeyframe(AnimationKeyTime.Time(newKeyframe.time, newKeyframe.curve.clip.frameRate))) - newKeyframe.curve.RemoveKeyframe(AnimationKeyTime.Time(newKeyframe.time, newKeyframe.curve.clip.frameRate)); + var keyTime = AnimationKeyTime.Time(newKeyframe.time, newKeyframe.curve.clip.frameRate); + + if (newKeyframe.curve.HasKeyframe(keyTime)) + newKeyframe.curve.RemoveKeyframe(keyTime); // When copy-pasting multiple keyframes (curve), its a continous thing. This is why we delete the existing keyframes in the pasted range. if (lastTargetCurve == newKeyframe.curve) newKeyframe.curve.RemoveKeysAtRange(lastTime, newKeyframe.time); - newKeyframe.curve.m_Keyframes.Add(newKeyframe); + newKeyframe.curve.AddKeyframe(newKeyframe, keyTime); SelectKey(newKeyframe); // TODO: Optimize to only save curve once instead once per keyframe SaveCurve(newKeyframe.curve.clip, newKeyframe.curve, kEditCurveUndoLabel); @@ -1660,7 +1662,7 @@ public float clipFrameRate // Reposition all keyframes to match the new sampling rate foreach (var curve in selection.curves) { - foreach (var key in curve.m_Keyframes) + foreach (var key in curve.keyframes) { int frame = AnimationKeyTime.Time(key.time, clipFrameRate).frame; key.time = AnimationKeyTime.Frame(frame, value).time; diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowUtility.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowUtility.cs index dfcbc37c23..73cfea90c6 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowUtility.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowUtility.cs @@ -280,7 +280,7 @@ public static AnimationWindowKeyframe AddKeyframeToCurve(AnimationWindowCurve cu } else if (type == typeof(bool) || type == typeof(float) || type == typeof(int)) { - Keyframe tempKey = new Keyframe(time.time, (float)value); + Keyframe tempKey = new Keyframe(time.time, Convert.ToSingle(value)); if (type == typeof(bool)) { AnimationUtility.SetKeyLeftTangentMode(ref tempKey, TangentMode.Constant); @@ -903,7 +903,7 @@ public static float GetNextKeyframeTime(AnimationWindowCurve[] curves, float cur foreach (AnimationWindowCurve curve in curves) { - foreach (AnimationWindowKeyframe keyframe in curve.m_Keyframes) + foreach (AnimationWindowKeyframe keyframe in curve.keyframes) { AnimationKeyTime keyTime = AnimationKeyTime.Time(keyframe.time, frameRate); if (keyTime.frame <= candidateKeyTime.frame && keyTime.frame >= nextTime.frame) @@ -929,7 +929,7 @@ public static float GetPreviousKeyframeTime(AnimationWindowCurve[] curves, float foreach (AnimationWindowCurve curve in curves) { - foreach (AnimationWindowKeyframe keyframe in curve.m_Keyframes) + foreach (AnimationWindowKeyframe keyframe in curve.keyframes) { AnimationKeyTime keyTime = AnimationKeyTime.Time(keyframe.time, frameRate); if (keyTime.frame >= candidateKeyTime.frame && keyTime.frame <= previousTime.frame) @@ -1304,8 +1304,8 @@ public static AnimationWindowKeyframe CurveSelectionToAnimationWindowKeyframe(Cu { int curveID = curve.GetHashCode(); if (curveID == curveSelection.curveID) - if (curve.m_Keyframes.Count > curveSelection.key) - return curve.m_Keyframes[curveSelection.key]; + if (curve.keyframes.Count > curveSelection.key) + return curve.keyframes[curveSelection.key]; } return null; diff --git a/Editor/Mono/Animation/AnimationWindow/CurveEditorWindow.cs b/Editor/Mono/Animation/AnimationWindow/CurveEditorWindow.cs index 4d93f8e54d..86e23e41ff 100644 --- a/Editor/Mono/Animation/AnimationWindow/CurveEditorWindow.cs +++ b/Editor/Mono/Animation/AnimationWindow/CurveEditorWindow.cs @@ -34,7 +34,7 @@ public enum NormalizationMode static CurveEditorWindow s_SharedCurveEditor; - CurveEditor m_CurveEditor; + internal CurveEditor m_CurveEditor; AnimationCurve m_Curve; Color m_Color; diff --git a/Editor/Mono/Animation/AnimationWindow/DopeLine.cs b/Editor/Mono/Animation/AnimationWindow/DopeLine.cs index c576b1cf2c..da6b52f5f0 100644 --- a/Editor/Mono/Animation/AnimationWindow/DopeLine.cs +++ b/Editor/Mono/Animation/AnimationWindow/DopeLine.cs @@ -98,7 +98,7 @@ public List keys { m_Keys = new List(); foreach (AnimationWindowCurve curve in m_Curves) - foreach (AnimationWindowKeyframe key in curve.m_Keyframes) + foreach (AnimationWindowKeyframe key in curve.keyframes) m_Keys.Add(key); m_Keys.Sort((a, b) => a.time.CompareTo(b.time)); diff --git a/Editor/Mono/Animation/AnimationWindow/DopeSheetEditor.cs b/Editor/Mono/Animation/AnimationWindow/DopeSheetEditor.cs index bcf4722b50..2107149e31 100644 --- a/Editor/Mono/Animation/AnimationWindow/DopeSheetEditor.cs +++ b/Editor/Mono/Animation/AnimationWindow/DopeSheetEditor.cs @@ -805,7 +805,7 @@ private void AssignSpriteToSpriteRenderer(AnimationWindowCurve curve) if (rootGameObject == null) return; - var hasValidCurve = curve.m_Keyframes.Count > 0 && curve.binding.type == typeof(SpriteRenderer); + var hasValidCurve = curve.keyframes.Count > 0 && curve.binding.type == typeof(SpriteRenderer); if (!hasValidCurve) return; @@ -814,7 +814,7 @@ private void AssignSpriteToSpriteRenderer(AnimationWindowCurve curve) if (!hasValidSpriteRenderer) return; - var keyframe = curve.m_Keyframes[0]; + var keyframe = curve.keyframes[0]; var sprite = keyframe.value as Sprite; if (sprite != null) { @@ -1112,12 +1112,12 @@ public void FrameSelected() { foreach (AnimationWindowCurve curve in state.activeCurves) { - int keyCount = curve.m_Keyframes.Count; + int keyCount = curve.keyframes.Count; if (keyCount > 1) { - Vector2 pt1 = new Vector2(curve.m_Keyframes[0].time, 0.0f); - Vector2 pt2 = new Vector2(curve.m_Keyframes[keyCount - 1].time, 0.0f); + Vector2 pt1 = new Vector2(curve.keyframes[0].time, 0.0f); + Vector2 pt2 = new Vector2(curve.keyframes[keyCount - 1].time, 0.0f); if (firstKey) { diff --git a/Editor/Mono/Animation/AnimatorController.cs b/Editor/Mono/Animation/AnimatorController.cs index 97caba4458..3174f1a380 100644 --- a/Editor/Mono/Animation/AnimatorController.cs +++ b/Editor/Mono/Animation/AnimatorController.cs @@ -81,6 +81,16 @@ internal void RemoveLayers(List layerIndexes) AnimatorControllerLayer[] layerVector = this.layers; foreach (var layerIndex in layerIndexes) { + for (var i = 0; i < layerVector.Length; ++i) + { + var syncedLayerIndex = layerVector[i].syncedLayerIndex; + if (syncedLayerIndex > layerIndex) + { + // synced layer is after the layer being removed, so it's going to be shifted upon removal + layerVector[i].syncedLayerIndex = syncedLayerIndex - 1; + } + } + RemoveLayerInternal(layerIndex, ref layerVector); } this.layers = layerVector; @@ -259,6 +269,15 @@ public void SetStateEffectiveMotion(AnimatorState state, Motion motion) public void SetStateEffectiveMotion(AnimatorState state, Motion motion, int layerIndex) { + //delete existing nested blend tree asset + Motion selectedMotion = GetStateEffectiveMotion(state, layerIndex); + BlendTree blendTree = selectedMotion as BlendTree; + + if (blendTree != null && !AssetDatabase.IsMainAsset(blendTree)) + { + MecanimUtilities.DestroyBlendTreeRecursive(blendTree); + } + if (layers[layerIndex].syncedLayerIndex == -1) { undoHandler.DoUndo(state, "Set Motion"); diff --git a/Editor/Mono/Animation/EditorCurveBinding.bindings.cs b/Editor/Mono/Animation/EditorCurveBinding.bindings.cs index 15e8f88f70..7e21d2094e 100644 --- a/Editor/Mono/Animation/EditorCurveBinding.bindings.cs +++ b/Editor/Mono/Animation/EditorCurveBinding.bindings.cs @@ -8,6 +8,8 @@ using UnityEngine.Playables; using UnityEngine.Scripting.APIUpdating; using UnityEngine.Internal; +using UnityEngine; +using static UnityEditor.AnimationUtility; namespace UnityEditor { @@ -128,6 +130,17 @@ static public EditorCurveBinding DiscreteCurve(string inPath, System.Type inType binding.m_isSerializeReferenceCurve = 0; binding.m_isUnknowCurve = 0; + DiscreteBindingResult result = AnimationUtility.IsDiscreteIntBinding(binding); + if (result == DiscreteBindingResult.IncompatibleFieldType || result == DiscreteBindingResult.MissingDiscreteAttribute) + { + Debug.LogWarning( + $"Property [" + inPropertyName + "] is not a supported discrete curve binding. " + + "Discrete curves only support [" + typeof(Enum) + "] and [" + typeof(int) + " with the `DiscreteEvaluation` attribute]."); + + binding.m_isDiscreteCurve = 0; + binding.m_isUnknowCurve = 1; + } + return binding; } diff --git a/Editor/Mono/Animation/TransitionPreview.cs b/Editor/Mono/Animation/TransitionPreview.cs index 1c2433fb7a..6b26f509e6 100644 --- a/Editor/Mono/Animation/TransitionPreview.cs +++ b/Editor/Mono/Animation/TransitionPreview.cs @@ -269,7 +269,7 @@ private void ResampleTransition(AnimatorStateTransition transition, AvatarMask l AnimatorStateInfo currentState = m_AvatarPreview.Animator.GetCurrentAnimatorStateInfo(m_LayerIndex); m_LeftStateWeightA = currentState.normalizedTime; m_LeftStateTimeA = currentTime; - while (!hasFinished && currentTime < maxDuration) + while (!hasFinished) { m_AvatarPreview.Animator.Update(stepTime); @@ -303,7 +303,7 @@ private void ResampleTransition(AnimatorStateTransition transition, AvatarMask l m_LeftStateTimeB = currentTime; } - if (hasTransitioned) + if (hasTransitioned || hasFinished) { m_RightStateWeightB = currentState.normalizedTime; m_RightStateTimeB = currentTime; @@ -329,6 +329,11 @@ private void ResampleTransition(AnimatorStateTransition transition, AvatarMask l float leftDuration = (m_LeftStateTimeB - m_LeftStateTimeA) / (m_LeftStateWeightB - m_LeftStateWeightA); float rightDuration = (m_RightStateTimeB - m_RightStateTimeA) / (m_RightStateWeightB - m_RightStateWeightA); + // Ensure step times make sense based on these timings + // If step time is too small, the samping will take too long + currentStateStepTime = Mathf.Max(currentStateStepTime, leftDuration / 600.0f); + nextStateStepTime = Mathf.Max(nextStateStepTime, rightDuration / 600.0f); + if (m_MustSampleMotions) { // Do this as infrequently as possible diff --git a/Editor/Mono/AnimatorController.bindings.cs b/Editor/Mono/AnimatorController.bindings.cs index eee994fcb2..40803d59f3 100644 --- a/Editor/Mono/AnimatorController.bindings.cs +++ b/Editor/Mono/AnimatorController.bindings.cs @@ -33,6 +33,7 @@ extern public AnimatorControllerLayer[] layers [FreeFunction(Name = "AnimatorControllerBindings::GetLayers", HasExplicitThis = true)] get; [FreeFunction(Name = "AnimatorControllerBindings::SetLayers", HasExplicitThis = true, ThrowsException = true)] + [param: Unmarshalled] set; } @@ -41,6 +42,7 @@ extern public AnimatorControllerParameter[] parameters [FreeFunction(Name = "AnimatorControllerBindings::GetParameters", HasExplicitThis = true)] get; [FreeFunction(Name = "AnimatorControllerBindings::SetParameters", HasExplicitThis = true, ThrowsException = true)] + [param: Unmarshalled] set; } @@ -133,6 +135,6 @@ internal extern bool isAssetBundled extern internal ScriptableObject[] Internal_GetEffectiveBehaviours([NotNull] AnimatorState state, int layerIndex); [FreeFunction(Name = "AnimatorControllerBindings::Internal_SetEffectiveBehaviours", HasExplicitThis = true)] - extern internal void Internal_SetEffectiveBehaviours([NotNull] AnimatorState state, int layerIndex, ScriptableObject[] behaviours); + extern internal void Internal_SetEffectiveBehaviours([NotNull] AnimatorState state, int layerIndex, [Unmarshalled] ScriptableObject[] behaviours); } } diff --git a/Editor/Mono/Annotation/AnnotationWindow.cs b/Editor/Mono/Annotation/AnnotationWindow.cs index 2f3eaf2875..ddbb67f43f 100644 --- a/Editor/Mono/Annotation/AnnotationWindow.cs +++ b/Editor/Mono/Annotation/AnnotationWindow.cs @@ -155,7 +155,7 @@ internal static bool ShowAtPosition(Rect buttonRect, bool isGameView) { // We could not use realtimeSinceStartUp since it is set to 0 when entering/exitting playmode, we assume an increasing time when comparing time. long nowMilliSeconds = System.DateTime.Now.Ticks / System.TimeSpan.TicksPerMillisecond; - bool justClosed = nowMilliSeconds < s_LastClosedTime + 50; + bool justClosed = nowMilliSeconds < s_LastClosedTime + 400; if (!justClosed) { Event.current.Use(); diff --git a/Editor/Mono/Annotation/LayerVisibilityWindow.cs b/Editor/Mono/Annotation/LayerVisibilityWindow.cs index adbcf89971..c65667c8a7 100644 --- a/Editor/Mono/Annotation/LayerVisibilityWindow.cs +++ b/Editor/Mono/Annotation/LayerVisibilityWindow.cs @@ -86,7 +86,7 @@ internal static bool ShowAtPosition(Rect buttonRect) { // We could not use realtimeSinceStartUp since it is set to 0 when entering/exitting playmode, we assume an increasing time when comparing time. long nowMilliSeconds = System.DateTime.Now.Ticks / System.TimeSpan.TicksPerMillisecond; - bool justClosed = nowMilliSeconds < s_LastClosedTime + 50; + bool justClosed = nowMilliSeconds < s_LastClosedTime + 400; if (!justClosed) { Event.current.Use(); diff --git a/Editor/Mono/AssemblyHelper.cs b/Editor/Mono/AssemblyHelper.cs index 22c83df2dc..73a0f4acab 100644 --- a/Editor/Mono/AssemblyHelper.cs +++ b/Editor/Mono/AssemblyHelper.cs @@ -19,6 +19,7 @@ using Debug = UnityEngine.Debug; using Unity.Profiling; using UnityEditor.AssetImporters; +using UnityEditor.Scripting.ScriptCompilation; using UnityEngine.Scripting.APIUpdating; namespace UnityEditor @@ -258,24 +259,28 @@ public static string[] GetDefaultAssemblySearchPaths() } [RequiredByNativeCode] - public static void ExtractAllClassesThatAreUserExtendedScripts(string path, out string[] classNamesArray, out string[] classNameSpacesArray, out string[] movedFromNamespacesArray) + public static bool ExtractAllClassesThatAreUserExtendedScripts(string path, out string[] classNamesArray, out string[] classNameSpacesArray, out string[] movedFromNamespacesArray) { var typesDerivedFromMonoBehaviour = TypeCache.GetTypesDerivedFrom(); var typesDerivedFromScriptableObject = TypeCache.GetTypesDerivedFrom(); var typesDerivedFromScriptedImporter = TypeCache.GetTypesDerivedFrom(); - var fullPath = Path.GetFullPath(path); - IEnumerable userTypes = typesDerivedFromMonoBehaviour.Where(x => Path.GetFullPath(x.Assembly.Location) == fullPath); + var fileName = Path.GetFileName(path); + IEnumerable userTypes = typesDerivedFromMonoBehaviour.Where(x => Path.GetFileName(x.Assembly.Location) == fileName); userTypes = userTypes - .Concat(typesDerivedFromScriptableObject.Where(x => Path.GetFullPath(x.Assembly.Location) == fullPath)) - .Concat(typesDerivedFromScriptedImporter.Where(x => Path.GetFullPath(x.Assembly.Location) == fullPath)).ToList(); + .Concat(typesDerivedFromScriptableObject.Where(x => Path.GetFileName(x.Assembly.Location) == fileName)) + .Concat(typesDerivedFromScriptedImporter.Where(x => Path.GetFileName(x.Assembly.Location) == fileName)).ToList(); List classNames = new List(userTypes.Count()); List nameSpaces = new List(userTypes.Count()); List originalNamespaces = new List(userTypes.Count()); + string pathToAssembly = null; foreach (var userType in userTypes) { + if (string.IsNullOrEmpty(pathToAssembly)) + pathToAssembly = Path.GetFullPath(userType.Assembly.Location); + classNames.Add(userType.Name); nameSpaces.Add(userType.Namespace); @@ -286,6 +291,8 @@ public static void ExtractAllClassesThatAreUserExtendedScripts(string path, out classNamesArray = classNames.ToArray(); classNameSpacesArray = nameSpaces.ToArray(); movedFromNamespacesArray = originalNamespaces.ToArray(); + + return !Utility.IsPathsEqual(pathToAssembly, path); } /// Extract information about all types in the specified assembly, searchDirs might be used to resolve dependencies. diff --git a/Editor/Mono/AssemblyInfo/AssemblyInfo.cs b/Editor/Mono/AssemblyInfo/AssemblyInfo.cs index 25a9f04dff..652704e7d6 100644 --- a/Editor/Mono/AssemblyInfo/AssemblyInfo.cs +++ b/Editor/Mono/AssemblyInfo/AssemblyInfo.cs @@ -7,6 +7,7 @@ // ADD_NEW_PLATFORM_HERE [assembly: InternalsVisibleTo("Unity.LiveNotes")] +[assembly: InternalsVisibleTo("Unity.Audio.Tests")] [assembly: InternalsVisibleTo("Unity.Burst")] [assembly: InternalsVisibleTo("Unity.Burst.Editor")] [assembly: InternalsVisibleTo("Unity.Cloud.Collaborate.Editor")] @@ -26,15 +27,16 @@ [assembly: InternalsVisibleTo("Unity.IntegrationTests")] [assembly: InternalsVisibleTo("Unity.DeploymentTests.Services")] [assembly: InternalsVisibleTo("Unity.IntegrationTests.UnityAnalytics")] +[assembly: InternalsVisibleTo("Unity.PerformanceIntegrationTests")] [assembly: InternalsVisibleTo("Unity.Timeline.Editor")] [assembly: InternalsVisibleTo("Unity.PackageManagerUI.Develop.Editor")] [assembly: InternalsVisibleTo("Unity.DeviceSimulator.Editor")] - [assembly: InternalsVisibleTo("Unity.Timeline.EditorTests")] [assembly: InternalsVisibleTo("UnityEditor.Graphs")] [assembly: InternalsVisibleTo("UnityEditor.UWP.Extensions")] [assembly: InternalsVisibleTo("UnityEditor.iOS.Extensions.Common")] [assembly: InternalsVisibleTo("UnityEditor.iOS.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.VisionOS.Extensions")] [assembly: InternalsVisibleTo("UnityEditor.AppleTV.Extensions")] [assembly: InternalsVisibleTo("UnityEditor.Android.Extensions")] [assembly: InternalsVisibleTo("UnityEditor.XboxOne.Extensions")] @@ -111,6 +113,7 @@ [assembly: InternalsVisibleTo("Unity.XR.Remoting.Editor")] [assembly: InternalsVisibleTo("UnityEngine.Common")] [assembly: InternalsVisibleTo("Unity.UI.Builder.Editor")] +[assembly: InternalsVisibleTo("UnityEditor.UIElements.Tests.Tests")] // for UI Test Framework [assembly: InternalsVisibleTo("UnityEditor.UIBuilderModule")] [assembly: InternalsVisibleTo("Unity.UI.Builder.EditorTests")] [assembly: InternalsVisibleTo("Unity.GraphViewTestUtilities.Editor")] @@ -136,6 +139,11 @@ [assembly: InternalsVisibleTo("UnityEditor.Android.Extensions")] +[assembly: InternalsVisibleTo("Unity.Entities.Build")] + +[assembly: InternalsVisibleTo("Unity.Muse.Common.Bridge")] +[assembly: InternalsVisibleTo("Unity.Muse.Chat.Bridge")] + [assembly: InternalsVisibleTo("Unity.Scenes")] [assembly: AssemblyIsEditorAssembly] diff --git a/Editor/Mono/AssemblyValidation.cs b/Editor/Mono/AssemblyValidation.cs index 41841e9827..db8c1f753b 100644 --- a/Editor/Mono/AssemblyValidation.cs +++ b/Editor/Mono/AssemblyValidation.cs @@ -6,8 +6,10 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using System.Runtime.InteropServices; using Mono.Cecil; +using Unity.IO.LowLevel.Unsafe; using UnityEditor.Scripting.ScriptCompilation; using UnityEngine.Scripting; @@ -136,6 +138,34 @@ public static Error[] ValidateAssemblies(string[] assemblyPaths, bool enableLogg return errors; } + [RequiredByNativeCode] + internal static Error[] ValidateRoslynAnalyzers(string[] analyzerPaths) + { + var readerParameters = new ReaderParameters + { + ReadingMode = ReadingMode.Deferred + }; + List errors = new List(); + foreach(var analyzer in analyzerPaths) + { + using (var analyzerDefinition = AssemblyDefinition.ReadAssembly(analyzer, readerParameters)) + { + var netstandardVersion = analyzerDefinition.MainModule.AssemblyReferences.Where(r => r.Name == "netstandard").FirstOrDefault(); + if (netstandardVersion != null && netstandardVersion.Version >= new Version(2, 1)) + { + errors.Add(new Error + { + assemblyPath = analyzer, + flags = ErrorFlags.ReferenceHasErrors, + message = $"{analyzerDefinition.Name.Name} references {netstandardVersion}. A roslyn analyzer should reference netstandard version 2.0" + }); + } + + } + } + return errors.ToArray(); + } + [RequiredByNativeCode] public static Error[] ValidateAssemblyDefinitionFiles() { @@ -195,14 +225,14 @@ public static bool PluginCompatibleWithEditor(string path) return pluginImporter.GetCompatibleWithEditor(); } - public static bool ShouldValidateReferences(string path) + public static bool ShouldValidateReferences(string path, + Dictionary allPrecompiledAssemblies) { - var pluginImporter = AssetImporter.GetAtPath(path) as PluginImporter; - if (pluginImporter == null) + if (!allPrecompiledAssemblies.TryGetValue(path, out var precompiledAssembly)) return true; - return pluginImporter.ValidateReferences; + return precompiledAssembly.Flags.HasFlag(AssemblyFlags.ValidateAssembly); } public static void PrintAssemblyDefinitions(AssemblyDefinition[] assemblyDefinitions) @@ -289,6 +319,11 @@ public static void CheckAssemblyReferences(string[] assemblyPaths, }; } + var precompiledAssemblies = EditorCompilationInterface.Instance + .PrecompiledAssemblyProvider.GetAllPrecompiledAssemblies() + .Where(x => x.Flags.HasFlag(AssemblyFlags.UserAssembly)); + var allPrecompiledAssemblies = precompiledAssemblies.ToDictionary(x => AssetPath.ReplaceSeparators(VirtualFileSystem.ToLogicalPath(x.Path))); + for (int i = 0; i < assemblyPaths.Length; ++i) { if (errors[i].HasFlag(ErrorFlags.IncompatibleWithEditor)) @@ -298,7 +333,7 @@ public static void CheckAssemblyReferences(string[] assemblyPaths, // Check if "Validate References" option is enabled // in the PluginImporter - if (!ShouldValidateReferences(assemblyPath)) + if (!ShouldValidateReferences(AssetPath.ReplaceSeparators(assemblyPath), allPrecompiledAssemblies)) continue; ResolveAndSetupReferences(i, diff --git a/Editor/Mono/AssetDatabase/AssetDatabaseSearching.cs b/Editor/Mono/AssetDatabase/AssetDatabaseSearching.cs index 7571d8dc45..fedd4d13e0 100644 --- a/Editor/Mono/AssetDatabase/AssetDatabaseSearching.cs +++ b/Editor/Mono/AssetDatabase/AssetDatabaseSearching.cs @@ -65,6 +65,7 @@ private static IEnumerator FindInFolders(SearchFilter searchFilter, Func FindInFolders(SearchFilter searchFilter, Func FindEverywhere(SearchFilter searchFilter, Func< rootPaths.Add(package.assetPath); } } + + HierarchyProperty lastProperty = null; foreach (var rootPath in rootPaths) { var property = new HierarchyProperty(rootPath); - property.SetSearchFilter(searchFilter); + if (lastProperty != null) + property.CopySearchFilterFrom(lastProperty); + else + property.SetSearchFilter(searchFilter); + lastProperty = property; while (property.Next(null)) { yield return selector(property); diff --git a/Editor/Mono/AssetPipeline/IHVImageFormatImporter.bindings.cs b/Editor/Mono/AssetPipeline/IHVImageFormatImporter.bindings.cs index 76a0e47420..7b2efea6b6 100644 --- a/Editor/Mono/AssetPipeline/IHVImageFormatImporter.bindings.cs +++ b/Editor/Mono/AssetPipeline/IHVImageFormatImporter.bindings.cs @@ -74,5 +74,17 @@ public extern int streamingMipmapsPriority get; set; } + + public extern bool ignoreMipmapLimit + { + get; + set; + } + + public extern string mipmapLimitGroupName + { + get; + set; + } } } diff --git a/Editor/Mono/AssetPipeline/LocalCacheServer.cs b/Editor/Mono/AssetPipeline/LocalCacheServer.cs index 08ac81fec4..e4a6ea2d66 100644 --- a/Editor/Mono/AssetPipeline/LocalCacheServer.cs +++ b/Editor/Mono/AssetPipeline/LocalCacheServer.cs @@ -2,27 +2,13 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License -using System.Diagnostics; using System.IO; -using System; -using System.Net; -using System.Net.Sockets; -using UnityEditor.Scripting; using UnityEditor.Utils; -using UnityEngine; -using UnityEngine.Scripting; namespace UnityEditor { - internal class LocalCacheServer : ScriptableSingleton + internal class LocalCacheServer { - [SerializeField] public string path; - [SerializeField] public int port; - [SerializeField] public ulong size; - [SerializeField] public int pid = -1; - [SerializeField] public string time; - - public const string SizeKey = "LocalCacheServerSize"; public const string PathKey = "LocalCacheServerPath"; public const string CustomPathKey = "LocalCacheServerCustomPath"; @@ -36,195 +22,21 @@ public static string GetCacheLocation() return result; } - public static void CreateCacheDirectory() - { - string cacheDirectoryPath = GetCacheLocation(); - if (Directory.Exists(cacheDirectoryPath) == false) - Directory.CreateDirectory(cacheDirectoryPath); - } - - void Create(int _port, ulong _size) - { - var nodeExecutable = Paths.Combine(EditorApplication.applicationContentsPath, "Tools", "nodejs"); - if (Application.platform == RuntimePlatform.WindowsEditor) - nodeExecutable = Paths.Combine(nodeExecutable, "node.exe"); - else - nodeExecutable = Paths.Combine(nodeExecutable, "bin", "node"); - - CreateCacheDirectory(); - path = GetCacheLocation(); - var cacheServerJs = Paths.Combine(EditorApplication.applicationContentsPath, "Tools", "CacheServer", "main.js"); - var processStartInfo = new ProcessStartInfo(nodeExecutable) - { - Arguments = "\"" + cacheServerJs + "\"" - + " --port " + _port - + " --path \"" + path - + "\" --nolegacy" - + " --monitor-parent-process " + Process.GetCurrentProcess().Id - // node.js has issues running on windows with stdout not redirected. - // so we silence logging to avoid that. And also to avoid CacheServer - // spamming the editor logs on OS X. - + " --silent" - + " --size " + _size, - UseShellExecute = false, - CreateNoWindow = true - }; - - var p = new Process(); - p.StartInfo = processStartInfo; - p.Start(); - - port = _port; - pid = p.Id; - size = _size; - time = p.StartTime.ToString(); - Save(true); - } - - public static int GetRandomUnusedPort() - { - var listener = new TcpListener(IPAddress.Any, 0); - listener.Start(); - var port = ((IPEndPoint)listener.LocalEndpoint).Port; - listener.Stop(); - return port; - } - - public static bool PingHost(string host, int port, int timeout) - { - try - { - using (var client = new TcpClient()) - { - var result = client.BeginConnect(host, port, null, null); - result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(timeout)); - return client.Connected; - } - } - catch - { - return false; - } - } - - public static bool WaitForServerToComeAlive(int port) - { - DateTime start = DateTime.Now; - DateTime maximum = start.AddSeconds(5); - while (DateTime.Now < maximum) - { - if (PingHost("localhost", port, 10)) - { - System.Console.WriteLine("Server Came alive after {0} ms", (DateTime.Now - start).TotalMilliseconds); - return true; - } - } - return false; - } - - public static void Kill() - { - if (instance.pid == -1) - return; - - Process p = null; - try - { - p = Process.GetProcessById(instance.pid); - p.Kill(); - instance.pid = -1; - } - catch - { - // if we could not get a process, there is non alive. continue. - } - } - - public static void CreateIfNeeded() - { - // See if we can get an existing process with the PID we remembered. - Process p = null; - try - { - p = Process.GetProcessById(instance.pid); - } - catch - { - // if we could not get a process, there is non alive. continue. - } - - ulong size = (ulong)EditorPrefs.GetInt(SizeKey, 10) * 1024 * 1024 * 1024; - // Check if this process is really the one we used (and not another one reusing the PID). - if (p != null && p.StartTime.ToString() == instance.time) - { - if (instance.size == size && instance.path == GetCacheLocation()) - { - // We have a server running for this setup, which we can reuse, but make sure that the cache server directory exists in case it was cleaned earlier - CreateCacheDirectory(); - return; - } - else - { - // This server does not match our setup. Kill it, so we can start a new one. - Kill(); - } - } - - // No existing server we can use. Start a new one. - instance.Create(GetRandomUnusedPort(), size); - WaitForServerToComeAlive(instance.port); - } - public static void Setup() { var mode = (AssetPipelinePreferences.CacheServerMode)EditorPrefs.GetInt("CacheServerMode"); if (mode == AssetPipelinePreferences.CacheServerMode.Local) - CreateIfNeeded(); - else - Kill(); - } - - [UsedByNativeCode] - public static int GetLocalCacheServerPort() - { - Setup(); - return instance.port; + { + EditorGUILayout.HelpBox("Local CacheServer is no longer supported", MessageType.Info, true); + } } public static void Clear() { - Kill(); string cacheDirectoryPath = GetCacheLocation(); if (Directory.Exists(cacheDirectoryPath)) Directory.Delete(cacheDirectoryPath, true); } - - public static bool CheckCacheLocationExists() - { - return Directory.Exists(GetCacheLocation()); - } - - public static bool CheckValidCacheLocation(string path) - { - if (Directory.Exists(path)) - { - var contents = Directory.GetFileSystemEntries(path); - foreach (var dir in contents) - { - var name = Path.GetFileName(dir).ToLower(); - if (name.Length == 2) - continue; - if (name == "temp") - continue; - if (name == ".ds_store") - continue; - if (name == "desktop.ini") - continue; - return false; - } - } - return true; - } } } diff --git a/Editor/Mono/AssetPipeline/SpeedTreeImporter.bindings.cs b/Editor/Mono/AssetPipeline/SpeedTreeImporter.bindings.cs index 7c05bf4d56..761c59fe1f 100644 --- a/Editor/Mono/AssetPipeline/SpeedTreeImporter.bindings.cs +++ b/Editor/Mono/AssetPipeline/SpeedTreeImporter.bindings.cs @@ -55,7 +55,7 @@ public extern bool isV8 ///////////////////////////////////////////////////////////////////////////// - // Common material properties + // Material properties public extern Color mainColor { get; set; } @@ -71,6 +71,24 @@ public extern bool isV8 public extern Color hueVariation { get; set; } public extern float alphaTestRef { get; set; } + public extern bool enableBumpByDefault { get; set; } + public extern bool enableHueByDefault { get; set; } + public extern bool enableSubsurfaceByDefault { get; set; } + + ///////////////////////////////////////////////////////////////////////////// + // Lighting properties + + public extern bool castShadowsByDefault { get; set; } + public extern bool receiveShadowsByDefault { get; set; } + public extern bool useLightProbesByDefault { get; set; } + public extern int reflectionProbeUsagesByDefault { get; set; } + + ///////////////////////////////////////////////////////////////////////////// + // Wind properties + + public static readonly string[] windQualityNames = new[] { "None", "Fastest", "Fast", "Better", "Best", "Palm" }; + public extern int bestWindQuality { get; } + public extern int selectedWindQuality { get; set; } ///////////////////////////////////////////////////////////////////////////// @@ -82,11 +100,21 @@ public extern bool hasBillboard get; } + public extern bool enableSmoothLODTransition { get; set; } public extern bool animateCrossFading { get; set; } public extern float billboardTransitionCrossFadeWidth { get; set; } public extern float fadeOutWidth { get; set; } + public extern bool[] enableSettingOverride + { + [FreeFunction(Name = "SpeedTreeImporterBindings::GetEnableSettingOverride", HasExplicitThis = true)] + get; + [NativeThrows] + [FreeFunction(Name = "SpeedTreeImporterBindings::SetEnableSettingOverride", HasExplicitThis = true)] + set; + } + public extern float[] LODHeights { [FreeFunction(Name = "SpeedTreeImporterBindings::GetLODHeights", HasExplicitThis = true)] @@ -159,10 +187,6 @@ public extern bool[] enableSubsurface set; } - public static readonly string[] windQualityNames = new[] { "None", "Fastest", "Fast", "Better", "Best", "Palm" }; - - public extern int bestWindQuality { get; } - public extern int[] windQualities { [FreeFunction(Name = "SpeedTreeImporterBindings::GetWindQuality", HasExplicitThis = true)] @@ -233,4 +257,56 @@ public bool SearchAndRemapMaterials(string materialFolderPath) return changedMappings; } } + + class SpeedTreePostProcessor : AssetPostprocessor + { + private static void FixExtraTexture_sRGB(IEnumerable subAssets) + { + AssetDatabase.StartAssetEditing(); + + foreach (var subAsset in subAssets) + { + if (subAsset is Material) + { + Material m = subAsset as Material; + Texture tex = m.GetTexture("_ExtraTex"); + if (tex) + { + string texturePath = AssetDatabase.GetAssetOrScenePath(tex); + TextureImporter texImporter = AssetImporter.GetAtPath(texturePath) as TextureImporter; + if(texImporter) + { + // Multiple materials may be referencing the same ExtraTexture, therefore + // we'll need to check the importer's setting and only queue a single reimport + // for a given texture. + if (texImporter.sRGBTexture) + { + texImporter.sRGBTexture = false; // extra texture does not contain color data, hence shouldn't be sRGB. + texImporter.SaveAndReimport(); + } + } + } + } + } + AssetDatabase.StopAssetEditing(); + } + + static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) + { + foreach (var asset in importedAssets) + { + bool st8 = asset.EndsWith(".st", StringComparison.InvariantCultureIgnoreCase); + if(st8) + { + // Check the external materials in case the user has extracted + Dictionary externalAssets = (AssetImporter.GetAtPath(asset) as SpeedTreeImporter).GetExternalObjectMap(); + FixExtraTexture_sRGB(externalAssets.Values); + + // Check the object subassets -- updates the materials if they're embedded in the SpeedTree asset + UnityEngine.Object[] subAssets = AssetDatabase.LoadAllAssetsAtPath(asset); + FixExtraTexture_sRGB(subAssets); + } + } + } + } } diff --git a/Editor/Mono/AssetPipeline/TextureGenerator.bindings.cs b/Editor/Mono/AssetPipeline/TextureGenerator.bindings.cs index ceb38f59a1..dc23da8b68 100644 --- a/Editor/Mono/AssetPipeline/TextureGenerator.bindings.cs +++ b/Editor/Mono/AssetPipeline/TextureGenerator.bindings.cs @@ -246,11 +246,16 @@ public static unsafe class TextureGenerator { public static TextureGenerationOutput GenerateTexture(TextureGenerationSettings settings, NativeArray colorBuffer) { - return GenerateTextureImpl(settings, colorBuffer.GetUnsafeReadOnlyPtr(), colorBuffer.Length * UnsafeUtility.SizeOf()); + return GenerateTextureImpl(settings, colorBuffer.GetUnsafeReadOnlyPtr(), colorBuffer.Length * UnsafeUtility.SizeOf(), 4); + } + + public static TextureGenerationOutput GenerateTexture(TextureGenerationSettings settings, NativeArray colorBuffer) + { + return GenerateTextureImpl(settings, colorBuffer.GetUnsafeReadOnlyPtr(), colorBuffer.Length * UnsafeUtility.SizeOf(), 16); } [NativeThrows] [NativeMethod("GenerateTextureScripting")] - extern static unsafe TextureGenerationOutput GenerateTextureImpl(TextureGenerationSettings settings, void* colorBuffer, int colorBufferLength); + extern static unsafe TextureGenerationOutput GenerateTextureImpl(TextureGenerationSettings settings, void* colorBuffer, int colorBufferLength, int bytesPerPixel); } } diff --git a/Editor/Mono/AssetPipeline/TextureImporter.bindings.cs b/Editor/Mono/AssetPipeline/TextureImporter.bindings.cs index 5e35aa50f7..7c68b7846d 100644 --- a/Editor/Mono/AssetPipeline/TextureImporter.bindings.cs +++ b/Editor/Mono/AssetPipeline/TextureImporter.bindings.cs @@ -20,13 +20,8 @@ namespace UnityEditor [NativeHeader("Editor/Src/EditorUserBuildSettings.h")] public sealed partial class TextureImporter : AssetImporter { - private string GetFixedPlatformName(string platform) - { - var targetGroup = BuildPipeline.GetBuildTargetGroupByName(platform); - if (targetGroup != BuildTargetGroup.Unknown) - return BuildPipeline.GetBuildTargetGroupName(targetGroup); - return platform; - } + [FreeFunction] + private static extern string GetFixedPlatformName(string platform); [Obsolete("textureFormat is no longer accessible at the TextureImporter level. For old 'simple' formats use the textureCompression property for the equivalent automatic choice (Uncompressed for TrueColor, Compressed and HQCommpressed for 16 bits). For platform specific formats use the [[PlatformTextureSettings]] API. Using this setter will setup various parameters to match the new automatic system as well as possible. Getter will return the last value set.")] public extern TextureImporterFormat textureFormat @@ -98,7 +93,9 @@ public bool GetPlatformTextureSettings(string platform, out int maxTextureSize, return GetPlatformTextureSettings(platform, out maxTextureSize, out textureFormat, out compressionQuality, out etc1AlphaSplitEnabled); } - // C++ implementation will return default platform if the requested platform does not have any settings yet. + // C++ implementation will return default platform if the requested platform is not valid. + // See "Editor/Mono/BuildPipeline/BuildPlatform.cs" -> "GetValidPlatformNames" for more + // information regarding what is considered to be a valid platform. [NativeName("GetPlatformTextureSettings")] private extern TextureImporterPlatformSettings GetPlatformTextureSetting_Internal(string platform); @@ -136,10 +133,15 @@ public TextureImporterFormat GetAutomaticFormat(string platform) if (bp.name == platform) { return DefaultFormatFromTextureParameters(settings, - platformSettings, + !platformSettings.overridden ? GetDefaultPlatformTextureSettings() : platformSettings, DoesSourceTextureHaveAlpha(), IsSourceTextureHDR(), bp.defaultTarget); + + // Regarding the "GetDefaultPlatformTextureSettings" call: in case 1281084, we made it so that platform settings stop automatically + // resetting to the default platform's settings when the platform override is disabled. This introduced a regression where + // "GetAutomaticFormat" would not return the actual format used by platforms with a disabled override, (as in, the one indicated in + // the default platform's settings) which is why we pass in the default platform's settings instead. } } @@ -251,7 +253,8 @@ public static bool IsDefaultPlatformTextureFormatValid(TextureImporterType textu [NativeProperty("VTOnly")] public extern bool vtOnly { get; set; } - internal extern bool ignoreMasterTextureLimit { get; set; } + public extern bool ignoreMipmapLimit { get; set; } + public extern string mipmapLimitGroupName { get; set; } // Generate mip maps for the texture? public extern bool mipmapEnabled { get; set; } @@ -449,9 +452,30 @@ public void ReadTextureSettings(TextureImporterSettings dest) // Set texture importers settings from [[TextureImporterSettings]] class. public void SetTextureSettings(TextureImporterSettings src) { + ValidateAndCorrectTextureImporterSettings(src); settings = src; } + private void ValidateAndCorrectTextureImporterSettings(TextureImporterSettings m_Settings) + { + switch (m_Settings.textureType) + { + case TextureImporterType.Sprite: + m_Settings.npotScale = ValidateAndCorrectSetting(m_Settings.npotScale, TextureImporterNPOTScale.None, nameof(m_Settings.npotScale)); + break; + } + } + + private T ValidateAndCorrectSetting(T actual, T expected, string settingName) + { + if (!actual.Equals(expected)) + { + Debug.LogWarning($"You cannot set {settingName} to {actual} for this texture type. It has been reset to {expected}."); + return expected; + } + return actual; + } + private extern TextureImporterSettings settings { get; set; } [NativeName("GetImportInspectorWarning")] @@ -465,7 +489,6 @@ public void SetTextureSettings(TextureImporterSettings src) internal extern bool removeMatte { get; set; } public extern bool ignorePngGamma { get; set; } - internal static readonly int MaxTextureSizeAllowedForReadable = 8192; //keep in sync with TextureImporter.h // This is for remapping Sprite that are renamed. extern internal bool GetNameFromInternalIDMap(long id, ref string name); diff --git a/Editor/Mono/AssetPipeline/TextureImporterEnums.cs b/Editor/Mono/AssetPipeline/TextureImporterEnums.cs index dad91871d1..796c0dbf18 100644 --- a/Editor/Mono/AssetPipeline/TextureImporterEnums.cs +++ b/Editor/Mono/AssetPipeline/TextureImporterEnums.cs @@ -382,4 +382,12 @@ public enum TextureImporterSwizzle Zero = 8, One = 9 } + + // Cookie light type mode for [[TextureImporter]]. + internal enum TextureImporterCookieLightType + { + Spot = 0, + Directional = 1, + Point = 2 + } } diff --git a/Editor/Mono/AssetPipeline/TextureImporterTypes.bindings.cs b/Editor/Mono/AssetPipeline/TextureImporterTypes.bindings.cs index b3cb72d8c6..9132f3c1ab 100644 --- a/Editor/Mono/AssetPipeline/TextureImporterTypes.bindings.cs +++ b/Editor/Mono/AssetPipeline/TextureImporterTypes.bindings.cs @@ -6,6 +6,7 @@ using System.Runtime.InteropServices; using UnityEngine; using UnityEngine.Bindings; +using UnityEngine.Serialization; namespace UnityEditor { @@ -70,8 +71,8 @@ public sealed class TextureImporterSettings [SerializeField] int m_VTOnly; - [SerializeField] - int m_IgnoreMasterTextureLimit; + [SerializeField, FormerlySerializedAs("m_IgnoreMasterTextureLimit")] + int m_IgnoreMipmapLimit; [SerializeField] int m_NPOTScale; @@ -125,6 +126,9 @@ public sealed class TextureImporterSettings [SerializeField] int m_IgnorePngGamma; + [SerializeField] + int m_CookieMode; + // memory layout of these is in TextureSettings.h [SerializeField] [NativeName("m_TextureSettings.m_FilterMode")] @@ -344,10 +348,10 @@ public bool vtOnly get { return m_VTOnly != 0; } set { m_VTOnly = value ? 1 : 0; } } - internal bool ignoreMasterTextureLimit + public bool ignoreMipmapLimit { - get { return m_IgnoreMasterTextureLimit != 0; } - set { m_IgnoreMasterTextureLimit = value ? 1 : 0; } + get { return m_IgnoreMipmapLimit != 0; } + set { m_IgnoreMipmapLimit = value ? 1 : 0; } } public TextureImporterNPOTScale npotScale @@ -600,6 +604,8 @@ public sealed partial class TextureImporterPlatformSettings [SerializeField] int m_Overridden = 0; [SerializeField] + int m_IgnorePlatformSupport = 0; + [SerializeField] int m_MaxTextureSize = 2048; [SerializeField] int m_ResizeAlgorithm = (int)TextureResizeAlgorithm.Mitchell; @@ -632,6 +638,12 @@ public bool overridden set { m_Overridden = value ? 1 : 0; } } + public bool ignorePlatformSupport + { + get { return m_IgnorePlatformSupport != 0; } + set { m_IgnorePlatformSupport = value ? 1 : 0; } + } + public int maxTextureSize { get { return m_MaxTextureSize; } diff --git a/Editor/Mono/AssetPreviewUpdater.cs b/Editor/Mono/AssetPreviewUpdater.cs index bed2f81cdc..3bab2dafd2 100644 --- a/Editor/Mono/AssetPreviewUpdater.cs +++ b/Editor/Mono/AssetPreviewUpdater.cs @@ -3,6 +3,8 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Scripting; namespace UnityEditor { @@ -19,11 +21,11 @@ public static Texture2D CreatePreview(Object obj, Object[] subAssets, string ass if (obj == null) return null; - System.Type type = CustomEditorAttributes.FindCustomEditorType(obj, false); + var type = CustomEditorAttributes.FindCustomEditorType(obj, false); if (type == null) return null; - System.Reflection.MethodInfo info = type.GetMethod("RenderStaticPreview"); + var info = type.GetMethod("RenderStaticPreview"); if (info == null) { Debug.LogError("Fail to find RenderStaticPreview base method"); @@ -33,13 +35,11 @@ public static Texture2D CreatePreview(Object obj, Object[] subAssets, string ass if (info.DeclaringType == typeof(Editor)) return null; - - Editor editor = Editor.CreateEditor(obj); - + var editor = Editor.CreateEditor(obj); if (editor == null) return null; - - Texture2D tex = editor.RenderStaticPreview(assetPath, subAssets, width, height); + + var previewTexture = editor.RenderStaticPreview(assetPath, subAssets, width, height); // For debugging we write the preview to a file (keep) //{ @@ -50,8 +50,7 @@ public static Texture2D CreatePreview(Object obj, Object[] subAssets, string ass //} Object.DestroyImmediate(editor); - - return tex; + return previewTexture; } } } diff --git a/Editor/Mono/AttributeHelper.cs b/Editor/Mono/AttributeHelper.cs index a7aa63dac9..95d72e45d3 100644 --- a/Editor/Mono/AttributeHelper.cs +++ b/Editor/Mono/AttributeHelper.cs @@ -145,7 +145,7 @@ struct MonoCreateAssetItem } [RequiredByNativeCode] - static MonoCreateAssetItem[] ExtractCreateAssetMenuItems(Assembly assembly) + static MonoCreateAssetItem[] ExtractCreateAssetMenuItems() { var result = new List(); @@ -166,6 +166,11 @@ static MonoCreateAssetItem[] ExtractCreateAssetMenuItems(Assembly assembly) if (!System.IO.Path.HasExtension(fileName)) fileName = fileName + ".asset"; + // trim the trailing space from the menu name: + // 1. visually it is hard to differentialte menu names with or without spaces. + // 2. when asset menu is searched, it will search the trimmed menu name, so it will create a edge case where a menu name with space could not be found after creation. + menuItemName = menuItemName.TrimEnd(); + var item = new MonoCreateAssetItem { menuItem = menuItemName, diff --git a/Editor/Mono/Audio/Mixer/GUI/AudioMixerWindow.cs b/Editor/Mono/Audio/Mixer/GUI/AudioMixerWindow.cs index 199bacfb82..925f4f8075 100644 --- a/Editor/Mono/Audio/Mixer/GUI/AudioMixerWindow.cs +++ b/Editor/Mono/Audio/Mixer/GUI/AudioMixerWindow.cs @@ -136,11 +136,11 @@ static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAsse void UpdateAfterAssetChange() { + m_AllControllers = FindAllAudioMixerControllers(); + if (m_MixersTree == null) Init(); - m_AllControllers = FindAllAudioMixerControllers(); - if (m_Controller != null) { m_Controller.SanitizeGroupViews(); @@ -238,8 +238,14 @@ static List FindAllAudioMixerControllers() foreach (var prop in AssetDatabase.FindAllAssets(new SearchFilter() { classNames = new[] { "AudioMixerController" } })) { var controller = prop.pptrValue as AudioMixerController; + if (controller) - result.Add(controller); + { + if (controller.HasValidSnapshots()) + result.Add(controller); + else + Debug.LogError($"Can not display audio mixer window for '{controller.name}' as it could not be properly initialized. The mixer asset is possibly corrupted."); + } } return result; } @@ -341,6 +347,12 @@ public void UndoRedoPerformed(in UndoRedoInfo info) void OnMixerControllerChanged() { + if (m_Controller != null && !m_Controller.HasValidSnapshots()) + { + Debug.LogError($"Can not display audio mixer window for '{m_Controller.name}' as it could not be properly initialized. The mixer asset is possibly corrupted."); + return; + } + if (m_Controller) m_Controller.ClearEventHandlers(); @@ -369,9 +381,15 @@ void DetectControllerChange() AudioMixerController oldController = m_Controller; if (Selection.activeObject is AudioMixerController) m_Controller = Selection.activeObject as AudioMixerController; + if (m_Controller != oldController) { - OnMixerControllerChanged(); + if (m_Controller.HasValidSnapshots()) + OnMixerControllerChanged(); + else + { + Debug.LogError($"Can not display audio mixer window for '{m_Controller.name}' as it could not be properly initialized. The mixer asset is possibly corrupted."); + } } } diff --git a/Editor/Mono/Audio/Mixer/GUI/TreeViewForAudioMixerGroups.cs b/Editor/Mono/Audio/Mixer/GUI/TreeViewForAudioMixerGroups.cs index f578bd06d9..6c06fbcaa7 100644 --- a/Editor/Mono/Audio/Mixer/GUI/TreeViewForAudioMixerGroups.cs +++ b/Editor/Mono/Audio/Mixer/GUI/TreeViewForAudioMixerGroups.cs @@ -243,7 +243,7 @@ public override void FetchData() List allowedInstanceIDs = ObjectSelector.get.allowedInstanceIDs; var controllers = new List(); - foreach (var prop in AssetDatabase.FindAllAssets(new SearchFilter() { classNames = new[] { "AudioMixerController" } })) + foreach (var prop in AssetDatabase.FindAllAssets(new SearchFilter() { classNames = new[] { "AudioMixerController" }, searchArea = SearchFilter.SearchArea.AllAssets })) { var controller = prop.pptrValue as AudioMixerController; if (ShouldShowController(controller, allowedInstanceIDs)) diff --git a/Editor/Mono/Audio/UIElements/OnAudioFilterReadLevelMeter.cs b/Editor/Mono/Audio/UIElements/OnAudioFilterReadLevelMeter.cs new file mode 100644 index 0000000000..121fe6ec90 --- /dev/null +++ b/Editor/Mono/Audio/UIElements/OnAudioFilterReadLevelMeter.cs @@ -0,0 +1,25 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using UnityEngine; +using UnityEngine.UIElements; + +namespace UnityEditor.Audio.UIElements +{ + internal class OnAudioFilterReadLevelMeter : IMGUIContainer + { + AudioFilterGUI m_IMGUI_AudioFilterGUI = new AudioFilterGUI(); + + public OnAudioFilterReadLevelMeter(MonoBehaviour behaviour) + { + onGUIHandler = () => + { + if (GUIView.current != null) + { + m_IMGUI_AudioFilterGUI.DrawAudioFilterGUI(behaviour); + } + }; + } + } +} diff --git a/Editor/Mono/BuildPipeline.bindings.cs b/Editor/Mono/BuildPipeline.bindings.cs index 91304f909a..ee258d1a34 100644 --- a/Editor/Mono/BuildPipeline.bindings.cs +++ b/Editor/Mono/BuildPipeline.bindings.cs @@ -183,7 +183,16 @@ public enum BuildAssetBundleOptions //AssetBundleAllowEditorOnlyScriptableObjects = 1 << 14, //Removes the Unity Version number in the Archive File & Serialized File headers during the build. - AssetBundleStripUnityVersion = 32768 // 1 << 15 + AssetBundleStripUnityVersion = 32768, // 1 << 15 + + // Calculate bundle hash on the bundle content + UseContentHash = 65536, // 1 << 16 + + // Use when AssetBundle dependencies need to be calculated recursively, such as when you have a dependency chain of matching typed Scriptable Objects + RecurseDependencies = 131072, // 1 << 17 + + // Sprites are normally copied to all bundles that reference them. This flag prevents that behavior if the sprite is not in an atlas. + StripUnatlasedSpriteCopies = 262144 // 1 << 18 } // Keep in sync with CanAppendBuild in EditorUtility.h @@ -235,6 +244,16 @@ public enum PlayerConnectionInitiateMode PlayerListens } + [StructLayout(LayoutKind.Sequential)] + public struct BuildAssetBundlesParameters + { + public string outputPath { get; set; } + public AssetBundleBuild[] bundleDefinitions { get; set; } + public BuildAssetBundleOptions options { get; set; } + public BuildTarget targetPlatform { get; set; } + public int subtarget { get; set; } + } + // Lets you programmatically build players or AssetBundles which can be loaded from the web. [NativeHeader("Editor/Mono/BuildPipeline.bindings.h")] [StaticAccessor("BuildPipeline", StaticAccessorType.DoubleColon)] @@ -243,6 +262,7 @@ public class BuildPipeline [FreeFunction(IsThreadSafe = true)] public static extern BuildTargetGroup GetBuildTargetGroup(BuildTarget platform); + // Uses the implementation in "BuildPipeline.bindings.h" internal static extern BuildTargetGroup GetBuildTargetGroupByName(string platform); internal static extern BuildTarget GetBuildTargetByName(string platform); @@ -354,10 +374,33 @@ private static BuildReport BuildPlayer(string[] scenes, string locationPathName, scenes[i] = scenes[i].Replace('\\', '/').Replace("//", "/"); } + if ((options & BuildOptions.Development) == 0) + { + if ((options & BuildOptions.AllowDebugging) != 0) + { + throw new ArgumentException("Non-development build cannot allow debugging. Either add the Development build option, or remove the AllowDebugging build option."); + } + + if ((options & BuildOptions.EnableDeepProfilingSupport) != 0) + { + throw new ArgumentException("Non-development build cannot allow deep profiling support. Either add the Development build option, or remove the EnableDeepProfilingSupport build option."); + } + + if ((options & BuildOptions.ConnectWithProfiler) != 0) + { + throw new ArgumentException("Non-development build cannot allow auto-connecting the profiler. Either add the Development build option, or remove the ConnectWithProfiler build option."); + } + } + try { return BuildPlayerInternal(scenes, locationPathName, assetBundleManifestPath, buildTargetGroup, target, subtarget, options, extraScriptingDefines); } + catch (System.ArgumentException argumentException) + { + Debug.LogException(argumentException); + return null; + } catch (System.Exception exception) { // In some case BuildPlayer might let a null reference exception fall through. Prevent data loss by just exiting. @@ -475,8 +518,16 @@ public static string BuildStreamedSceneAssetBundle(string[] levels, string locat private static BuildReport BuildPlayerInternal(string[] levels, string locationPathName, string assetBundleManifestPath, BuildTargetGroup buildTargetGroup, BuildTarget target, int subtarget, BuildOptions options, string[] extraScriptingDefines) { - if (!BuildPlayerWindow.DefaultBuildMethods.IsBuildPathValid(locationPathName)) - throw new Exception("Invalid Build Path: " + locationPathName); + if (!BuildPlayerWindow.DefaultBuildMethods.IsBuildPathValid(locationPathName, out var msg)) + throw new ArgumentException($"Invalid build path: '{locationPathName}'. {msg}"); + + if (buildTargetGroup == BuildTargetGroup.Standalone) + { + if (subtarget == (int)StandaloneBuildSubtarget.NoSubtarget) + subtarget = (int)EditorUserBuildSettings.standaloneBuildSubtarget; + + EditorUserBuildSettings.standaloneBuildSubtarget = (StandaloneBuildSubtarget)subtarget; + } return BuildPlayerInternalNoCheck(levels, locationPathName, assetBundleManifestPath, buildTargetGroup, target, subtarget, options, extraScriptingDefines, false); } @@ -618,14 +669,37 @@ internal static AssetBundleManifest BuildAssetBundles(string outputPath, AssetBu if (builds == null) throw new ArgumentException("AssetBundleBuild cannot be null."); + // For Standalone platforms, the Default subtarget means to use the current active one + if (targetPlatformGroup == BuildTargetGroup.Standalone && + (StandaloneBuildSubtarget)subtarget == StandaloneBuildSubtarget.NoSubtarget) + { + subtarget = EditorUserBuildSettings.GetActiveSubtargetFor(targetPlatform); + } + return BuildAssetBundlesWithInfoInternal(outputPath, builds, assetBundleOptions, targetPlatformGroup, targetPlatform, subtarget); } + [NativeThrows] + public static AssetBundleManifest BuildAssetBundles(BuildAssetBundlesParameters buildParameters) + { + if (buildParameters.targetPlatform == 0 || buildParameters.targetPlatform == BuildTarget.NoTarget) + { + buildParameters.targetPlatform = EditorUserBuildSettings.activeBuildTarget; + + // Note: subtarget is associated with multiple enums, and 0 may have a specific meaning, + // so we only auto-set it when the target is also coming from the build settings + buildParameters.subtarget = EditorUserBuildSettings.GetActiveSubtargetFor(buildParameters.targetPlatform); + } + + BuildTargetGroup targetPlatformGroup = BuildPipeline.GetBuildTargetGroup(buildParameters.targetPlatform); + return BuildAssetBundles(buildParameters.outputPath, buildParameters.bundleDefinitions, buildParameters.options, targetPlatformGroup, buildParameters.targetPlatform, buildParameters.subtarget); + } + [NativeThrows] private static extern AssetBundleManifest BuildAssetBundlesWithInfoInternal(string outputPath, AssetBundleBuild[] builds, BuildAssetBundleOptions assetBundleOptions, BuildTargetGroup targetPlatformGroup, BuildTarget targetPlatform, int subtarget); [FreeFunction("GetPlayerDataSessionId")] - internal static extern string GetSessionIdForBuildTarget(BuildTarget target); + internal static extern string GetSessionIdForBuildTarget(BuildTarget target, int subtarget); [FreeFunction("ExtractCRCFromAssetBundleManifestFile")] public static extern bool GetCRCForAssetBundle(string targetPath, out uint crc); @@ -703,6 +777,7 @@ private static bool DoesBuildTargetSupportPlayerConnectionPlayerToEditor(BuildTa targetPlatform == BuildTarget.StandaloneWindows || targetPlatform == BuildTarget.StandaloneWindows64 || targetPlatform == BuildTarget.StandaloneLinux64 || + targetPlatform == BuildTarget.iOS || // Android: support connection from player to Editor in both cases // connecting to 127.0.0.1 (when both Editor and Android are on localhost using USB cable) // connecting to , the Android and PC has to be on the same subnet diff --git a/Editor/Mono/BuildPipeline/AssemblyStripper.cs b/Editor/Mono/BuildPipeline/AssemblyStripper.cs index 28518df3e3..7098eed0d1 100644 --- a/Editor/Mono/BuildPipeline/AssemblyStripper.cs +++ b/Editor/Mono/BuildPipeline/AssemblyStripper.cs @@ -620,7 +620,7 @@ private static void GetUnityLinkerPlatformStringsFromBuildTarget(BuildTarget tar platform = "Android"; architecture = ""; break; - case BuildTarget.CloudRendering: + case BuildTarget.LinuxHeadlessSimulation: case BuildTarget.StandaloneLinux64: platform = "Linux"; architecture = "x64"; diff --git a/Editor/Mono/BuildPipeline/BuildFailedException.cs b/Editor/Mono/BuildPipeline/BuildFailedException.cs index 5af2f66087..eb27406457 100644 --- a/Editor/Mono/BuildPipeline/BuildFailedException.cs +++ b/Editor/Mono/BuildPipeline/BuildFailedException.cs @@ -10,6 +10,18 @@ namespace UnityEditor.Build [RequiredByNativeCode] public class BuildFailedException : Exception { + // We can set the BuildFailedException to be silent if we know we have already printed an + // error message for the failure. That is the case when the BuildFailedException originates + // from the BeeBuildPostprocessor. That way we can avoid redundant error messages about builds + // failing. + private bool m_Silent; + + internal BuildFailedException(string message, bool silent = false) : + base(message) + { + m_Silent = silent; + } + public BuildFailedException(string message) : base(message) { @@ -20,6 +32,12 @@ public BuildFailedException(Exception innerException) : { } + [RequiredByNativeCode] + private bool IsSilent() + { + return m_Silent; + } + [RequiredByNativeCode] private Exception BuildFailedException_GetInnerException() { diff --git a/Editor/Mono/BuildPipeline/CodeStrippingUtils.cs b/Editor/Mono/BuildPipeline/CodeStrippingUtils.cs index d8860be6c8..4d0f0e86cb 100644 --- a/Editor/Mono/BuildPipeline/CodeStrippingUtils.cs +++ b/Editor/Mono/BuildPipeline/CodeStrippingUtils.cs @@ -225,6 +225,11 @@ public static string[] UserAssemblies { targetAssemblyNames[allTargetAssemblies.Length + i] = s_TreatedAsUserAssemblies[i]; } + for (int i=0; i(buildReportPath.ReadAllText()), - scenes = scenes, - buildOptions = buildOptions - }; - return tracker.DoCheckDirty(); + DataBuildDirtyTracker tracker = new DataBuildDirtyTracker() + { + buildData = JsonUtility.FromJson(buildReportPath.ReadAllText()), + scenes = scenes, + buildOptions = buildOptions + }; + return tracker.DoCheckDirty(); + } + catch (Exception e) + { + Console.WriteLine($"Rebuilding Data files because the build data file is corrupt: {e}"); + return true; + } } } } diff --git a/Editor/Mono/BuildPipeline/DesktopStandaloneBuildWindowExtension.cs b/Editor/Mono/BuildPipeline/DesktopStandaloneBuildWindowExtension.cs index 08000cf330..8f1b76e417 100644 --- a/Editor/Mono/BuildPipeline/DesktopStandaloneBuildWindowExtension.cs +++ b/Editor/Mono/BuildPipeline/DesktopStandaloneBuildWindowExtension.cs @@ -244,6 +244,11 @@ public override bool EnabledBuildButton() protected virtual string GetCannotBuildPlayerInCurrentSetupError() { var namedBuildTarget = EditorUserBuildSettingsUtils.CalculateSelectedNamedBuildTarget(); + return GetBuildPlayerError(namedBuildTarget); + } + + internal string GetBuildPlayerError(NamedBuildTarget namedBuildTarget) + { var scriptingBackend = PlayerSettings.GetScriptingBackend(namedBuildTarget); if (namedBuildTarget == NamedBuildTarget.Server) diff --git a/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs b/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs index 0dee4d8b75..51591cf7a3 100644 --- a/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs +++ b/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs @@ -5,6 +5,7 @@ using System; using UnityEditor; using UnityEditor.Build; +using UnityEditor.Build.Reporting; using UnityEditor.Modules; using UnityEditorInternal; using UnityEngine; @@ -69,31 +70,37 @@ protected DesktopStandalonePostProcessor(bool hasMonoPlayers, bool hasIl2CppPlay m_HasServerCoreCLRPlayers = hasServerCoreCLRPlayers; } - public override string PrepareForBuild(BuildOptions options, BuildTarget target) + public override string PrepareForBuild(BuildPlayerOptions buildOptions) { - var namedBuildTarget = NamedBuildTarget.FromActiveSettings(target); + var namedBuildTarget = NamedBuildTarget.FromActiveSettings(buildOptions.target); var isServer = namedBuildTarget == NamedBuildTarget.Server; switch (PlayerSettings.GetScriptingBackend(namedBuildTarget)) { case ScriptingImplementation.Mono2x: - if ((!isServer && !m_HasMonoPlayers) || (isServer && !m_HasServerMonoPlayers)) + if (!isServer && !m_HasMonoPlayers) return "Currently selected scripting backend (Mono) is not installed."; + if (isServer && !m_HasServerMonoPlayers) + return $"Dedicated Server support for {GetPlatformNameForBuildProgram(default)} is not installed."; break; case ScriptingImplementation.IL2CPP: - if ((!isServer && !m_HasIl2CppPlayers) || (isServer && !m_HasServerIl2CppPlayers)) + if (!isServer && !m_HasIl2CppPlayers) return "Currently selected scripting backend (IL2CPP) is not installed."; + if (isServer && !m_HasServerIl2CppPlayers) + return $"Dedicated Server support for {GetPlatformNameForBuildProgram(default)} is not installed."; break; #pragma warning disable 618 case ScriptingImplementation.CoreCLR: - if ((!isServer && !m_HasCoreCLRPlayers) || (isServer && !m_HasServerCoreCLRPlayers)) + if (!isServer && !m_HasCoreCLRPlayers) return "Currently selected scripting backend (CoreCLR) is not installed."; + if (isServer && !m_HasServerCoreCLRPlayers) + return $"Dedicated Server support for {GetPlatformNameForBuildProgram(default)} is not installed."; break; default: return $"Unknown scripting backend: {PlayerSettings.GetScriptingBackend(namedBuildTarget)}"; } - return base.PrepareForBuild(options, target); + return base.PrepareForBuild(buildOptions); } internal class ScriptingImplementations : DefaultScriptingImplementations diff --git a/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs b/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs index 60ab1ea335..a904539ad5 100644 --- a/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs +++ b/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs @@ -39,7 +39,17 @@ public abstract class Sysroot public abstract string GetSysrootPath(); public abstract string GetToolchainPath(); public abstract string GetIl2CppCompilerFlags(); + // The sysroot package does not currently contain an implemenation for this method, adding a default implementation to avoid breaking stuff + public virtual string[] GetIl2CppAdditionalLibraries() => Array.Empty(); + // The sysroot package does not currently contain an implemenation for this method, adding a default implementation to avoid breaking stuff + public virtual string[] GetIl2CppAdditionalDefines() => Array.Empty(); + // The sysroot package does not currently contain an implemenation for this method, adding a default implementation to avoid breaking stuff + public virtual string[] GetIl2CppAdditionalIncludeDirectories() => Array.Empty(); + // The sysroot package does not currently contain an implemenation for this method, adding a default implementation to avoid breaking stuff + public virtual string[] GetIl2CppAdditionalLinkDirectories() => Array.Empty(); public abstract string GetIl2CppLinkerFlags(); + // The sysroot package does not currently contain an implemenation for this method, adding a default implementation to avoid breaking stuff + public virtual string GetIl2CppLinkerFlagsFile() => null; } } @@ -109,7 +119,7 @@ private static bool GetTargetPlatformAndArchFromBuildTarget(BuildTarget target, switch (target) { case BuildTarget.StandaloneLinux64: - case BuildTarget.CloudRendering: + case BuildTarget.LinuxHeadlessSimulation: targetPlatform = "linux"; targetArch = "x86_64"; return true; diff --git a/Editor/Mono/BuildPipeline/MonoInternalCallGenerator.cs b/Editor/Mono/BuildPipeline/MonoInternalCallGenerator.cs index 41cde7c918..9f25c4d127 100644 --- a/Editor/Mono/BuildPipeline/MonoInternalCallGenerator.cs +++ b/Editor/Mono/BuildPipeline/MonoInternalCallGenerator.cs @@ -112,7 +112,7 @@ static public void WriteCPlusPlusFileForStaticAOTModuleRegistration(BuildTarget } w.WriteLine(""); - w.WriteLine("#if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR"); + w.WriteLine("#if (defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR) || (defined(TARGET_VISIONOS_SIMULATOR) && TARGET_VISIONOS_SIMULATOR)"); w.WriteLine(" #define DECL_USER_FUNC(f) void f() __attribute__((weak_import))"); w.WriteLine(" #define REGISTER_USER_FUNC(f)\\"); w.WriteLine(" do {\\"); @@ -152,7 +152,7 @@ static public void WriteCPlusPlusFileForStaticAOTModuleRegistration(BuildTarget w.WriteLine("#define DLL_EXPORT"); w.WriteLine("#endif"); - w.WriteLine("#if !(TARGET_IPHONE_SIMULATOR)"); + w.WriteLine("#if !(TARGET_IPHONE_SIMULATOR || TARGET_VISIONOS_SIMULATOR)"); w.WriteLine(" extern gboolean mono_aot_only;"); for (int q = 0; q < fileNames.Length; ++q) @@ -165,7 +165,7 @@ static public void WriteCPlusPlusFileForStaticAOTModuleRegistration(BuildTarget w.WriteLine(" extern gpointer* mono_aot_module_{0}_info; // {1}", assemblyName, fileName); } - w.WriteLine("#endif // !(TARGET_IPHONE_SIMULATOR)"); + w.WriteLine("#endif // !(TARGET_IPHONE_SIMULATOR || TARGET_VISIONOS_SIMULATOR)"); foreach (string nmethod in nativeMethods) { w.WriteLine(" DECL_USER_FUNC({0});", nmethod); @@ -175,7 +175,7 @@ static public void WriteCPlusPlusFileForStaticAOTModuleRegistration(BuildTarget w.WriteLine("DLL_EXPORT void RegisterMonoModules()"); w.WriteLine("{"); - w.WriteLine("#if !(TARGET_IPHONE_SIMULATOR) && !defined(__arm64__)"); + w.WriteLine("#if !(TARGET_IPHONE_SIMULATOR || TARGET_VISIONOS_SIMULATOR) && !defined(__arm64__)"); w.WriteLine(" mono_aot_only = true;"); if (buildTarget == BuildTarget.iOS) @@ -192,7 +192,7 @@ static public void WriteCPlusPlusFileForStaticAOTModuleRegistration(BuildTarget w.WriteLine(" mono_aot_register_module(mono_aot_module_{0}_info);", assemblyName); } - w.WriteLine("#endif // !(TARGET_IPHONE_SIMULATOR) && !defined(__arm64__)"); + w.WriteLine("#endif // !(TARGET_IPHONE_SIMULATOR || TARGET_VISIONOS_SIMULATOR) && !defined(__arm64__)"); w.WriteLine(""); if (buildTarget == BuildTarget.iOS) diff --git a/Editor/Mono/BuildPipeline/NamedBuildTarget.cs b/Editor/Mono/BuildPipeline/NamedBuildTarget.cs index 6130437d89..ce563c1465 100644 --- a/Editor/Mono/BuildPipeline/NamedBuildTarget.cs +++ b/Editor/Mono/BuildPipeline/NamedBuildTarget.cs @@ -21,9 +21,11 @@ namespace UnityEditor.Build "PS4", "XboxOne", "tvOS", + "VisionOS", "Nintendo Switch", "Stadia", "CloudRendering", + "LinuxHeadlessSimulation", "Lumin", "GameCoreScarlett", "GameCoreXboxOne", @@ -42,9 +44,12 @@ namespace UnityEditor.Build public static readonly NamedBuildTarget PS4 = new NamedBuildTarget("PS4"); public static readonly NamedBuildTarget XboxOne = new NamedBuildTarget("XboxOne"); public static readonly NamedBuildTarget tvOS = new NamedBuildTarget("tvOS"); + public static readonly NamedBuildTarget VisionOS = new NamedBuildTarget("VisionOS"); public static readonly NamedBuildTarget NintendoSwitch = new NamedBuildTarget("Nintendo Switch"); public static readonly NamedBuildTarget Stadia = new NamedBuildTarget("Stadia"); - public static readonly NamedBuildTarget CloudRendering = new NamedBuildTarget("CloudRendering"); + public static readonly NamedBuildTarget LinuxHeadlessSimulation = new NamedBuildTarget("LinuxHeadlessSimulation"); + [System.Obsolete("CloudRendering is deprecated, please use LinuxHeadlessSimulation (UnityUpgradable) -> LinuxHeadlessSimulation", false)] + public static readonly NamedBuildTarget CloudRendering = LinuxHeadlessSimulation; public static readonly NamedBuildTarget EmbeddedLinux = new NamedBuildTarget("EmbeddedLinux"); public static readonly NamedBuildTarget QNX = new NamedBuildTarget("QNX"); @@ -93,20 +98,20 @@ public static NamedBuildTarget FromBuildTargetGroup(BuildTargetGroup buildTarget return NamedBuildTarget.XboxOne; case BuildTargetGroup.tvOS: return NamedBuildTarget.tvOS; + case BuildTargetGroup.VisionOS: + return NamedBuildTarget.VisionOS; case BuildTargetGroup.Switch: return NamedBuildTarget.NintendoSwitch; case BuildTargetGroup.Stadia: return NamedBuildTarget.Stadia; - case BuildTargetGroup.CloudRendering: - return NamedBuildTarget.CloudRendering; + case BuildTargetGroup.LinuxHeadlessSimulation: + return NamedBuildTarget.LinuxHeadlessSimulation; case BuildTargetGroup.EmbeddedLinux: return NamedBuildTarget.EmbeddedLinux; case BuildTargetGroup.QNX: return NamedBuildTarget.QNX; // Build targets that are not explicitly listed - case BuildTargetGroup.Lumin: - return new NamedBuildTarget("Lumin"); case BuildTargetGroup.GameCoreXboxSeries: return new NamedBuildTarget("GameCoreScarlett"); case BuildTargetGroup.GameCoreXboxOne: diff --git a/Editor/Mono/BuildPipeline/PostprocessBuildPlayer.cs b/Editor/Mono/BuildPipeline/PostprocessBuildPlayer.cs index de5dfd3a34..0ad4db775c 100644 --- a/Editor/Mono/BuildPipeline/PostprocessBuildPlayer.cs +++ b/Editor/Mono/BuildPipeline/PostprocessBuildPlayer.cs @@ -158,12 +158,12 @@ internal static string GetStreamingAssetsBundleManifestPath() return manifestPath; } - static public string PrepareForBuild(BuildOptions options, BuildTargetGroup targetGroup, BuildTarget target) + static public string PrepareForBuild(BuildPlayerOptions buildOptions) { - var postprocessor = ModuleManager.GetBuildPostProcessor(targetGroup, target); + var postprocessor = ModuleManager.GetBuildPostProcessor(buildOptions.targetGroup, buildOptions.target); if (postprocessor == null) return null; - return postprocessor.PrepareForBuild(options, target); + return postprocessor.PrepareForBuild(buildOptions); } [RequiredByNativeCode] @@ -373,10 +373,14 @@ static public void Postprocess(BuildTargetGroup targetGroup, BuildTarget target, { postprocessor.PostProcess(args, out props); } - catch (System.Exception e) + catch (BuildFailedException) + { + throw; + } + catch (Exception e) { // Rethrow exceptions during build postprocessing as BuildFailedException, so we don't pretend the build was fine. - throw new UnityEditor.Build.BuildFailedException(e); + throw new BuildFailedException(e); } report.AddAppendix(props); diff --git a/Editor/Mono/BuildPipeline/RuntimeClassMetadata.cs b/Editor/Mono/BuildPipeline/RuntimeClassMetadata.cs index 08b7c66406..1b385e54ef 100644 --- a/Editor/Mono/BuildPipeline/RuntimeClassMetadata.cs +++ b/Editor/Mono/BuildPipeline/RuntimeClassMetadata.cs @@ -51,8 +51,16 @@ public void AddNativeClassID(int ID) public void SetUsedTypesInUserAssembly(string[] typeNames, string assemblyName) { - if (!m_UsedTypesPerUserAssembly.TryGetValue(assemblyName, out HashSet types)) - m_UsedTypesPerUserAssembly[assemblyName] = types = new HashSet(); + string assemblyFileName = assemblyName; + if (!assemblyFileName.EndsWith(".dll")) + { + assemblyFileName = assemblyName + ".dll"; + } + + if (!m_UsedTypesPerUserAssembly.TryGetValue(assemblyFileName, out HashSet types)) + { + m_UsedTypesPerUserAssembly[assemblyFileName] = types = new HashSet(); + } foreach (var typeName in typeNames) types.Add(typeName); @@ -142,11 +150,23 @@ public Dictionary GetAllManagedTypesInScenes() engineModuleTypes.Add(managedName); } - items.Add("UnityEngine.dll", engineModuleTypes.ToArray()); + bool engineModuleTypesAdded = false; foreach (var userAssembly in m_UsedTypesPerUserAssembly) + { + if (userAssembly.Key == "UnityEngine.dll" && !engineModuleTypesAdded) + { + engineModuleTypes.UnionWith(userAssembly.Value); + items.Add(userAssembly.Key, engineModuleTypes.ToArray()); + engineModuleTypesAdded = true; + continue; + } items.Add(userAssembly.Key, userAssembly.Value.ToArray()); - + } + if (!engineModuleTypesAdded) + { + items.Add("UnityEngine.dll", engineModuleTypes.ToArray()); + } return items; } @@ -253,7 +273,7 @@ internal void AddUserAssembly(string assembly) internal string[] GetUserAssemblies() { - return m_UserAssemblies.ToArray(); + return m_UserAssemblies.Where(s => IsDLLUsed(s)).ToArray(); } } } diff --git a/Editor/Mono/BuildPipelineExperimental.cs b/Editor/Mono/BuildPipelineExperimental.cs index 3824c38380..340ca7d452 100644 --- a/Editor/Mono/BuildPipelineExperimental.cs +++ b/Editor/Mono/BuildPipelineExperimental.cs @@ -2,13 +2,16 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; + namespace UnityEditor.Experimental { + [Obsolete("BuildPipelineExperimental is no longer supported and will be removed")] public static class BuildPipelineExperimental { public static string GetSessionIdForBuildTarget(BuildTarget target) { - return BuildPipeline.GetSessionIdForBuildTarget(target); + return BuildPipeline.GetSessionIdForBuildTarget(target, 0); } } } diff --git a/Editor/Mono/BuildPlayerSceneTreeView.cs b/Editor/Mono/BuildPlayerSceneTreeView.cs index c2aacb8a9c..115c8792d7 100644 --- a/Editor/Mono/BuildPlayerSceneTreeView.cs +++ b/Editor/Mono/BuildPlayerSceneTreeView.cs @@ -13,21 +13,22 @@ internal class BuildPlayerSceneTreeViewItem : TreeViewItem { private const string kAssetsFolder = "Assets/"; private const string kSceneExtension = ".unity"; - public static int kInvalidCounter = -1; + private string m_FullName; + public bool active; public int counter; - public string fullName; - public GUID guid; - public void UpdateName() + public string fullName { - var name = AssetDatabase.GUIDToAssetPath(guid.ToString()); - if (name != fullName) + get => m_FullName; + set { - fullName = name; + if (m_FullName == value) + return; - displayName = fullName; + m_FullName = value; + displayName = m_FullName; if (displayName.StartsWith(kAssetsFolder)) displayName = displayName.Remove(0, kAssetsFolder.Length); var ext = displayName.LastIndexOf(kSceneExtension); @@ -35,13 +36,21 @@ public void UpdateName() displayName = displayName.Substring(0, ext); } } + public GUID guid; + + public void UpdateName() + { + var name = AssetDatabase.GUIDToAssetPath(guid.ToString()); + if (!string.IsNullOrEmpty(name) && name != fullName) + fullName = name; + } - public BuildPlayerSceneTreeViewItem(int id, int depth, GUID g, bool state) : base(id, depth) + public BuildPlayerSceneTreeViewItem(EditorBuildSettingsScene scene) : base(scene.guid.GetHashCode(), 0) { - active = state; + active = scene.enabled; counter = kInvalidCounter; - guid = g; - fullName = ""; + guid = scene.guid; + fullName = scene.path; UpdateName(); } } @@ -71,7 +80,7 @@ protected override TreeViewItem BuildRoot() List scenes = new List(EditorBuildSettings.scenes); foreach (var sc in scenes) { - var item = new BuildPlayerSceneTreeViewItem(sc.guid.GetHashCode(), 0, sc.guid, sc.enabled); + var item = new BuildPlayerSceneTreeViewItem(sc); root.AddChild(item); } return root; @@ -314,6 +323,11 @@ public EditorBuildSettingsScene[] GetSceneList() { var sceneItem = rootItem.children[index] as BuildPlayerSceneTreeViewItem; sceneList[index] = new EditorBuildSettingsScene(sceneItem.fullName, sceneItem.active); + + // If the scene was deleted AssetPathToGUID may not work and the guid will be lost + // In that case restore it to the previous value + if (sceneList[index].guid.Empty() && !sceneItem.guid.Empty()) + sceneList[index].guid = sceneItem.guid; } return sceneList; } diff --git a/Editor/Mono/BuildPlayerWindow.cs b/Editor/Mono/BuildPlayerWindow.cs index c560f60427..0f1ce8fd5b 100644 --- a/Editor/Mono/BuildPlayerWindow.cs +++ b/Editor/Mono/BuildPlayerWindow.cs @@ -47,6 +47,7 @@ class Styles public string noModuleLoaded = L10n.Tr("No {0} module loaded."); public GUIContent openDownloadPage = EditorGUIUtility.TrTextContent("Open Download Page"); public GUIContent installModuleWithHub = EditorGUIUtility.TrTextContent("Install with Unity Hub"); + public string EditorWillNeedToBeReloaded = L10n.Tr("Note: Editor will need to be restarted to load any newly installed modules"); public string infoText = L10n.Tr("{0} is not included in your Unity Pro license. Your {0} build will include a Unity Personal Edition splash screen.\n\nYou must be eligible to use Unity Personal Edition to use this build option. Please refer to our EULA for further information."); public GUIContent eula = EditorGUIUtility.TrTextContent("Eula"); public string addToYourPro = L10n.Tr("Add {0} to your Unity Pro license"); @@ -69,14 +70,14 @@ public GUIContent GetDownloadErrorForTarget(BuildTarget target) public GUIContent autoconnectProfiler = EditorGUIUtility.TrTextContent("Autoconnect Profiler", "When the build is started, an open Profiler Window will automatically connect to the Player and start profiling. The \"Build And Run\" option will also automatically open the Profiler Window."); public GUIContent autoconnectProfilerDisabled = EditorGUIUtility.TrTextContent("Autoconnect Profiler", "Profiling is only enabled in a Development Player."); public GUIContent buildWithDeepProfiler = EditorGUIUtility.TrTextContent("Deep Profiling Support", "Build Player with Deep Profiling Support. This might affect Player performance."); - public GUIContent buildWithDeepProfilerDisabled = EditorGUIUtility.TrTextContent("Deep Profiling", "Profiling is only enabled in a Development Player."); + public GUIContent buildWithDeepProfilerDisabled = EditorGUIUtility.TrTextContent("Deep Profiling Support", "Profiling is only enabled in a Development Player."); public GUIContent allowDebugging = EditorGUIUtility.TrTextContent("Script Debugging", "Enable this setting to allow your script code to be debugged."); public GUIContent waitForManagedDebugger = EditorGUIUtility.TrTextContent("Wait For Managed Debugger", "Show a dialog where you can attach a managed debugger before any script execution. Can also use volume Up or Down button to confirm on Android."); public GUIContent managedDebuggerFixedPort = EditorGUIUtility.TrTextContent("Managed Debugger Fixed Port", "Use the specified port to attach to the managed debugger. If 0, the port will be automatically selected."); public GUIContent explicitNullChecks = EditorGUIUtility.TrTextContent("Explicit Null Checks"); public GUIContent explicitDivideByZeroChecks = EditorGUIUtility.TrTextContent("Divide By Zero Checks"); public GUIContent explicitArrayBoundsChecks = EditorGUIUtility.TrTextContent("Array Bounds Checks"); - public GUIContent learnAboutUnityCloudBuild = EditorGUIUtility.TrTextContent("Learn about Unity Cloud Build"); + public GUIContent learnAboutUnityCloudBuild = EditorGUIUtility.TrTextContent("Learn about Unity Build Automation"); public GUIContent compressionMethod = EditorGUIUtility.TrTextContent("Compression Method", "Compression applied to Player data (scenes and resources).\nDefault - none or default platform compression.\nLZ4 - fast compression suitable for Development Builds.\nLZ4HC - higher compression rate variance of LZ4, causes longer build times. Works best for Release Builds."); public readonly GUIContent assetImportOverrides = EditorGUIUtility.TrTextContent("Asset Import Overrides", "Asset import overrides for local development. Reducing maximum texture size or compression settings can speed up asset imports and platform switches."); @@ -239,6 +240,10 @@ void AddOpenScenes() for (int i = 0; i < SceneManager.sceneCount; i++) { Scene scene = SceneManager.GetSceneAt(i); + + if (EditorSceneManager.IsAuthoringScene(scene)) + continue; + if (scene.path.Length == 0 && !EditorSceneManager.SaveScene(scene, "", false)) continue; @@ -810,6 +815,7 @@ void ShowBuildTargetSettings() Help.BrowseURL(url); } } + GUILayout.Label(styles.EditorWillNeedToBeReloaded, EditorStyles.wordWrappedMiniLabel); GUIBuildButtons(false, false, false, platform, postprocessor); return; } diff --git a/Editor/Mono/BuildPlayerWindowBuildMethods.cs b/Editor/Mono/BuildPlayerWindowBuildMethods.cs index eb36afe539..7f78736505 100644 --- a/Editor/Mono/BuildPlayerWindowBuildMethods.cs +++ b/Editor/Mono/BuildPlayerWindowBuildMethods.cs @@ -9,6 +9,7 @@ using System.Collections; using System.IO; using System; +using System.Linq; using UnityEditor.Build.Reporting; using UnityEditor.Connect; using UnityEditor.Profiling; @@ -97,10 +98,8 @@ internal static void CallBuildMethods(bool askForBuildLocation, BuildOptions def else DefaultBuildMethods.BuildPlayer(options); } - catch (BuildMethodException e) + catch (BuildMethodException) { - if (!string.IsNullOrEmpty(e.Message)) - Debug.LogError(e); } finally { @@ -190,8 +189,8 @@ public static void BuildPlayer(BuildPlayerOptions options) // it should not be automatically deleted by the Unity Editor, even if it is empty (case 1073851) if (options.target != BuildTarget.XboxOne && !locationPathExistedBeforeBuild) DeleteBuildFolderIfEmpty(report.summary.outputPath); - Debug.LogError(resultStr); - throw new BuildMethodException(report.SummarizeErrors()); + Debug.LogError(resultStr + "\n" + report.SummarizeErrors()); + throw new BuildMethodException(); default: Debug.Log(resultStr); break; @@ -315,7 +314,7 @@ internal static BuildPlayerOptions GetBuildPlayerOptionsInternal(bool askForBuil EditorBuildSettingsScene[] editorScenes = EditorBuildSettings.scenes; foreach (EditorBuildSettingsScene scene in editorScenes) { - if (scene.enabled) + if (scene.enabled && !string.IsNullOrEmpty(scene.path)) scenesList.Add(scene.path); } @@ -361,10 +360,26 @@ private static bool PickBuildLocation(BuildTargetGroup targetGroup, BuildTarget } string title = "Build " + BuildPlatforms.instance.GetBuildTargetDisplayName(targetGroup, target, subtarget); - string path = EditorUtility.SaveBuildPanel(target, title, defaultFolder, defaultName, extension, out updateExistingBuild); - if (path == string.Empty) - return false; + string path; + bool isValidPath = false; + do + { + path = EditorUtility.SaveBuildPanel(target, title, defaultFolder, defaultName, extension, out updateExistingBuild); + if (path == string.Empty) + return false; + + if (IsBuildPathValid(path, out var msg)) + { + isValidPath = true; + } + else if (!EditorUtility.DisplayDialog("Invalid build path", msg, "Ok", "Cancel")) + { + Debug.LogError($"Invalid build path: '{path}'. {msg}"); + return false; + } + + } while (!isValidPath); if (isWindowsStandalone) { @@ -372,9 +387,6 @@ private static bool PickBuildLocation(BuildTargetGroup targetGroup, BuildTarget path = Path.Combine(path, Paths.MakeValidFileName(PlayerSettings.productName) + '.' + extension); } - if (!IsBuildPathValid(path)) - return false; - // Enforce extension if needed if (extension != string.Empty && FileUtil.GetPathExtension(path).ToLower() != extension) path += '.' + extension; @@ -415,14 +427,14 @@ private static string NormalizePath(string path) fullPath = string.IsNullOrEmpty(fullPath) ? string.Empty : Path.GetFullPath(fullPath); - fullPath = fullPath.ToLower(); if (Path.DirectorySeparatorChar == '/') return fullPath; return fullPath.Replace(Path.DirectorySeparatorChar, '/'); } - internal static bool IsBuildPathValid(string path) + internal static bool IsBuildPathValid(string path, out string errorMessage) { + errorMessage = default; var cleanedPath = NormalizePath(path); if (cleanedPath.Equals(string.Empty) && IsInstallInBuildFolderOption()) @@ -430,22 +442,37 @@ internal static bool IsBuildPathValid(string path) var basePath = NormalizePath(Application.dataPath + "/../"); - var assetsPath = NormalizePath(basePath + "/Assets"); - var settingsPath = NormalizePath(basePath + "/ProjectSettings"); - var tempPath = NormalizePath(basePath + "/Temp"); - var libraryPath = NormalizePath(basePath + "/Library"); - var userSettingsPath = NormalizePath(basePath + "/UserSettings"); - var userDesktopPath = NormalizePath(Environment.GetFolderPath(Environment.SpecialFolder.Desktop)); + // Allow build into the Temp folder (used by Unity TestRunner) + var invalidPaths = new[] + { + NormalizePath(basePath + "/Assets"), + NormalizePath(basePath + "/ProjectSettings"), + NormalizePath(basePath + "/Library"), + NormalizePath(basePath + "/Packages"), + NormalizePath(basePath + "/UserSettings") + }; + + var invalidPath = invalidPaths.FirstOrDefault(p => cleanedPath.Contains(p, StringComparison.OrdinalIgnoreCase)); + if (!string.IsNullOrEmpty(invalidPath)) + { + var dirName = Path.GetFileName(invalidPath); + errorMessage = $"The '{dirName}' directory is an internal work directory of Unity and " + + "projects should not be built inside it. Please choose another directory for the build output."; + return false; + } - if (basePath.Contains(cleanedPath) || - cleanedPath == assetsPath || - cleanedPath == settingsPath || - cleanedPath == tempPath || - cleanedPath == libraryPath || - cleanedPath == userSettingsPath || - cleanedPath == userDesktopPath) + if (cleanedPath.Equals(basePath, StringComparison.OrdinalIgnoreCase)) + { + errorMessage = "The project root directory should not be used as a build output directory. " + + "Please create a subdirectory for the build output."; + return false; + } + + var userDesktopPath = NormalizePath(Environment.GetFolderPath(Environment.SpecialFolder.Desktop)); + if (cleanedPath.Equals(userDesktopPath, StringComparison.OrdinalIgnoreCase)) { - Debug.LogError("Invalid build path: " + cleanedPath); + errorMessage = "The desktop directory should not be used as a build output directory. " + + "Please create a subdirectory for the build output."; return false; } diff --git a/Editor/Mono/BuildTarget.cs b/Editor/Mono/BuildTarget.cs index 25d38dd15e..7ce014b8e9 100644 --- a/Editor/Mono/BuildTarget.cs +++ b/Editor/Mono/BuildTarget.cs @@ -21,7 +21,7 @@ public enum BuildTarget [System.Obsolete("StandaloneOSXIntel has been removed in 2017.3")] StandaloneOSXIntel = 4, - // Build a Windows standalone. + // Build a 32 bit Windows standalone. StandaloneWindows = 5, // *undocumented* @@ -56,7 +56,7 @@ public enum BuildTarget [System.Obsolete("StandaloneLinux has been removed in 2019.2")] StandaloneLinux = 17, - // Build a Windows x86_64 standalone. + // Build a Windows standalone. StandaloneWindows64 = 19, // *undocumented* @@ -118,12 +118,16 @@ public enum BuildTarget Switch = 38, + [System.Obsolete("Lumin has been removed in 2022.2")] Lumin = 39, Stadia = 40, + [System.Obsolete("CloudRendering is deprecated, please use LinuxHeadlessSimulation (UnityUpgradable) -> LinuxHeadlessSimulation", false)] CloudRendering = 41, + LinuxHeadlessSimulation = 41, // LinuxHeadlessSimulation intenionally set to the same as CloudRendering + [System.Obsolete("GameCoreScarlett is deprecated, please use GameCoreXboxSeries (UnityUpgradable) -> GameCoreXboxSeries", false)] GameCoreScarlett = 42, GameCoreXboxSeries = 42, // GameCoreXboxSeries intentionally set to the same as GameCoreScarlett @@ -135,6 +139,8 @@ public enum BuildTarget QNX = 46, + VisionOS = 47, + // obsolete identifiers. We're using different values so that ToString() works. [System.Obsolete("Use iOS instead (UnityUpgradable) -> iOS", true)] iPhone = -1, diff --git a/Editor/Mono/BuildTargetConverter.cs b/Editor/Mono/BuildTargetConverter.cs index 1bc2a4e4eb..ba4e7f88d4 100644 --- a/Editor/Mono/BuildTargetConverter.cs +++ b/Editor/Mono/BuildTargetConverter.cs @@ -22,7 +22,7 @@ internal static class BuildTargetConverter return RuntimePlatform.PS5; case BuildTarget.StandaloneLinux64: return RuntimePlatform.LinuxPlayer; - case BuildTarget.CloudRendering: + case BuildTarget.LinuxHeadlessSimulation: return RuntimePlatform.LinuxPlayer; case BuildTarget.StandaloneOSX: return RuntimePlatform.OSXPlayer; @@ -40,10 +40,10 @@ internal static class BuildTargetConverter return RuntimePlatform.IPhonePlayer; case BuildTarget.tvOS: return RuntimePlatform.tvOS; + case BuildTarget.VisionOS: + return RuntimePlatform.VisionOS; case BuildTarget.WebGL: return RuntimePlatform.WebGLPlayer; - case BuildTarget.Lumin: - return RuntimePlatform.Lumin; case BuildTarget.GameCoreXboxSeries: return RuntimePlatform.GameCoreXboxSeries; case BuildTarget.GameCoreXboxOne: diff --git a/Editor/Mono/BuildTargetGroup.cs b/Editor/Mono/BuildTargetGroup.cs index 3fd8a1415f..9244cf3241 100644 --- a/Editor/Mono/BuildTargetGroup.cs +++ b/Editor/Mono/BuildTargetGroup.cs @@ -95,11 +95,14 @@ public enum BuildTargetGroup Switch = 27, + [Obsolete("Lumin has been removed in 2022.2")] Lumin = 28, Stadia = 29, + [System.Obsolete("CloudRendering is deprecated, please use LinuxHeadlessSimulation (UnityUpgradable) -> LinuxHeadlessSimulation", false)] CloudRendering = 30, + LinuxHeadlessSimulation = 30, [System.Obsolete("GameCoreScarlett is deprecated, please use GameCoreXboxSeries (UnityUpgradable) -> GameCoreXboxSeries", false)] GameCoreScarlett = 31, @@ -111,5 +114,7 @@ public enum BuildTargetGroup EmbeddedLinux = 34, QNX = 35, + + VisionOS = 36, } } diff --git a/Editor/Mono/CSPreProcess.cs b/Editor/Mono/CSPreProcess.cs index 43feac90ed..a0be1a22c4 100644 --- a/Editor/Mono/CSPreProcess.cs +++ b/Editor/Mono/CSPreProcess.cs @@ -27,7 +27,9 @@ static bool Run(bool includeModules) const int logIdentifierForUnityEditorCompilationMessages = 2345; UnityEngine.Debug.RemoveLogEntriesByIdentifier(logIdentifierForUnityEditorCompilationMessages); foreach (var message in messages) - UnityEngine.Debug.LogCompilerMessage(message.message, message.file, message.line, message.column, true, message.type == CompilerMessageType.Error, logIdentifierForUnityEditorCompilationMessages); + { + UnityEngine.Debug.LogCompilerMessage(message.message, message.file, message.line, message.column, true, message.type == CompilerMessageType.Error, logIdentifierForUnityEditorCompilationMessages, 0); + } return exitcode == 0; } diff --git a/Editor/Mono/Clipboard/Clipboard.cs b/Editor/Mono/Clipboard/Clipboard.cs index 85cd6d777a..7d9a17970f 100644 --- a/Editor/Mono/Clipboard/Clipboard.cs +++ b/Editor/Mono/Clipboard/Clipboard.cs @@ -18,6 +18,72 @@ internal static class Clipboard { static ClipboardState m_State = new ClipboardState(); + public static bool hasLong + { + get + { + FetchState(); + m_State.FetchLong(); + return m_State.m_HasLong.Value; + } + } + + public static long longValue + { + get + { + FetchState(); + m_State.FetchLong(); + return m_State.m_ValueLong; + } + + set => EditorGUIUtility.systemCopyBuffer = value.ToString(); + } + + public static bool hasUlong + { + get + { + FetchState(); + m_State.FetchUlong(); + return m_State.m_HasUlong.Value; + } + } + + public static ulong uLongValue + { + get + { + FetchState(); + m_State.FetchUlong(); + return m_State.m_ValueUlong; + } + + set => EditorGUIUtility.systemCopyBuffer = value.ToString(); + } + + public static bool hasUint + { + get + { + FetchState(); + m_State.FetchUint(); + return m_State.m_HasUint.Value; + } + } + + public static uint uIntValue + { + get + { + FetchState(); + m_State.FetchUint(); + return m_State.m_ValueUint; + } + + set => EditorGUIUtility.systemCopyBuffer = value.ToString(); + } + public static bool hasInteger { get diff --git a/Editor/Mono/Clipboard/ClipboardContextMenu.cs b/Editor/Mono/Clipboard/ClipboardContextMenu.cs index b0d30b69e5..649a46d0cb 100644 --- a/Editor/Mono/Clipboard/ClipboardContextMenu.cs +++ b/Editor/Mono/Clipboard/ClipboardContextMenu.cs @@ -164,10 +164,64 @@ internal static void SetupPropertyCopyPaste(SerializedProperty property, Generic } break; case SerializedPropertyType.Integer: - SetupAction(property, menu, evt, - p => Clipboard.integerValue = p.intValue, - p => Clipboard.hasInteger, - p => p.intValue = Clipboard.integerValue); + { + switch (property.numericType) + { + case SerializedPropertyNumericType.Int64: + SetupAction(property, menu, evt, + p => Clipboard.longValue = p.longValue, + p => Clipboard.hasLong, + p => p.longValue = Clipboard.longValue); + break; + + case SerializedPropertyNumericType.UInt64: + SetupAction(property, menu, evt, + p => Clipboard.uLongValue = p.ulongValue, + p => Clipboard.hasUlong, + p => p.ulongValue = Clipboard.uLongValue); + break; + + case SerializedPropertyNumericType.UInt32: + SetupAction(property, menu, evt, + p => Clipboard.uIntValue = p.uintValue, + p => Clipboard.hasUint, + p => p.uintValue = Clipboard.uIntValue); + break; + + case SerializedPropertyNumericType.UInt16: + SetupAction(property, menu, evt, + p => Clipboard.uIntValue = (System.UInt16)p.uintValue, + p => Clipboard.hasUint, + p => p.uintValue = (System.UInt16)Clipboard.uIntValue); + break; + + case SerializedPropertyNumericType.Int16: + SetupAction(property, menu, evt, + p => Clipboard.integerValue = (System.Int16)p.intValue, + p => Clipboard.hasInteger, + p => p.intValue = (System.UInt16)Clipboard.integerValue); + break; + case SerializedPropertyNumericType.UInt8: + SetupAction(property, menu, evt, + p => Clipboard.uIntValue = (System.Byte)p.uintValue, + p => Clipboard.hasUint, + p => p.uintValue = (System.Byte)Clipboard.uIntValue); + break; + case SerializedPropertyNumericType.Int8: + SetupAction(property, menu, evt, + p => Clipboard.integerValue = (System.Byte)p.intValue, + p => Clipboard.hasInteger, + p => p.intValue = (System.Byte)Clipboard.integerValue); + break; + + default: + SetupAction(property, menu, evt, + p => Clipboard.integerValue = p.intValue, + p => Clipboard.hasInteger, + p => p.intValue = Clipboard.integerValue); + break; + } + } break; case SerializedPropertyType.Float: SetupAction(property, menu, evt, diff --git a/Editor/Mono/Clipboard/ClipboardParser.cs b/Editor/Mono/Clipboard/ClipboardParser.cs index e3c9134e83..56ca046e64 100644 --- a/Editor/Mono/Clipboard/ClipboardParser.cs +++ b/Editor/Mono/Clipboard/ClipboardParser.cs @@ -187,6 +187,30 @@ public static bool ParseQuaternion(string text, out Quaternion res) return int.TryParse(text, out res); } + internal static bool? ParseLong(string text, out long res) + { + res = 0; + if (string.IsNullOrEmpty(text)) + return false; + return long.TryParse(text, out res); + } + + internal static bool? ParseUlong(string text, out ulong res) + { + res = 0; + if (string.IsNullOrEmpty(text)) + return false; + return ulong.TryParse(text, out res); + } + + internal static bool? ParseUint(string text, out uint res) + { + res = 0; + if(string.IsNullOrEmpty(text)) + return false; + return uint.TryParse(text, out res); + } + internal static bool? ParseFloat(string text, out float res) { res = 0; diff --git a/Editor/Mono/Clipboard/ClipboardState.cs b/Editor/Mono/Clipboard/ClipboardState.cs index b55d4cd7a0..45d13f5463 100644 --- a/Editor/Mono/Clipboard/ClipboardState.cs +++ b/Editor/Mono/Clipboard/ClipboardState.cs @@ -167,6 +167,33 @@ internal void FetchInteger() m_HasInteger = ClipboardParser.ParseInteger(m_RawContents, out m_ValueInteger); } + internal bool? m_HasLong; + internal long m_ValueLong; + + internal void FetchLong() + { + if (!m_HasLong.HasValue) + m_HasLong = ClipboardParser.ParseLong(m_RawContents, out m_ValueLong); + } + + internal bool? m_HasUlong; + internal ulong m_ValueUlong; + + internal void FetchUlong() + { + if (!m_HasUlong.HasValue) + m_HasUlong = ClipboardParser.ParseUlong(m_RawContents, out m_ValueUlong); + } + + internal bool? m_HasUint; + internal uint m_ValueUint; + + internal void FetchUint() + { + if (!m_HasUint.HasValue) + m_HasUint = ClipboardParser.ParseUint(m_RawContents, out m_ValueUint); + } + internal bool? m_HasFloat; internal float m_ValueFloat; internal void FetchFloat() diff --git a/Editor/Mono/CodeEditor/CodeEditorAnalytics.cs b/Editor/Mono/CodeEditor/CodeEditorAnalytics.cs index 192aa02dd6..f8fb84d1c0 100644 --- a/Editor/Mono/CodeEditor/CodeEditorAnalytics.cs +++ b/Editor/Mono/CodeEditor/CodeEditorAnalytics.cs @@ -31,9 +31,6 @@ static bool EnableAnalytics() public static void SendCodeEditorUsage(IExternalCodeEditor codeEditor) { - if (!UnityEngine.Analytics.Analytics.enabled) - return; - if (!EnableAnalytics()) return; diff --git a/Editor/Mono/CodeEditor/CodeEditorProjectSync.cs b/Editor/Mono/CodeEditor/CodeEditorProjectSync.cs index e741864e7e..1d2132bbad 100644 --- a/Editor/Mono/CodeEditor/CodeEditorProjectSync.cs +++ b/Editor/Mono/CodeEditor/CodeEditorProjectSync.cs @@ -36,7 +36,7 @@ public static void PostprocessSyncProject( CodeEditor.Editor.CurrentCodeEditor.SyncIfNeeded(addedAssets, deletedAssets, movedAssets, movedFromAssetPaths, importedAssets); } - [MenuItem("Assets/Open C# Project")] + [MenuItem("Assets/Open C# Project", secondaryPriority = 1)] static void SyncAndOpenSolution() { // Ensure that the mono islands are up-to-date diff --git a/Editor/Mono/CodeEditor/DefaultExternalCodeEditor.cs b/Editor/Mono/CodeEditor/DefaultExternalCodeEditor.cs index 9dcb83225e..56dd6830d4 100644 --- a/Editor/Mono/CodeEditor/DefaultExternalCodeEditor.cs +++ b/Editor/Mono/CodeEditor/DefaultExternalCodeEditor.cs @@ -8,13 +8,15 @@ using Unity.CodeEditor; using UnityEditorInternal; using UnityEngine; +using UnityEngine.Assertions; +using NiceIO; namespace UnityEditor { internal class DefaultExternalCodeEditor : IExternalCodeEditor { static readonly GUIContent k_ResetArguments = EditorGUIUtility.TrTextContent("Reset argument"); - static readonly string[] supportedExtensions = {"json", "asmdef", "log", "cs", "uxml", "uss", "shader", "compute", "cginc", "hlsl", "glslinc", "template", "raytrace" }; + static readonly string[] supportedExtensions = { "json", "asmdef", "log", "cs", "uxml", "uss", "shader", "compute", "cginc", "hlsl", "glslinc", "template", "raytrace" }; static bool IsOSX => Application.platform == RuntimePlatform.OSXEditor; static bool IsWindows => Application.platform == RuntimePlatform.WindowsEditor; static bool IsLinux => Application.platform == RuntimePlatform.LinuxEditor; @@ -37,7 +39,7 @@ string Arguments // So on OSX we change the key for per application for script editor args, // to avoid reading the one from previous versions. // The year 2021: Delete mac hack. - if (Application.platform == RuntimePlatform.OSXEditor) + if (IsOSX) { var oldMac = EditorPrefs.GetString("kScriptEditorArgs_" + Installation); if (!string.IsNullOrEmpty(oldMac)) @@ -50,7 +52,7 @@ string Arguments } set { - if (Application.platform == RuntimePlatform.OSXEditor) + if (IsOSX) { EditorPrefs.SetString("kScriptEditorArgs_" + Installation, value); } @@ -134,11 +136,15 @@ public bool OpenProject(string path, int line, int column) return false; } - string applicationPath = CodeEditor.CurrentEditorPath.Trim(); + var applicationPath = CodeEditor.CurrentEditorPath.Trim(); + var doesNotExistWarning = + $"External Code Editor application path does not exist ({applicationPath})! Please select a different application."; - if (!string.IsNullOrEmpty(applicationPath) && !File.Exists(applicationPath)) + var npath = new NPath(applicationPath); + if (applicationPath == null || !npath.Exists()) { - UnityEngine.Debug.LogWarning($"External Code Editor application path does not exist ({applicationPath})! Please select a different application"); + UnityEngine.Debug.LogWarning(doesNotExistWarning); + return false; } if (applicationPath == CodeEditor.SystemDefaultPath) @@ -146,25 +152,21 @@ public bool OpenProject(string path, int line, int column) return InternalEditorUtility.OpenFileAtLineExternal(path, -1, -1); } - if (IsOSX) - { - return CodeEditor.OSOpenFile(applicationPath, CodeEditor.ParseArgument(Arguments, path, line, column)); - } - string fileName = ""; string arguments = ""; - - if (IsLinux) - { - fileName = applicationPath; - arguments = CodeEditor.ParseArgument(Arguments, path, line, column); - } - - if (IsWindows) + switch (Application.platform) { - fileName = "cmd.exe"; - arguments = "/C \"" + CodeEditor.QuoteForProcessStart(applicationPath) + - " " + CodeEditor.ParseArgument(Arguments, path, line, column) + "\""; + case RuntimePlatform.OSXEditor: + return CodeEditor.OSOpenFile(applicationPath, CodeEditor.ParseArgument(Arguments, path, line, column)); + case RuntimePlatform.LinuxEditor: + fileName = applicationPath; + arguments = CodeEditor.ParseArgument(Arguments, path, line, column); + break; + case RuntimePlatform.WindowsEditor: + fileName = "cmd.exe"; + arguments = "/C \"" + CodeEditor.QuoteForProcessStart(applicationPath) + + " " + CodeEditor.ParseArgument(Arguments, path, line, column) + "\""; + break; } var process = new Process diff --git a/Editor/Mono/Collab/CloudBuildStatus.cs b/Editor/Mono/Collab/CloudBuildStatus.cs deleted file mode 100644 index 6a88be7158..0000000000 --- a/Editor/Mono/Collab/CloudBuildStatus.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - - -using System; -using System.Runtime.InteropServices; -using UnityEngine.Scripting; - -namespace UnityEditor.Collaboration -{ - // Keep internal and undocumented until we expose more functionality - //*undocumented - [StructLayout(LayoutKind.Sequential)] - [UsedByNativeCode] - internal struct CloudBuildStatus - { - private string m_Platform; - private bool m_Complete; - private bool m_Successful; - - internal CloudBuildStatus(string platform = "", bool complete = false, bool success = false) - { - m_Platform = platform; - m_Complete = complete; - m_Successful = success; - } - - public string platform { get { return m_Platform; } } - public bool complete { get { return m_Complete; } } - public bool success { get { return m_Successful; } } - } -} diff --git a/Editor/Mono/Collab/Collab.bindings.cs b/Editor/Mono/Collab/Collab.bindings.cs deleted file mode 100644 index be25871f3b..0000000000 --- a/Editor/Mono/Collab/Collab.bindings.cs +++ /dev/null @@ -1,290 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using UnityEditor.Connect; -using UnityEngine; -using UnityEngine.Bindings; -using UnityEngine.Scripting; - -namespace UnityEditor.Collaboration -{ - [NativeHeader("Editor/Src/Collab/CollabInfo.h")] - [StructLayout(LayoutKind.Sequential)] - internal struct CollabInfo - { - public bool ready { get { return m_Ready; } } - public bool update { get { return m_Update; } } - public bool publish { get { return m_Publish; } } - public bool inProgress { get { return m_InProgress; } } - public bool maintenance { get { return m_Maintenance; } } - public bool conflict { get { return m_Conflict; } } - public bool refresh { get { return m_Refresh; } } - public bool seat { get { return m_HasSeat; } } - public string tip { get { return m_Tip; } } - - public bool Equals(CollabInfo other) - { - return m_Update == other.m_Update && - m_Publish == other.m_Publish && - m_InProgress == other.m_InProgress && - m_Maintenance == other.m_Maintenance && - m_Conflict == other.m_Conflict && - m_Refresh == other.m_Refresh && - m_HasSeat == other.m_HasSeat && - m_Ready == other.m_Ready && - string.Equals(m_Tip, other.m_Tip); - } - - bool m_Update; - bool m_Publish; - bool m_InProgress; - bool m_Maintenance; - bool m_Conflict; - bool m_Refresh; - bool m_HasSeat; - bool m_Ready; - string m_Tip; - } - - [NativeHeader("Editor/Src/Collab/Collab.h")] - [NativeHeader("Editor/Src/Collab/Collab.bindings.h")] - [StructLayout(LayoutKind.Sequential)] - [StaticAccessor("Collab::Get()", StaticAccessorType.Arrow)] - partial class Collab - { - [NativeMethod("Get")] - static extern IntPtr GetNativeCollab(); - - public extern CollabInfo collabInfo { get; } - - public static extern int GetRevisionsData( - bool withChanges, int startIndex, int numRevisions); - - public static extern int GetSingleRevisionData(bool withChanges, string id); - - public static extern RevisionsData PopulateRevisionsData(IntPtr nativeData); - public static extern Revision PopulateSingleRevisionData(IntPtr nativeData); - - public extern void SetSeat(bool value); - - public extern void RefreshSeatAvailabilityAsync(); - - public extern string GetProjectGUID(); - - public extern bool ShouldDoInitialCommit(); - - [NativeMethod("DiffFileWithBaseAsync")] - public extern void ShowDifferences(string path); - - public extern void SendNotification(); - - public extern void SetError(int errorCode); - - public extern void ClearError(int errorCode); - - public extern void ClearErrors(); - - public extern void ForceRefresh(bool refreshAssetDatabase); - - public extern void SetCollabEnabledForCurrentProject(bool enabled); - - public extern bool IsCollabEnabledForCurrentProject(); - - public extern bool IsAssetIgnored(string path); - - public extern bool ShouldTrackAsset(string path); - - [ThreadAndSerializationSafe] - public extern string GetProjectPath(); - - [ThreadAndSerializationSafe] - public extern bool IsConnected(); - - [ThreadAndSerializationSafe] - public extern bool AnyJobRunning(); - - [ThreadAndSerializationSafe] - public extern bool JobRunning(int a_jobID); - - public extern CollabStates GetAssetState(string guid); - public extern CollabStates GetSelectedAssetState(); - public extern CollabStateID GetCollabState(); - - [FreeFunction(HasExplicitThis = true)] - public extern bool ValidateSelectiveCommit(); - - [FreeFunction(HasExplicitThis = true, ThrowsException = true)] - public extern void Disconnect(); - - [FreeFunction(HasExplicitThis = true, ThrowsException = true)] - public extern void CancelJobByType(int jobType, bool forceCancel); - - [FreeFunction(HasExplicitThis = true, ThrowsException = true)] - public extern void DoInitialCommit(); - - [FreeFunction(HasExplicitThis = true, ThrowsException = true)] - public extern void Update(string revisionID, bool updateToRevision); - - [FreeFunction(HasExplicitThis = true, ThrowsException = true)] - public extern void RevertFile(string path, bool forceOverwrite); - - [FreeFunction(HasExplicitThis = true, ThrowsException = true)] - public extern void RevertFiles(ChangeItem[] changeItems, bool forceOverwrite); - - [FreeFunction(HasExplicitThis = true, ThrowsException = true)] - public extern void LaunchConflictExternalMerge(string path); - - [FreeFunction(HasExplicitThis = true, ThrowsException = true)] - public extern void ShowConflictDifferences(string path); - - [FreeFunction(HasExplicitThis = true, ThrowsException = true)] - public extern void ResyncSnapshot(); - - [FreeFunction(HasExplicitThis = true, ThrowsException = true)] - public extern void GoBackToRevision(string revisionID, bool updateToRevision); - - [FreeFunction(HasExplicitThis = true, ThrowsException = true)] - public extern void ResyncToRevision(string revisionID); - - [NativeMethod("GetConflictsManager().GetAllConflicts")] - public extern Change[] GetCollabConflicts(); - - // Conflict Management - [NativeMethod("GetConflictsManager().CheckConflictsResolvedExternal")] - public extern void CheckConflictsResolvedExternal(); - - [NativeMethod("GetTestHelper().AreTestsRunning")] - public extern bool AreTestsRunning(); - - [NativeMethod("GetTestHelper().SetTestsRunning")] - public extern void SetTestsRunning(bool running); - - [NativeMethod("GetTestHelper().ClearOperationsFailure")] - public extern void ClearAllFailures(); - - [NativeMethod("GetTestHelper().UnmarkOperationFailure")] - public extern void ClearNextOperationFailure(); - - [NativeMethod("GetTestHelper().UnmarkOperationFailureForFile")] - public extern void ClearNextOperationFailureForFile(string path); - - [NativeMethod("GetTestHelper().GetGUIDForTests")] - public extern string GetGUIDForTests(); - - [NativeMethod("GetTestHelper().NewGUIDForTests")] - public extern void NewGUIDForTests(); - - [NativeMethod("GetTestHelper().MarkOperationFailure")] - public extern void FailNextOperation(Collab.Operation operation, int code); - - [NativeMethod("GetTestHelper().MarkOperationTimeOut")] - public extern void TimeOutNextOperation(Collab.Operation operation, int timeOutSec); - - [NativeMethod("GetTestHelper().MarkOperationFailureForFile")] - public extern void FailNextOperationForFile(string path, Collab.Operation operation, int code); - - [NativeMethod("GetTestHelper().MarkOperationTimeOutForFile")] - public extern void TimeOutNextOperationForFile(string path, Collab.Operation operation, int timeOutSec); - - [FreeFunction(HasExplicitThis = true, ThrowsException = true)] - public extern void TestPostSoftLockAsCollaborator(string projectGuid, string projectPath, string machineGuid, - string assetGuid); - - [FreeFunction(HasExplicitThis = true, ThrowsException = true)] - public extern void TestClearSoftLockAsCollaborator(string projectGuid, string projectPath, string machineGuid, - string softLockHash); - - // Private helper methods for bindings - [NativeMethod("GetConflictsManager().SetConflictsState")] - extern bool SetConflictsResolved(string[] paths, CollabStates state); - - [NativeMethod("OnAssetBundleNameChanged")] - extern void OnPostprocessAssetbundleNameChanged(string assetPath, string previousAssetBundleName, string newAssetBundleName); - - [NativeMethod("GetError")] - internal extern bool GetErrorInternal(int errorFilter, out UnityErrorInfo info); - - [NativeMethod(HasExplicitThis = true, ThrowsException = true)] - extern Change[] GetChangesToPublishInternal(); - - [NativeMethod(HasExplicitThis = true, ThrowsException = true)] - extern ChangeItem[] GetChangeItemsToPublishInternal_V2(); - - [NativeMethod(HasExplicitThis = true, ThrowsException = true, IsThreadSafe = true)] - extern void SetChangesToPublishInternal(ChangeItem[] changes); - - [NativeMethod(HasExplicitThis = true, ThrowsException = true, IsThreadSafe = true)] - extern Change[] GetSelectedChangesInternal(); - - [NativeMethod(HasExplicitThis = true, ThrowsException = true, IsThreadSafe = true)] - extern ChangeItem[] GetSelectedChangeItemsInternal_V2(); - - [NativeMethod(HasExplicitThis = true, ThrowsException = true, IsThreadSafe = true)] - extern void UpdateChangesToPublish(); - - [NativeMethod(Name = "GetJobProgress", HasExplicitThis = true, ThrowsException = true)] - extern bool GetJobProgressInternal([Out] ProgressInfo info, int jobId); - - [NativeMethod(HasExplicitThis = true, ThrowsException = true)] - public extern void Publish(string comment, bool useSelectedAssets, bool confirmMatchesPrevious); - - [NativeMethod(HasExplicitThis = true, ThrowsException = true)] - public extern void PublishAssetsAsync(string comment, ChangeItem[] changes); - - [NativeMethod(HasExplicitThis = true, ThrowsException = true, IsThreadSafe = true)] - public extern void ClearSelectedChangesToPublish(); - - [NativeMethod(HasExplicitThis = true, ThrowsException = true, IsThreadSafe = true)] - public extern void SendCollabInfoNotification(); - - [NativeMethod(HasExplicitThis = true, ThrowsException = true)] - public extern SoftLock[] GetSoftLocks(string assetGuid); - } - - // keep in sync with CollabSettingType in C++ - internal enum CollabSettingType - { - InProgressEnabled = 0, - InProgressProjectEnabled = 1, - InProgressGlobalEnabled = 2 - } - - // keep in sync with CollabSettingStatus in C++ - internal enum CollabSettingStatus - { - None = 0, - Available = 1 - } - - [NativeHeader("Editor/Src/Collab/CollabSettingsManager.h")] - [StaticAccessor("GetCollabSettingsManager()", StaticAccessorType.Dot)] - internal class CollabSettingsManager - { - [RequiredByNativeCode] - static void NotifyStatusListeners(CollabSettingType type, CollabSettingStatus status) - { - if (statusNotifier[type] != null) - statusNotifier[type](type, status); - } - - public delegate void SettingStatusChanged(CollabSettingType type, CollabSettingStatus status); - public static Dictionary statusNotifier = new Dictionary(); - - static CollabSettingsManager() - { - foreach (CollabSettingType type in Enum.GetValues(typeof(CollabSettingType))) - statusNotifier[type] = null; - } - - public static extern bool IsAvailable(CollabSettingType type); - - public static extern bool inProgressEnabled - { - get; - } - } -} diff --git a/Editor/Mono/Collab/Collab.cs b/Editor/Mono/Collab/Collab.cs deleted file mode 100644 index 0056fbac88..0000000000 --- a/Editor/Mono/Collab/Collab.cs +++ /dev/null @@ -1,620 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using System; -using System.IO; -using System.Collections.Generic; -using UnityEngine; -using UnityEngine.Scripting; -using UnityEditor.Web; -using UnityEditorInternal; -using UnityEditor.Connect; -using UnityEditor.SceneManagement; - -namespace UnityEditor.Collaboration -{ - internal delegate void StateChangedDelegate(CollabInfo info); - internal delegate void RevisionChangedDelegate(CollabInfo info, string rev, string action); - internal delegate void SetErrorDelegate(UnityErrorInfo error); - internal delegate void ErrorDelegate(); - internal delegate bool ShowToolbarAtPositionDelegate(Rect screenRect); - internal delegate bool IsToolbarVisibleDelegate(); - internal delegate void ShowHistoryWindowDelegate(); - internal delegate void ShowChangesWindowDelegate(); - internal delegate void CloseToolbarDelegate(); - internal delegate void ChangesChangedDelegate(Change[] changes, bool isFiltered); - internal delegate void ChangeItemsChangedDelegate(ChangeItem[] changes, bool isFiltered); - - //*undocumented - // We want to raise this exception from Cpp code but it fails - //public class CollabException: Exception - //{ - // public CollabException(string message) : base(message) - // { - // } - //} - - [InitializeOnLoad] - internal partial class Collab - { - // Pointer to native Collab object used by automatic bindings system. -#pragma warning disable 414 // The private field is assigned but its value is never used - IntPtr m_nativeCollab = IntPtr.Zero; - - public event StateChangedDelegate StateChanged; - - public event StateChangedDelegate RevisionUpdated; - public event RevisionChangedDelegate RevisionUpdated_V2; - - public event StateChangedDelegate JobsCompleted; - - public event ErrorDelegate ErrorOccurred; - public event SetErrorDelegate ErrorOccurred_V2; - - public event ErrorDelegate ErrorCleared; - - public event ChangeItemsChangedDelegate ChangeItemsChanged; - public event ChangeItemsChangedDelegate SelectedChangeItemsChanged; - public event StateChangedDelegate CollabInfoChanged; - - // Toolbar delegates - public static ShowToolbarAtPositionDelegate ShowToolbarAtPosition = null; - public static IsToolbarVisibleDelegate IsToolbarVisible = null; - public static CloseToolbarDelegate CloseToolbar = null; - - // Preferences link delegates - public static ShowHistoryWindowDelegate ShowHistoryWindow = null; - public static ShowChangesWindowDelegate ShowChangesWindow = null; - - private static Collab s_Instance; - private static bool s_IsFirstStateChange = true; - - [SerializeField] - public CollabFilters collabFilters = new CollabFilters(); - - public String projectBrowserSingleSelectionPath { get; set; } - - public String projectBrowserSingleMetaSelectionPath { get; set; } - - public string[] currentProjectBrowserSelection; - - // Must keep in sync with C++ in CollabCommon.h - [Flags] - public enum Operation - { - Noop = 0, - Publish = 1 << 0, - Update = 1 << 1, - Revert = 1 << 2, - GoBack = 1 << 3, - Restore = 1 << 4, - Diff = 1 << 5, - ConflictDiff = 1 << 6, - Exclude = 1 << 7, - Include = 1 << 8, - ChooseMine = 1 << 9, - ChooseTheirs = 1 << 10, - ExternalMerge = 1 << 11, - } - - // Must keep in sync with C++ in CollabCommon.h - // Explicit uint so "Any" state works with C++ - [Flags] - public enum CollabStates : uint - { - kCollabNone = 0, - kCollabLocal = 1, - - kCollabSynced = 1 << 1, - kCollabOutOfSync = 1 << 2, - kCollabIgnored = 1 << 3, - kCollabCheckedOutLocal = 1 << 4, - kCollabCheckedOutRemote = 1 << 5, - kCollabDeletedLocal = 1 << 6, - kCollabDeletedRemote = 1 << 7, - kCollabAddedLocal = 1 << 8, - kCollabAddedRemote = 1 << 9, - kCollabConflicted = 1 << 10, - kCollabMovedLocal = 1 << 11, - kCollabMovedRemote = 1 << 12, - kCollabUpdating = 1 << 13, - kCollabReadOnly = 1 << 14, - kCollabMetaFile = 1 << 15, - kCollabUseMine = 1 << 16, - kCollabUseTheir = 1 << 17, - kCollabMerged = 1 << 18, - kCollabPendingMerge = 1 << 19, - kCollabFolderMetaFile = 1 << 20, - KCollabContentChanged = 1 << 21, - KCollabContentConflicted = 1 << 22, - KCollabContentDeleted = 1 << 23, - - // always keep most significant - kCollabInvalidState = 1 << 30, - - kAnyLocalChanged = (kCollabAddedLocal | kCollabCheckedOutLocal | kCollabDeletedLocal | kCollabMovedLocal), - kAnyLocalEdited = (kCollabAddedLocal | kCollabCheckedOutLocal | kCollabMovedLocal), - kCollabAny = 0xFFFFFFFF - } - - internal enum CollabStateID { None, Uninitialized, Initialized } - - public static string[] clientType = - { - "Cloud Server", - "Mock Server" - }; - - internal static string editorPrefCollabClientType = "CollabConfig_Client"; - - public static string GetProjectClientType() - { - var cvalue = EditorUserSettings.GetConfigValue(editorPrefCollabClientType); - return string.IsNullOrEmpty(cvalue) ? clientType[0] : cvalue; - } - - public static Collab instance - { - get - { - return s_Instance; - } - } - - // Instance of VersionControl interface implementation - private static IVersionControl s_VersionControlInstance; - - public static void SetVersionControl(IVersionControl instance) - { - s_VersionControlInstance = instance; - // Initialize version control based on whether collab is enabled - if (s_Instance != null) - { - if (s_Instance.IsCollabEnabledForCurrentProject()) - { - instance.OnEnableVersionControl(); - } - else - { - instance.OnDisableVersionControl(); - } - } - } - - [UsedByNativeCode] - internal static bool HasVersionControl() - { - return s_VersionControlInstance != null; - } - - [UsedByNativeCode] - internal static void ShowChangesWindowView() - { - if (ShowChangesWindow != null) - { - ShowChangesWindow(); - } - } - - [UsedByNativeCode] - static bool SupportsDownloads() - { - if (s_VersionControlInstance != null) - { - return s_VersionControlInstance.SupportsDownloads(); - } - else - { - return false; - } - } - - [UsedByNativeCode] - static bool OnEnableVersionControl() - { - if (s_VersionControlInstance != null) - { - return s_VersionControlInstance.OnEnableVersionControl(); - } - else - { - return true; - } - } - - [UsedByNativeCode] - static void OnDisableVersionControl() - { - if (s_VersionControlInstance != null) - { - s_VersionControlInstance.OnDisableVersionControl(); - } - } - - [UsedByNativeCode] - static ChangeItem[] GetChanges() - { - if (s_VersionControlInstance != null) - { - return s_VersionControlInstance.GetChanges(); - } - else - { - return null; - } - } - - [UsedByNativeCode] - static void MergeDownloadedFiles(bool isFullDownload) - { - if (s_VersionControlInstance != null) - { - s_VersionControlInstance.MergeDownloadedFiles(isFullDownload); - } - } - - [UsedByNativeCode] - static bool SupportsAsyncChanges() - { - if (s_VersionControlInstance != null) - { - return s_VersionControlInstance.SupportsAsyncChanges(); - } - else - { - return false; - } - } - - internal static CollabStates GetAssetState(string assetGuid, string assetPath) - { - if (s_VersionControlInstance != null) - { - return s_VersionControlInstance.GetAssetState(assetGuid, assetPath); - } - else - { - return instance.GetAssetState(assetGuid); - } - } - - public void RefreshAvailableLocalChangesSynchronous() - { - IVersionControl_V2 vc_v2 = s_VersionControlInstance as IVersionControl_V2; - - // If our VersionControlInstance isn't v2, this whole method is a no-op - if (vc_v2 != null) - { - vc_v2.RefreshAvailableLocalChangesSynchronous(); - UpdateChangesToPublish(); - } - } - - // Static constructor for Collab - static Collab() - { - s_Instance = new Collab(); - s_Instance.projectBrowserSingleSelectionPath = string.Empty; - s_Instance.projectBrowserSingleMetaSelectionPath = string.Empty; - s_Instance.m_nativeCollab = GetNativeCollab(); - ObjectListArea.postAssetIconDrawCallback += CollabProjectHook.OnProjectWindowIconOverlay; - AssetsTreeViewGUI.postAssetIconDrawCallback += CollabProjectHook.OnProjectBrowserNavPanelIconOverlay; - InitializeSoftlocksViewController(); - CollabSettingsManager.statusNotifier[CollabSettingType.InProgressEnabled] += OnSettingStatusChanged; - CollabSettingsManager.statusNotifier[CollabSettingType.InProgressEnabled] += SoftlockViewController.Instance.softLockFilters.OnSettingStatusChanged; - } - - public static void OnSettingStatusChanged(CollabSettingType type, CollabSettingStatus status) - { - InitializeSoftlocksViewController(); - } - - public static bool InitializeSoftlocksViewController() - { - if (!CollabSettingsManager.IsAvailable(CollabSettingType.InProgressEnabled)) - return false; - - if (CollabSettingsManager.inProgressEnabled) - SoftlockViewController.Instance.TurnOn(); - else - SoftlockViewController.Instance.TurnOff(); - return true; - } - - - public bool GetError(UnityConnect.UnityErrorFilter errorFilter, out UnityErrorInfo info) - { - return GetErrorInternal((int)errorFilter, out info) && info.code > 0; - } - - public void CancelJob(int jobType) - { - try - { - CancelJobByType(jobType, false); - } - catch (Exception ex) - { - UnityEngine.Debug.Log("Cannot cancel job, reason:" + ex.Message); - } - } - - public void UpdateEditorSelectionCache() - { - var result = new List(); - - foreach (var elem in Selection.assetGUIDsDeepSelection) - { - var path = AssetDatabase.GUIDToAssetPath(elem); - result.Add(path); - - var meta = path + ".meta"; - if (File.Exists(meta)) - { - result.Add(meta); - } - } - currentProjectBrowserSelection = result.ToArray(); - } - - public CollabInfo GetCollabInfo() - { - return collabInfo; - } - - public static bool IsDiffToolsAvailable() - { - return InternalEditorUtility.GetAvailableDiffTools().Length > 0; - } - - public void SaveAssets() - { - AssetDatabase.SaveAssets(); - } - - public static void SwitchToDefaultMode() - { - bool in2D = EditorSettings.defaultBehaviorMode == EditorBehaviorMode.Mode2D; - var sv = SceneView.lastActiveSceneView; - if (sv != null && sv.in2DMode != in2D) - { - sv.in2DMode = in2D; - } - } - - public void ShowInProjectBrowser(string filterString) - { - collabFilters.ShowInProjectBrowser(filterString); - } - - public bool SetConflictsResolvedMine(string[] paths) - { - return SetConflictsResolved(paths, CollabStates.kCollabUseMine); - } - - public bool SetConflictsResolvedTheirs(string[] paths) - { - return SetConflictsResolved(paths, CollabStates.kCollabUseTheir); - } - - public PublishInfo GetChangesToPublish() - { - Change[] changes = GetChangesToPublishInternal(); - bool isFiltered = false; - - if (SupportsAsyncChanges()) - { - changes = GetSelectedChangesInternal(); - if (Toolbar.isLastShowRequestPartial) - { - isFiltered = true; - } - } - - return new PublishInfo() - { - changes = changes, - filter = isFiltered - }; - } - - public PublishInfo_V2 GetChangesToPublish_V2() - { - ChangeItem[] changes = GetChangeItemsToPublishInternal_V2(); - - return new PublishInfo_V2() - { - changes = changes, - filter = false - }; - } - - public void SetChangesToPublish(ChangeItem[] changes) - { - SetChangesToPublishInternal(changes); - } - - public ProgressInfo GetJobProgress(int jobId) - { - ProgressInfo info = new ProgressInfo(); - if (GetJobProgressInternal(info, jobId)) - return info; - - return null; - } - - private static void OnStateChanged() - { - // register only once - if (s_IsFirstStateChange) - { - s_IsFirstStateChange = false; - UnityConnect.instance.StateChanged += OnUnityConnectStateChanged; - } - var handler = instance.StateChanged; - if (handler != null) - { - handler(instance.collabInfo); - } - } - - [RequiredByNativeCode] - private static void OnRevisionUpdated(string revisionId, string action) - { - var handler = instance.RevisionUpdated; - if (handler != null) - { - handler(instance.collabInfo); - } - - var handler_v2 = instance.RevisionUpdated_V2; - if (handler_v2 != null) - { - handler_v2(instance.collabInfo, revisionId, action); - } - } - - [RequiredByNativeCode] - private static void OnChangeItemsChanged(ChangeItem[] changes, bool isFiltered) - { - var handler = instance.ChangeItemsChanged; - if (handler != null) - { - handler(changes, isFiltered); - } - } - - [RequiredByNativeCode] - private static void OnSelectedChangeItemsChanged(ChangeItem[] changeItems, bool isFiltered) - { - var handler = instance.SelectedChangeItemsChanged; - if (handler != null) - { - handler(changeItems, isFiltered); - } - } - - [RequiredByNativeCode] - private static void OnCollabInfoChanged() - { - var handler = instance.CollabInfoChanged; - if (handler != null) - { - handler(instance.collabInfo); - } - } - - [RequiredByNativeCode] - private static void SetCollabError(int code, int priority, int behavior, string msg, string shortmsg, string codeStr) - { - var handler = instance.ErrorOccurred; - - if (handler != null) - { - handler(); - } - var handler_v2 = instance.ErrorOccurred_V2; - - if (handler_v2 != null) - { - handler_v2(new UnityErrorInfo() { code = code, priority = priority, behaviour = behavior, msg = msg, shortMsg = shortmsg, codeStr = codeStr }); - } - } - - [RequiredByNativeCode] - private static void ClearCollabError() - { - var handler = instance.ErrorCleared; - if (handler != null) - handler(); - } - - private static void OnJobsCompleted() - { - var handler = instance.JobsCompleted; - if (handler != null) - { - handler(instance.collabInfo); - } - CollabTesting.OnJobsCompleted(); - } - - private static void PublishDialog(string changelist) - { - if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) - { - return; - } - - var dialog = CollabPublishDialog.ShowCollabWindow(changelist); - - if (dialog.Options.DoPublish) - Collab.instance.Publish(dialog.Options.Comments, true, false); - } - - private static void CannotPublishDialog(string infoMessage) - { - CollabCannotPublishDialog.ShowCollabWindow(infoMessage); - } - - private static void OnUnityConnectStateChanged(ConnectInfo state) - { - instance.SendNotification(); - } - - public static void OnProgressEnabledSettingStatusChanged(CollabSettingType type, CollabSettingStatus status) - { - if (type == CollabSettingType.InProgressEnabled && status == CollabSettingStatus.Available) - { - if (CollabSettingsManager.inProgressEnabled) - { - SoftlockViewController.Instance.softLockFilters.ShowInFavoriteSearchFilters(); - } - - CollabSettingsManager.statusNotifier[CollabSettingType.InProgressEnabled] -= OnProgressEnabledSettingStatusChanged; - } - } - - - [RequiredByNativeCode] - static void OnCollabEnabledForCurrentProject(bool enabled) - { - if (enabled) - { - instance.StateChanged += instance.collabFilters.OnCollabStateChanged; - instance.collabFilters.ShowInFavoriteSearchFilters(); - if (CollabSettingsManager.IsAvailable(CollabSettingType.InProgressEnabled)) - { - if (CollabSettingsManager.inProgressEnabled) - { - SoftlockViewController.Instance.softLockFilters.ShowInFavoriteSearchFilters(); - } - } - else - { - CollabSettingsManager.statusNotifier[CollabSettingType.InProgressEnabled] -= OnProgressEnabledSettingStatusChanged; - CollabSettingsManager.statusNotifier[CollabSettingType.InProgressEnabled] += OnProgressEnabledSettingStatusChanged; - } - } - else - { - instance.StateChanged -= instance.collabFilters.OnCollabStateChanged; - instance.collabFilters.HideFromFavoriteSearchFilters(); - SoftlockViewController.Instance.softLockFilters.HideFromFavoriteSearchFilters(); - CollabSettingsManager.statusNotifier[CollabSettingType.InProgressEnabled] -= OnProgressEnabledSettingStatusChanged; - - if (ProjectBrowser.s_LastInteractedProjectBrowser != null) - { - if (ProjectBrowser.s_LastInteractedProjectBrowser.Initialized() && ProjectBrowser.s_LastInteractedProjectBrowser.IsTwoColumns()) - { - int instanceID = AssetDatabase.GetMainAssetInstanceID("assets"); - ProjectBrowser.s_LastInteractedProjectBrowser.SetFolderSelection(new int[] { instanceID }, true); - } - ProjectBrowser.s_LastInteractedProjectBrowser.SetSearch(""); - ProjectBrowser.s_LastInteractedProjectBrowser.Repaint(); - } - } - } - } -} diff --git a/Editor/Mono/Collab/CollabChange.cs b/Editor/Mono/Collab/CollabChange.cs deleted file mode 100644 index 1297fdb044..0000000000 --- a/Editor/Mono/Collab/CollabChange.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - - -using System; -using System.Runtime.InteropServices; -using UnityEngine.Bindings; - -namespace UnityEditor.Collaboration -{ - // Keep internal and undocumented until we expose more functionality - //*undocumented - [StructLayout(LayoutKind.Sequential)] - [NativeType(CodegenOptions = CodegenOptions.Custom, Header = "Editor/Src/Collab/CollabChange.h", - IntermediateScriptingStructName = "ScriptingCollabChange")] - [NativeHeader("Editor/Src/Collab/Collab.bindings.h")] - [NativeAsStruct] - internal class Change - { - [Flags] - public enum RevertableStates : uint - { - Revertable = 1 << 0, - NotRevertable = 1 << 1, - - Revertable_File = 1 << 2, - Revertable_Folder = 1 << 3, - Revertable_EmptyFolder = 1 << 4, - - NotRevertable_File = 1 << 5, - NotRevertable_Folder = 1 << 6, - NotRevertable_FileAdded = 1 << 7, - NotRevertable_FolderAdded = 1 << 8, - NotRevertable_FolderContainsAdd = 1 << 9, - - // do not exceed Javascript Number range - InvalidRevertableState = (uint)1 << 31 - } - - string m_Path; - Collab.CollabStates m_State; - RevertableStates m_RevertableState; - string m_RelatedTo; - string m_LocalStatus; - string m_RemoteStatus; - string m_ResolveStatus; - - Change() {} - - public string path { get { return m_Path; } } - public Collab.CollabStates state { get { return m_State; } } - public bool isRevertable { get { return HasRevertableState(RevertableStates.Revertable); } } - public RevertableStates revertableState { get { return m_RevertableState; } } - public string relatedTo { get { return m_RelatedTo; } } - - public bool isMeta { get { return HasState(Collab.CollabStates.kCollabMetaFile); } } - public bool isConflict { get { return HasState(Collab.CollabStates.kCollabConflicted) || HasState(Collab.CollabStates.kCollabPendingMerge); } } - public bool isFolderMeta { get { return HasState(Collab.CollabStates.kCollabFolderMetaFile); } } - public bool isResolved { get { return HasState(Collab.CollabStates.kCollabUseMine) || HasState(Collab.CollabStates.kCollabUseTheir) || HasState(Collab.CollabStates.kCollabMerged); } } - - public string localStatus { get { return m_LocalStatus; } } - public string remoteStatus { get { return m_RemoteStatus; } } - public string resolveStatus { get { return m_ResolveStatus; } } - - internal bool HasState(Collab.CollabStates states) - { - return (m_State & states) != 0; - } - - internal bool HasRevertableState(RevertableStates revertableStates) - { - return (m_RevertableState & revertableStates) != 0; - } - } - - internal class PublishInfo - { - public Change[] changes; - public bool filter; - } -} diff --git a/Editor/Mono/Collab/CollabChangeAction.cs b/Editor/Mono/Collab/CollabChangeAction.cs deleted file mode 100644 index 8d65911b51..0000000000 --- a/Editor/Mono/Collab/CollabChangeAction.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - - -using System; -using System.Runtime.InteropServices; -using UnityEngine.Scripting; - -namespace UnityEditor.Collaboration -{ - // Keep internal and undocumented until we expose more functionality - //*undocumented - [StructLayout(LayoutKind.Sequential)] - [UsedByNativeCode] - internal struct ChangeAction - { - private string m_Path; - private string m_Action; - - public ChangeAction(string path = "", string action = "") - { - m_Path = path; - m_Action = action; - } - - public string path { get { return m_Path; } } - public string action { get { return m_Action; } } - } -} diff --git a/Editor/Mono/Collab/CollabChangeItem.cs b/Editor/Mono/Collab/CollabChangeItem.cs index cfca4a5ab9..ad380552c0 100644 --- a/Editor/Mono/Collab/CollabChangeItem.cs +++ b/Editor/Mono/Collab/CollabChangeItem.cs @@ -6,29 +6,3 @@ using System.Runtime.InteropServices; using UnityEngine.Bindings; -namespace UnityEditor.Collaboration -{ - [StructLayout(LayoutKind.Sequential)] - [NativeType(CodegenOptions = CodegenOptions.Custom, Header = "Editor/Src/Collab/CollabClient.h", - IntermediateScriptingStructName = "ScriptingCollabChangeItem")] - [NativeHeader("Editor/Src/Collab/Collab.bindings.h")] - [NativeAsStruct] - internal class ChangeItem - { - public string Path { get; set; } - public Change.RevertableStates RevertableState { get; set; } - public string RelatedTo { get; set; } - public string RevisionId { get; set; } - public string Hash { get; set; } - public Collab.CollabStates State { get; set; } - public long Size { get; set; } - public string DownloadPath { get; set; } - public string FromPath { get; set; } - } - - internal class PublishInfo_V2 - { - public ChangeItem[] changes; - public bool filter; - } -} diff --git a/Editor/Mono/Collab/CollabDialogs.cs b/Editor/Mono/Collab/CollabDialogs.cs deleted file mode 100644 index 155f8766e1..0000000000 --- a/Editor/Mono/Collab/CollabDialogs.cs +++ /dev/null @@ -1,146 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using System; -using System.IO; -using System.Collections.Generic; -using UnityEngine; -using UnityEditor; -using UnityEditor.Web; -using UnityEditorInternal; -using UnityEditor.Connect; - -namespace UnityEditor.Collaboration -{ - internal struct PublishDialogOptions - { - public string Comments; - public bool DoPublish; - } - - internal class CollabPublishDialog : EditorWindow - { - public static CollabPublishDialog ShowCollabWindow(string changelist) - { - CollabPublishDialog dialog = ScriptableObject.CreateInstance(); - dialog.Changelist = changelist; - - var rect = new Rect(100, 100, 600, 225); - dialog.minSize = new Vector2(rect.width, rect.height); - dialog.maxSize = new Vector2(rect.width, rect.height); - dialog.position = rect; - dialog.ShowModal(); - - dialog.m_Parent.window.m_DontSaveToLayout = true; - - return dialog; - } - - static GUIContent DescribeChangesText = EditorGUIUtility.TrTextContent("Describe your changes here"); - static GUIContent ChangeAssetsText = EditorGUIUtility.TrTextContent("Changed assets:"); - static GUIContent PublishText = EditorGUIUtility.TrTextContent("Publish"); - static GUIContent CancelText = EditorGUIUtility.TrTextContent("Cancel"); - - public Vector2 scrollView; - public string Changelist; - - public CollabPublishDialog() - { - Options.Comments = ""; - } - - public void OnGUI() - { - GUILayout.BeginVertical(); - GUILayout.Label(DescribeChangesText); - Options.Comments = GUILayout.TextArea(Options.Comments, 1000, GUILayout.MinHeight(80)); - - - GUILayout.Label(ChangeAssetsText); - scrollView = EditorGUILayout.BeginScrollView(scrollView, false, false); - GUIStyle style = new GUIStyle(); - Vector2 textSize = style.CalcSize(new GUIContent(Changelist)); - EditorGUILayout.SelectableLabel(Changelist, EditorStyles.textField, GUILayout.ExpandHeight(true), GUILayout.MinHeight(textSize.y)); - EditorGUILayout.EndScrollView(); - - GUILayout.FlexibleSpace(); - - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - - if (GUILayout.Button(CancelText)) - { - Options.DoPublish = false; - Close(); - } - - if (GUILayout.Button(PublishText)) - { - Options.DoPublish = true; - Close(); - } - - GUILayout.EndHorizontal(); - GUILayout.EndVertical(); - } - - public PublishDialogOptions Options; - } - - internal class CollabCannotPublishDialog : EditorWindow - { - public static CollabCannotPublishDialog ShowCollabWindow(string infoMessage) - { - CollabCannotPublishDialog dialog = ScriptableObject.CreateInstance(); - dialog.InfoMessage = infoMessage; - - var rect = new Rect(100, 100, 600, 150); - dialog.minSize = new Vector2(rect.width, rect.height); - dialog.maxSize = new Vector2(rect.width, rect.height); - dialog.position = rect; - dialog.ShowModal(); - - dialog.m_Parent.window.m_DontSaveToLayout = true; - - return dialog; - } - - static GUIContent WarningText = EditorGUIUtility.TextContent(string.Format( - "Files that have been moved or in a changed folder cannot be selectively published, " + - "please use the Publish option in the collab window to publish all your changes.")); - static GUIContent IssuesText = EditorGUIUtility.TrTextContent("Issues:"); - static GUIContent AcceptText = EditorGUIUtility.TrTextContent("Accept"); - - public Vector2 scrollPosition; - public string InfoMessage; - - public void OnGUI() - { - GUILayout.BeginVertical(); - GUI.skin.label.wordWrap = true; - - GUILayout.BeginVertical(); - GUILayout.Label(WarningText); - - GUILayout.Label(IssuesText); - - scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); - GUIStyle warnStyle = new GUIStyle(); - warnStyle.normal.textColor = new Color(1f, 0.28f, 0f); - GUILayout.Label(string.Format(InfoMessage), warnStyle); - GUILayout.EndScrollView(); - - GUILayout.EndVertical(); - - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - - if (GUILayout.Button(AcceptText)) - Close(); - GUILayout.EndHorizontal(); - - GUILayout.EndVertical(); - } - } -} diff --git a/Editor/Mono/Collab/CollabEditorHooks.cs b/Editor/Mono/Collab/CollabEditorHooks.cs deleted file mode 100644 index 89965e7b3f..0000000000 --- a/Editor/Mono/Collab/CollabEditorHooks.cs +++ /dev/null @@ -1,196 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using System; -using System.Collections.Generic; -using System.IO; -using UnityEngine; -using UnityEditor.Web; -using UnityEditor.Connect; - -namespace UnityEditor.Collaboration -{ - // Display hooks for the main project window. Icons are overlayed to show the version control state. - internal class CollabProjectHook - { - // GUI callback for each item visible in the project window/object list area - public static void OnProjectWindowIconOverlay(Rect iconRect, string guid, bool isListMode) - { - DrawProjectBrowserIconOverlay(iconRect, guid, isListMode); - } - - // Draw icons in the Favorites/Asset Folder area of the project browser - public static void OnProjectBrowserNavPanelIconOverlay(Rect iconRect, string guid) - { - DrawProjectBrowserIconOverlay(iconRect, guid, true); - } - - private static void DrawProjectBrowserIconOverlay(Rect iconRect, string guid, bool isListMode) - { - if (Collab.instance.IsCollabEnabledForCurrentProject()) - { - Collab.CollabStates assetState = GetAssetState(guid); - Overlay.DrawOverlays(assetState, iconRect, isListMode); - } - } - - public static Collab.CollabStates GetAssetState(String assetGuid) - { - if (!Collab.instance.IsCollabEnabledForCurrentProject()) - { - return Collab.CollabStates.kCollabNone; - } - - Collab.CollabStates assetState = Collab.GetAssetState(assetGuid, AssetDatabase.GUIDToAssetPath(assetGuid)); - return assetState; - } - } - - internal class Overlay - { - public const double k_OverlaySizeOnSmallIcon = 0.6; - public const double k_OverlaySizeOnLargeIcon = 0.35; - - private static readonly Dictionary s_Overlays = new Dictionary(); - - protected static void LoadOverlays() - { - // Order of priority must match GetLocalStatus (CollabClient.h) - s_Overlays.Clear(); - s_Overlays.Add(Collab.CollabStates.kCollabIgnored, EditorGUIUtility.IconContent("CollabExclude Icon")); - s_Overlays.Add(Collab.CollabStates.kCollabConflicted, EditorGUIUtility.IconContent("CollabConflict Icon")); - s_Overlays.Add(Collab.CollabStates.kCollabPendingMerge, EditorGUIUtility.IconContent("CollabConflict Icon")); - s_Overlays.Add(Collab.CollabStates.kCollabMovedLocal, EditorGUIUtility.IconContent("CollabMoved Icon")); - s_Overlays.Add(Collab.CollabStates.kCollabCheckedOutLocal | Collab.CollabStates.kCollabMovedLocal, EditorGUIUtility.IconContent("CollabMoved Icon")); - s_Overlays.Add(Collab.CollabStates.kCollabCheckedOutLocal, EditorGUIUtility.IconContent("CollabEdit Icon")); - s_Overlays.Add(Collab.CollabStates.kCollabAddedLocal, EditorGUIUtility.IconContent("CollabCreate Icon")); - s_Overlays.Add(Collab.CollabStates.kCollabDeletedLocal, EditorGUIUtility.IconContent("CollabDeleted Icon")); - - // The folder overlay should take precedence on the folder content's status. - s_Overlays.Add(Collab.CollabStates.KCollabContentConflicted, EditorGUIUtility.IconContent("CollabChangesConflict Icon")); - s_Overlays.Add(Collab.CollabStates.KCollabContentChanged, EditorGUIUtility.IconContent("CollabChanges Icon")); - s_Overlays.Add(Collab.CollabStates.KCollabContentDeleted, EditorGUIUtility.IconContent("CollabChangesDeleted Icon")); - } - - protected static bool AreOverlaysLoaded() - { - if (s_Overlays.Count == 0) - return false; - - foreach (var icon in s_Overlays.Values) - { - if (icon == null) - return false; - } - - return true; - } - - protected static Collab.CollabStates GetOverlayStateForAsset(Collab.CollabStates assetStates) - { - foreach (var state in s_Overlays.Keys) - { - if (HasState(assetStates, state)) - return state; - } - - return Collab.CollabStates.kCollabNone; - } - - protected static void DrawOverlayElement(Collab.CollabStates singleState, Rect itemRect) - { - GUIContent content; - if (s_Overlays.TryGetValue(singleState, out content)) - { - Texture overlay = content.image; - if (overlay != null) - { - GUI.DrawTexture(itemRect, overlay, ScaleMode.ScaleToFit); - } - } - } - - protected static bool HasState(Collab.CollabStates assetStates, Collab.CollabStates includesState) - { - return ((assetStates & includesState) == includesState); - } - - public static void DrawOverlays(Collab.CollabStates assetState, Rect itemRect, bool isListMode) - { - if (assetState == Collab.CollabStates.kCollabInvalidState || assetState == Collab.CollabStates.kCollabNone) - return; - - if (Event.current.type != EventType.Repaint) - return; - - if (!AreOverlaysLoaded()) - LoadOverlays(); - - var state = GetOverlayStateForAsset(assetState); - DrawOverlayElement(state, GetRectForTopRight(itemRect, GetScale(itemRect, isListMode))); - } - - // Return a new Rect with its width and height scaled, and converted to the ceiling. - public static Rect ScaleRect(Rect rect, double scale) - { - Rect scaledRect = new Rect(rect); - scaledRect.width = Convert.ToInt32(Math.Ceiling(rect.width * scale)); - scaledRect.height = Convert.ToInt32(Math.Ceiling(rect.height * scale)); - return scaledRect; - } - - public static double GetScale(Rect rect, bool isListMode) - { - double scale = k_OverlaySizeOnLargeIcon; - if (isListMode) - { - scale = k_OverlaySizeOnSmallIcon; - } - return scale; - } - - public static Rect GetRectForTopRight(Rect projectBrowserDrawRect, double scale) - { - Rect scaledRect = ScaleRect(projectBrowserDrawRect, scale); - scaledRect.x += (projectBrowserDrawRect.width - scaledRect.width); - return scaledRect; - } - - public static Rect GetRectForBottomRight(Rect projectBrowserDrawRect, double scale) - { - Rect scaledRect = ScaleRect(projectBrowserDrawRect, scale); - scaledRect.x += (projectBrowserDrawRect.width - scaledRect.width); - scaledRect.y += (projectBrowserDrawRect.height - scaledRect.height); - return scaledRect; - } - } - - internal static class TextureUtility - { - public static Texture2D LoadTextureFromApplicationContents(string path) - { - var tex = new Texture2D(2, 2); - - string resourcesPath = Path.Combine(Path.Combine(Path.Combine(EditorApplication.applicationContentsPath, "Resources"), "Collab"), "overlays"); - path = Path.Combine(resourcesPath, path); - - try - { - using (var fs = File.OpenRead(path)) - { - var bytes = new byte[fs.Length]; - fs.Read(bytes, 0, (int)fs.Length); - if (!tex.LoadImage(bytes)) return null; - } - } - catch (Exception) - { - Debug.LogWarning("Collab Overlay Texture load fail, path: " + path); - return null; - } - - return tex; - } - } -} diff --git a/Editor/Mono/Collab/CollabFilters.cs b/Editor/Mono/Collab/CollabFilters.cs deleted file mode 100644 index 9a2373fc4a..0000000000 --- a/Editor/Mono/Collab/CollabFilters.cs +++ /dev/null @@ -1,169 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - - -using System; -using System.Collections.Generic; -using System.Linq; -using UnityEditor; -using UnityEngine; - -namespace UnityEditor.Collaboration -{ - internal abstract class AbstractFilters - { - [SerializeField] - private List m_Filters; - - public List filters { get { return m_Filters; } set { m_Filters = value; } } - - public abstract void InitializeFilters(); - - public bool ContainsSearchFilter(string name, string searchString) - { - foreach (var filter in filters) - { - if (filter[0] == name && filter[1] == searchString) - return true; - } - return false; - } - - public void ShowInFavoriteSearchFilters() - { - if (SavedSearchFilters.GetRootInstanceID() == 0) - { - SavedSearchFilters.AddInitializedListener(ShowInFavoriteSearchFilters); - return; - } - - SavedSearchFilters.RemoveInitializedListener(ShowInFavoriteSearchFilters); - int prevInstanceID = 0; - foreach (var filter in filters) - { - int instanceID = SavedSearchFilters.GetFilterInstanceID(filter[0], filter[1]); - if (instanceID == 0) - { - SearchFilter searchFilter = SearchFilter.CreateSearchFilterFromString(filter[1]); - if (prevInstanceID == 0) - prevInstanceID = SavedSearchFilters.AddSavedFilter(filter[0], searchFilter, 64); - else - prevInstanceID = SavedSearchFilters.AddSavedFilterAfterInstanceID(filter[0], searchFilter, 64, prevInstanceID, false); - } - } - - SavedSearchFilters.RefreshSavedFilters(); - - foreach (ProjectBrowser pb in ProjectBrowser.GetAllProjectBrowsers()) - { - pb.Repaint(); - } - } - - public void HideFromFavoriteSearchFilters() - { - SavedSearchFilters.RefreshSavedFilters(); - - foreach (ProjectBrowser pb in ProjectBrowser.GetAllProjectBrowsers()) - { - pb.Repaint(); - } - } - } - - internal class CollabFilters : AbstractFilters - { - [SerializeField] - private bool m_SearchFilterWasSet = false; - - public override void InitializeFilters() - { - filters = new List() - { - new string[] { "All Modified", "v:any" }, - new string[] { "All Conflicts", "v:conflicted" }, - new string[] { "All Excluded" , "v:ignored"}, - }; - } - - public CollabFilters() - { - InitializeFilters(); - } - - public void ShowInProjectBrowser(string filterString) - { - ProjectBrowser browser = ProjectBrowser.s_LastInteractedProjectBrowser; - if (browser == null) - { - List browsers = ProjectBrowser.GetAllProjectBrowsers(); - if (browsers != null && browsers.Count > 0) - { - browser = browsers.First(); - } - } - - if (!string.IsNullOrEmpty(filterString)) - { - if (browser == null) - { - browser = EditorWindow.GetWindow(); - ShowInFavoriteSearchFilters(); - browser.RepaintImmediately(); - } - - m_SearchFilterWasSet = true; - - string filterSearchString = "v:" + filterString; - if (browser.IsTwoColumns()) - { - foreach (var filter in filters) - { - if (filterSearchString == filter[1]) - { - int instanceID = SavedSearchFilters.GetFilterInstanceID(filter[0], filterSearchString); - if (instanceID > ProjectWindowUtil.k_FavoritesStartInstanceID) - { - browser.SetFolderSelection(new int[] { instanceID }, true); - break; - } - } - } - } - - browser.SetSearch(filterSearchString); - browser.Repaint(); - browser.Focus(); - } - else - { - if (m_SearchFilterWasSet) - { - if (browser != null) - { - if (browser.IsTwoColumns()) - { - int instanceID = AssetDatabase.GetMainAssetInstanceID("assets"); - browser.SetFolderSelection(new int[] { instanceID }, true); - } - browser.SetSearch(""); - browser.Repaint(); - } - } - m_SearchFilterWasSet = false; - } - } - - public void OnCollabStateChanged(CollabInfo info) - { - if (!info.ready || info.inProgress || info.maintenance) - return; - - foreach (ProjectBrowser pb in ProjectBrowser.GetAllProjectBrowsers()) - { - pb.RefreshSearchIfFilterContains("v:"); - } - } - } -} diff --git a/Editor/Mono/Collab/CollabProgressInfo.cs b/Editor/Mono/Collab/CollabProgressInfo.cs deleted file mode 100644 index 5727a41a7d..0000000000 --- a/Editor/Mono/Collab/CollabProgressInfo.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using System.Runtime.InteropServices; -using UnityEngine.Bindings; - -namespace UnityEditor.Collaboration -{ - // Keep internal and undocumented until we expose more functionality - //*undocumented - [StructLayout(LayoutKind.Sequential)] - [NativeType(CodegenOptions = CodegenOptions.Custom, Header = "Editor/Src/Collab/CollabProgressInfo.h", - IntermediateScriptingStructName = "ScriptingCollabProgressInfo")] - [NativeHeader("Editor/Src/Collab/Collab.bindings.h")] - [NativeAsStruct] - internal class ProgressInfo - { - public enum ProgressType : uint - { - None = 0, - Count = 1, - Percent = 2, - Both = 3 - } - - int m_JobId; - string m_Title; - string m_ExtraInfo; - ProgressType m_ProgressType; - int m_Percentage; - int m_CurrentCount; - int m_TotalCount; - bool m_Completed; - bool m_Cancelled; - bool m_CanCancel; - string m_LastErrorString; - ulong m_LastError; - - public int jobId { get { return m_JobId; } } - public string title { get { return m_Title; } } - public string extraInfo { get { return m_ExtraInfo; } } - public int currentCount { get { return m_CurrentCount; } } - public int totalCount { get { return m_TotalCount; } } - public bool completed { get { return m_Completed; } } - public bool cancelled { get { return m_Cancelled; } } - public bool canCancel { get { return m_CanCancel; } } - public string lastErrorString { get { return m_LastErrorString; } } - public ulong lastError { get { return m_LastError; } } - - public int percentComplete - { - get - { - if (m_ProgressType == ProgressType.Percent || m_ProgressType == ProgressType.Both) - { - return m_Percentage; - } - - if (m_ProgressType == ProgressType.Count) - { - if (m_TotalCount == 0) return 0; - return (m_CurrentCount * 100) / m_TotalCount; - } - return 0; - } - } - - public bool isProgressTypeCount { get { return (m_ProgressType == ProgressType.Count || m_ProgressType == ProgressType.Both); } } - public bool isProgressTypePercent { get { return (m_ProgressType == ProgressType.Percent || m_ProgressType == ProgressType.Both); } } - public bool errorOccured { get { return (m_LastError != 0); } } - } -} diff --git a/Editor/Mono/Collab/CollabRevision.cs b/Editor/Mono/Collab/CollabRevision.cs deleted file mode 100644 index d00b479d95..0000000000 --- a/Editor/Mono/Collab/CollabRevision.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using System.Runtime.InteropServices; -using UnityEngine.Bindings; -using UnityEngine.Scripting; - -namespace UnityEditor.Collaboration -{ - // Keep internal and undocumented until we expose more functionality - //*undocumented - [StructLayout(LayoutKind.Sequential)] - [UsedByNativeCode] - internal struct Revision - { - [NativeName("m_CommitterName")] - private string m_AuthorName; - [NativeName("m_CommitterEmail")] - private string m_Author; - private string m_Comment; - private string m_RevisionID; - private string m_Reference; - private ulong m_TimeStamp; - // Whether this revision has been obtained by the client - private bool m_IsObtained; - private ChangeAction[] m_Entries; - private CloudBuildStatus[] m_BuildStatuses; - - internal Revision(string revisionID = "", string authorName = "", string author = "", - string comment = "", string reference = "", ulong timeStamp = 0, - bool isObtained = false, ChangeAction[] entries = null, - CloudBuildStatus[] buildStatuses = null) - { - m_AuthorName = authorName; - m_Author = author; - m_Comment = comment; - m_RevisionID = revisionID; - m_Reference = reference; - m_TimeStamp = timeStamp; - m_IsObtained = isObtained; - m_Entries = entries ?? new ChangeAction[0]; - m_BuildStatuses = buildStatuses ?? new CloudBuildStatus[0]; - } - - public string authorName { get { return m_AuthorName; } } - public string author { get { return m_Author; } } - public string comment { get { return m_Comment; } } - public string revisionID { get { return m_RevisionID; } } - public string reference { get { return m_Reference; } } - public ulong timeStamp { get { return m_TimeStamp; } } - public bool isObtained { get { return m_IsObtained; } } - public ChangeAction[] entries { get { return m_Entries; } } - public CloudBuildStatus[] buildStatuses { get { return m_BuildStatuses; } } - } -} diff --git a/Editor/Mono/Collab/CollabRevisionsData.cs b/Editor/Mono/Collab/CollabRevisionsData.cs deleted file mode 100644 index 0c13f70010..0000000000 --- a/Editor/Mono/Collab/CollabRevisionsData.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using System.Runtime.InteropServices; -using UnityEngine.Bindings; -using UnityEngine.Scripting; - -namespace UnityEditor.Collaboration -{ - // Keep internal and undocumented until we expose more functionality - //*undocumented - [StructLayout(LayoutKind.Sequential)] - [UsedByNativeCode] - internal struct RevisionsData - { - private int m_RevisionsInRepo; - private int m_RevisionOffset; - private int m_ReturnedRevisions; - private Revision[] m_Revisions; - - public int RevisionsInRepo {get { return m_RevisionsInRepo; }} - public int RevisionOffset {get { return m_RevisionOffset; }} - public int ReturnedRevisions {get { return m_ReturnedRevisions; }} - public Revision[] Revisions {get { return m_Revisions; }} - } -} diff --git a/Editor/Mono/Collab/CollabTesting.cs b/Editor/Mono/Collab/CollabTesting.cs index 38b0e268a3..06c451a530 100644 --- a/Editor/Mono/Collab/CollabTesting.cs +++ b/Editor/Mono/Collab/CollabTesting.cs @@ -64,8 +64,6 @@ public static void Execute() if (_enumerator == null) return; - if (Collab.instance.AnyJobRunning()) - return; try { diff --git a/Editor/Mono/Collab/CollabToUVCSBridge.cs b/Editor/Mono/Collab/CollabToUVCSBridge.cs new file mode 100644 index 0000000000..77ae6baf8f --- /dev/null +++ b/Editor/Mono/Collab/CollabToUVCSBridge.cs @@ -0,0 +1,546 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +// This file was introduced as part of the collab removal, it supports any versions of com.unity.collab-proxy under 1.17.7 +// It allows for an error free upgrade by provinding mock classes for the removed ones. + +using System; +using System.Diagnostics; +using System.Collections.Generic; + +using UnityEditor.Connect; +using UnityEditor.PackageManager; +using UnityEngine; + +#pragma warning disable 0067 +#pragma warning disable 0618 +namespace UnityEditor.Collaboration +{ + internal static class LogObsolete + { + static bool s_Initialized; + static bool s_NeedsLogging; + static Stopwatch s_Stopwatch = Stopwatch.StartNew(); + + internal static void Log() + { + if (!s_Initialized) + s_NeedsLogging = IsObsolete("com.unity.collab-proxy", "1.1"); + + s_Initialized = true; + + if (!s_NeedsLogging) + return; + + if (s_Stopwatch.ElapsedMilliseconds < 1000) + return; + + UnityEngine.Debug.unityLogger.LogWarning( + "com.unity.collab-proxy", + "This version of the package is not supported, please upgrade to the latest version. https://unity.com/solutions/version-control"); + + s_Stopwatch.Restart(); + } + + internal static bool IsObsolete(string packageName, string version) + { + UnityEditor.PackageManager.PackageInfo package = null; + try + { + var list = Client.List(true); + var stp = Stopwatch.StartNew(); + + while(!list.IsCompleted && stp.ElapsedMilliseconds < 1000) + System.Threading.Thread.Sleep(100); + + if (list.Result == null) + return false; + + foreach(var p in list.Result) + { + if (p.name == packageName) + { + package = p; + break; + } + } + } + catch + { + return false; + } + + if (package == null) + return false; + + if (package.version == null) + return false; + + return package.version.StartsWith(version); + } + } + + internal class Collab + { + static Collab s_instance = null; + public static Collab instance { + get + { + if (s_instance == null) + s_instance = new Collab(); + + LogObsolete.Log(); + return s_instance; + } + } + + Collab() + { + } + + [Flags] + public enum Operation + { + Noop = 0, + Publish = 1 << 0, + Update = 1 << 1, + Revert = 1 << 2, + GoBack = 1 << 3, + Restore = 1 << 4, + Diff = 1 << 5, + ConflictDiff = 1 << 6, + Exclude = 1 << 7, + Include = 1 << 8, + ChooseMine = 1 << 9, + ChooseTheirs = 1 << 10, + ExternalMerge = 1 << 11, + } + + [Flags] + public enum CollabStates : uint + { + kCollabNone = 0, + kCollabLocal = 1, + kCollabSynced = 1 << 1, + kCollabOutOfSync = 1 << 2, + kCollabIgnored = 1 << 3, + kCollabCheckedOutLocal = 1 << 4, + kCollabCheckedOutRemote = 1 << 5, + kCollabDeletedLocal = 1 << 6, + kCollabDeletedRemote = 1 << 7, + kCollabAddedLocal = 1 << 8, + kCollabAddedRemote = 1 << 9, + kCollabConflicted = 1 << 10, + kCollabMovedLocal = 1 << 11, + kCollabMovedRemote = 1 << 12, + kCollabUpdating = 1 << 13, + kCollabReadOnly = 1 << 14, + kCollabMetaFile = 1 << 15, + kCollabUseMine = 1 << 16, + kCollabUseTheir = 1 << 17, + kCollabMerged = 1 << 18, + kCollabPendingMerge = 1 << 19, + kCollabFolderMetaFile = 1 << 20, + KCollabContentChanged = 1 << 21, + KCollabContentConflicted = 1 << 22, + KCollabContentDeleted = 1 << 23, + kCollabInvalidState = 1 << 30, + kAnyLocalChanged = (kCollabAddedLocal | kCollabCheckedOutLocal | kCollabDeletedLocal | kCollabMovedLocal), + kAnyLocalEdited = (kCollabAddedLocal | kCollabCheckedOutLocal | kCollabMovedLocal), + kCollabAny = 0xFFFFFFFF + } + + internal enum CollabStateID { None, Uninitialized, Initialized } + + internal static class BindingsMarshaller + { + public static IntPtr ConvertToNative(Collab collab){ return IntPtr.Zero; } + } + + public event StateChangedDelegate StateChanged; + public event StateChangedDelegate RevisionUpdated; + public event RevisionChangedDelegate RevisionUpdated_V2; + public event StateChangedDelegate JobsCompleted; + public event ErrorDelegate ErrorOccurred; + public event SetErrorDelegate ErrorOccurred_V2; + public event ErrorDelegate ErrorCleared; + public event ChangeItemsChangedDelegate ChangeItemsChanged; + public event ChangeItemsChangedDelegate SelectedChangeItemsChanged; + public event StateChangedDelegate CollabInfoChanged; + public static int GetRevisionsData(bool withChanges, int startIndex, int numRevisions){ return 0; } + public static int GetSingleRevisionData(bool withChanges, string id){ return 0; } + public static RevisionsData PopulateRevisionsData(IntPtr nativeData){ return new RevisionsData(); } + public static Revision PopulateSingleRevisionData(IntPtr nativeData){ return new Revision(); } + public static ShowToolbarAtPositionDelegate ShowToolbarAtPosition = null; + public static IsToolbarVisibleDelegate IsToolbarVisible = null; + public static CloseToolbarDelegate CloseToolbar = null; + public static ShowHistoryWindowDelegate ShowHistoryWindow = null; + public static ShowChangesWindowDelegate ShowChangesWindow = null; + public static string[] clientType = Array.Empty(); + internal static string editorPrefCollabClientType = string.Empty; + public static string GetProjectClientType() { return string.Empty; } + public static void SetVersionControl(IVersionControl instance){} + internal static bool HasVersionControl(){ return false; } + internal static void ShowChangesWindowView(){} + internal static CollabStates GetAssetState(string assetGuid, string assetPath){ return (CollabStates)0; } + public static void OnSettingStatusChanged(CollabSettingType type, CollabSettingStatus status){} + public static bool InitializeSoftlocksViewController(){ return false; } + public static bool IsDiffToolsAvailable(){ return false; } + public static void SwitchToDefaultMode(){} + public static void OnProgressEnabledSettingStatusChanged(CollabSettingType type, CollabSettingStatus status){} + + public CollabInfo collabInfo { get; } + public void SetSeat(bool value){} + public void RefreshSeatAvailabilityAsync(){} + public string GetProjectGUID(){ return string.Empty; } + public bool ShouldDoInitialCommit(){ return false; } + public void ShowDifferences(string path){} + public void SendNotification(){} + public void SetError(int errorCode){} + public void ClearError(int errorCode){} + public void ClearErrors(){} + public void ForceRefresh(bool refreshAssetDatabase){} + public void SetCollabEnabledForCurrentProject(bool enabled){} + public bool IsCollabEnabledForCurrentProject(){ return false; } + public bool IsAssetIgnored(string path){ return false; } + public bool ShouldTrackAsset(string path){ return false; } + public string GetProjectPath(){ return string.Empty; } + public bool IsConnected(){ return false; } + public bool AnyJobRunning(){ return false; } + public bool JobRunning(int a_jobID){ return false; } + public CollabStates GetAssetState(string guid){ return (CollabStates)0; } + public CollabStates GetSelectedAssetState(){ return (CollabStates)0; } + public CollabStateID GetCollabState(){ return (CollabStateID)0; } + public bool ValidateSelectiveCommit(){ return false; } + public void Disconnect(){} + public void CancelJobByType(int jobType, bool forceCancel){} + public void DoInitialCommit(){} + public void Update(string revisionID, bool updateToRevision){} + public void RevertFile(string path, bool forceOverwrite){} + public void RevertFiles(ChangeItem[] changeItems, bool forceOverwrite){} + public void LaunchConflictExternalMerge(string path){} + public void ShowConflictDifferences(string path){} + public void ResyncSnapshot(){} + public void GoBackToRevision(string revisionID, bool updateToRevision){} + public void ResyncToRevision(string revisionID){} + public Change[] GetCollabConflicts(){ return Array.Empty(); } + public void CheckConflictsResolvedExternal(){} + public bool AreTestsRunning(){ return false; } + public void SetTestsRunning(bool running){} + public void ClearAllFailures(){} + public void ClearNextOperationFailure(){} + public void ClearNextOperationFailureForFile(string path){} + public string GetGUIDForTests(){ return string.Empty; } + public void NewGUIDForTests(){} + public void FailNextOperation(Collab.Operation operation, int code){} + public void TimeOutNextOperation(Collab.Operation operation, int timeOutSec){} + public void FailNextOperationForFile(string path, Collab.Operation operation, int code){} + public void TimeOutNextOperationForFile(string path, Collab.Operation operation, int timeOutSec){} + public void TestPostSoftLockAsCollaborator(string projectGuid, string projectPath, string machineGuid, string assetGuid){} + public void TestClearSoftLockAsCollaborator(string projectGuid, string projectPath, string machineGuid, string softLockHash){} + internal bool GetErrorInternal(int errorFilter, out UnityErrorInfo info){ info = new UnityErrorInfo(); return false; } + public void Publish(string comment, bool useSelectedAssets, bool confirmMatchesPrevious){} + public void PublishAssetsAsync(string comment, ChangeItem[] changes){} + public void ClearSelectedChangesToPublish(){} + public void SendCollabInfoNotification(){} + public CollabFilters collabFilters = new CollabFilters(); + public String projectBrowserSingleSelectionPath { get; set; } + public String projectBrowserSingleMetaSelectionPath { get; set; } + public string[] currentProjectBrowserSelection; + public void RefreshAvailableLocalChangesSynchronous(){} + public bool GetError(UnityConnect.UnityErrorFilter errorFilter, out UnityErrorInfo info){ info = new UnityErrorInfo(); return false; } + public void CancelJob(int jobType){} + public void UpdateEditorSelectionCache(){} + public CollabInfo GetCollabInfo(){ return new CollabInfo(); } + public void SaveAssets(){} + public void ShowInProjectBrowser(string filterString){} + public bool SetConflictsResolvedMine(string[] paths){ return false; } + public bool SetConflictsResolvedTheirs(string[] paths){ return false; } + public PublishInfo GetChangesToPublish() { return new PublishInfo(); } + public PublishInfo_V2 GetChangesToPublish_V2() { return new PublishInfo_V2(); } + public void SetChangesToPublish(ChangeItem[] changes){} + public ProgressInfo GetJobProgress(int jobId){ return new ProgressInfo(); } + } + + internal enum CollabSettingType + { + InProgressEnabled = 0, + InProgressProjectEnabled = 1, + InProgressGlobalEnabled = 2 + } + + internal enum CollabSettingStatus + { + None = 0, + Available = 1 + } + + internal class CollabSettingsManager + { + public delegate void SettingStatusChanged(CollabSettingType type, CollabSettingStatus status); + public static Dictionary statusNotifier = new Dictionary(); + + static CollabSettingsManager(){} + + public static bool IsAvailable(CollabSettingType type) + { + return false; + } + + public static bool inProgressEnabled { get; } + } + + internal class ProgressInfo + { + public enum ProgressType : uint + { + None = 0, + Count = 1, + Percent = 2, + Both = 3 + } + + public int jobId { get { return 0; } } + public string title { get { return string.Empty; } } + public string extraInfo { get { return string.Empty; } } + public int currentCount { get { return 0; } } + public int totalCount { get { return 0; } } + public bool completed { get { return true; } } + public bool cancelled { get { return false; } } + public bool canCancel { get { return false; } } + public string lastErrorString { get { return string.Empty; } } + public ulong lastError { get { return 0; } } + public int percentComplete { get{ return 0; } } + public bool isProgressTypeCount { get { return false; } } + public bool isProgressTypePercent { get { return false; } } + public bool errorOccured { get { return false; } } + } + + internal class ChangeItem + { + public string Path { get; set; } + public Change.RevertableStates RevertableState { get; set; } + public string RelatedTo { get; set; } + public string RevisionId { get; set; } + public string Hash { get; set; } + public Collab.CollabStates State { get; set; } + public long Size { get; set; } + public string DownloadPath { get; set; } + public string FromPath { get; set; } + } + + internal class PublishInfo + { + public Change[] changes; + public bool filter; + } + + internal class PublishInfo_V2 + { + public ChangeItem[] changes; + public bool filter; + } + + internal class RevisionsResult + { + public List Revisions = new List(); + public int RevisionsInRepo = -1; + public int Count { get { return 0; } } + + public void Clear(){} + } + + internal interface IRevisionsService + { + event RevisionsDelegate FetchRevisionsCallback; + void GetRevisions(int offset, int count); + string tipRevision { get; } + string currentUser { get; } + } + + internal class RevisionsService : IRevisionsService + { + public event RevisionsDelegate FetchRevisionsCallback; + public event SingleRevisionDelegate FetchSingleRevisionCallback; + + public string tipRevision { get { return string.Empty; } } + public string currentUser { get { return string.Empty; } } + + public RevisionsService(Collab collabInstance, UnityConnect connectInstance) + { + } + + public void GetRevisions(int offset, int count){} + + public void GetRevision(string revId){} + } + + internal class Change + { + public enum RevertableStates : uint + { + Revertable = 1 << 0, + NotRevertable = 1 << 1, + Revertable_File = 1 << 2, + Revertable_Folder = 1 << 3, + Revertable_EmptyFolder = 1 << 4, + NotRevertable_File = 1 << 5, + NotRevertable_Folder = 1 << 6, + NotRevertable_FileAdded = 1 << 7, + NotRevertable_FolderAdded = 1 << 8, + NotRevertable_FolderContainsAdd = 1 << 9, + InvalidRevertableState = (uint)1 << 31 + } + + public string path { get { return string.Empty; } } + public Collab.CollabStates state { get { return (Collab.CollabStates)0; } } + public bool isRevertable { get { return false; } } + public RevertableStates revertableState { get { return (RevertableStates)0; } } + public string relatedTo { get { return string.Empty; } } + public bool isMeta { get { return false; } } + public bool isConflict { get { return false; } } + public bool isFolderMeta { get { return false; } } + public bool isResolved { get { return false; } } + public string localStatus { get { return string.Empty; } } + public string remoteStatus { get { return string.Empty; } } + public string resolveStatus { get { return string.Empty; } } + + internal bool HasState(Collab.CollabStates states) + { + return false; + } + + internal bool HasRevertableState(RevertableStates revertableStates) + { + return false; + } + } + + internal abstract class AbstractFilters + { + public List filters { get; set;} + public abstract void InitializeFilters(); + public bool ContainsSearchFilter(string name, string searchString){ return false; } + public void ShowInFavoriteSearchFilters(){} + public void HideFromFavoriteSearchFilters(){} + } + + internal class CollabFilters : AbstractFilters + { + public override void InitializeFilters(){} + public void ShowInProjectBrowser(string filterString){} + public void OnCollabStateChanged(CollabInfo info){} + } + + + internal delegate void StateChangedDelegate(CollabInfo info); + internal delegate void RevisionChangedDelegate(CollabInfo info, string rev, string action); + internal delegate void SetErrorDelegate(UnityErrorInfo error); + internal delegate void ErrorDelegate(); + internal delegate bool ShowToolbarAtPositionDelegate(Rect screenRect); + internal delegate bool IsToolbarVisibleDelegate(); + internal delegate void ShowHistoryWindowDelegate(); + internal delegate void ShowChangesWindowDelegate(); + internal delegate void CloseToolbarDelegate(); + internal delegate void ChangesChangedDelegate(Change[] changes, bool isFiltered); + internal delegate void ChangeItemsChangedDelegate(ChangeItem[] changes, bool isFiltered); + delegate void RevisionsDelegate(RevisionsResult revisionsResult); + delegate void SingleRevisionDelegate(Revision? revision); + + internal struct CollabInfo + { + public bool ready { get { return true; } } + public bool update { get { return false; } } + public bool publish { get { return false; } } + public bool inProgress { get { return false; } } + public bool maintenance { get { return false; } } + public bool conflict { get { return false; } } + public bool refresh { get { return false; } } + public bool seat { get { return false; } } + public string tip { get { return string.Empty; } } + public bool Equals(CollabInfo other){ return false; } + } + + internal struct ChangeAction + { + public ChangeAction(string path = "", string action = ""){} + public string path { get { return string.Empty; } } + public string action { get { return string.Empty; } } + } + + internal struct Revision + { + internal Revision(string revisionID = "", string authorName = "", string author = "", string comment = "", string reference = "", ulong timeStamp = 0, bool isObtained = false, ChangeAction[] entries = null, CloudBuildStatus[] buildStatuses = null){} + public string authorName { get { return string.Empty; } } + public string author { get { return string.Empty; } } + public string comment { get { return string.Empty; } } + public string revisionID { get { return string.Empty; } } + public string reference { get { return string.Empty; } } + public ulong timeStamp { get { return 0; } } + public bool isObtained { get { return false; } } + public ChangeAction[] entries { get { return Array.Empty(); } } + public CloudBuildStatus[] buildStatuses { get { return Array.Empty(); } } + } + + internal struct CloudBuildStatus + { + internal CloudBuildStatus(string platform = "", bool complete = false, bool success = false){} + public string platform { get { return string.Empty; } } + public bool complete { get { return false; } } + public bool success { get { return false; } } + } + + internal struct RevisionData + { + public string id; + public int index; + public DateTime timeStamp; + public string authorName; + public string comment; + public bool obtained; + public bool current; + public bool inProgress; + public bool enabled; + public BuildState buildState; + public int buildFailures; + public ICollection changes; + public int changesTotal; + public bool changesTruncated; + } + + internal struct RevisionsData + { + public int RevisionsInRepo {get { return 0; }} + public int RevisionOffset {get { return 0; }} + public int ReturnedRevisions {get { return 0; }} + public Revision[] Revisions {get { return Array.Empty(); }} + } + + internal enum HistoryState + { + Error, + Offline, + Maintenance, + LoggedOut, + NoSeat, + Disabled, + Waiting, + Ready, + } + + internal enum BuildState + { + None, + Configure, + Success, + Failed, + InProgress, + } + + internal struct ChangeData + { + public string path; + public string action; + } +} diff --git a/Editor/Mono/Collab/IVersionControl.cs b/Editor/Mono/Collab/IVersionControl.cs index e636800498..14a9ad00bd 100644 --- a/Editor/Mono/Collab/IVersionControl.cs +++ b/Editor/Mono/Collab/IVersionControl.cs @@ -10,9 +10,7 @@ internal interface IVersionControl bool SupportsAsyncChanges(); bool OnEnableVersionControl(); void OnDisableVersionControl(); - ChangeItem[] GetChanges(); void MergeDownloadedFiles(bool isFullDownload); - Collab.CollabStates GetAssetState(string assetGuid, string assetPath); } internal interface IVersionControl_V2 : IVersionControl diff --git a/Editor/Mono/Collab/RevisionsService.cs b/Editor/Mono/Collab/RevisionsService.cs deleted file mode 100644 index 56bea66492..0000000000 --- a/Editor/Mono/Collab/RevisionsService.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using System; -using System.Collections.Generic; -using UnityEditor.Collaboration; -using UnityEditor.Connect; -using UnityEngine; -using UnityEngine.Scripting; - -namespace UnityEditor.Collaboration -{ - delegate void RevisionsDelegate(RevisionsResult revisionsResult); - delegate void SingleRevisionDelegate(Revision? revision); - internal class RevisionsResult - { - public List Revisions = new List(); - public int RevisionsInRepo = -1; - - public int Count { get { return Revisions.Count; } } - - public void Clear() - { - Revisions.Clear(); - RevisionsInRepo = -1; - } - } - - internal interface IRevisionsService - { - event RevisionsDelegate FetchRevisionsCallback; - void GetRevisions(int offset, int count); - string tipRevision { get; } - string currentUser { get; } - } - - internal class RevisionsService : IRevisionsService - { - public event RevisionsDelegate FetchRevisionsCallback; - public event SingleRevisionDelegate FetchSingleRevisionCallback; - - protected Collab collab; - protected UnityConnect connect; - private static RevisionsService instance; - - public string tipRevision { get { return collab.collabInfo.tip; } } - public string currentUser { get { return connect.GetUserInfo().userName; } } - - public RevisionsService(Collab collabInstance, UnityConnect connectInstance) - { - collab = collabInstance; - connect = connectInstance; - instance = this; - } - - public void GetRevisions(int offset, int count) - { - // Only send down request for the desired data. - Collab.GetRevisionsData(true, offset, count); - } - - public void GetRevision(string revId) - { - Collab.GetSingleRevisionData(true, revId); - } - - [RequiredByNativeCode] - private static void onFetchSingleRevision(IntPtr ptr) - { - Revision? ret = null; - if (instance.FetchSingleRevisionCallback != null && ptr != IntPtr.Zero) - { - Revision nativeStruct = Collab.PopulateSingleRevisionData(ptr); - // this copies the content as it's a struct not a class. - ret = nativeStruct; - } - - instance.FetchSingleRevisionCallback(ret); - } - - [RequiredByNativeCode] - private static void OnFetchRevisions(IntPtr nativeData) - { - RevisionsService service = instance; - if (service == null || service.FetchRevisionsCallback == null) - return; - - RevisionsResult history = null; - if (nativeData != IntPtr.Zero) - { - RevisionsData data = Collab.PopulateRevisionsData(nativeData); - history = new RevisionsResult(); - history.Revisions.AddRange(data.Revisions); - history.RevisionsInRepo = data.RevisionsInRepo; - } - - service.FetchRevisionsCallback(history); - } - } -} diff --git a/Editor/Mono/Collab/Softlocks/CollabSoftLocks.cs b/Editor/Mono/Collab/Softlocks/CollabSoftLocks.cs deleted file mode 100644 index 44574720dc..0000000000 --- a/Editor/Mono/Collab/Softlocks/CollabSoftLocks.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - - -using System.Runtime.InteropServices; -using UnityEngine.Bindings; -using UnityEngine.Scripting; - -namespace UnityEditor.Collaboration -{ - // Keep internal and undocumented until we expose more functionality - //*undocumented - [StructLayout(LayoutKind.Sequential)] - [NativeType(CodegenOptions = CodegenOptions.Custom, Header = "Editor/Src/Collab/Softlocks/CollabSoftLock.h", - IntermediateScriptingStructName = "ScriptingSoftLock")] - [NativeHeader("Editor/Src/Collab/Collab.bindings.h")] - [NativeAsStruct] - internal class SoftLock - { - string m_UserID; - string m_MachineID; - string m_DisplayName; - ulong m_TimeStamp; - string m_Hash; - - SoftLock() {} - - public string userID { get { return m_UserID; } } - public string machineID { get { return m_MachineID; } } - public string displayName { get { return m_DisplayName; } } - public ulong timeStamp { get { return m_TimeStamp; } } - public string hash { get { return m_Hash; } } - } -} - diff --git a/Editor/Mono/Collab/Softlocks/SoftlockData.cs b/Editor/Mono/Collab/Softlocks/SoftlockData.cs deleted file mode 100644 index 759a931cd0..0000000000 --- a/Editor/Mono/Collab/Softlocks/SoftlockData.cs +++ /dev/null @@ -1,166 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - - -using System; -using System.Collections.Generic; -using UnityEngine.SceneManagement; -using UnityEngine.Scripting; - -namespace UnityEditor.Collaboration -{ - // Access and query the current set of softlock data. - internal static class SoftLockData - { - internal delegate void OnSoftlockUpdate(string[] assetGUIDs); - internal static OnSoftlockUpdate SoftlockSubscriber = null; - - // Invoked from C++ - [RequiredByNativeCode] - public static void SetSoftlockChanges(string[] assetGUIDs) - { - if (null != SoftlockSubscriber) - { - SoftlockSubscriber(assetGUIDs); - } - } - - // Returns whether the given object has soft lock support - // (i.e. is tracked in the back-end for simultaneous changes). - // Note: Scene is supported, but isn't a Unity Object. - public static bool AllowsSoftLocks(UnityEngine.Object unityObject) - { - if (unityObject == null) - { - throw new ArgumentNullException("unityObject"); - } - - bool supportsSoftLocks = false; - if (unityObject.GetType().Equals(typeof(SceneAsset))) - { - supportsSoftLocks = true; - } - else - { - supportsSoftLocks = IsPrefab(unityObject); - } - return supportsSoftLocks; - } - - public static bool IsPrefab(UnityEngine.Object unityObject) - { - return PrefabUtility.IsPartOfAnyPrefab(unityObject); - } - - public static bool IsPrefab(string assetGUID) - { - bool isPrefab = false; - UnityEngine.Object unityObject; - if (AssetAccess.TryGetAssetFromGUID(assetGUID, out unityObject)) - { - isPrefab = IsPrefab(unityObject); - } - return isPrefab; - } - - // Soft locks are present when collab is enabled and other users are - // editing the given object. - // Failure: assigns false to 'hasSoftLocks', returns false. - // Success: assigns true or false to 'hasSoftLocks', returns true. - public static bool TryHasSoftLocks(UnityEngine.Object objectWithGUID, out bool hasSoftLocks) - { - string assetGuid = null; - AssetAccess.TryGetAssetGUIDFromObject(objectWithGUID, out assetGuid); - bool success = TryHasSoftLocks(assetGuid, out hasSoftLocks); - return success; - } - - public static bool TryHasSoftLocks(string assetGuid, out bool hasSoftLocks) - { - hasSoftLocks = false; - bool success = false; - int count = 0; - - if (TryGetSoftlockCount(assetGuid, out count)) - { - success = true; - hasSoftLocks = (count > 0); - } - return success; - } - - // Provides the number of additional users editing the given scene. - // Failure: assigns 0 to count, return false. - // Success: assigns a value in [0, n] to count, returns true. - public static bool TryGetSoftlockCount(Scene scene, out int count) - { - bool success = false; - count = 0; - - if (!scene.IsValid()) - { - return false; - } - string assetGUID = AssetDatabase.AssetPathToGUID(scene.path); - success = TryGetSoftlockCount(assetGUID, out count); - return success; - } - - // Provides the number of additional users editing the given object. - // Failure: assigns 0 to count, return false. - // Success: assigns a value in [0, n] to count, returns true. - public static bool TryGetSoftlockCount(UnityEngine.Object objectWithGUID, out int count) - { - string assetGUID = null; - AssetAccess.TryGetAssetGUIDFromObject(objectWithGUID, out assetGUID); - bool success = TryGetSoftlockCount(assetGUID, out count); - return success; - } - - // Provides the number of additional users editing the given 'assetGUID'. - // Failure: assigns 0 to count, return false. - // Success: assigns a value in [0, n] to count, returns true. - public static bool TryGetSoftlockCount(string assetGuid, out int count) - { - bool success = false; - count = 0; - List softLocks = null; - - if (TryGetLocksOnAssetGUID(assetGuid, out softLocks)) - { - count = softLocks.Count; - success = true; - } - return success; - } - - // Provides a list of 'SoftLock' items, representing - // the additional users editing the given assetGUID. - // Failure: assigns empty list to 'softLocks', return false. - // Success: assigns the retrieved list to 'softLocks', return true. (May be empty). - public static bool TryGetLocksOnAssetGUID(string assetGuid, out List softLocks) - { - if (assetGuid == null) - { - throw new ArgumentNullException("assetGuid"); - } - - if (!Collab.instance.IsCollabEnabledForCurrentProject() || assetGuid.Length == 0) - { - softLocks = new List(); - return false; - } - - SoftLock[] _softlocks = Collab.instance.GetSoftLocks(assetGuid); - softLocks = new List(); - - for (int index = 0; index < _softlocks.Length; index++) - { - softLocks.Add(_softlocks[index]); - } - return true; - } - } -} - diff --git a/Editor/Mono/Collab/Softlocks/SoftlockFilter.cs b/Editor/Mono/Collab/Softlocks/SoftlockFilter.cs deleted file mode 100644 index a1a56d4571..0000000000 --- a/Editor/Mono/Collab/Softlocks/SoftlockFilter.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - - -using System; -using System.Collections.Generic; -using System.Linq; -using UnityEditor; - -namespace UnityEditor.Collaboration -{ - internal class SoftLockFilters : AbstractFilters - { - public override void InitializeFilters() - { - filters = new List() - { - new string[] { "All In Progress" , "s:inprogress"}, - }; - } - - public SoftLockFilters() - { - InitializeFilters(); - } - - public void OnSettingStatusChanged(CollabSettingType type, CollabSettingStatus status) - { - if (type == CollabSettingType.InProgressEnabled && (status == CollabSettingStatus.Available)) - { - if (Collab.instance.IsCollabEnabledForCurrentProject() && CollabSettingsManager.inProgressEnabled) - ShowInFavoriteSearchFilters(); - else - HideFromFavoriteSearchFilters(); - } - } - } -} diff --git a/Editor/Mono/Collab/Softlocks/SoftlockUIData.cs b/Editor/Mono/Collab/Softlocks/SoftlockUIData.cs deleted file mode 100644 index e0d8c1df20..0000000000 --- a/Editor/Mono/Collab/Softlocks/SoftlockUIData.cs +++ /dev/null @@ -1,207 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - - -using System; -using System.Collections.Generic; -using UnityEngine; -using UnityEngine.SceneManagement; -using UnityEditor.SceneManagement; -using UnityEditor.Web; - -namespace UnityEditor.Collaboration -{ - // Composes Softlock data into structures used by the UI. - internal static class SoftLockUIData - { - private static Dictionary s_ImageCache = new Dictionary(); - private static Dictionary s_ImageNameCache = new Dictionary(); - private const string kIconMipSuffix = " Icon"; - - public enum SectionEnum - { - None, - Inspector, - Scene, - ProjectBrowser - } - - #region General - - // Provides the names of all additional users editing the asset - // with the given 'assetGuid'. - // Defaults to an empty list. - public static List GetLocksNamesOnAsset(string assetGuid) - { - List softLocks = null; - List names = new List(); - - if (SoftLockData.TryGetLocksOnAssetGUID(assetGuid, out softLocks)) - { - foreach (SoftLock softLock in softLocks) - { - names.Add(softLock.displayName); - } - } - return names; - } - - #endregion - #region Scene - - // Provides the names of all additional users editing the scene. - // Defaults to an empty list. - public static List GetLocksNamesOnScene(Scene scene) - { - List names = GetLockNamesOnScenePath(scene.path); - return names; - } - - public static List GetLockNamesOnScenePath(string scenePath) - { - string assetGuid = AssetDatabase.AssetPathToGUID(scenePath); - List names = GetLocksNamesOnAsset(assetGuid); - return names; - } - - public static string GetSceneNameFromPath(string scenePath) - { - string name = ""; - if (null != scenePath) - { - name = scenePath; - } - return name; - } - - // Provides the names of all additional users editing each scene. - // Defaults to an empty list, and may contain empty sub-lists. - public static List> GetLockNamesOnScenes(List scenes) - { - List> namesByScene = new List>(); - - if (scenes == null) - { - return namesByScene; - } - - foreach (Scene scene in scenes) - { - List names = GetLocksNamesOnScene(scene); - namesByScene.Add(names); - } - return namesByScene; - } - - // For each iteration, returns the pair (scene name : list of other users' names). - public static IEnumerable>> GetLockNamesOnOpenScenes() - { - if (Collab.instance.IsCollabEnabledForCurrentProject()) - { - for (int sceneIndex = 0; sceneIndex < EditorSceneManager.sceneCount; sceneIndex++) - { - Scene scene = SceneManager.GetSceneAt(sceneIndex); - List names = GetLocksNamesOnScene(scene); - string sceneName = scene.name; - if (String.IsNullOrEmpty(sceneName)) - { - // Default for unnamed scenes. - sceneName = "Untitled"; - } - KeyValuePair> sceneData = new KeyValuePair>(sceneName, names); - yield return sceneData; - } - } - } - - public static int CountOfLocksOnOpenScenes() - { - int count = 0; - - foreach (KeyValuePair> sceneData in GetLockNamesOnOpenScenes()) - { - count += sceneData.Value.Count; - } - return count; - } - - #endregion - #region Game Object - - // The usernames of additional people editing the given 'objectWithGUID'. - // Defaults to an empty list. - public static List GetLockNamesOnObject(UnityEngine.Object objectWithGUID) - { - string assetGUID = null; - AssetAccess.TryGetAssetGUIDFromObject(objectWithGUID, out assetGUID); - List names = GetLocksNamesOnAsset(assetGUID); - return names; - } - - #endregion - #region Icons - - // The icon for the particular section in the editor. - // Defaults to null. - public static Texture GetIconForSection(SectionEnum section) - { - string iconName = IconNameForSection(section); - Texture texture = GetIconForName(iconName); - return texture; - } - - private static string IconNameForSection(SectionEnum section) - { - string iconName; - if (!s_ImageNameCache.TryGetValue(section, out iconName)) - { - switch (section) - { - case SectionEnum.Inspector: - case SectionEnum.Scene: - iconName = "SoftlockInline.png"; - break; - - case SectionEnum.ProjectBrowser: - iconName = String.Format("SoftlockProjectBrowser{0}", kIconMipSuffix); - break; - - default: - return null; - } - s_ImageNameCache.Add(section, iconName); - } - return iconName; - } - - private static Texture GetIconForName(string fileName) - { - if (String.IsNullOrEmpty(fileName)) - { - return null; - } - - Texture texture; - // Note: a previous texture may have been destroyed - // by the system on the c++ side. - if (!s_ImageCache.TryGetValue(fileName, out texture) || texture == null) - { - if (fileName.EndsWith(kIconMipSuffix)) - { - texture = EditorGUIUtility.FindTexture(fileName) as Texture; - } - else - { - texture = EditorGUIUtility.LoadIconRequired(fileName) as Texture; - } - s_ImageCache.Remove(fileName); - s_ImageCache.Add(fileName, texture); - } - return texture; - } - - #endregion - } -} - diff --git a/Editor/Mono/Collab/Softlocks/SoftlockViewController.cs b/Editor/Mono/Collab/Softlocks/SoftlockViewController.cs deleted file mode 100644 index e4de3221f8..0000000000 --- a/Editor/Mono/Collab/Softlocks/SoftlockViewController.cs +++ /dev/null @@ -1,533 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - - -using UnityEngine; -using System.Collections.Generic; -using UnityEditor.Collaboration; -using UnityEngine.SceneManagement; -using UnityEditor.SceneManagement; -using System; -using UnityEditor.Web; - -namespace UnityEditor -{ - // Displays the Softlocks UI in the various areas of the Editor. - internal class SoftlockViewController - { - private static SoftlockViewController s_Instance; - public GUIStyle k_Style = null; - public GUIStyle k_StyleEmpty = GUIStyle.none; // For tooltips only. - public GUIContent k_Content = null; - - // Stores UI strings for reuse and Editor (inspector) references to trigger - // a repaint when softlock data changes. - private SoftlockViewController.Cache m_Cache = null; - - private const string k_TooltipHeader = "Unpublished changes by:"; - private const string k_TooltipPrefabHeader = "Unpublished Prefab changes by:"; - private const string k_TooltipNamePrefix = " \n \u2022 "; // u2022 displays a • (bullet point) - - private SoftlockViewController() {} - ~SoftlockViewController() {} - - [SerializeField] - private SoftLockFilters m_SoftLockFilters = new SoftLockFilters(); - - public SoftLockFilters softLockFilters { get { return m_SoftLockFilters; } } - - public static SoftlockViewController Instance - { - get - { - if (s_Instance == null) - { - s_Instance = new SoftlockViewController(); - s_Instance.m_Cache = new Cache(); - } - return s_Instance; - } - } - - // Initialises dependencies. - public void TurnOn() - { - RegisterDataDelegate(); - RegisterDrawDelegates(); - Repaint(); - } - - public void TurnOff() - { - UnregisterDataDelegate(); - UnregisterDrawDelegates(); - } - - private void UnregisterDataDelegate() - { - SoftLockData.SoftlockSubscriber -= Instance.OnSoftlockUpdate; - } - - private void RegisterDataDelegate() - { - UnregisterDataDelegate(); - SoftLockData.SoftlockSubscriber += Instance.OnSoftlockUpdate; - } - - private void UnregisterDrawDelegates() - { - ObjectListArea.postAssetIconDrawCallback -= Instance.DrawProjectBrowserGridUI; - ObjectListArea.postAssetLabelDrawCallback -= Instance.DrawProjectBrowserListUI; - Editor.OnPostIconGUI -= Instance.DrawInspectorUI; - GameObjectTreeViewGUI.OnPostHeaderGUI -= Instance.DrawSceneUI; - } - - // Connects to the areas of the Editor that display softlocks. - private void RegisterDrawDelegates() - { - UnregisterDrawDelegates(); - ObjectListArea.postAssetIconDrawCallback += Instance.DrawProjectBrowserGridUI; - ObjectListArea.postAssetLabelDrawCallback += Instance.DrawProjectBrowserListUI; - AssetsTreeViewGUI.postAssetLabelDrawCallback += Instance.DrawSingleColumnProjectBrowserUI; - Editor.OnPostIconGUI += Instance.DrawInspectorUI; - GameObjectTreeViewGUI.OnPostHeaderGUI += Instance.DrawSceneUI; - } - - // Returns true when the 'editor' supports Softlock UI and the - // user has Collaborate permissions. - private bool HasSoftlockSupport(Editor editor) - { - if (!Collab.instance.IsCollabEnabledForCurrentProject() || editor == null || editor.targets.Length > 1) - { - return false; - } - - if (editor.target == null || !SoftLockData.AllowsSoftLocks(editor.target)) - { - return false; - } - - // Support Scene and Game object Inspector headers, not others like MaterialEditor. - bool hasSupport = true; - Type editorType = editor.GetType(); - - if (editorType != typeof(GameObjectInspector) && editorType != typeof(GenericInspector)) - { - hasSupport = false; - } - - return hasSupport; - } - - private bool HasSoftlocks(string assetGUID) - { - if (!Collab.instance.IsCollabEnabledForCurrentProject()) - { - return false; - } - - bool hasSoftLocks; - bool isValid = (SoftLockData.TryHasSoftLocks(assetGUID, out hasSoftLocks) && hasSoftLocks); - return isValid; - } - - // Redraws softlock UI associated with the given list of 'assetGUIDs'. - public void OnSoftlockUpdate(string[] assetGUIDs) - { - // Remove cached UI for the assetGUIDs before triggered a redraw. - m_Cache.InvalidateAssetGUIDs(assetGUIDs); - Repaint(); - } - - // Repaints all the areas where softlocks are displayed. - public void Repaint() - { - RepaintInspectors(); - RepaintSceneHierarchy(); - RepaintProjectBrowsers(); - } - - private void RepaintSceneHierarchy() - { - List sceneUIs = SceneHierarchyWindow.GetAllSceneHierarchyWindows(); - foreach (SceneHierarchyWindow sceneUI in sceneUIs) - { - sceneUI.Repaint(); - } - } - - private void RepaintInspectors() - { - foreach (Editor editor in m_Cache.GetEditors()) - { - // Does not repaint when editor is not visible, but the editor's - // "DockArea" tab will redraw either way. - editor.Repaint(); - } - } - - private void RepaintProjectBrowsers() - { - foreach (ProjectBrowser pb in ProjectBrowser.GetAllProjectBrowsers()) - { - pb.RefreshSearchIfFilterContains("s:"); - pb.Repaint(); - } - } - - // Draws in the Hierarchy header, left of the context menu. - public float DrawSceneUI(Rect availableRect, string scenePath) - { - string assetGUID = AssetDatabase.AssetPathToGUID(scenePath); - if (!HasSoftlocks(assetGUID)) - { - return availableRect.xMax; - } - - int lockCount; - SoftLockData.TryGetSoftlockCount(assetGUID, out lockCount); - - GUIContent content = GetGUIContent(); - content.image = SoftLockUIData.GetIconForSection(SoftLockUIData.SectionEnum.Scene); - content.text = GetDisplayCount(lockCount); - content.tooltip = Instance.GetTooltip(assetGUID); - - Vector2 contentSize = GetStyle().CalcSize(content); - Rect drawRect = new Rect(availableRect.position, contentSize); - const int kRightMargin = 4; - drawRect.x = (availableRect.width - drawRect.width) - kRightMargin; - EditorGUI.LabelField(drawRect, content); - - return drawRect.xMin; - } - - // Assigned as a callback to Editor.OnPostHeaderGUI - // Draws the Scene Inspector (Editor.cs) as well as the Game Object Inspector (GameObjectInspector.cs) - private void DrawInspectorUI(Editor editor, Rect drawRect) - { - if (!HasSoftlockSupport(editor)) - { - return; - } - - m_Cache.StoreEditor(editor); - string assetGUID = null; - AssetAccess.TryGetAssetGUIDFromObject(editor.target, out assetGUID); - - if (!HasSoftlocks(assetGUID)) - { - return; - } - - Texture icon = SoftLockUIData.GetIconForSection(SoftLockUIData.SectionEnum.ProjectBrowser); - if (icon != null) - { - DrawIconWithTooltips(drawRect, icon, assetGUID); - } - } - - // Assigned callback to ObjectListArea.OnPostAssetDrawDelegate. - // Draws either overtop of the project browser asset (when in grid view). - private void DrawProjectBrowserGridUI(Rect iconRect, string assetGUID, bool isListMode) - { - if (isListMode || !HasSoftlocks(assetGUID)) - { - return; - } - - Rect drawRect = Rect.zero; - Texture icon = SoftLockUIData.GetIconForSection(SoftLockUIData.SectionEnum.ProjectBrowser); - if (icon != null) - { - drawRect = Overlay.GetRectForBottomRight(iconRect, Overlay.k_OverlaySizeOnLargeIcon); - DrawIconWithTooltips(drawRect, icon, assetGUID); - } - } - - // Should draw only in listMode and expects 'drawRect' to be the designed space for the icon, - // and not the entire row. - private bool DrawProjectBrowserListUI(Rect drawRect, string assetGUID, bool isListMode) - { - if (!isListMode || !HasSoftlocks(assetGUID)) - { - return false; - } - - // center icon. - Rect iconRect = drawRect; - iconRect.width = drawRect.height; - iconRect.x = (float)Math.Round(drawRect.center.x - (iconRect.width / 2F)); - return DrawInProjectBrowserListMode(iconRect, assetGUID); - } - - // Expects 'drawRect' to be the available width of the row. - private bool DrawSingleColumnProjectBrowserUI(Rect drawRect, string assetGUID) - { - if (ProjectBrowser.s_LastInteractedProjectBrowser.IsTwoColumns() || !HasSoftlocks(assetGUID)) - { - return false; - } - - Rect iconRect = drawRect; - iconRect.width = drawRect.height; - float spacingFromEnd = (iconRect.width / 2F); - iconRect.x = (float)Math.Round(drawRect.xMax - iconRect.width - spacingFromEnd); - return DrawInProjectBrowserListMode(iconRect, assetGUID); - } - - private bool DrawInProjectBrowserListMode(Rect iconRect, string assetGUID) - { - Texture icon = SoftLockUIData.GetIconForSection(SoftLockUIData.SectionEnum.ProjectBrowser); - bool didDraw = false; - if (icon != null) - { - DrawIconWithTooltips(iconRect, icon, assetGUID); - didDraw = true; - } - return didDraw; - } - - private void DrawIconWithTooltips(Rect iconRect, Texture icon, string assetGUID) - { - GUI.DrawTexture(iconRect, icon, ScaleMode.ScaleToFit); - DrawTooltip(iconRect, GetTooltip(assetGUID)); - } - - private void DrawTooltip(Rect frame, string tooltip) - { - GUIContent content = GetGUIContent(); - content.tooltip = tooltip; - GUI.Label(frame, content, k_StyleEmpty); - } - - #region String Helpers - - // Returns a string formatted as a vertical list of names with a heading. - private string GetTooltip(string assetGUID) - { - string formattedText; - if (!m_Cache.TryGetTooltipForGUID(assetGUID, out formattedText)) - { - List softLockNames = SoftLockUIData.GetLocksNamesOnAsset(assetGUID); - string tooltipHeaderText = (SoftLockData.IsPrefab(assetGUID) ? k_TooltipPrefabHeader : k_TooltipHeader); - formattedText = tooltipHeaderText; - - foreach (string name in softLockNames) - { - formattedText += k_TooltipNamePrefix + name + " "; - } - m_Cache.StoreTooltipForGUID(assetGUID, formattedText); - } - return formattedText; - } - - // Retrieves a previously generated string from cache - // or creates a string displaying the given 'count' surrounded by brackets. - // e.g. "(0)" - private static string GetDisplayCount(int count) - { - string totalLocksText; - if (!Instance.m_Cache.TryGetDisplayCount(count, out totalLocksText)) - { - totalLocksText = count.ToString(); - Instance.m_Cache.StoreDisplayCount(count, totalLocksText); - } - return totalLocksText; - } - - // When the given 'text' exceeds the given 'width', out-of-bound characters - // are removed as well as a few more to display a trailing ellipsis. - // If 'text' does not exceed width, text is returned. - private string FitTextToWidth(string text, float width, GUIStyle style) - { - int characterCountVisible = style.GetNumCharactersThatFitWithinWidth(text, width); - if (characterCountVisible > 1 && characterCountVisible != text.Length) - { - string ellipsedText; - int characterLength = (characterCountVisible - 1); - if (!Instance.m_Cache.TryGetEllipsedNames(text, characterLength, out ellipsedText)) - { - ellipsedText = text.Substring(0, characterLength) + (" \u2026"); // 'horizontal ellipsis' (U+2026) is: ... - Instance.m_Cache.StoreEllipsedNames(text, ellipsedText, characterLength); - } - return ellipsedText; - } - return text; - } - - #endregion - #region GUI Content - - public GUIContent GetGUIContent() - { - if (k_Content == null) - { - k_Content = new GUIContent(); - } - - k_Content.tooltip = string.Empty; - k_Content.text = null; - k_Content.image = null; - - return k_Content; - } - - public GUIStyle GetStyle() - { - if (k_Style == null) - { - k_Style = new GUIStyle(EditorStyles.label); - k_Style.normal.background = null; - } - return k_Style; - } - - #endregion - - // Stores UI strings for reuse and Editors as WeakReferences. - private class Cache - { - private List m_EditorReferences = new List(); - private List m_CachedWeakReferences = new List(); - private static Dictionary s_CachedStringCount = new Dictionary(); - private Dictionary m_AssetGUIDToTooltip = new Dictionary(); - private Dictionary> m_NamesListToEllipsedNames = new Dictionary>(); - - public Cache() {} - - // Removes cached strings references by the given 'assetGUIDs'. - public void InvalidateAssetGUIDs(string[] assetGUIDs) - { - for (int index = 0; index < assetGUIDs.Length; index++) - { - string assetGUID = assetGUIDs[index]; - m_AssetGUIDToTooltip.Remove(assetGUID); - } - } - - // Failure: assigns empty string ("") to 'ellipsedNames', returns false. - // Success: assigns the cached string to 'ellipsedNames', returns true. - public bool TryGetEllipsedNames(string allNames, int characterLength, out string ellipsedNames) - { - Dictionary ellipsedVersions; - if (m_NamesListToEllipsedNames.TryGetValue(allNames, out ellipsedVersions)) - { - return ellipsedVersions.TryGetValue(characterLength, out ellipsedNames); - } - ellipsedNames = ""; - return false; - } - - // 'allNames' and 'characterLength' will be the keys to access the cached 'ellipsedNames' - // see TryGetEllipsedNames() for retrieval. - public void StoreEllipsedNames(string allNames, string ellipsedNames, int characterLength) - { - Dictionary ellipsedVersions; - if (!m_NamesListToEllipsedNames.TryGetValue(allNames, out ellipsedVersions)) - { - ellipsedVersions = new Dictionary(); - } - ellipsedVersions[characterLength] = ellipsedNames; - m_NamesListToEllipsedNames[allNames] = ellipsedVersions; - } - - // Failure: assigns empty string ("") to 'tooltipText', returns false. - // Success: assigns the cached string to 'tooltipText', returns true. - public bool TryGetTooltipForGUID(string assetGUID, out string tooltipText) - { - return m_AssetGUIDToTooltip.TryGetValue(assetGUID, out tooltipText); - } - - // 'assetGUID' will be the key to access the cached 'tooltipText' - // see TryGetTooltipForGUID() for retrieval. - public void StoreTooltipForGUID(string assetGUID, string tooltipText) - { - m_AssetGUIDToTooltip[assetGUID] = tooltipText; - } - - // Failure: assigns empty string ("") to 'displayText', returns false. - // Success: assigns the cached string to 'displayText', returns true. - public bool TryGetDisplayCount(int count, out string displayText) - { - return s_CachedStringCount.TryGetValue(count, out displayText); - } - - // 'count' will be the key to access the cached 'displayText' - // see TryGetDisplayCount() for retrieval. - public void StoreDisplayCount(int count, string displayText) - { - s_CachedStringCount.Add(count, displayText); - } - - // Contains at most the list of all previously given Editors - // via StoreEditor(). Garbage collected Editor(s) will be missing. - public List GetEditors() - { - List editors = new List(); - - for (int index = 0; index < m_EditorReferences.Count; index++) - { - WeakReference reference = m_EditorReferences[index]; - Editor editor = reference.Target as Editor; - - if (editor == null) - { - m_EditorReferences.RemoveAt(index); - m_CachedWeakReferences.Add(reference); - index--; - } - else - { - editors.Add(editor); - } - } - return editors; - } - - // Stores the Editor in a WeakReference. - public void StoreEditor(Editor editor) - { - bool canAdd = true; - - // Check for duplicates and purge any null targets. - for (int index = 0; canAdd && (index < m_EditorReferences.Count); index++) - { - WeakReference reference = m_EditorReferences[index]; - Editor storedEditor = reference.Target as Editor; - - if (storedEditor == null) - { - m_EditorReferences.RemoveAt(index); - m_CachedWeakReferences.Add(reference); - index--; - } - else if (storedEditor == editor) - { - canAdd = false; - break; - } - } - - if (canAdd) - { - WeakReference editorReference; - - // Reuse any old WeakReference if available. - if (m_CachedWeakReferences.Count > 0) - { - editorReference = m_CachedWeakReferences[0]; - m_CachedWeakReferences.RemoveAt(0); - } - else - { - editorReference = new WeakReference(null); - } - editorReference.Target = editor; - m_EditorReferences.Add(editorReference); - } - } - } - } -} - diff --git a/Editor/Mono/Collab/Views/ICollabHistoryWindow.cs b/Editor/Mono/Collab/Views/ICollabHistoryWindow.cs deleted file mode 100644 index ab312c85a4..0000000000 --- a/Editor/Mono/Collab/Views/ICollabHistoryWindow.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using System; -using System.Collections.Generic; - -namespace UnityEditor.Collaboration -{ - internal delegate void PageChangeAction(int page); - internal delegate void RevisionAction(string revisionId, bool updateToRevision); - internal delegate void ShowBuildAction(string revisionId); - - internal enum HistoryState - { - Error, - Offline, - Maintenance, - LoggedOut, - NoSeat, - Disabled, - Waiting, - Ready, - } - - internal enum BuildState - { - None, - Configure, - Success, - Failed, - InProgress, - } - - internal struct RevisionData - { - public string id; - public int index; - public DateTime timeStamp; - public string authorName; - public string comment; - - // Whether this revision is on the client - public bool obtained; - public bool current; - public bool inProgress; - public bool enabled; - - public BuildState buildState; - public int buildFailures; - - public ICollection changes; - public int changesTotal; - public bool changesTruncated; - } - - internal struct ChangeData - { - public string path; - public string action; - } - - internal interface ICollabHistoryWindow - { - void UpdateState(HistoryState state, bool force); - void UpdateRevisions(IEnumerable items, string tip, int totalRevisions, int currentPage); - - bool revisionActionsEnabled { get; set; } - int itemsPerPage { set; } - string inProgressRevision { get; set; } - PageChangeAction OnPageChangeAction { set; } - RevisionAction OnGoBackAction { set; } - RevisionAction OnUpdateAction { set; } - RevisionAction OnRestoreAction { set; } - ShowBuildAction OnShowBuildAction { set; } - Action OnShowServicesAction { set; } - } -} diff --git a/Editor/Mono/Commands/CommandService.cs b/Editor/Mono/Commands/CommandService.cs index a78ca32515..10f56233d8 100644 --- a/Editor/Mono/Commands/CommandService.cs +++ b/Editor/Mono/Commands/CommandService.cs @@ -152,8 +152,16 @@ private static IEnumerable ScanAttributes() var commands = new List(); foreach (var mi in TypeCache.GetMethodsWithAttribute()) { - if (!(Delegate.CreateDelegate(typeof(CommandHandler), mi) is CommandHandler callback)) + CommandHandler callback = null; + try + { + callback = (CommandHandler)Delegate.CreateDelegate(typeof(CommandHandler), mi); + } + catch(Exception e) + { + Debug.LogError($"Cannot create CommandHandler from Attribute: {mi.Name} {e.Message}"); continue; + } foreach (var attr in mi.GetCustomAttributes()) { diff --git a/Editor/Mono/Commands/GOCreationCommands.cs b/Editor/Mono/Commands/GOCreationCommands.cs index 6caef996d3..f2f6d706f5 100644 --- a/Editor/Mono/Commands/GOCreationCommands.cs +++ b/Editor/Mono/Commands/GOCreationCommands.cs @@ -80,14 +80,14 @@ internal static void Place(GameObject go, GameObject parent, bool ignoreSceneVie Selection.activeGameObject = go; } - [MenuItem("GameObject/Create Empty %#n", priority = 0)] + [MenuItem("GameObject/Create Empty %#n", priority = 0, secondaryPriority = 1)] static void CreateEmpty(MenuCommand menuCommand) { var parent = menuCommand.context as GameObject; Place(ObjectFactory.CreateGameObject("GameObject"), parent); } - [MenuItem("GameObject/Create Empty Child &#n", priority = 0)] + [MenuItem("GameObject/Create Empty Child &#n", priority = 0, secondaryPriority = 2)] static void CreateEmptyChild(MenuCommand menuCommand) { var parent = menuCommand.context as GameObject; @@ -109,7 +109,7 @@ static void CreateEmptyChild(MenuCommand menuCommand) } // Avoiding executing this method per-object, by adding menu item manually in SceneHierarchy - [MenuItem("GameObject/Create Empty Parent %#g", priority = 0)] + [MenuItem("GameObject/Create Empty Parent %#g", priority = 0, secondaryPriority = 3)] internal static void CreateEmptyParent() { Transform[] selected = Selection.transforms; @@ -196,6 +196,12 @@ internal static void CreateEmptyParent() } SceneHierarchyWindow.lastInteractedHierarchyWindow.SetExpanded(go.GetInstanceID(), true); + + // Ensure empty parent after reparenting jumps into rename mode if needed UUM-15042 + if (SceneHierarchyWindow.s_EnterRenameModeForNewGO) + { + SceneHierarchyWindow.FrameAndRenameNewGameObject(); + } } // Set back default parent object if we have one @@ -342,7 +348,7 @@ static void CreatePointLight(MenuCommand menuCommand) Place(go, parent); } - [MenuItem("GameObject/Light/Spotlight", priority = 3)] + [MenuItem("GameObject/Light/Spot Light", priority = 3)] static void CreateSpotLight(MenuCommand menuCommand) { var parent = menuCommand.context as GameObject; diff --git a/Editor/Mono/ConsoleWindow.cs b/Editor/Mono/ConsoleWindow.cs index e9fa620986..b8fa1b6f32 100644 --- a/Editor/Mono/ConsoleWindow.cs +++ b/Editor/Mono/ConsoleWindow.cs @@ -29,14 +29,8 @@ internal class ConsoleWindow : EditorWindow, IHasCustomMenu private static Font m_MonospaceFont; private static int m_DefaultFontSize; private static List s_MethodsToHideInCallstack = null; - private static Dictionary s_GenericMethodSignatureRegex = null; - - internal struct HideInCallstackGenericMethodKey - { - internal string namespaceName; - internal string className; - internal string methodName; - } + private static Dictionary s_GenericMethodSignatureRegex = null; + private static bool m_ShouldSkipClearingConsoleAfterBuild = false; //TODO: move this out of here internal class Constants @@ -163,7 +157,6 @@ internal static void UpdateLogStyleFixedHeights() ListViewState m_ListView; string m_ActiveText = ""; StringBuilder m_CopyString; - private int m_LastPingedEntry = -1; bool m_DevBuild; int m_CallstackTextStart = 0; private Mode m_ActiveMode = Mode.None; @@ -171,6 +164,8 @@ internal static void UpdateLogStyleFixedHeights() Vector2 m_TextScroll = Vector2.zero; int m_LastActiveEntryIndex = -1; + [NonSerialized] + int m_IndexHintCache; bool m_RestoreLatestSelection; //Make sure the minimum height of the panels can accomodate the cpmplete scroll bar icons @@ -459,18 +454,13 @@ void SetActiveEntry(LogEntry entry) m_ActiveMode = (Mode)entry.mode; entry.callstackTextStartUTF8 = entry.message.Length; m_CallstackTextStart = entry.callstackTextStartUTF16; - // ping object referred by the log entry - if (entry.instanceID != 0 && m_LastPingedEntry != entry.globalLineIndex) - { - EditorGUIUtility.PingObject(entry.instanceID); - m_LastPingedEntry = entry.globalLineIndex; - } + var entryRow = LogEntries.GetEntryRowIndex(entry.globalLineIndex, m_IndexHintCache); + m_IndexHintCache = entryRow; } else { m_CallstackTextStart = 0; m_ActiveText = string.Empty; - m_LastPingedEntry = -1; m_ListView.row = -1; m_CopyString.Clear(); m_ActiveMode = Mode.None; @@ -639,8 +629,9 @@ internal void OnGUI() selectedRow = m_ListView.row; DestroyLatestRestoreEntry(); LogEntry entry = new LogEntry(); - LogEntries.GetEntryInternal(m_ListView.row, entry); - m_LastActiveEntryIndex = entry.globalLineIndex; + LogEntries.GetEntryInternal(el.row, entry); + if (entry.instanceID != 0 && e.clickCount != 2) + EditorGUIUtility.PingObject(entry.instanceID); if (e.clickCount == 2) openSelectedItem = true; } @@ -747,6 +738,7 @@ const bool { SetActiveEntry(entry); m_LastActiveEntryIndex = entry.globalLineIndex; + activeEntryChanged?.Invoke(); } @@ -870,7 +862,7 @@ internal static string StacktraceWithHyperlinks(string stacktraceText, int calls for (int i = 0; i < lines.Length; ++i) { - string textBeforeFilePath = ") (at "; + string textBeforeFilePath = " (at "; int filePathIndex = lines[i].IndexOf(textBeforeFilePath, StringComparison.Ordinal); if (filePathIndex > 0) { @@ -908,7 +900,7 @@ internal static string StacktraceWithHyperlinks(string stacktraceText, int calls return textWithHyperlinks.ToString(); } - internal static string GetCallstackFormattedSignatureFromGenericMethod(Dictionary methodSignatureRegex, MethodInfo method, string line) + internal static string GetCallstackFormattedSignatureFromGenericMethod(Dictionary methodSignatureRegex, MethodInfo method, string line) { if (string.IsNullOrEmpty(line) || method == null || methodSignatureRegex == null) return null; @@ -917,14 +909,7 @@ internal static string GetCallstackFormattedSignatureFromGenericMethod(Dictionar if (classType == null) return null; - var ns = classType.Namespace; - var key = new HideInCallstackGenericMethodKey() - { - namespaceName = ns, - className = classType.Name, - methodName = method.Name - }; - if (!methodSignatureRegex.TryGetValue(key, out Regex regex)) + if (!methodSignatureRegex.TryGetValue(method, out Regex regex)) return null; if (regex == null) @@ -1004,7 +989,7 @@ internal static string GetCallstackFormattedScriptingExceptionSignature(MethodIn return sb.ToString(); } - internal static string[] StripCallstack(Mode mode, Dictionary methodSignatureRegex, List methodsToHideInCallstack, string[] lines) + internal static string[] StripCallstack(Mode mode, Dictionary methodSignatureRegex, List methodsToHideInCallstack, string[] lines) { if (methodsToHideInCallstack == null || methodSignatureRegex == null || lines == null) return lines; @@ -1102,6 +1087,8 @@ public void AddItemsToMenu(GenericMenu menu) AddStackTraceLoggingMenu(menu); } + internal ListViewState GetListViewState() { return m_ListView; } + private static void OnFontButtonValueChange() { m_UseMonospaceFont = !m_UseMonospaceFont; @@ -1140,14 +1127,14 @@ private static void OnStripLoggingCallstackButtonValueChange() SetFlag(ConsoleFlags.StripLoggingCallstack, s_StripLoggingCallstack); } - internal static (List, Dictionary) InitializeHideInCallstackMethodsCache() + internal static (List, Dictionary) InitializeHideInCallstackMethodsCache() { var methods = TypeCache.GetMethodsWithAttribute(); if (methods.Count == 0) return (null, null); var methodsToHideInCallstack = new List(); - var genericMethodSignatureRegexes = new Dictionary(); + var genericMethodSignatureRegexes = new Dictionary(); foreach (var method in methods) { methodsToHideInCallstack.Add(method); @@ -1161,19 +1148,39 @@ internal static (List, Dictionary m_UnsavedEditorWindows; @@ -79,6 +76,20 @@ internal void __internalAwake() hideFlags = HideFlags.DontSave; } + + // Pixel-position on screen. + public Rect position + { + get => Internal_Position; + set + { + if (!View.IsValidViewRect(value)) + throw new ArgumentException($"Invalid position: {value}"); + + Internal_Position = value; + } + } + internal ShowMode showMode => (ShowMode)m_ShowMode; private string m_WindowID = null; @@ -127,11 +138,15 @@ internal void ShowPopupWithMode(ShowMode mode, bool giveFocus) m_RootView.SetWindowRecurse(this); Internal_SetTitle(m_Title); Save(); + + // System windows that come from the OS (like a context menu from search) need theming + SetBackgroundColor(skinBackgroundColor); + // only set focus if mode is a popupMenu. Internal_BringLiveAfterCreation(true, giveFocus, false); // Fit window to screen - needs to be done after bringing the window live - position = FitWindowRectToScreen(m_PixelRect, true, false); + position = FitRectToScreen(m_PixelRect, m_PixelRect.center, true, this); rootView.position = new Rect(0, 0, GUIUtility.RoundToPixelGrid(m_PixelRect.width), GUIUtility.RoundToPixelGrid(m_PixelRect.height)); rootView.Reflow(); } @@ -147,21 +162,20 @@ internal void ShowPopupWithMode(ShowMode mode, bool giveFocus) static Color skinBackgroundColor => EditorGUIUtility.isProSkin ? darkSkinColor : lightSkinColor; // Show the editor window. - public void Show(ShowMode showMode, bool loadPosition, bool displayImmediately, bool setFocus, int displayIndex = 0) + public void Show(ShowMode showMode, bool loadPosition, bool displayImmediately, bool setFocus) { try { if (showMode == ShowMode.MainWindow && s_MainWindow && s_MainWindow != this) throw new InvalidOperationException("Trying to create a second main window from layout when one already exists."); - bool useMousePos = showMode == ShowMode.AuxWindow || showMode == ShowMode.Fullscreen; + bool useMousePos = showMode == ShowMode.AuxWindow; if (showMode == ShowMode.AuxWindow) showMode = ShowMode.Utility; if (showMode == ShowMode.Utility || showMode == ShowMode.ModalUtility || showMode == ShowMode.AuxWindow - || showMode == ShowMode.Fullscreen || IsPopup(showMode)) m_DontSaveToLayout = true; @@ -175,7 +189,7 @@ public void Show(ShowMode showMode, bool loadPosition, bool displayImmediately, var initialMaximizedState = m_Maximized; - Internal_Show(m_PixelRect, m_ShowMode, m_MinSize, m_MaxSize, displayIndex); + Internal_Show(m_PixelRect, m_ShowMode, m_MinSize, m_MaxSize); // Tell the main view its now in this window (quick hack to get platform-specific code to move its views to the right window) if (m_RootView) @@ -214,7 +228,11 @@ public void Show(ShowMode showMode, bool loadPosition, bool displayImmediately, internal void FitWindowToScreen(bool useMousePos) { - position = FitWindowRectToScreen(m_PixelRect, true, useMousePos); + if (useMousePos) + position = FitRectToMouseScreen(m_PixelRect, true, this); + else + position = FitRectToScreen(m_PixelRect, m_PixelRect.center, true, this); + if (rootView) rootView.position = new Rect(0, 0, GUIUtility.RoundToPixelGrid(m_PixelRect.width), GUIUtility.RoundToPixelGrid(m_PixelRect.height)); } @@ -228,6 +246,12 @@ public void OnEnable() public void SetMinMaxSizes(Vector2 min, Vector2 max) { + if (!View.IsValidViewSize(min)) + throw new ArgumentException($"Invalid minimum size: {min}"); + + if (!View.IsValidViewSize(max)) + throw new ArgumentException($"Invalid maximum size: {max}"); + m_MinSize = min; m_MaxSize = max; Rect r = position; @@ -370,6 +394,7 @@ internal void InternalCloseWindow() } DestroyImmediate(this, true); + EditorWindow.UpdateWindowMenuListing(); } private static List FindUnsavedChanges(View view) diff --git a/Editor/Mono/CustomEditorAttributes.cs b/Editor/Mono/CustomEditorAttributes.cs index 37302cfdfa..cafe1af250 100644 --- a/Editor/Mono/CustomEditorAttributes.cs +++ b/Editor/Mono/CustomEditorAttributes.cs @@ -198,10 +198,11 @@ private static int SortUnityTypesFirst(MonoEditorType typeA, MonoEditorType type var xAssemblyIsUnity = xAssemblyName.StartsWith("Unity.") || xAssemblyName.StartsWith("UnityEditor.") || xAssemblyName.StartsWith("UnityEngine."); var yAssemblyIsUnity = yAssemblyName.StartsWith("Unity.") || yAssemblyName.StartsWith("UnityEditor.") || yAssemblyName.StartsWith("UnityEngine."); - if ((xAssemblyIsUnity && yAssemblyIsUnity) || (!xAssemblyIsUnity && !yAssemblyIsUnity)) return 0; - else if (xAssemblyIsUnity && !yAssemblyIsUnity) return 1; - else if (!xAssemblyIsUnity && yAssemblyIsUnity) return -1; - return xAssemblyName.CompareTo(yAssemblyName); + if ((xAssemblyIsUnity && yAssemblyIsUnity) || (!xAssemblyIsUnity && !yAssemblyIsUnity)) + return string.CompareOrdinal(typeA.m_InspectorType.FullName, typeB.m_InspectorType.FullName); + else if (xAssemblyIsUnity) return 1; + else + return -1; } } } diff --git a/Editor/Mono/CustomInspectorStubs.cs b/Editor/Mono/CustomInspectorStubs.cs index 0793e52640..0a54fe5bed 100644 --- a/Editor/Mono/CustomInspectorStubs.cs +++ b/Editor/Mono/CustomInspectorStubs.cs @@ -68,10 +68,18 @@ private InputManager() {} [SettingsProvider] internal static SettingsProvider CreateProjectSettingsProvider() { - var provider = AssetSettingsProvider.CreateProviderFromAssetPath( - "Project/Input Manager", "ProjectSettings/InputManager.asset", - SettingsProvider.GetSearchKeywordsFromPath("ProjectSettings/InputManager.asset")); - return provider; + // The new input system adds objects to InputManager.asset. This means we can't use AssetSettingsProvider.CreateProviderFromAssetPath + // as it will load *all* objects at that path and try to create an editor for it. + // NOTE: When the input system package is uninstalled, InputManager.asset will contain serialized MonoBehaviour objects for which + // the C# classes are no longer available. They will thus not load correctly and appear as null entries. + var obj = AssetDatabase.LoadAssetAtPath("ProjectSettings/InputManager.asset"); + if (obj != null && obj.name == "InputManager") + { + var provider = AssetSettingsProvider.CreateProviderFromObject("Project/Input Manager", obj, + SettingsProvider.GetSearchKeywordsFromPath("ProjectSettings/InputManager.asset")); + return provider; + } + return null; } } diff --git a/Editor/Mono/DataMode.cs b/Editor/Mono/DataMode.cs index fb934d692c..4ce5f777c4 100644 --- a/Editor/Mono/DataMode.cs +++ b/Editor/Mono/DataMode.cs @@ -4,15 +4,15 @@ using System; using System.Collections.Generic; - +using System.Linq; +using UnityEngine; using UnityObject = UnityEngine.Object; using DataModeSupportHandler = UnityEditor.DeclareDataModeSupportAttribute.DataModeSupportHandler; namespace UnityEditor { /// - /// Options for the different modes of an that implements - /// or . + /// Options for the different modes of an . /// // // Dev note: @@ -26,150 +26,260 @@ namespace UnityEditor // Sincerely, // The #dots-editor team // + [Serializable] public enum DataMode // Values must be kept in sync with `DataMode.h` { /// - /// Represents a situation or context in which the usage of data modes is not applicable. + /// Represents a situation or context in which the usage of is not applicable. /// /// - /// This mode informs the docking area that the data modes switch should now be displayed. + /// This mode disables the DataMode switch in the docking area. /// Disabled = 0, /// - /// Uses a mode where only authoring data is available. + /// Uses this mode where only authoring data is available. /// /// /// In this mode, only authoring data is available. When exiting Play mode, Unity retains authoring data. /// Authoring = 1, /// - /// Uses a mode where a mix of authoring and runtime data is available. + /// Uses this mode where a mix of authoring and runtime data is available. /// /// - /// In this mode, a mixture of authoring and runtime data is available. **Important:** When exiting Play mode, - /// Unity loses runtime data. However, it retains any authoring data. + /// In this mode, a mixture of authoring and runtime data is available. + /// When exiting Play mode, Unity loses runtime data. However, it retains any authoring data. /// Mixed = 2, /// - /// Uses a mode where only runtime data is available. + /// Uses this mode where only runtime data is available. /// /// - /// In this mode, only runtime data is available. **Important:** When exiting Play mode, Unity loses runtime - /// data. + /// In this mode, only runtime data is available. When exiting Play mode, Unity loses runtime data. /// Runtime = 3 } /// - /// Implement this interface to allow an to handle changes. + /// Container for the different parameters of the event. + /// + /// DataMode to which the should change. + /// Whether the change was initiated by the DataMode switcher UI + /// at the top-right of the Editor window. + public readonly struct DataModeChangeEventArgs + { + public readonly DataMode nextDataMode; + public readonly bool changedThroughUI; + + public DataModeChangeEventArgs(DataMode nextDataMode, bool changedThroughUI) + { + this.nextDataMode = nextDataMode; + this.changedThroughUI = changedThroughUI; + } + } + + /// + /// Interface with which any can interact with functionalities. + /// To obtain an instance, use > /// /// - /// This interface displays a switch in the docking area when the window is visible and lists the supported modes in - /// the contextual menu for that window. Use this interface if your window only needs to react to direct user - /// interactions with the data mode switch or the contextual menu. If your window needs to change its state based on - /// other factors, like entering or exiting play mode, you should implement - /// instead. + /// This interface displays a switch in the docking area when the window is visible and has + /// more than one supported DataModes. /// - public interface IDataModeHandler + public interface IDataModeController { /// - /// Returns the currently active for the implementor . + /// Returns the currently active for the that + /// owns this instance of IDataModeController. + /// + DataMode dataMode { get; } + + /// + /// Event for subscribing to changes. /// /// - /// Unity does not serialize or store this value. It is the window's responsibility to do so. + /// This method accepts >. + /// For example, you can register to this method to update the contents of the window for the given data mode. /// - DataMode dataMode { get; } + event Action dataModeChanged; /// - /// - /// A list of the s the supports. - /// - /// - /// That list of the s the supports varies based - /// on a number of factors, so it should only contain the modes available to the current context. + /// Updates the list of s that the supports, + /// and sets the preferred DataMode to be used when the DataMode switcher UI is set to Automatic. + /// + /// + /// That list of the DataModes the Editor window supports varies based on a number of factors, + /// so it should only contain the DataModes available to the current context. /// For example, a window might support the and /// modes when in Edit mode, and the and modes when /// in Play mode. A common pattern for that case is to store two lists internally and use /// to select which one to return. - /// - /// - IReadOnlyList supportedDataModes { get; } - - /// - /// Unity calls this method automatically before any call to is made. If the - /// method returns false, is called instead. - /// - /// - /// The for which support is being tested. + /// A list of the supported DataModes. + /// + /// Preferred DataMode to use given the current context when the DataMode switcher UI is set to Automatic. /// - /// - /// Whether the currently supports the specified . - /// - bool IsDataModeSupported(DataMode mode); - - /// - /// Unity calls this method automatically when a user clicks the switch in the docking - /// area tied to the implementing . - /// - /// - /// This method informs the window to change its to whatever mode should come after - /// the current. In most cases, a window only supports two data modes at a time, but it is possible to support - /// all three. Also, a window that supports all three modes might want the switch to only toggle between two - /// specific modes and rely on the contextual menu to change to the third mode. /// - void SwitchToNextDataMode(); + void UpdateSupportedDataModes(IList supportedDataMode, DataMode preferredDataMode); /// - /// Unity calls this method automatically whenever the Editor wants an to be in a - /// specific . + /// Requests a change for the . /// /// - /// By convention, Unity always calls before calling this method. If the - /// data mode is not supported, is called instead. + /// If the DataMode switcher UI is currently set to Automatic, the Editor window also + /// changes to that preferred DataMode. + /// > /// - /// - /// The explicit data mode to which the Editor window should change. + /// + /// The DataMode to which the Editor window should change. /// - void SwitchToDataMode(DataMode mode); + /// + /// Whether the Editor window has accepted the requested DataMode change. + /// > + bool TryChangeDataMode(DataMode newDataMode); + } - /// - /// Unity calls this method automatically whenever going to a requested is impossible - /// because of the result of . - /// - /// - /// This method is a fallback to make sure the is always in a valid state. - /// - /// + // DataModeController handles DataMode related actions internally. + // Each Editor window has a DataModeController instance. + [Serializable] + internal sealed class DataModeController : IDataModeController + { + static readonly DataMode[] k_DefaultModes = Array.Empty(); + + public event Action dataModeChanged; + + [SerializeField] DataMode m_DataMode = DataMode.Disabled; + public DataMode dataMode + { + get => m_DataMode; + private set => m_DataMode = value; + } + + [SerializeField] DataMode m_PreferredDataMode = DataMode.Disabled; + public DataMode preferredDataMode + { + get => m_PreferredDataMode; + private set => m_PreferredDataMode = value; + } + + [SerializeField] DataMode[] m_SupportedDataModes = k_DefaultModes; + public IList supportedDataModes + { + get => m_SupportedDataModes; + private set => m_SupportedDataModes = value.ToArray(); + } + + [SerializeField] internal bool isAutomatic = true; + + readonly List m_DataModeSanitizationCache = new List(3); // Number of modes, minus `Disabled` + + public void UpdateSupportedDataModes(IList supported, DataMode preferred) + { + SanitizeSupportedDataModesList(supported.ToList(), m_DataModeSanitizationCache); + + supportedDataModes = m_DataModeSanitizationCache.Count != 0 ? m_DataModeSanitizationCache : k_DefaultModes; + + preferredDataMode = supportedDataModes.Count switch + { + 0 => DataMode.Disabled, + 1 => supportedDataModes[0], + _ => supportedDataModes.Contains(preferred) ? preferred : supportedDataModes[0] + }; + + if (!isAutomatic || dataMode == preferredDataMode) + return; + + // Recover if automatic + dataMode = preferredDataMode; + dataModeChanged?.Invoke(new DataModeChangeEventArgs(dataMode, false)); + } + + static void SanitizeSupportedDataModesList(IReadOnlyList originalList, List sanitizedList) + { + sanitizedList.Clear(); + + foreach (var mode in originalList) + { + if (mode == DataMode.Disabled) + continue; // Never list `DataMode.Disabled` + + if (sanitizedList.Contains(mode)) + continue; // Prevent duplicate entries + + sanitizedList.Add(mode); + } + + // Ensure we are displaying the data modes in a predefined order, regardless of + // the order in which the user defined their list. + sanitizedList.Sort(); + } + + public bool ShouldDrawDataModesSwitch() + { + return dataMode != DataMode.Disabled + // We don't want to show DataMode switch if there are not + // at least 2 modes supported at the current moment. + && supportedDataModes.Count > 1; + } + + public bool TryChangeDataMode(DataMode newDataMode) + { + // Only change if currently in automatic mode + if (!isAutomatic || dataMode == newDataMode || !supportedDataModes.Contains(newDataMode)) + return false; + + dataMode = newDataMode; + dataModeChanged?.Invoke(new DataModeChangeEventArgs(newDataMode, false)); + return true; + } + + // Invoked when user interacts with the DataMode dropdown menu, for internal use only. + internal void SwitchToAutomatic() + { + if (isAutomatic) + return; + + isAutomatic = true; + + if (dataMode == preferredDataMode) + return; + + // If the DataMode is not supported in current context, we fall back to default one. + dataMode = preferredDataMode; + dataModeChanged?.Invoke(new DataModeChangeEventArgs(dataMode, true)); + } + + // Invoked when user interacts with the DataMode dropdown men, for internal use only. + internal void SwitchToStickyDataMode(DataMode stickyDataMode) + { + isAutomatic = false; + + if (dataMode == stickyDataMode) + return; + + dataMode = supportedDataModes.Contains(stickyDataMode) + ? stickyDataMode + : preferredDataMode; + + dataModeChanged?.Invoke(new DataModeChangeEventArgs(dataMode, true)); + } + } + + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + [Obsolete("IDataModeHandler has been deprecated, please use EditorWindow.dataModeController instead.", false)] + public interface IDataModeHandler + { + DataMode dataMode { get; } + IReadOnlyList supportedDataModes { get; } + bool IsDataModeSupported(DataMode mode); + void SwitchToNextDataMode(); + void SwitchToDataMode(DataMode mode); void SwitchToDefaultDataMode(); } - /// - /// Implement this interface to allow an to handle changes and - /// alter its internally. - /// - /// - /// This interface displays a switch in the docking area when the window is visible and lists the supported modes in - /// the contextual menu for that window. Use this interface if your window needs to control its mode internally - /// based on factors other than the user directly interacting with the data mode switch or the contextual menu, for - /// example, entering or exiting Play mode. If your window does not need to control its own mode, use - /// instead. - /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + [Obsolete("IDataModeHandlerAndDispatcher has been deprecated, please use EditorWindow.dataModeController instead.", false)] public interface IDataModeHandlerAndDispatcher : IDataModeHandler { - /// - /// - /// Calls the methods in its invocation list when the changes due to an external factor - /// and passes the new data mode as an argument. - /// - /// - /// An external factor refers to any action which results in a data mode change that Unity did not initiate - /// directly through calling either , - /// , or . - /// - /// - /// For example, when entering or exiting Play mode, some windows might want to force a data mode switch. - /// - /// event Action dataModeChanged; } diff --git a/Editor/Mono/Delayer.cs b/Editor/Mono/Delayer.cs index fdfdb39a65..342d242cd4 100644 --- a/Editor/Mono/Delayer.cs +++ b/Editor/Mono/Delayer.cs @@ -10,86 +10,135 @@ class Delayer { private long m_LastExecutionTime; private Action m_Action; - private readonly double m_DebounceDelay; + private readonly long m_DebounceDelay; private object m_Context; private readonly bool m_IsThrottle; + private readonly bool m_FirstExecuteImmediate; + private bool m_DelayInProgress; public static Delayer Throttle(Action action, double delay = 0.2) { - return new Delayer(action, delay, true); + return new Delayer(action, delay, true, false); + } + + public static Delayer Throttle(Action action, double delay, bool firstExecuteImmediate) + { + return new Delayer(action, delay, true, firstExecuteImmediate); } public static Delayer Debounce(Action action, double delay = 0.2) { - return new Delayer(action, delay, false); + return new Delayer(action, delay, false, false); + } + + public static Delayer Debounce(Action action, double delay, bool firstExecuteImmediate) + { + return new Delayer(action, delay, false, firstExecuteImmediate); } public void Execute(object context = null) { m_Context = context; + if (m_IsThrottle) { - if (m_LastExecutionTime == 0) + if (m_LastExecutionTime == 0 || !m_DelayInProgress) Throttle(); } else { - m_LastExecutionTime = DateTime.UtcNow.Ticks; - Debounce(); + if (m_FirstExecuteImmediate && m_LastExecutionTime == 0) + { + m_Action?.Invoke(m_Context); + m_LastExecutionTime = DateTime.UtcNow.Ticks; + } + else + { + m_LastExecutionTime = DateTime.UtcNow.Ticks; + Debounce(); + } } } - private Delayer(Action action, double delay, bool isThrottle) + private Delayer(Action action, double delay, bool isThrottle, bool firstExecuteImmediate) { m_Action = action; - m_DebounceDelay = delay; + m_DebounceDelay = TimeSpan.FromSeconds(delay).Ticks; m_IsThrottle = isThrottle; + m_FirstExecuteImmediate = firstExecuteImmediate; } public void Dispose() { - EditorApplication.delayCall -= Debounce; - EditorApplication.delayCall -= Throttle; + EditorApplication.tick -= Debounce; + EditorApplication.tick -= Throttle; m_Context = null; m_Action = null; + m_DelayInProgress = false; } private void Debounce() { - EditorApplication.delayCall -= Debounce; var currentTime = DateTime.UtcNow.Ticks; if (m_LastExecutionTime != 0 && DelayHasPassed(currentTime)) { + m_DelayInProgress = false; + EditorApplication.tick -= Debounce; m_Action?.Invoke(m_Context); m_LastExecutionTime = 0; } else { - EditorApplication.delayCall += Debounce; + if (!m_DelayInProgress) + EditorApplication.tick += Debounce; + m_DelayInProgress = true; } } private void Throttle() { - EditorApplication.delayCall -= Throttle; var currentTime = DateTime.UtcNow.Ticks; - if (m_LastExecutionTime != 0 && DelayHasPassed(currentTime)) + + if (m_FirstExecuteImmediate) { - m_Action?.Invoke(m_Context); - m_LastExecutionTime = 0; + if (m_LastExecutionTime == 0 || DelayHasPassed(currentTime)) + { + m_DelayInProgress = false; + EditorApplication.tick -= Throttle; + m_Action?.Invoke(m_Context); + m_LastExecutionTime = currentTime; + } + else + { + if (!m_DelayInProgress) + EditorApplication.tick += Throttle; + m_DelayInProgress = true; + } } else { - if (m_LastExecutionTime == 0) - m_LastExecutionTime = currentTime; - EditorApplication.delayCall += Throttle; + if (m_LastExecutionTime != 0 && DelayHasPassed(currentTime)) + { + m_DelayInProgress = false; + EditorApplication.tick -= Throttle; + m_Action?.Invoke(m_Context); + m_LastExecutionTime = 0; + } + else + { + if (m_LastExecutionTime == 0) + m_LastExecutionTime = currentTime; + if (!m_DelayInProgress) + EditorApplication.tick += Throttle; + m_DelayInProgress = true; + } } } private bool DelayHasPassed(long currentTime) { var timeSpan = new TimeSpan(currentTime - m_LastExecutionTime); - return timeSpan.TotalSeconds > m_DebounceDelay; + return timeSpan.Ticks >= m_DebounceDelay; } } } diff --git a/Editor/Mono/DeploymentTargets/IDeploymentTargetsExtension.cs b/Editor/Mono/DeploymentTargets/IDeploymentTargetsExtension.cs index e2ce8d3085..7b15f4c4b6 100644 --- a/Editor/Mono/DeploymentTargets/IDeploymentTargetsExtension.cs +++ b/Editor/Mono/DeploymentTargets/IDeploymentTargetsExtension.cs @@ -17,6 +17,7 @@ internal struct DeploymentTargetId // changes between launches. internal static readonly DeploymentTargetId kDefault = new DeploymentTargetId("__builtin__target_default"); internal static readonly DeploymentTargetId kAll = new DeploymentTargetId("__builtin__target_all"); + internal static readonly DeploymentTargetId kEnterIP = new DeploymentTargetId("__builtin__enter_ip"); public string id; diff --git a/Editor/Mono/Display/EditorDisplayFullscreenSetting.cs b/Editor/Mono/Display/EditorDisplayFullscreenSetting.cs deleted file mode 100644 index 040b762cbc..0000000000 --- a/Editor/Mono/Display/EditorDisplayFullscreenSetting.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using System; -using UnityEngine; - -namespace UnityEditor -{ - [Serializable] - internal class EditorDisplayFullscreenSetting - { - public enum Mode - { - DoNothing, - FullscreenOnPlaymode, - AlwaysFullscreen - } - - public EditorDisplayFullscreenSetting(int id, string name) - { - displayId = id; - displayName = name; - mode = Mode.DoNothing; - enabled = false; - viewWindowTitle = string.Empty; - playModeViewSettings = null; - } - - public string displayName; - public int displayId; - - public bool enabled; - - public Mode mode; - - public string viewWindowTitle; - - [SerializeReference] - public IPlayModeViewFullscreenSettings playModeViewSettings; - } -} diff --git a/Editor/Mono/Display/EditorDisplayManager.cs b/Editor/Mono/Display/EditorDisplayManager.cs deleted file mode 100644 index 1b6945c0fb..0000000000 --- a/Editor/Mono/Display/EditorDisplayManager.cs +++ /dev/null @@ -1,267 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using System; -using UnityEngine; -using System.Collections.Generic; -using System.Linq; -using UnityEditor.Modules; - -namespace UnityEditor -{ - internal enum DisplayAPIControlMode - { - FromEditor, - FromRuntime - } - - [InitializeOnLoad] - internal class EditorDisplayManager : ScriptableSingleton - { - private PlayModeView[] m_views; - private int m_displayCount; - private DisplayAPIControlMode m_mode; - private int m_maxDisplays; - private BuildTarget m_currentBuildTarget; - - static EditorDisplayManager() - { - UnsubscribeEditorDisplayCallback(); - SubscribeEditorDisplayCallback(); - } - - private static void SubscribeEditorDisplayCallback() - { - Display.onGetSystemExt += GetSystemExtImpl; - Display.onGetRenderingExt += GetRenderingExtImpl; - Display.onGetRenderingBuffers += GetRenderingBuffersImpl; - Display.onSetRenderingResolution += SetRenderingResolutionImpl; - Display.onActivateDisplay += ActivateDisplayImpl; - Display.onSetParams += SetParamsImpl; - Display.onRelativeMouseAt += RelativeMouseAtImpl; - Display.onGetActive += GetActiveImpl; - Display.onRequiresBlitToBackbuffer += RequiresBlitToBackbufferImpl; - Display.onRequiresSrgbBlitToBackbuffer += RequiresSrgbBlitToBackbufferImpl; - } - - private static void UnsubscribeEditorDisplayCallback() - { - Display.onGetSystemExt -= GetSystemExtImpl; - Display.onGetRenderingExt -= GetRenderingExtImpl; - Display.onGetRenderingBuffers -= GetRenderingBuffersImpl; - Display.onSetRenderingResolution -= SetRenderingResolutionImpl; - Display.onActivateDisplay -= ActivateDisplayImpl; - Display.onSetParams -= SetParamsImpl; - Display.onRelativeMouseAt -= RelativeMouseAtImpl; - Display.onGetActive -= GetActiveImpl; - Display.onRequiresBlitToBackbuffer -= RequiresBlitToBackbufferImpl; - Display.onRequiresSrgbBlitToBackbuffer -= RequiresSrgbBlitToBackbufferImpl; - } - - private void OnEnable() - { - Initialize(); - EditorApplication.update += OnUpdate; - } - - private void OnDisable() - { - EditorApplication.update -= OnUpdate; - } - - public void Initialize() - { - m_currentBuildTarget = EditorUserBuildSettings.activeBuildTarget; - m_displayCount = 0; - - m_maxDisplays = ModuleManager.ShouldShowMultiDisplayOption() ? - GetDisplayNamesForBuildTarget(EditorUserBuildSettings.activeBuildTarget).Length : 1; - - m_views = new PlayModeView[m_maxDisplays]; - UpdateAssociatedPlayModeView(); - } - - private void OnUpdate() - { - if (m_currentBuildTarget != EditorUserBuildSettings.activeBuildTarget) - { - Initialize(); - } - } - - private void UpdateAssociatedPlayModeView() - { - for (var i = 0; i < m_maxDisplays; ++i) - { - var view = PlayModeView.GetAssociatedViewForTargetDisplay(i); - if (m_views[i] == view) - { - continue; - } - - m_views[i] = view; - if (view != null) - { - EditorDisplayUtility.AddVirtualDisplay(i, (int)view.targetSize.x, (int)view.targetSize.y); - } - else - { - EditorDisplayUtility.RemoveVirtualDisplay(i); - } - } - - UpdateDisplayList(false); - } - - private void UpdateDisplayList(bool recreate) - { - recreate |= m_mode != EditorFullscreenController.DisplayAPIMode; - m_mode = EditorFullscreenController.DisplayAPIMode; - - if (EditorFullscreenController.DisplayAPIMode == DisplayAPIControlMode.FromEditor) - { - var previousDisplayCount = m_displayCount; - for (var i = m_maxDisplays - 1; i >= 0; --i) - { - if (m_views[i] != null) - { - m_displayCount = i + 1; - recreate |= m_displayCount != previousDisplayCount; - break; - } - } - } - else - { - var nDisplays = EditorDisplayUtility.GetNumberOfConnectedDisplays(); - recreate |= m_displayCount != nDisplays; - m_displayCount = nDisplays; - } - - if (recreate) - { - var displayList = new IntPtr[m_displayCount]; - for (var i = 0; i < m_displayCount; ++i) - { - displayList[i] = new IntPtr(i); - } - - Display.RecreateDisplayList(displayList); - } - } - - internal static GUIContent[] GetDisplayNamesForBuildTarget(BuildTarget buildTarget) - { - var platformDisplayNames = Modules.ModuleManager.GetDisplayNames(buildTarget.ToString()); - return platformDisplayNames ?? DisplayUtility.GetGenericDisplayNames(); - } - - private static void GetSystemExtImpl(IntPtr nativeDisplay, out int w, out int h) - { - var manager = instance; - if (manager.m_mode == DisplayAPIControlMode.FromEditor) - { - var view = manager.m_views[(int)nativeDisplay]; - - if (view == null) - { - w = 0; - h = 0; - } - else - { - w = (int)view.position.width; - h = (int)view.position.height; - } - } - else - { - w = (int)EditorDisplayUtility.GetDisplayWidth((int)nativeDisplay); - h = (int)EditorDisplayUtility.GetDisplayHeight((int)nativeDisplay); - } - } - - private static void GetRenderingExtImpl(IntPtr nativeDisplay, out int w, out int h) - { - var manager = instance; - if (manager.m_mode == DisplayAPIControlMode.FromEditor) - { - var view = manager.m_views[(int)nativeDisplay]; - - if (view == null) - { - w = 0; - h = 0; - } - else - { - w = (int)view.targetSize.x; - h = (int)view.targetSize.y; - } - } - else - { - w = (int)EditorDisplayUtility.GetDisplayWidth((int)nativeDisplay); - h = (int)EditorDisplayUtility.GetDisplayHeight((int)nativeDisplay); - } - } - - private static void GetRenderingBuffersImpl(IntPtr nativeDisplay, out RenderBuffer color, - out RenderBuffer depth) - { - color = new RenderBuffer(); - depth = new RenderBuffer(); - } - - private static void SetRenderingResolutionImpl(IntPtr nativeDisplay, int w, int h) - { - var manager = instance; - if (manager.m_mode == DisplayAPIControlMode.FromEditor) - { - var view = manager.m_views[(int)nativeDisplay]; - if (view != null) { - view.SetPlayModeViewSize(new Vector2(w, h)); - } - } - } - - private static void ActivateDisplayImpl(IntPtr nativeDisplay, int width, int height, RefreshRate refreshRate) - { - var manager = instance; - if (manager.m_mode == DisplayAPIControlMode.FromRuntime) - { - EditorFullscreenController.BeginFullscreen((int)nativeDisplay, width, height); - } - } - - private static void SetParamsImpl(IntPtr nativeDisplay, int width, int height, int x, int y) - { - // do nothing. - } - - private static int RelativeMouseAtImpl(int x, int y, out int rx, out int ry) - { - // TODO, unused? - rx = 0; - ry = 0; - return 0; - } - - private static bool GetActiveImpl(IntPtr nativeDisplay) - { - var view = instance.m_views[(int)nativeDisplay]; - return view != null; - } - - private static bool RequiresBlitToBackbufferImpl(IntPtr nativeDisplay) - { - return false; - } - - private static bool RequiresSrgbBlitToBackbufferImpl(IntPtr nativeDisplay) - { - return false; - } - } -} // namespace diff --git a/Editor/Mono/Display/EditorDisplaySettingsProfile.cs b/Editor/Mono/Display/EditorDisplaySettingsProfile.cs deleted file mode 100644 index 3cb40c15c5..0000000000 --- a/Editor/Mono/Display/EditorDisplaySettingsProfile.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using System; -using UnityEngine; -using System.Collections.Generic; -using System.Linq; - -namespace UnityEditor -{ - [Serializable] - internal class EditorDisplaySettingsProfile - { - [SerializeField] private string m_name; - [SerializeField] private List m_settings; - [SerializeField] private BuildTarget m_buildTarget; - [SerializeField] private DisplayAPIControlMode displayAPIMode; - [SerializeField] private bool m_displayAPIUseSystemConfiguration; - - public string Name - { - get => m_name; - set => m_name = value; - } - - public BuildTarget Target - { - get => m_buildTarget; - set => m_buildTarget = value; - } - - public DisplayAPIControlMode DisplayAPIMode - { - get => displayAPIMode; - set => displayAPIMode = value; - } - - public bool DisplayAPIUseSystemConfiguration - { - get => m_displayAPIUseSystemConfiguration; - set => m_displayAPIUseSystemConfiguration = value; - } - - public List Settings => m_settings; - - public EditorDisplaySettingsProfile(string name) - { - m_name = name; - m_buildTarget = EditorUserBuildSettings.activeBuildTarget; - displayAPIMode = DisplayAPIControlMode.FromEditor; - } - - public EditorDisplayFullscreenSetting GetEditorDisplayFullscreenSetting(int displayId) - { - if (m_settings == null) - { - m_settings = new List(); - } - - return m_settings.FirstOrDefault(setting => setting.displayId == displayId); - } - - public void AddEditorDisplayFullscreenSetting(EditorDisplayFullscreenSetting setting) - { - if (m_settings == null) - { - m_settings = new List(); - } - - m_settings.Add(setting); - } - - public void RemoveEditorDisplayFullscreenSetting(EditorDisplayFullscreenSetting setting) - { - m_settings.Remove(setting); - } - } -} // namespace diff --git a/Editor/Mono/Display/EditorDisplayUtility.bindings.cs b/Editor/Mono/Display/EditorDisplayUtility.bindings.cs deleted file mode 100644 index 5119008563..0000000000 --- a/Editor/Mono/Display/EditorDisplayUtility.bindings.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using UnityEngine; -using UnityEngine.Bindings; - -namespace UnityEditor -{ - [NativeHeader("Editor/Platform/Interface/GUIView.h")] - [NativeHeader("Runtime/Graphics/EditorDisplayManager.h")] - internal static partial class EditorDisplayUtility - { - [FreeFunction] - public static extern int GetNumberOfConnectedDisplays(); - - [FreeFunction] - public static extern void AddVirtualDisplay(int index, int width, int height); - - [FreeFunction] - public static extern void RemoveVirtualDisplay(int index); - - [FreeFunction] - public static extern void SetSortDisplayOrder(bool enabled); - - [FreeFunction] - public static extern string GetDisplayName(int index); - - [FreeFunction] - public static extern int GetDisplayId(int index); - - [FreeFunction] - public static extern int GetDisplayWidth(int index); - - [FreeFunction] - public static extern int GetDisplayHeight(int index); - - [FreeFunction] - public static extern int GetMainDisplayId(); - } -} diff --git a/Editor/Mono/Display/EditorFullscreenController.cs b/Editor/Mono/Display/EditorFullscreenController.cs deleted file mode 100644 index 07e6aa8612..0000000000 --- a/Editor/Mono/Display/EditorFullscreenController.cs +++ /dev/null @@ -1,1237 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using System; -using UnityEngine; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using UnityEditor.Modules; -using UnityEditor.ShortcutManagement; -using UnityEditorInternal; -using UnityEngine.Bindings; -using UnityEngine.Scripting; - -namespace UnityEditor -{ - [InitializeOnLoad] - [FilePathAttribute("Library/EditorDisplaySettings.asset", FilePathAttribute.Location.ProjectFolder)] - internal class EditorFullscreenController : ScriptableSingleton - { - private const float kDisplayCheckIntervalSec = 1.0f; - - [SerializeField] private List m_profiles = null; - [SerializeField] private bool m_isSortDisplayOrder = true; - [SerializeField] private bool m_showNotificationOnFullscreen = true; - [SerializeField] private bool m_showToolbarOnFullscreen = false; - [SerializeField] private int m_selectedProfileIndex; - [SerializeField] private EditorDisplayFullscreenSetting m_mainDisplaySetting; - - private PlayModeStateChange m_state; - private EditorDisplayFullscreenSetting m_defaultSetting; - - internal bool isPlaying => m_state == PlayModeStateChange.ExitingEditMode; - - private float m_tLastTimeChecked; - private int m_numberOfConnectedDisplays; - - private string[] m_displayNames; - private int[] m_displayIds; - private int m_mainDisplayId; - - private EnumData m_buildTargetData; - private bool m_buildTargetDataInitialized; - - private Dictionary m_AvailableWindowTypes; - private Dictionary m_DisplaySettings; - private List m_FullscreenContainerWindows; - - // This is a stub for selecting the fullscreen option via the game view toolbar GUI. - // Instead of having configurable profiles, we'll use the main display setting that is - // created by default and modify which display we want to see it on via the toolbar GUI - // dropdown. In the future we may want to hook it up to the currently active display profile. - - // This is a stub. in the future references to this should be replaced with the currently active profile. - - internal static void SetSettingsForCurrentDisplay(int display) - { - var c = instance; - - if (c.m_DisplaySettings == null) - { - c.m_DisplaySettings = new Dictionary(); - } - - if (c.m_DisplaySettings.ContainsKey(display)) - { - c.m_mainDisplaySetting = c.m_DisplaySettings[display]; - return; - } - - var newDisplay = new EditorDisplayFullscreenSetting(c.m_DisplaySettings.Count + 1, "Main Display"); - newDisplay.enabled = true; - newDisplay.playModeViewSettings = new GameViewFullscreenSettings(); - newDisplay.viewWindowTitle = GetWindowTitle(typeof(GameView)); - - c.m_DisplaySettings.Add(display, newDisplay); - c.m_mainDisplaySetting = c.m_DisplaySettings[display]; - } - - internal static bool isFullscreenOnPlay - { - get => instance.m_mainDisplaySetting.mode == EditorDisplayFullscreenSetting.Mode.FullscreenOnPlaymode; - set => instance.SetFullscreenMainDisplay(value); - } - - // This is a stub. Future references to this should be replaced with the currently active profile. - internal static bool isToolbarEnabledOnFullscreen - { - get - { - return (instance.m_mainDisplaySetting.playModeViewSettings is GameViewFullscreenSettings settings) - ? settings.ShowToolbar - : false; - } - set - { - instance.SetShowToolbarOnMainDisplay(value); - } - } - - // This is a stub. Future references to this should be replaced with the currently active profile. - internal static int fullscreenDisplayId - { - get => instance.m_mainDisplaySetting.displayId; - set => instance.SetFullscreenDisplayId(value); - } - - internal static int targetDisplayID - { - get - { - return (instance.m_mainDisplaySetting.playModeViewSettings is GameViewFullscreenSettings settings) - ? settings.DisplayNumber - : 0; - } - set - { - if (instance.m_mainDisplaySetting.playModeViewSettings is GameViewFullscreenSettings settings) - { - settings.DisplayNumber = value; - } - } - } - - internal static bool enableVSync - { - get - { - return (instance.m_mainDisplaySetting.playModeViewSettings is GameViewFullscreenSettings settings) - ? settings.VsyncEnabled - : false; - } - set - { - if (instance.m_mainDisplaySetting.playModeViewSettings is GameViewFullscreenSettings settings) - { - settings.VsyncEnabled = value; - } - } - } - - internal static int selectedSizeIndex - { - get - { - return (instance.m_mainDisplaySetting.playModeViewSettings is GameViewFullscreenSettings settings) - ? settings.SelectedSizeIndex - : 0; - } - set - { - if (instance.m_mainDisplaySetting.playModeViewSettings is GameViewFullscreenSettings settings) - { - settings.SelectedSizeIndex = value; - } - } - } - - // This is a stub. Future references to this should be replaced with the currently active profile. - internal static Vector2 fullscreenDisplayRenderSize - { - get => instance.GetDisplayRenderSizeFromId(fullscreenDisplayId); - } - - internal static DisplayAPIControlMode DisplayAPIMode - { - get - { - var c = instance; - if (c.m_profiles == null || c.m_profiles.Count <= 0) - { - c.InitializeProfile(); - } - - if (c.m_profiles == null || c.m_profiles.Count <= c.m_selectedProfileIndex) - return DisplayAPIControlMode.FromEditor; - - return c.m_profiles[c.m_selectedProfileIndex].DisplayAPIMode; - } - } - - internal static void OnEnterPlaymode() - { - var c = instance; - c.m_state = PlayModeStateChange.ExitingEditMode; - c.RefreshDisplayStateWithCurrentProfile(); - } - - internal static void OnExitPlaymode() - { - var c = instance; - c.m_state = PlayModeStateChange.ExitingPlayMode; - c.RefreshDisplayStateWithCurrentProfile(); - } - - internal static void SetMainDisplayPlayModeViewType(Type playModeViewType) - { - SetDisplayPlayModeViewType(instance.m_mainDisplaySetting, playModeViewType); - } - - internal static int[] GetConnectedDisplayIds() - { - return instance.m_displayIds; - } - - internal static string[] GetConnectedDisplayNames() - { - return instance.m_displayNames; - } - - internal static string[] GetConnectedDisplayIdsAndNames() - { - int displayCount = instance.m_displayNames.Length; - string[] displayList = new string[displayCount]; - - for (int i = 0; i < displayCount; i++) - { - displayList[i] = i + ": " + instance.m_displayNames[i]; - } - return displayList; - } - - private void InitializeProfile() - { - if (m_profiles != null) - { - return; - } - - m_mainDisplaySetting = new EditorDisplayFullscreenSetting(0, "Main Display"); - m_mainDisplaySetting.enabled = true; - m_mainDisplaySetting.playModeViewSettings = new GameViewFullscreenSettings(); - m_mainDisplaySetting.viewWindowTitle = GetWindowTitle(typeof(GameView)); - - m_profiles = new List(); - var defaultProfile = new EditorDisplaySettingsProfile("Default"); - defaultProfile.AddEditorDisplayFullscreenSetting(m_defaultSetting); - - var displayAPIProfile = new EditorDisplaySettingsProfile("Simulate Standalone") - { - DisplayAPIMode = DisplayAPIControlMode.FromRuntime - }; - - for (var i = 0; i < m_displayNames.Length; ++i) - { - displayAPIProfile.AddEditorDisplayFullscreenSetting(CreateDefaultEditorDisplayFullscreenSetting(i)); - } - - m_profiles.Add(defaultProfile); - m_profiles.Add(displayAPIProfile); - m_selectedProfileIndex = 0; - } - - private EditorDisplayFullscreenSetting CreateDefaultEditorDisplayFullscreenSetting(int displayNumber) - { - var setting = new EditorDisplayFullscreenSetting(m_displayIds[displayNumber], m_displayNames[displayNumber]) {enabled = true}; - var gvSetting = new GameViewFullscreenSettings {DisplayNumber = displayNumber}; - setting.playModeViewSettings = gvSetting; - setting.viewWindowTitle = GetWindowTitle(typeof(GameView)); - - return setting; - } - - private void OnEnable() - { - EditorApplication.globalEventHandler += HandleToggleFullscreenKeyShortcut; - - UpdateDisplayNamesAndIds(); - m_buildTargetDataInitialized = false; - - if (m_profiles == null) - { - InitializeProfile(); - } - - m_FullscreenContainerWindows = new List(); - m_defaultSetting = new EditorDisplayFullscreenSetting(0, string.Empty); - m_defaultSetting.enabled = true; - m_defaultSetting.playModeViewSettings = new GameViewFullscreenSettings(); - m_defaultSetting.viewWindowTitle = GetWindowTitle(typeof(GameView)); - - EditorDisplayUtility.SetSortDisplayOrder(m_isSortDisplayOrder); - RefreshDisplayStateWithCurrentProfile(); - - EditorApplication.update += CheckDisplayNumberChanged; - } - - private void OnDisable() - { - EditorApplication.update -= CheckDisplayNumberChanged; - EditorApplication.globalEventHandler -= HandleToggleFullscreenKeyShortcut; - } - - public void CheckDisplayNumberChanged() - { - var tNow = Time.realtimeSinceStartup; - if (tNow - m_tLastTimeChecked < kDisplayCheckIntervalSec) - return; - - var ncDisplays = EditorDisplayUtility.GetNumberOfConnectedDisplays(); - if (ncDisplays != m_numberOfConnectedDisplays) - { - UpdateDisplayNamesAndIds(); - HandleDisplayProfileChange(); - } - - m_tLastTimeChecked = tNow; - } - - private static void SetDisplayPlayModeViewType(EditorDisplayFullscreenSetting setting, Type playModeViewType) - { - setting.playModeViewSettings = CreatePlayModeViewSettingsForType(playModeViewType); - setting.viewWindowTitle = GetWindowTitle(playModeViewType); - } - - private void HandleDisplayProfileChange() - { - // When hardware display configuration changes, - // always go back to default profile. - SetCurrentProfile(0); - RefreshDisplayStateWithCurrentProfile(); - } - - private void UpdateDisplayNamesAndIds() - { - m_numberOfConnectedDisplays = EditorDisplayUtility.GetNumberOfConnectedDisplays(); - m_displayNames = new string[m_numberOfConnectedDisplays]; - m_displayIds = new int[m_numberOfConnectedDisplays]; - m_mainDisplayId = EditorDisplayUtility.GetMainDisplayId(); - - for (var i = 0; i < m_numberOfConnectedDisplays; ++i) - { - m_displayIds[i] = EditorDisplayUtility.GetDisplayId(i); - m_displayNames[i] = EditorDisplayUtility.GetDisplayName(i); - } - - if (m_profiles != null && m_profiles.Count > 1) - { - var displayAPIProfile = m_profiles[1]; - - for (var i = 0; i < m_numberOfConnectedDisplays; ++i) - { - if (displayAPIProfile.Settings.Count <= i) - { - displayAPIProfile.AddEditorDisplayFullscreenSetting(CreateDefaultEditorDisplayFullscreenSetting(i)); - } - else - { - displayAPIProfile.Settings[i].displayId = m_displayIds[i]; - } - } - } - } - - private void GetInstalledBuildTargetData() - { - if (m_buildTargetDataInitialized) - return; - - var buildTargetData = EnumDataUtility.GetCachedEnumData(typeof(BuildTarget)); - var installedBuildTargetCount = - (from BuildTarget target in buildTargetData.values - let @group = BuildPipeline.GetBuildTargetGroup(target) - where BuildPipeline.IsBuildTargetSupported(@group, target) select target).Count(); - - m_buildTargetData = new EnumData - { - values = new Enum[installedBuildTargetCount], - displayNames = new string[installedBuildTargetCount], - tooltip = new string[installedBuildTargetCount], - flagValues = new int[installedBuildTargetCount], - flags = buildTargetData.flags, - serializable = buildTargetData.serializable, - underlyingType = buildTargetData.underlyingType, - unsigned = buildTargetData.unsigned - }; - - for (int i = 0, j = 0; i < buildTargetData.values.Length; ++i) - { - var target = (BuildTarget) buildTargetData.values[i]; - var group = BuildPipeline.GetBuildTargetGroup(target); - if (BuildPipeline.IsBuildTargetSupported(group, target)) - { - m_buildTargetData.values[j] = buildTargetData.values[i]; - m_buildTargetData.displayNames[j] = buildTargetData.displayNames[i]; - m_buildTargetData.tooltip[j] = buildTargetData.tooltip[i]; - m_buildTargetData.flagValues[j] = buildTargetData.flagValues[i]; - ++j; - } - } - - m_buildTargetDataInitialized = true; - } - - private EditorDisplayFullscreenSetting GetSettingForDisplay(int displayIndex) - { - if (m_profiles == null) - { - InitializeProfile(); - } - - var displayId = EditorDisplayUtility.GetDisplayId(displayIndex); - if (DisplayAPIMode == DisplayAPIControlMode.FromEditor && - displayId == EditorDisplayUtility.GetMainDisplayId()) - { - return m_mainDisplaySetting; - } - - var s = m_profiles[m_selectedProfileIndex].GetEditorDisplayFullscreenSetting(displayId); - if (s == null || !s.enabled) - { - return m_defaultSetting; - } - - return s; - } - - private EditorDisplayFullscreenSetting GetSettingForDisplayById(int displayId) - { - return GetSettingForDisplay(GetDisplayIndexFromId(displayId)); - } - - private int GetDisplayIndexFromId(int displayId) - { - var nDisplays = EditorDisplayUtility.GetNumberOfConnectedDisplays(); - for (var i = 0; i < nDisplays; ++i) - { - if (displayId == EditorDisplayUtility.GetDisplayId(i)) - { - return i; - } - } - return 0; - } - - private Vector2 GetDisplayRenderSizeFromId(int displayId) - { - int idx = GetDisplayIndexFromId(displayId); - int width = EditorDisplayUtility.GetDisplayWidth(idx); - int height = EditorDisplayUtility.GetDisplayHeight(idx); - Vector2 size = new Vector2(width, height); - return size; - } - - private void RefreshDisplayStateWithCurrentProfile() - { - ApplySettings(m_mainDisplaySetting.displayId, m_mainDisplaySetting); - } - - private static ContainerWindow FindContainerWindow(int displayIndex) - { - var windows = ContainerWindow.windows; - - return windows.FirstOrDefault(w => - w.m_IsFullscreenContainer && - w.m_DisplayIndex == displayIndex); - } - - internal static void ClearAllFullscreenWindows() - { - var windows = ContainerWindow.windows; - foreach (var w in windows) - { - if (w.m_IsFullscreenContainer) - { - w.Close(); - } - } - } - - internal static void BeginFullscreen(int displayIndex, int targetWidth, int targetHeight) - { - var containerWindow = FindContainerWindow(displayIndex); - if (containerWindow == null) - { - instance.BeginFullScreen(displayIndex, instance.GetSettingForDisplay(displayIndex), targetWidth, targetHeight); - } - } - - [RequiredByNativeCode] - internal static void EndFullscreen(int displayIndex, bool closeWindow) - { - ContainerWindow containerWindow = null; - if (closeWindow) - { - containerWindow = FindContainerWindow(displayIndex); - } - instance.EndFullScreen(displayIndex, containerWindow); - - if (displayIndex == 0) - { - instance.SetFullscreenMainDisplay(false, false); - } - } - - private void ApplySettings(int displayIndex, EditorDisplayFullscreenSetting setting) - { - var containerWindow = FindContainerWindow(displayIndex); - - if (containerWindow == null && - (setting.mode == EditorDisplayFullscreenSetting.Mode.AlwaysFullscreen || - (setting.mode == EditorDisplayFullscreenSetting.Mode.FullscreenOnPlaymode && instance.isPlaying))) - { - BeginFullScreen(displayIndex, setting); - return; - } - - if (setting.mode == EditorDisplayFullscreenSetting.Mode.DoNothing || - (setting.mode == EditorDisplayFullscreenSetting.Mode.FullscreenOnPlaymode && !instance.isPlaying)) - { - EndFullScreen(displayIndex, containerWindow); - return; - } - - if (containerWindow != null) - { - var hostView = (HostView)containerWindow.rootView; - var playModeView = (PlayModeView)hostView.actualView; - - if (playModeView != null) - { - playModeView.ApplyEditorDisplayFullscreenSetting(setting.playModeViewSettings); - } - } - } - - private void BeginFullScreen(int displayIndex, EditorDisplayFullscreenSetting setting, int targetWidth = 0, int targetHeight = 0) - { - var viewType = typeof(GameView); - if (setting.playModeViewSettings == null) - { - SetDisplayPlayModeViewType(setting, typeof(GameView)); - } - - var attributes = setting.playModeViewSettings.GetType().GetCustomAttributes(typeof(FullscreenSettingsForAttribute), false); - if (attributes.Length > 0) - { - var proposedType = ((FullscreenSettingsForAttribute) attributes[0]).AssignedType; - if (typeof(PlayModeView).IsAssignableFrom(proposedType)) - { - viewType = proposedType; - } - else - { - Debug.LogError($"Type assigned for FullscreenSettingsFor is not a subclass of PlayModeView. PlayModeViewSettings={setting.playModeViewSettings.GetType()}, AssignedType{proposedType}"); - } - } - - var playModeView = ScriptableObject.CreateInstance(viewType) as PlayModeView; - - // Now create a new hostView and container window (popup style -> no borders) and maximize it - var hostView = ScriptableObject.CreateInstance(); - hostView.name = $"HostView {setting.displayName}"; - hostView.actualView = playModeView; - playModeView.m_Parent = hostView; - playModeView.isFullscreen = true; - - var containerWindow = ScriptableObject.CreateInstance(); - containerWindow.name = $"Unity Fullscreen Window {setting.displayName}"; - containerWindow.m_DontSaveToLayout = true; - containerWindow.m_IsFullscreenContainer = true; - containerWindow.m_DisplayIndex = displayIndex; - - // this ensures the fullscreen game view is shown on the same screen has the normal GameView is currently on - containerWindow.rootView = hostView; - - playModeView.wantsMouseMove = true; - playModeView.MakeParentsSettingsMatchMe(); - - playModeView.ApplyEditorDisplayFullscreenSetting(setting.playModeViewSettings); - if (targetWidth != 0 && targetHeight != 0) - { - playModeView.targetSize = new Vector2(targetWidth, targetHeight); - } - containerWindow.Show(ShowMode.Fullscreen, false, true, true, displayIndex); - containerWindow.ToggleFullscreen(displayIndex); - hostView.Focus(); - - playModeView.m_Parent.SetAsStartView(); - playModeView.m_Parent.SetAsLastPlayModeView(); - SuppressViewsFromRendering(containerWindow.GetDisplayId(), true); - - if (instance.m_showNotificationOnFullscreen && setting == instance.m_mainDisplaySetting) - { - ShowFullscreenNotification(playModeView); - } - if (instance.m_showToolbarOnFullscreen && setting == instance.m_mainDisplaySetting) - { - SetShowToolbarOnMainDisplay(true); - } - m_FullscreenContainerWindows.Add(containerWindow); - } - - private void EndFullScreen(int displayIndex, ContainerWindow w) - { - SuppressViewsFromRendering(EditorDisplayUtility.GetDisplayId(displayIndex), false); - - if (w != null) - { - w.Close(); - m_FullscreenContainerWindows.Remove(w); - } - - // Refocus main window after ending fullscreen. - var mainWindow = WindowLayout.FindMainWindow(); - if (mainWindow != null && mainWindow.rootView != null) { - var hostView = mainWindow.rootView as HostView; - if (hostView != null) - hostView.Focus(); - } - } - - internal static List GetFullscreenContainersForDisplayIndex(int displayIndex) - { - List containers = new List(); - foreach (var cw in instance.m_FullscreenContainerWindows) - { - if (cw.m_DisplayIndex == displayIndex) - { - containers.Add(cw); - } - } - - return containers; - } - - private bool IsGoingFullscreenOnPlaymode(int displayId) - { - var setting = GetSettingForDisplayById(displayId); - return setting.mode != EditorDisplayFullscreenSetting.Mode.DoNothing; - } - - private static void ShowFullscreenNotification(PlayModeView playView) - { - var binding = ShortcutManager.instance.GetShortcutBinding(kFullscreenToggle); - - playView.ShowNotification(EditorGUIUtility.TextContentWithIcon(string.Format(Styles.disableFullscreenMainDisplayFormatContent.text, binding), "FullscreenNotification")); - playView.Repaint(); - } - - private void SuppressViewsFromRendering(int displayId, bool suppress) - { - var playModeViews = Resources.FindObjectsOfTypeAll(typeof(PlayModeView)); - foreach (PlayModeView playModeView in playModeViews) - { - if (playModeView.m_Parent == null || - playModeView.m_Parent.window == null || - playModeView.m_Parent.window.m_IsFullscreenContainer) - { - // The fullscreen window should never suppress rendering. - playModeView.suppressRenderingForFullscreen = false; - playModeView.SetPlayModeView(true); - continue; - } - - var windowDisplayId = playModeView.m_Parent.window.GetDisplayId(); - if (windowDisplayId == displayId) - { - // This play mode view is rendering on the same display we're going to fullscreen on. Always suppress. - playModeView.suppressRenderingForFullscreen = suppress; - playModeView.SetPlayModeView(!suppress); - continue; - } - - if (playModeView.enterPlayModeBehavior == PlayModeView.EnterPlayModeBehavior.PlayFullscreen) - { - // This play mode view is going to spawn another fullscreen view. We should suppress rendering - // from this game view so as to not duplicate/double our number of rendered views. - playModeView.suppressRenderingForFullscreen = suppress; - playModeView.SetPlayModeView(!suppress); - continue; - } - } - } - - private static void Save() - { - instance.Save(true); - } - - private const string kBaseMenuPath = "Window/Displays"; - private const string kFullscreenOnPlayMenu = kBaseMenuPath + "/Toggle Fullscreen"; - private const string kShowToolbarOnFullscreenMenu = kBaseMenuPath + "/Show Toolbar on Fullscreen"; - private const string kProfilePath = kBaseMenuPath + "/Profiles/"; - public const string kFullscreenToggle = "Window/Fullscreen Game View"; - private static float sLastToggleShortcutTriggerTime = 0f; - private static float kToggleShortcutTriggerTimeoutInSeconds = 1f; - - void ToggleFullscreen() - { - var playModeViews = Resources.FindObjectsOfTypeAll(typeof(PlayModeView)); - foreach (PlayModeView playModeView in playModeViews) - { - if (playModeView.m_Parent == null || playModeView.m_Parent.window == null) - continue; - - if (playModeView.enterPlayModeBehavior != PlayModeView.EnterPlayModeBehavior.PlayFullscreen) - continue; // Do nothing with non-fullscreen game views. - - int displayIdx = playModeView.fullscreenMonitorIdx; - if (m_DisplaySettings.ContainsKey(displayIdx)) - { - var displaySetting = m_DisplaySettings[displayIdx]; - - if (displaySetting.mode == EditorDisplayFullscreenSetting.Mode.AlwaysFullscreen || - displaySetting.mode == EditorDisplayFullscreenSetting.Mode.FullscreenOnPlaymode) - { - displaySetting.mode = EditorDisplayFullscreenSetting.Mode.DoNothing; - } - else - { - displaySetting.mode = EditorDisplayFullscreenSetting.Mode.AlwaysFullscreen; - } - instance.ApplySettings(displayIdx, displaySetting); - } - } - } - - void HandleToggleFullscreenKeyShortcut() - { - if (!Application.isPlaying || m_DisplaySettings == null) - return; - - var evt = Event.current; - var binding = ShortcutManager.instance.GetShortcutBinding("Window/Fullscreen Game View"); - var keys = binding.keyCombinationSequence; - - if (evt.type != EventType.KeyUp) - return; - - if (keys.Where(x => x.keyCode == evt.keyCode).Count() <= 0) - return; - - // Detect if the right key combinaison is active or not. - var shiftNecessary = keys.Where(x => (x.modifiers & ShortcutModifiers.Shift) == ShortcutModifiers.Shift).Count() > 0; - var containShift = (evt.modifiers & EventModifiers.Shift) == EventModifiers.Shift; - - if (shiftNecessary && !containShift) - return; - - var altNecessary = keys.Where(x => (x.modifiers & ShortcutModifiers.Alt) == ShortcutModifiers.Alt).Count() > 0; - var containAlt = (evt.modifiers & EventModifiers.Alt) == EventModifiers.Alt; - - if (altNecessary && !containAlt) - return; - - var ctrlNecessary = keys.Where(x => (x.modifiers & ShortcutModifiers.Control) == ShortcutModifiers.Control).Count() > 0; - var containCtrl = (evt.modifiers & EventModifiers.Control) == EventModifiers.Control; - - if (ctrlNecessary && !containCtrl) - return; - - // OSX will animate windows moving to fullscreen and toggling fullscreen again during this animation will - // cause a slew of bugs. Rather than lock this shortcut until the op is completed, instead provide a reasonable - // timeout for triggering this shortcut again. - if (sLastToggleShortcutTriggerTime + kToggleShortcutTriggerTimeoutInSeconds < Time.realtimeSinceStartup) - { - sLastToggleShortcutTriggerTime = Time.realtimeSinceStartup; - ToggleFullscreen(); - evt.Use(); - } - } - - [ClutchShortcutAttribute(kFullscreenToggle, KeyCode.F7, ShortcutModifiers.Shift | ShortcutModifiers.Control)] - internal static void FullscreenKeyHandler(ShortcutArguments args) - { - // The CTRL + SHIFT + F7 event doesn't work when a Game View is focused. - // It's a current limitation by the Shortcut Manager. Instead the kFullscreenToggle - // shortcut is handled by HandleToggleFullscreenKeyShortcut function which is a global - // event handler. - } - - static EditorFullscreenController() - { - EditorApplication.update -= DelayReloadWindowDisplayMenu; - EditorApplication.update += DelayReloadWindowDisplayMenu; - } - - private static void DelayReloadWindowDisplayMenu() - { - EditorApplication.update -= DelayReloadWindowDisplayMenu; - instance.ReloadWindowDisplayMenu(); - EditorUtility.Internal_UpdateAllMenus(); - } - - internal void ReloadWindowDisplayMenu() - { - Menu.RemoveMenuItem(kBaseMenuPath); - - var displayMenuItemPriority = 200; - - Menu.AddMenuItem(kFullscreenOnPlayMenu, "", isFullscreenOnPlay, displayMenuItemPriority++, ToggleFullscreenMainDisplay, null); - Menu.AddMenuItem(kShowToolbarOnFullscreenMenu, "", isToolbarEnabledOnFullscreen, displayMenuItemPriority++, ToggleShowToolbarOnMainDisplay, null); - - Menu.AddSeparator(kBaseMenuPath, displayMenuItemPriority++); - - displayMenuItemPriority += 500; - - for (var i = 0; i < m_profiles.Count; ++i) - { - var index = i; - var profile = m_profiles[i]; - - if (i == 0 || profile.Target == EditorUserBuildSettings.activeBuildTarget) - { - Menu.AddMenuItem(kProfilePath + profile.Name, "", m_selectedProfileIndex == i, displayMenuItemPriority++, - () => { SetCurrentProfile(index); }, () => !EditorApplication.isPlaying); - } - } - } - - private static void SetCurrentProfile(int index) - { - if (instance.m_profiles.Count <= index) - { - return; - } - - if (instance.m_selectedProfileIndex != index) - { - var previousMenuPath = kProfilePath + instance.m_profiles[instance.m_selectedProfileIndex].Name; - var currentMenuPath = kProfilePath + instance.m_profiles[index].Name; - Menu.SetChecked(previousMenuPath, false); - Menu.SetChecked(currentMenuPath, true); - - instance.m_selectedProfileIndex = index; - } - - instance.RefreshDisplayStateWithCurrentProfile(); - } - - private void ToggleFullscreenMainDisplay() - { - SetFullscreenMainDisplay(!isFullscreenOnPlay); - } - - private void SetFullscreenMainDisplay(bool enabled, bool refresh = true) - { - if (isFullscreenOnPlay == enabled) - return; - - m_mainDisplaySetting.mode = enabled - ? EditorDisplayFullscreenSetting.Mode.FullscreenOnPlaymode - : EditorDisplayFullscreenSetting.Mode.DoNothing; - - Menu.SetChecked(kFullscreenOnPlayMenu, enabled); - if (refresh) - { - //RefreshDisplayStateWithCurrentProfile(); - } - Save(); - } - - private void SetFullscreenDisplayId(int displayId) - { - m_mainDisplaySetting.displayId = displayId; - } - - private void ToggleShowToolbarOnMainDisplay() - { - SetShowToolbarOnMainDisplay(!isToolbarEnabledOnFullscreen); - } - - private void SetShowToolbarOnMainDisplay(bool enabled) - { - if (!(m_mainDisplaySetting.playModeViewSettings is GameViewFullscreenSettings gameViewSetting) || - gameViewSetting.ShowToolbar == enabled) - { - return; - } - - gameViewSetting.ShowToolbar = enabled; - Menu.SetChecked(kShowToolbarOnFullscreenMenu, enabled); - RefreshDisplayStateWithCurrentProfile(); - Save(); - } - - private static string GetWindowTitle(Type type) - { - var attributes = type.GetCustomAttributes(typeof(EditorWindowTitleAttribute), true); - return attributes.Length > 0 ? ((EditorWindowTitleAttribute)attributes[0]).title : type.Name; - } - - private Dictionary GetAvailableWindowTypes() - { - return m_AvailableWindowTypes ?? (m_AvailableWindowTypes = TypeCache.GetTypesDerivedFrom(typeof(PlayModeView)).OrderBy(GetWindowTitle).ToDictionary(t => t, GetWindowTitle)); - } - - private class Styles - { - public static readonly GUIContent sortDisplayOrderContent = EditorGUIUtility.TrTextContent("Sort Display Order (*Windows Only)"); - public static readonly GUIContent showNotificationContent = EditorGUIUtility.TrTextContent("Show notification when entering fullscreen"); - public static readonly GUIContent showToolbarOnFullscreenContent = EditorGUIUtility.TrTextContent("Show game view toolbar on fullscreen"); - - public static readonly GUIContent[] modePopupDisplayTexts = - { - EditorGUIUtility.TrTextContent("Do nothing"), - EditorGUIUtility.TrTextContent("Fullscreen on Playmode"), - EditorGUIUtility.TrTextContent("Always Fullscreen"), - }; - public static readonly GUIContent addProfileContent = EditorGUIUtility.TrTextContent("Add Profile"); - public static readonly GUIContent nameContent = EditorGUIUtility.TrTextContent("Name"); - public static readonly GUIContent platformContent = EditorGUIUtility.TrTextContent("Platform"); - public static readonly GUIContent disconnectedDisplaysContent = EditorGUIUtility.TrTextContent("Disconnected Displays"); - - - public static readonly GUIContent mainDisplaySettingHelpTextContent = EditorGUIUtility.TrTextContent("Settings for main display is done from GameView."); - public static readonly GUIContent viewTypeContent = EditorGUIUtility.TrTextContent("View Type"); - public static readonly GUIContent vsyncContent = EditorGUIUtility.TrTextContent("VSync"); - public static readonly GUIContent gizmosContent = EditorGUIUtility.TrTextContent("Gizmos"); - public static readonly GUIContent toolbarContent = EditorGUIUtility.TrTextContent("Toolbar"); - public static readonly GUIContent statsContent = EditorGUIUtility.TrTextContent("Stats"); - public static readonly GUIContent displaySettingsLabelContent = EditorGUIUtility.TrTextContent("Display Settings"); - - public static readonly GUIContent mainDisplayFormatContent = EditorGUIUtility.TrTextContent("{0} (Main Display)"); - public static readonly GUIContent disconnectedDisplayFormatContent = EditorGUIUtility.TrTextContent("{0} (Disconnected)"); - public static readonly GUIContent disableFullscreenMainDisplayFormatContent = EditorGUIUtility.TrTextContent("Press {0} to exit fullscreen."); - - public static readonly GUIContent displayAPIMappingContent = EditorGUIUtility.TrTextContent("Standalone simulation monitor mapping"); - } - - private static void DrawPreferenceGUI(string searchContext) - { - if (instance.m_profiles == null) - { - instance.InitializeProfile(); - } - GUILayout.Space(12); - - EditorGUI.BeginChangeCheck(); - instance.m_isSortDisplayOrder = EditorGUILayout.ToggleLeft(Styles.sortDisplayOrderContent, instance.m_isSortDisplayOrder); - if (EditorGUI.EndChangeCheck()) - { - EditorDisplayUtility.SetSortDisplayOrder(instance.m_isSortDisplayOrder); - Save(); - } - - EditorGUI.BeginChangeCheck(); - instance.m_showNotificationOnFullscreen = EditorGUILayout.ToggleLeft(Styles.showNotificationContent, instance.m_showNotificationOnFullscreen); - instance.m_showToolbarOnFullscreen = EditorGUILayout.ToggleLeft(Styles.showToolbarOnFullscreenContent, instance.m_showToolbarOnFullscreen); - if (EditorGUI.EndChangeCheck()) - { - Save(); - } - - GUILayout.Space(12); - - - DrawDisplayAPIProfile(); - //DrawProfilePreferencesGUI(); //TODO Enables GUI for display profiles. - } - - private static void DrawProfilePreferencesGUI() - { - EditorDisplaySettingsProfile removingProfile = null; - EditorGUI.BeginChangeCheck(); - - // m_profiles[0] is always Default Profile. - // m_profiles[1] is always DisplayAPI Profile. - for(var i = 2; i < instance.m_profiles.Count; ++i) - { - if (!DrawPreferenceProfileGUI(instance.m_profiles[i])) - { - removingProfile = instance.m_profiles[i]; - break; - } - GUILayout.Space(12); - } - - if (removingProfile != null) - { - instance.m_profiles.Remove(removingProfile); - } - - GUILayout.Space(12); - - if (GUILayout.Button(Styles.addProfileContent, GUILayout.Width(150))) - { - var newProfile = new EditorDisplaySettingsProfile(instance.GetAppropriateNewProfileName()); - instance.m_profiles.Add(newProfile); - } - - GUILayout.Space(12); - - if (EditorGUI.EndChangeCheck()) - { - Save(); - } - } - - private string GetAppropriateNewProfileName() - { - const string kDefaultNewProfileName = "New Profile"; - if (m_profiles.Find(p => p.Name == kDefaultNewProfileName) == null) - { - return kDefaultNewProfileName; - } - - for (var i = 1;; ++i) - { - var name = $"New Profile ({i})"; - if (m_profiles.Find(p => p.Name == name) == null) - { - return name; - } - } - } - - private static void DrawDisplayAPIProfile() - { - if (!ModuleManager.ShouldShowMultiDisplayOption()) - { - return; - } - - using (new GUILayout.VerticalScope(EditorStyles.frameBox)) - { - GUILayout.Label(Styles.displayAPIMappingContent, EditorStyles.boldLabel); - GUILayout.Space(12); - - var displayNames = GetDisplayNamesForBuildTarget(EditorUserBuildSettings.activeBuildTarget); - - for (var i = 0; i < instance.m_numberOfConnectedDisplays; ++i) - { - EditorGUILayout.LabelField(displayNames[i], GUIContent.Temp(instance.m_displayNames[i])); - } - } - GUILayout.Space(12); - } - - private static bool DrawPreferenceProfileGUI(EditorDisplaySettingsProfile profile) - { - using (new GUILayout.VerticalScope(EditorStyles.frameBox)) - { - using (new GUILayout.HorizontalScope()) - { - GUILayout.Label(profile.Name, EditorStyles.boldLabel); - if (GUILayout.Button("-", GUILayout.Width(20))) - { - return false; - } - } - - GUILayout.Space(8); - - var newName = EditorGUILayout.TextField(Styles.nameContent, profile.Name, GUILayout.Width(400)); - if (newName != profile.Name) - { - profile.Name = newName; - } - - instance.GetInstalledBuildTargetData(); - - var selectedIndex = Array.IndexOf(instance.m_buildTargetData.values, profile.Target); - var newSelectedIndex = EditorGUILayout.Popup(Styles.platformContent, selectedIndex, instance.m_buildTargetData.displayNames, GUILayout.Width(400)); - if (selectedIndex != newSelectedIndex) - { - profile.Target = (BuildTarget) instance.m_buildTargetData.values[newSelectedIndex]; - } - - // if selecting unsupported build target, then do not let settings to edit - if (selectedIndex < 0) - { - return true; - } - - GUILayout.Space(8); - - for (var i = 0; i < instance.m_displayIds.Length; ++i) - { - var id = instance.m_displayIds[i]; - var isMainScreen = id == instance.m_mainDisplayId; - - var setting = profile.GetEditorDisplayFullscreenSetting(id); - if (setting == null) - { - setting = new EditorDisplayFullscreenSetting(id, instance.m_displayNames[i]); - profile.AddEditorDisplayFullscreenSetting(setting); - } - - DrawPlayModeViewSettingsGUI(profile.Target, setting, isMainScreen, false); - GUILayout.Space(8); - } - - bool drawDisconnectedSettingsHeader = false; - // For Display Settings which is currently disconnected - EditorDisplayFullscreenSetting removingSetting = null; - - foreach (var setting in profile.Settings) - { - if (instance.m_displayIds.Contains(setting.displayId)) - { - continue; - } - - if (!drawDisconnectedSettingsHeader) - { - GUILayout.Space(12); - GUILayout.Label(Styles.disconnectedDisplaysContent); - drawDisconnectedSettingsHeader = true; - } - - if (!DrawPlayModeViewSettingsGUI(profile.Target, setting, false, true)) - { - removingSetting = setting; - } - GUILayout.Space(8); - } - - if (removingSetting != null) - { - profile.RemoveEditorDisplayFullscreenSetting(removingSetting); - } - - GUILayout.Space(12); - } - - return true; - } - - private static bool DrawPlayModeViewSettingsGUI(BuildTarget target, EditorDisplayFullscreenSetting setting, bool isMainScreen, bool isDisconnected) - { - if (isMainScreen) - { - GUILayout.Label(string.Format(Styles.mainDisplayFormatContent.text, setting.displayName)); - EditorGUILayout.HelpBox(Styles.mainDisplaySettingHelpTextContent); - return true; - } - - var label = isDisconnected ? string.Format(Styles.disconnectedDisplayFormatContent.text, setting.displayName) : setting.displayName; - - using (new GUILayout.HorizontalScope()) - { - setting.enabled = EditorGUILayout.ToggleLeft(label, setting.enabled); - if (isDisconnected) - { - if (GUILayout.Button("-", GUILayout.Width(20))) - { - return false; - } - } - } - - if (!setting.enabled) - { - return true; - } - - using (new GUILayout.VerticalScope(EditorStyles.frameBox)) - { - var availableTypes = instance.GetAvailableWindowTypes(); - if (availableTypes.Count > 1) - { - var viewTitleNames = availableTypes.Values.ToList(); - var viewIndex = viewTitleNames.IndexOf(setting.viewWindowTitle); - var newViewIndex = EditorGUILayout.Popup( - Styles.viewTypeContent, viewTitleNames.IndexOf(setting.viewWindowTitle), - viewTitleNames.ToArray(), GUILayout.Width(400)); - if (newViewIndex != viewIndex) - { - setting.viewWindowTitle = viewTitleNames[newViewIndex]; - setting.playModeViewSettings = - CreatePlayModeViewSettingsForType(availableTypes.Keys.ToList()[newViewIndex]); - } - } - else { - if (string.IsNullOrEmpty(setting.viewWindowTitle)) - { - var typeNames = availableTypes.Values.ToList(); - setting.viewWindowTitle = typeNames[0]; - setting.playModeViewSettings = - CreatePlayModeViewSettingsForType(availableTypes.Keys.ToList()[0]); - } - EditorGUILayout.LabelField(Styles.viewTypeContent, new GUIContent(setting.viewWindowTitle), GUILayout.Width(300)); - } - EditorGUILayout.Space(); - - setting.mode = (EditorDisplayFullscreenSetting.Mode)EditorGUILayout.Popup((int)setting.mode, Styles.modePopupDisplayTexts, GUILayout.Width(200)); - if (setting.playModeViewSettings != null) - { - GUILayout.Space(4); - setting.playModeViewSettings.OnPreferenceGUI(target); - } - } - - return true; - } - - private static IPlayModeViewFullscreenSettings CreatePlayModeViewSettingsForType(Type playModeViewType) - { - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { - var settingsTypes = assembly.GetTypes() - .Where(t => !t.IsInterface) - .Where(t => typeof(IPlayModeViewFullscreenSettings).IsAssignableFrom(t)); - - foreach (var settingsType in settingsTypes) - { - var attributes = settingsType.GetCustomAttributes(typeof(FullscreenSettingsForAttribute), false); - if (attributes.Length > 0 && ((FullscreenSettingsForAttribute)attributes[0]).AssignedType == playModeViewType) - { - return (IPlayModeViewFullscreenSettings) settingsType.Assembly.CreateInstance(settingsType.FullName); - } - } - } - return null; - } - - internal static GUIContent[] GetDisplayNamesForBuildTarget(BuildTarget buildTarget) - { - var platformDisplayNames = Modules.ModuleManager.GetDisplayNames(buildTarget.ToString()); - return platformDisplayNames ?? DisplayUtility.GetGenericDisplayNames(); - } - - - [SettingsProvider] - internal static SettingsProvider CreateEditorDisplaySettingUserPreference() - { - var provider = new SettingsProvider("Preferences/Display Settings", SettingsScope.User) - { - label = Styles.displaySettingsLabelContent.text, - guiHandler = DrawPreferenceGUI, - activateHandler = (s, element) => - { - instance.UpdateDisplayNamesAndIds(); - }, - deactivateHandler = () => - { - instance.RefreshDisplayStateWithCurrentProfile(); - instance.ReloadWindowDisplayMenu(); - }, - keywords = SettingsProvider.GetSearchKeywordsFromGUIContentProperties() - }; - return provider; - } - } -} // namespace diff --git a/Editor/Mono/Display/FullscreenSettingsForAttribute.cs b/Editor/Mono/Display/FullscreenSettingsForAttribute.cs deleted file mode 100644 index 0672466a95..0000000000 --- a/Editor/Mono/Display/FullscreenSettingsForAttribute.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using System; -using UnityEngine.Bindings; - -namespace UnityEditor -{ - [AttributeUsage(AttributeTargets.Class, Inherited = false)] - [VisibleToOtherModules] - internal class FullscreenSettingsForAttribute : Attribute - { - public FullscreenSettingsForAttribute(Type assignedType) - { - AssignedType = assignedType; - } - - public Type AssignedType { get; set; } - } -} diff --git a/Editor/Mono/Display/GameViewFullscreenSettings.cs b/Editor/Mono/Display/GameViewFullscreenSettings.cs deleted file mode 100644 index 5998df7044..0000000000 --- a/Editor/Mono/Display/GameViewFullscreenSettings.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using System; -using UnityEngine; -using System.Collections.Generic; -using System.Linq; -using UnityEditor.ShortcutManagement; - -namespace UnityEditor -{ - [Serializable] - [FullscreenSettingsFor(typeof(GameView))] - internal class GameViewFullscreenSettings : IPlayModeViewFullscreenSettings - { - [SerializeField] private int m_displayNumber; - [SerializeField] private bool m_showToolbar; - [SerializeField] private bool m_vsyncEnabled; - [SerializeField] private bool m_showStats; - [SerializeField] private bool m_showGizmos; - [SerializeField] private int m_selectedSizeIndex; - - public int DisplayNumber - { - get => m_displayNumber; - set => m_displayNumber = value; - } - - public bool ShowToolbar - { - get => m_showToolbar; - set => m_showToolbar = value; - } - - public bool VsyncEnabled - { - get => m_vsyncEnabled; - set => m_vsyncEnabled = value; - } - - public bool ShowStats - { - get => m_showStats; - set => m_showStats = value; - } - - public bool ShowGizmos - { - get => m_showGizmos; - set => m_showGizmos = value; - } - - public int SelectedSizeIndex - { - get => m_selectedSizeIndex; - set => m_selectedSizeIndex = value; - } - - private class Styles - { - public static readonly GUIContent vsyncContent = EditorGUIUtility.TrTextContent("VSync"); - public static readonly GUIContent gizmosContent = EditorGUIUtility.TrTextContent("Gizmos"); - public static readonly GUIContent toolbarContent = EditorGUIUtility.TrTextContent("Toolbar"); - public static readonly GUIContent statsContent = EditorGUIUtility.TrTextContent("Stats"); - } - - public void OnPreferenceGUI(BuildTarget target) - { - using (new GUILayout.HorizontalScope()) - { - m_displayNumber = EditorGUILayout.Popup(m_displayNumber, EditorFullscreenController.GetDisplayNamesForBuildTarget(target), GUILayout.Width(80)); - GUILayout.Space(12); - m_vsyncEnabled = EditorGUILayout.ToggleLeft(Styles.vsyncContent, m_vsyncEnabled, GUILayout.Width(60)); - m_showToolbar = EditorGUILayout.ToggleLeft(Styles.toolbarContent, m_showToolbar, GUILayout.Width(70)); - m_showGizmos = EditorGUILayout.ToggleLeft(Styles.gizmosContent, m_showGizmos, GUILayout.Width(60)); - m_showStats = EditorGUILayout.ToggleLeft(Styles.statsContent, m_showStats, GUILayout.Width(60)); - } - } - } -} // namespace diff --git a/Editor/Mono/Display/IPlayModeViewFullscreenSettings.cs b/Editor/Mono/Display/IPlayModeViewFullscreenSettings.cs deleted file mode 100644 index bd7c04cd5b..0000000000 --- a/Editor/Mono/Display/IPlayModeViewFullscreenSettings.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using System; -using UnityEngine; -using System.Collections.Generic; -using System.Linq; -using UnityEditor.ShortcutManagement; - -namespace UnityEditor -{ - internal interface IPlayModeViewFullscreenSettings - { - int DisplayNumber { get; } - - bool VsyncEnabled { get; } - - void OnPreferenceGUI(BuildTarget target); - } -} // namespace diff --git a/Editor/Mono/EditorApplication.bindings.cs b/Editor/Mono/EditorApplication.bindings.cs index 6000f43af0..09c49de7e3 100644 --- a/Editor/Mono/EditorApplication.bindings.cs +++ b/Editor/Mono/EditorApplication.bindings.cs @@ -20,6 +20,7 @@ namespace UnityEditor [NativeHeader("Runtime/Camera/RenderSettings.h")] [NativeHeader("Runtime/Input/TimeManager.h")] [NativeHeader("Editor/Src/ProjectVersion.h")] + [NativeHeader("Runtime/Misc/BuildSettings.h")] [StaticAccessor("EditorApplicationBindings", StaticAccessorType.DoubleColon)] public sealed partial class EditorApplication { @@ -357,7 +358,24 @@ internal static void RestartEditorAndRecompileScripts() [StaticAccessor("GetApplication()", StaticAccessorType.Dot)] internal static extern void UpdateInteractionModeSettings(); + internal static extern void UpdateTooltipsInPlayModeSettings(); + [FreeFunction("GetProjectVersion().Write")] internal static extern void WriteVersion(); + + internal static extern GUID buildSessionGUID + { + [FreeFunction("GetBuildSettings().GetBuildSessionGUID")] + get; + } + + public static extern bool isFocused + { + [StaticAccessor("GetApplication()", StaticAccessorType.Dot)] + get; + + [StaticAccessor("GetApplication()", StaticAccessorType.Dot)] + private set; + } } } diff --git a/Editor/Mono/EditorApplication.cs b/Editor/Mono/EditorApplication.cs index d4ddb60ef4..05bd9a06e3 100644 --- a/Editor/Mono/EditorApplication.cs +++ b/Editor/Mono/EditorApplication.cs @@ -32,7 +32,7 @@ public enum PlayModeStateChange internal enum ScriptChangesDuringPlayOptions { RecompileAndContinuePlaying = 0, - // RecompileAfterFinishedPlaying = 1, Deprecated feature + RecompileAfterFinishedPlaying = 1, StopPlayingAndRecompile = 2 } @@ -244,6 +244,8 @@ public static event Action projectChanged internal static CallbackFunction assetBundleNameChanged; + internal static CallbackFunction fileMenuSaved; + // Delegate for changed keyboard modifier keys. public static CallbackFunction modifierKeysChanged; @@ -264,13 +266,13 @@ public static event Action playModeStateChanged [Obsolete("Use EditorApplication.playModeStateChanged and/or EditorApplication.pauseStateChanged")] public static CallbackFunction playmodeStateChanged; - // Global key up/down event that was not handled by anyone + // Global key up/down or mouse up/down/drag events that were not handled by anyone internal static CallbackFunction globalEventHandler; // Returns true when the pressed keys are defined in the Trigger internal static Func doPressedKeysTriggerAnyShortcut; - internal static event Action focusChanged; + public static event Action focusChanged; // Windows were reordered internal static CallbackFunction windowsReordered; @@ -340,19 +342,38 @@ internal static ApplicationTitleDescriptor GetApplicationTitleDescriptor() activeSceneName = Path.GetFileNameWithoutExtension(SceneManager.GetActiveScene().path); } - var desc = new ApplicationTitleDescriptor( - isTemporaryProject ? PlayerSettings.productName : Path.GetFileName(Path.GetDirectoryName(Application.dataPath)), - InternalEditorUtility.GetUnityDisplayVersion(), - activeSceneName, - BuildPipeline.GetBuildTargetGroupDisplayName(BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget)), - Coverage.enabled - ); + ApplicationTitleDescriptor desc; + if (PreferencesProvider.useProjectPathInTitle) + { + desc = new ApplicationTitleDescriptor( + Path.GetFullPath(Path.Combine(Application.dataPath, "..")), + (Unsupported.IsSourceBuild() || Unsupported.IsDeveloperMode()) ? InternalEditorUtility.GetUnityDisplayVersion() : Application.unityVersion, + activeSceneName, + BuildPipeline.GetBuildTargetGroupDisplayName(BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget)), + Coverage.enabled + ); + } + else + { + desc = new ApplicationTitleDescriptor( + GetDefaultProjectName(), + (Unsupported.IsSourceBuild() || Unsupported.IsDeveloperMode()) ? InternalEditorUtility.GetUnityDisplayVersion() : Application.unityVersion, + activeSceneName, + BuildPipeline.GetBuildTargetGroupDisplayName(BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget)), + Coverage.enabled + ); + } desc.title = GetDefaultMainWindowTitle(desc); return desc; } + internal static string GetDefaultProjectName() + { + return isTemporaryProject ? PlayerSettings.productName : Path.GetFileName(Path.GetDirectoryName(Application.dataPath)); + } + [RequiredByNativeCode] internal static void Internal_CallUpdateFunctions() { @@ -445,6 +466,12 @@ static void Internal_PlayModeStateChanged(PlayModeStateChange state) evt(state); } + [RequiredByNativeCode] + internal static void Internal_FileMenuSaved() + { + fileMenuSaved?.Invoke(); + } + static void Internal_CallKeyboardModifiersChanged() { modifierKeysChanged?.Invoke(); @@ -475,9 +502,10 @@ static void Internal_CallGlobalEventHandler() } [RequiredByNativeCode] - static void Internal_FocusChanged(bool isFocused) + static void Internal_FocusChanged(bool hasFocus) { - focusChanged?.Invoke(isFocused); + isFocused = hasFocus; + focusChanged?.Invoke(hasFocus); } [MenuItem("File/New Scene %n", priority = 150)] diff --git a/Editor/Mono/EditorAssemblies.bindings.cs b/Editor/Mono/EditorAssemblies.bindings.cs index bb20ba13e6..c3d63b2409 100644 --- a/Editor/Mono/EditorAssemblies.bindings.cs +++ b/Editor/Mono/EditorAssemblies.bindings.cs @@ -10,7 +10,7 @@ namespace UnityEditor { - [NativeHeader("Runtime/Mono/MonoAttributeHelpers.h")] + [NativeHeader("Runtime/Scripting/TypeCache.h")] static partial class EditorAssemblies { const BindingFlags k_DefaultMethodBindingFlags = @@ -28,30 +28,13 @@ internal static IEnumerable GetAllMethodsWithAttribute(BindingFla [FreeFunction(Name = "GetAllMethodsWithAttribute")] extern static object[] Internal_GetAllMethodsWithAttribute(Type attrType, BindingFlags staticness); - [Obsolete("Use public TypeCache.GetTypesWithAttribute<> API instead.")] - internal static IEnumerable GetAllTypesWithAttribute() where T : Attribute - { - return Internal_GetAllTypesWithAttribute(typeof(T)); - } + [FreeFunction("GetUnchangedAssemblyNames")] + internal static extern string[] GetUnchangedAssemblyNames(); - [FreeFunction(Name = "GetAllTypesWithAttribute")] - extern static Type[] Internal_GetAllTypesWithAttribute(Type attrType); - - [Obsolete("Use public TypeCache.GetTypesDerivedFrom<> API instead.")] - internal static IEnumerable GetAllTypesWithInterface() where T : class + internal static extern bool AllAssembliesAreUnchanged { - return GetAllTypesWithInterface(typeof(T)); + [FreeFunction("AllAssembliesAreUnchanged")] + get; } - - [Obsolete("Use public TypeCache.GetTypesDerivedFrom<> API instead.")] - private static IEnumerable GetAllTypesWithInterface(Type interfaceType) - { - if (!interfaceType.IsInterface) - throw new ArgumentException(string.Format("Specified type {0} is not an interface.", interfaceType), nameof(interfaceType)); - return Internal_GetAllTypesWithInterface(interfaceType); - } - - [FreeFunction(Name = "GetAllTypesWithInterface")] - extern static Type[] Internal_GetAllTypesWithInterface(Type interfaceType); } } diff --git a/Editor/Mono/EditorAssemblies.cs b/Editor/Mono/EditorAssemblies.cs index 5252744b34..4221d4ccae 100644 --- a/Editor/Mono/EditorAssemblies.cs +++ b/Editor/Mono/EditorAssemblies.cs @@ -70,32 +70,6 @@ static internal IEnumerable loadedTypes get { return loadedAssemblies.SelectMany(assembly => AssemblyHelper.GetTypesFromAssembly(assembly)); } } - [Obsolete("Use public TypeCache.GetTypesDerivedFrom<> API instead.")] - static internal IEnumerable SubclassesOf(Type parent) - { - return parent.IsInterface ? - GetAllTypesWithInterface(parent) : - SubclassesOfClass(parent); - } - - [Obsolete("Use public TypeCache.GetTypesDerivedFrom<> API instead.")] - static internal IEnumerable SubclassesOfClass(Type parent) - { - Type[] types; - if (!m_subClasses.TryGetValue(parent, out types)) - { - types = loadedTypes.Where(klass => klass.IsSubclassOf(parent)).ToArray(); - m_subClasses[parent] = types; - } - return types; - } - - [Obsolete("Use public TypeCache.GetTypesDerivedFrom<> API instead.")] - static internal IEnumerable SubclassesOfGenericType(Type genericType) - { - return loadedTypes.Where(klass => IsSubclassOfGenericType(klass, genericType)); - } - private static bool IsSubclassOfGenericType(Type klass, Type genericType) { if (klass.IsGenericType && klass.GetGenericTypeDefinition() == genericType) @@ -148,6 +122,10 @@ private static void ProcessInitializeOnLoadAttributes(Type[] types) { RuntimeHelpers.RunClassConstructor(type.TypeHandle); } + catch (TypeLoadException x) + { + Debug.LogError(x.InnerException); + } catch (TypeInitializationException x) { Debug.LogError(x.InnerException); @@ -160,7 +138,8 @@ private static void ProcessInitializeOnLoadAttributes(Type[] types) private static void ProcessInitializeOnLoadMethodAttributes() { bool reportTimes = (bool)Debug.GetDiagnosticSwitch("EnableDomainReloadTimings").value; - foreach (var method in TypeCache.GetMethodsWithAttribute()) + var methods = TypeCache.GetMethodsWithAttribute(); + foreach (var method in methods) { using (new EditorPerformanceMarker($"InitializeOnLoad {method.DeclaringType?.Name}.{method.Name}", method.DeclaringType).Auto()) using (_profilerMarkerProcessInitializeOnLoadMethodAttributes.Auto(reportTimes, () => $"{method.DeclaringType?.FullName}::{method.Name}")) diff --git a/Editor/Mono/EditorBuildSettings.bindings.cs b/Editor/Mono/EditorBuildSettings.bindings.cs index 4913631261..e9d00fafcc 100644 --- a/Editor/Mono/EditorBuildSettings.bindings.cs +++ b/Editor/Mono/EditorBuildSettings.bindings.cs @@ -45,7 +45,7 @@ public EditorBuildSettingsScene(GUID guid, bool enabled) public GUID guid { get { return m_guid; } set { m_guid = value; } } public static string[] GetActiveSceneList(EditorBuildSettingsScene[] scenes) { - return scenes.Where(scene => scene.enabled).Select(scene => scene.path).ToArray(); + return scenes.Where(scene => scene.enabled && !string.IsNullOrEmpty(scene.path)).Select(scene => scene.path).ToArray(); } public int CompareTo(object obj) @@ -74,27 +74,10 @@ private static void SceneListChanged() public static EditorBuildSettingsScene[] scenes { - get - { - var result = GetEditorBuildSettingsScenes(); - foreach (var scene in result) - { - if (scene.guid.Empty()) - { - scene.guid = new GUID(AssetDatabase.AssetPathToGUID(scene.path)); - } - else - { - scene.path = AssetDatabase.GUIDToAssetPath(scene.guid.ToString()); - } - } - return result; - } - set - { - SetEditorBuildSettingsScenes(value); - } + get => GetEditorBuildSettingsScenes(); + set => SetEditorBuildSettingsScenes(value); } + static extern EditorBuildSettingsScene[] GetEditorBuildSettingsScenes(); static extern void SetEditorBuildSettingsScenes(EditorBuildSettingsScene[] scenes); diff --git a/Editor/Mono/EditorGUI.cs b/Editor/Mono/EditorGUI.cs index 093ca2cbdd..d2cfec4198 100644 --- a/Editor/Mono/EditorGUI.cs +++ b/Editor/Mono/EditorGUI.cs @@ -15,7 +15,6 @@ using UnityEditor.Scripting.ScriptCompilation; using Object = UnityEngine.Object; using Event = UnityEngine.Event; -using UnityEditor.Build; using UnityEditor.StyleSheets; using UnityEditor.VersionControl; using UnityEngine.Internal; @@ -25,6 +24,7 @@ using System.Reflection; using Unity.Profiling; using UnityEngine.Experimental.Rendering; +using UnityEngine.UIElements; namespace UnityEditor { @@ -110,6 +110,10 @@ private enum DragCandidateState internal const float kObjectFieldThumbnailHeight = 64; internal const float kObjectFieldMiniThumbnailHeight = 18f; internal const float kObjectFieldMiniThumbnailWidth = 32f; + internal const float kLabelWidthRatio = 0.45f; + internal const float kLabelWidthPadding = 3f; + internal const float kLabelWidthMargin = 40f; + internal const float kMinLabelWidth = 120f; internal static string kFloatFieldFormatString = UINumericFieldsUtils.k_FloatFieldFormatString; internal static string kDoubleFieldFormatString = UINumericFieldsUtils.k_DoubleFieldFormatString; internal static string kIntFieldFormatString = UINumericFieldsUtils.k_IntFieldFormatString; @@ -151,7 +155,6 @@ private enum DragCandidateState private static readonly GUIContent s_PositionLabel = EditorGUIUtility.TrTextContent("Position"); private static readonly GUIContent s_SizeLabel = EditorGUIUtility.TrTextContent("Size"); internal static GUIContent s_PleasePressAKey = EditorGUIUtility.TrTextContent("[Please press a key]"); - private static string s_PrefabInContextPreviewValuesTooltip = L10n.Tr("This property is previewing the overridden value on the Prefab instance.\n\nTo edit this property, open this Prefab Asset in isolation by pressing the modifier key [Alt] while you open it."); internal static readonly GUIContent s_ClipingPlanesLabel = EditorGUIUtility.TrTextContent("Clipping Planes", "The distances from the Camera where rendering starts and stops."); internal static readonly GUIContent[] s_NearAndFarLabels = { EditorGUIUtility.TrTextContent("Near", "The closest point to the Camera where drawing occurs."), EditorGUIUtility.TrTextContent("Far", "The furthest point from the Camera that drawing occurs.") }; @@ -479,6 +482,7 @@ public static bool EndChangeCheck() internal class RecycledTextEditor : TextEditor { internal static bool s_ActuallyEditing = false; // internal so we can save this state. + internal static bool s_EditingWasCompleted = false; // internal so we can save this state. internal static bool s_AllowContextCutOrPaste = true; // e.g. selectable labels only allow for copying private long[] s_OriginalLongValues; private double[] s_OriginalDoubleValues; @@ -556,6 +560,7 @@ public virtual void EndEditing() controlID = 0; s_ActuallyEditing = false; + s_EditingWasCompleted = false; s_AllowContextCutOrPaste = true; UnityEditor.Undo.IncrementCurrentGroup(); @@ -853,7 +858,9 @@ internal static bool MightBePrintableKey(Event evt) return false; if (evt.keyCode >= KeyCode.JoystickButton0 && evt.keyCode <= KeyCode.Joystick8Button19) return false; - if (evt.keyCode >= KeyCode.F1 && evt.keyCode <= KeyCode.F15) + if (evt.keyCode >= KeyCode.F1 && evt.keyCode <= KeyCode.F15 || + // KeyCode.F15 (296) and KeyCode.F16 (670) are not contiguous + evt.keyCode >= KeyCode.F16 && evt.keyCode <= KeyCode.F24) return false; switch (evt.keyCode) { @@ -985,8 +992,8 @@ internal static string DoTextField(RecycledTextEditor editor, int id, Rect posit } } - // Inform editor that someone removed focus from us. - if (editor.controlID == id && GUIUtility.keyboardControl != id || (evt.type == EventType.ValidateCommand && evt.commandName == EventCommandNames.UndoRedoPerformed)) + // Inform editor that someone removed focus from us or a rename operation was completed. + if (editor.controlID == id && GUIUtility.keyboardControl != id || EditorGUIUtility.renameWasCompleted || (evt.type == EventType.ValidateCommand && evt.commandName == EventCommandNames.UndoRedoPerformed)) { editor.EndEditing(); } @@ -1220,7 +1227,7 @@ internal static string DoTextField(RecycledTextEditor editor, int id, Rect posit { if (editor.IsEditingControl(id)) { - if (style == EditorStyles.toolbarSearchField || style == EditorStyles.searchField) + if (style == EditorStyles.toolbarSearchField || style == EditorStyles.searchField || style.name.Contains("SearchText")) { s_OriginalText = ""; } @@ -2413,7 +2420,7 @@ internal static void GetInitialValue(ref NumberFieldValue value) if (value.isDouble) { double oldValue = default; - if (UINumericFieldsUtils.StringToDouble(s_OriginalText, out oldValue) && oldValue != value.doubleVal) + if (UINumericFieldsUtils.TryConvertStringToDouble(s_OriginalText, out oldValue) && oldValue != value.doubleVal) { value.doubleVal = oldValue; return; @@ -2422,7 +2429,7 @@ internal static void GetInitialValue(ref NumberFieldValue value) else { long oldValue = default; - if (UINumericFieldsUtils.StringToLong(s_OriginalText, out oldValue) && oldValue != value.longVal) + if (UINumericFieldsUtils.TryConvertStringToLong(s_OriginalText, out oldValue) && oldValue != value.longVal) { value.longVal = oldValue; return; @@ -2500,7 +2507,7 @@ internal static bool StringToDouble(string str, out double value) static void StringToDouble(string str, ref NumberFieldValue value) { - value.success = UINumericFieldsUtils.StringToDouble(str, out value.doubleVal, out value.expression); + value.success = UINumericFieldsUtils.TryConvertStringToDouble(str, out value.doubleVal, out value.expression); } internal static bool StringToLong(string str, out long value) @@ -2514,7 +2521,7 @@ internal static bool StringToLong(string str, out long value) static void StringToLong(string str, ref NumberFieldValue value) { value.expression = null; - value.success = UINumericFieldsUtils.StringToLong(str, out value.longVal, out value.expression); + value.success = UINumericFieldsUtils.TryConvertStringToLong(str, out value.longVal, out value.expression); } internal static int ArraySizeField(Rect position, GUIContent label, int value, GUIStyle style) @@ -2925,7 +2932,7 @@ private static float PowPreserveSign(float f, float p) return f < 0.0f ? -result : result; } - internal static GenericMenu FillPropertyContextMenu(SerializedProperty property, SerializedProperty linkedProperty = null, GenericMenu menu = null) + internal static GenericMenu FillPropertyContextMenu(SerializedProperty property, SerializedProperty linkedProperty = null, GenericMenu menu = null, VisualElement element = null) { if (property == null) return null; @@ -2972,11 +2979,10 @@ internal static GenericMenu FillPropertyContextMenu(SerializedProperty property, Object targetObject = property.serializedObject.targetObject; - var shouldDisplayPrefabContextMenuItems = property.prefabOverride || (linkedProperty?.prefabOverride ?? false); + bool shouldDisplayPrefabContextMenuItems = property.prefabOverride || (linkedProperty?.prefabOverride ?? false); // Only display the custom apply/revert menu for GameObjects/Components that are not part of a Prefab instance & variant. - var shouldDisplayApplyRevertProviderContextMenuItems = targetObject is IApplyRevertPropertyContextMenuItemProvider - && PrefabUtility.HasApplicableObjectOverrides(targetObject, false); + var shouldDisplayApplyRevertProviderContextMenuItems = targetObject is IApplyRevertPropertyContextMenuItemProvider; if (shouldDisplayPrefabContextMenuItems || shouldDisplayApplyRevertProviderContextMenuItems) { @@ -3019,7 +3025,9 @@ internal static GenericMenu FillPropertyContextMenu(SerializedProperty property, info.properties = properties; info.assetPath = AssetDatabase.GetAssetPath(sourceObject); GameObject rootObject = PrefabUtility.GetRootGameObject(sourceObject); - if (!PrefabUtility.IsPartOfPrefabThatCanBeAppliedTo(rootObject) || !PrefabUtility.CanPropertyBeAppliedToTarget(property, rootObject)) + if (EditorUtility.IsPersistent(targetObject) + || !PrefabUtility.IsPartOfPrefabThatCanBeAppliedTo(rootObject) + || !PrefabUtility.CanPropertyBeAppliedToTarget(property, rootObject)) pm.AddDisabledItem(menuItemContent); else pm.AddItem(menuItemContent, false, TargetChoiceHandler.ApplyPrefabPropertyOverride, info); @@ -3074,6 +3082,7 @@ internal static GenericMenu FillPropertyContextMenu(SerializedProperty property, pm.AddItem(EditorGUIUtility.TrTextContent("Duplicate Array Element"), false, (a) => { var list = ReorderableList.GetReorderableListFromSerializedProperty(parentArrayProperty); + var listView = element?.GetFirstAncestorOfType(); // If we have a ReorderableList associated with this property lets use list selection array // and apply this action to all selected elements thus having better integration with @@ -3101,6 +3110,10 @@ internal static GenericMenu FillPropertyContextMenu(SerializedProperty property, ReorderableList.InvalidateExistingListCaches(); } + else if (listView != null && listView.selectedIndices.Any()) + { + DuplicateListViewItems(listView, parentArrayProperty); + } else // Non reorderable { if (parentArrayIndex >= 0 && parentArrayIndex < parentArrayProperty.arraySize) @@ -3127,6 +3140,7 @@ internal static GenericMenu FillPropertyContextMenu(SerializedProperty property, pm.AddItem(EditorGUIUtility.TrTextContent("Delete Array Element"), false, (a) => { var list = ReorderableList.GetReorderableListFromSerializedProperty(parentArrayProperty); + var listView = element?.GetFirstAncestorOfType(); // If we have a ReorderableList associated with this property lets use list selection array // and apply this action to all selected elements thus having better integration with @@ -3150,6 +3164,10 @@ internal static GenericMenu FillPropertyContextMenu(SerializedProperty property, list.onChangedCallback(list); ReorderableList.InvalidateExistingListCaches(); } + else if (listView != null && listView.selectedIndices.Any()) + { + DeleteListViewItems(listView, parentArrayProperty); + } else // Non reorderable { if (parentArrayIndex >= 0 && parentArrayIndex < parentArrayProperty.arraySize) @@ -3223,6 +3241,61 @@ internal static GenericMenu FillPropertyContextMenu(SerializedProperty property, return pm; } + internal static void DeleteListViewItems(ListView listView, SerializedProperty parentArrayProperty) + { + var previousSelectedIndices = new List(listView.selectedIndices); + previousSelectedIndices.Sort(); + listView.ClearSelection(); + + for (int i = previousSelectedIndices.Count - 1; i >= 0; i--) + { + var index = previousSelectedIndices.ElementAt(i); + if (index >= listView.itemsSource.Count) continue; + + SerializedProperty resolvedProperty = parentArrayProperty.GetArrayElementAtIndex(index); + if (resolvedProperty != null) + { + if (!TargetChoiceHandler.DeleteArrayElement(resolvedProperty)) continue; + } + } + } + + internal static void DuplicateListViewItems(ListView listView, SerializedProperty parentArrayProperty) + { + var previousSelectedIndices = new List(listView.selectedIndices); + previousSelectedIndices.Sort(); + var newSelectedIndices = new List(); + listView.ClearSelection(); + + for (int i = previousSelectedIndices.Count - 1; i >= 0; i--) + { + var index = previousSelectedIndices.ElementAt(i); + if (index >= listView.itemsSource.Count) continue; + + SerializedProperty resolvedProperty = parentArrayProperty.GetArrayElementAtIndex(index); + if (resolvedProperty != null) + { + if (!TargetChoiceHandler.DuplicateArrayElement(resolvedProperty)) continue; + } + + // need to update the rest of the selected indices as an element was added + for (int j = i + 1; j < previousSelectedIndices.Count; j++) + { + previousSelectedIndices[j]++; + } + + for (int j = 0; j < newSelectedIndices.Count; j++) + { + newSelectedIndices[j]++; + } + + newSelectedIndices.Add(previousSelectedIndices[i] + 1); + } + + newSelectedIndices.Sort(); + listView.SetSelection(newSelectedIndices); + } + internal static void DoPropertyContextMenu(SerializedProperty property, SerializedProperty linkedProperty = null, GenericMenu menu = null) { GenericMenu pm = FillPropertyContextMenu(property, linkedProperty, menu); @@ -3644,7 +3717,7 @@ private static Enum EnumPopupInternal(Rect position, GUIContent label, Enum sele } var enumData = EnumDataUtility.GetCachedEnumData(enumType, !includeObsolete); - var i = Array.IndexOf(enumData.values, selected); + var i = showMixedValue ? -1 : Array.IndexOf(enumData.values, selected); var options = EnumNamesCache.GetEnumTypeLocalizedGUIContents(enumType, enumData); s_CurrentCheckEnumEnabled = checkEnabled; @@ -3662,7 +3735,7 @@ private static int EnumPopupInternal(Rect position, GUIContent label, int flagVa } var enumData = EnumDataUtility.GetCachedEnumData(enumType, !includeObsolete); - var i = Array.IndexOf(enumData.flagValues, flagValue); + var i = showMixedValue ? -1 : Array.IndexOf(enumData.flagValues, flagValue); var options = EnumNamesCache.GetEnumTypeLocalizedGUIContents(enumType, enumData); s_CurrentCheckEnumEnabled = checkEnabled; @@ -3676,7 +3749,11 @@ private static int IntPopupInternal(Rect position, GUIContent label, int selecte { // value --> index int i; - if (optionValues != null) + if (showMixedValue) + { + i = -1; + } + else if (optionValues != null) { for (i = 0; (i < optionValues.Length) && (selectedValue != optionValues[i]); ++i) { @@ -3712,9 +3789,7 @@ internal static void IntPopupInternal(Rect position, SerializedProperty property BeginChangeCheck(); int newValue = IntPopupInternal(position, label, property.intValue, displayedOptions, optionValues, EditorStyles.popup); if (EndChangeCheck()) - { property.intValue = newValue; - } EndProperty(); } @@ -3809,7 +3884,7 @@ public static int GetSelectedValueForControl(int controlID, int selected) } if (instance.m_ControlID == controlID) { - GUI.changed = selected != instance.m_SelectedIndex; + GUI.changed = showMixedValue || selected != instance.m_SelectedIndex; selected = instance.m_SelectedIndex; instance = null; evt.Use(); @@ -3848,7 +3923,8 @@ internal static int DoPopup(Rect position, int controlID, int selected, GUIConte } else { - buttonContent = popupValues[selected]; + buttonContent = new GUIContent(popupValues[selected]); + buttonContent.text = EditorUtility.ParseMenuName(buttonContent.text); } Event evt = Event.current; @@ -3877,7 +3953,7 @@ internal static int DoPopup(Rect position, int controlID, int selected, GUIConte } PopupCallbackInfo.instance = new PopupCallbackInfo(controlID); - EditorUtility.DisplayCustomMenu(position, popupValues, checkEnabled, showMixedValue ? -1 : selected, PopupCallbackInfo.instance.SetEnumValueDelegate, null); + EditorUtility.DisplayCustomMenu(position, popupValues, checkEnabled, showMixedValue ? -1 : selected, PopupCallbackInfo.instance.SetEnumValueDelegate, null, true); GUIUtility.keyboardControl = controlID; evt.Use(); } @@ -4118,9 +4194,7 @@ public static Enum EnumFlagsField(Rect position, GUIContent label, Enum enumValu public static Enum EnumFlagsField(Rect position, GUIContent label, Enum enumValue, [DefaultValue("false")] bool includeObsolete, [DefaultValue("null")] GUIStyle style = null) { - int changedFlags; - bool changedToValue; - return EnumFlagsField(position, label, enumValue, includeObsolete, out changedFlags, out changedToValue, style ?? EditorStyles.popup); + return EnumFlagsField(position, label, enumValue, includeObsolete, out _, out _, style ?? EditorStyles.popup); } // Internal version that also gives you back which flags were changed and what they were changed to. @@ -4514,11 +4588,19 @@ internal static Vector3 LinkedVector3Field(Rect position, GUIContent label, GUI GUIContent copy = label; Rect fullLabelRect = position; - if(proportionalScaleProperty != null) + BeginChangeCheck(); + + if (proportionalScaleProperty != null) + { BeginPropertyInternal(fullLabelRect, label, proportionalScaleProperty); + } + var scalePropertyId = -1; if (property != null) + { label = BeginPropertyInternal(position, label, property); + scalePropertyId = GUIUtility.keyboardControl; + } SerializedProperty copiedProperty = property == null ? property : property.Copy(); var toggle = EditorStyles.toggle.CalcSize(GUIContent.none); @@ -4547,13 +4629,26 @@ internal static Vector3 LinkedVector3Field(Rect position, GUIContent label, GUI position.width -= toggle.x + kDefaultSpacing; position.height = kSingleLineHeight; - var newValue = LinkedVector3Field(position, value, proportionalScale, mixedValues, initialScale, ref axisModified, copiedProperty); + if (proportionalScaleProperty != null) + { + EndProperty(); + } if (property != null) - EndProperty(); + { + // Note: due to how both the scale + constrainScale property drawn and handled in a custom fashion, the lastcontrolId never correspond + // to the scaleProperty. Also s_PendingPropertyKeyboardHandling is nullifed by the constrainScale property. + // Make it work for now but I feel this whole system is super brittle. + // This will be hopefully fixed up when we use uitk to create these editors. - if(proportionalScaleProperty != null) + var lastId = EditorGUIUtility.s_LastControlID; + EditorGUIUtility.s_LastControlID = scalePropertyId; + s_PendingPropertyKeyboardHandling = property; EndProperty(); + EditorGUIUtility.s_LastControlID = lastId; + } + + var newValue = LinkedVector3Field(position, value, proportionalScale, mixedValues, initialScale, ref axisModified, copiedProperty); return newValue; } @@ -4572,7 +4667,6 @@ static Vector3 LinkedVector3Field(Rect position, Vector3 value, bool proportiona s_Vector3Floats[1] = value.y; s_Vector3Floats[2] = value.z; position.height = kSingleLineHeight; - BeginChangeCheck(); LockingMultiFloatFieldInternal(position, proportionalScale, mixedValues, s_XYZLabels, s_Vector3Floats, new float[] {initialScale.x, initialScale.y, initialScale.z}, property, EditorGUI.CalcPrefixLabelWidth(s_XYZLabels[0]) + 3); if (EndChangeCheck()) { @@ -5963,7 +6057,7 @@ internal static void DoInspectorTitlebar(Rect position, int id, bool foldout, Ob } break; case EventType.Repaint: - textStyle.Draw(textRect, EditorGUIUtility.TempContent(ObjectNames.GetInspectorTitle(targetObjs[0])), hovered, pressed, foldout, hasFocus); + textStyle.Draw(textRect, EditorGUIUtility.TempContent(ObjectNames.GetInspectorTitle(targetObjs[0], targetObjs.Length > 1)), hovered, pressed, foldout, hasFocus); if (EditorGUIUtility.comparisonViewMode == EditorGUIUtility.ComparisonViewMode.None) { EditorStyles.optionsButtonStyle.Draw(settingsRect, GUIContent.none, id, foldout, settingsRect.Contains(Event.current.mousePosition)); @@ -6197,7 +6291,7 @@ internal static bool HelpIconButton(Rect position, Object[] objs) } else { - content.tooltip = string.Format("Open Reference for {0}.", helpTopic); + content.tooltip = string.Format("Open Reference for {0}.", ObjectNames.NicifyVariableName(helpTopic)); } } @@ -6286,7 +6380,7 @@ internal static bool FoldoutInternal(Rect position, bool foldout, GUIContent con // in the Inspector has different values. Don't show it when expanded, since the difference will be visible further down. if (showMixedValue && !foldout) { - style.Draw(drawRect, content, id, false); + style.Draw(drawRect, content, id, false, false); BeginHandleMixedValueContentColor(); Rect fieldPosition = origPosition; @@ -6296,7 +6390,7 @@ internal static bool FoldoutInternal(Rect position, bool foldout, GUIContent con } else { - style.Draw(drawRect, content, id, foldout); + style.Draw(drawRect, content, id, foldout, false); } break; case EventType.KeyDown: @@ -6738,28 +6832,11 @@ internal static GUIContent BeginPropertyInternal(Rect totalPosition, GUIContent animatedColor.a *= GUI.backgroundColor.a; GUI.backgroundColor = animatedColor; } - else + else if (PrefabUtility.IsPropertyBeingDrivenByPrefabStage(property)) { - Object target = property.serializedObject.targetObject; - GameObject go = PrefabUtility.GetGameObject(target); - if (go != null && go.scene.IsValid() && EditorSceneManager.IsPreviewScene(go.scene)) - { - var prefabStage = SceneManagement.PrefabStageUtility.GetCurrentPrefabStage(); - if (prefabStage != null && prefabStage.mode == SceneManagement.PrefabStage.Mode.InContext) - { - var propertyPath = property.propertyPath; - ScriptableObject driver = prefabStage; - if ( - (DrivenPropertyManagerInternal.IsDriving(driver, target, propertyPath)) - || - ((target is Transform || target is ParticleSystem || property.propertyType == SerializedPropertyType.Color) && DrivenPropertyManagerInternal.IsDrivingPartial(driver, target, propertyPath))) - { - GUI.enabled = false; - if (isCollectingTooltips) - s_PropertyFieldTempContent.tooltip = s_PrefabInContextPreviewValuesTooltip; - } - } - } + GUI.enabled = false; + if (isCollectingTooltips) + s_PropertyFieldTempContent.tooltip = PrefabStage.s_PrefabInContextPreviewValuesTooltip; } GUI.enabled &= property.editable; @@ -6908,6 +6985,8 @@ private static void DoPropertyFieldKeyboardHandling(SerializedProperty property) // Copy & Paste if (evt.commandName == EventCommandNames.Copy || evt.commandName == EventCommandNames.Paste) { + if (evt.commandName == EventCommandNames.Paste) + GUI.changed = true; ClipboardContextMenu.SetupPropertyCopyPaste(property, menu: null, evt: evt); } } @@ -7342,6 +7421,7 @@ internal static bool DefaultPropertyField(Rect position, SerializedProperty prop // Should we inline? All one-line vars as well as Vector2, Vector3, Rect and Bounds properties are inlined. if (!HasVisibleChildFields(property)) { + bool canUseExpression = ConstrainProportionsTransformScale.CanUseMathExpressions(property); switch (type) { case SerializedPropertyType.Integer: @@ -7361,7 +7441,8 @@ internal static bool DefaultPropertyField(Rect position, SerializedProperty prop for (var i = 0; i < values.Length; ++i) { values[i] = originalValues[i]; - val.expression.Evaluate(ref values[i], i, values.Length); + if(canUseExpression) + val.expression.Evaluate(ref values[i], i, values.Length); } property.allLongValues = values; } @@ -7398,7 +7479,7 @@ internal static bool DefaultPropertyField(Rect position, SerializedProperty prop for (var i = 0; i < values.Length; ++i) { values[i] = originalValues[i]; - if (val.expression.Evaluate(ref values[i], i, values.Length)) + if (canUseExpression && val.expression.Evaluate(ref values[i], i, values.Length)) { if (isFloat) values[i] = MathUtils.ClampToFloat(values[i]); @@ -7503,7 +7584,7 @@ internal static bool DefaultPropertyField(Rect position, SerializedProperty prop bool toggled = DropdownButton(position, toggleLabelContent, FocusType.Keyboard, EditorStyles.layerMaskField); if (toggled) { - PopupWindowWithoutFocus.Show(position, new MaskFieldDropDown(property)); + PopupWindow.Show(position, new MaskFieldDropDown(property)); GUIUtility.ExitGUI(); } break; @@ -8533,7 +8614,7 @@ private static void EnumPopup(Rect position, SerializedProperty property, GUICon else { BeginChangeCheck(); - int idx = Popup(position, label, property.hasMultipleDifferentValues ? -1 : property.enumValueIndex, EnumNamesCache.GetEnumLocalizedGUIContents(property)); + int idx = Popup(position, label, property.enumValueIndex, EnumNamesCache.GetEnumLocalizedGUIContents(property)); if (EndChangeCheck()) { property.enumValueIndex = idx; @@ -8961,2517 +9042,4 @@ internal static bool IsObjectPartOfTargetAssemblies(Object obj) } } } - - // Auto-layouted version of [[EditorGUI]] - sealed partial class EditorGUILayout - { - // @TODO: Make private (and rename to not claim it's a constant). Shouldn't really be used outside of EditorGUI. - // Places that use this directly should likely use GetControlRect instead. - internal static float kLabelFloatMinW => EditorGUIUtility.labelWidth + EditorGUIUtility.fieldWidth + EditorGUI.kSpacing; - - internal static float kLabelFloatMaxW => EditorGUIUtility.labelWidth + EditorGUIUtility.fieldWidth + EditorGUI.kSpacing; - - internal static Rect s_LastRect; - - internal const float kPlatformTabWidth = 30; - - internal static SavedBool s_SelectedDefault = new SavedBool("Platform.ShownDefaultTab", true); - - static GUIStyle s_TabOnlyOne; - static GUIStyle s_TabFirst; - static GUIStyle s_TabMiddle; - static GUIStyle s_TabLast; - - [ExcludeFromDocs] - public static bool Foldout(bool foldout, string content) - { - return Foldout(foldout, content, EditorStyles.foldout); - } - - public static bool Foldout(bool foldout, string content, [DefaultValue("EditorStyles.foldout")] GUIStyle style) - { - return Foldout(foldout, EditorGUIUtility.TempContent(content), false, style); - } - - [ExcludeFromDocs] - public static bool Foldout(bool foldout, GUIContent content) - { - return Foldout(foldout, content, EditorStyles.foldout); - } - - public static bool Foldout(bool foldout, GUIContent content, [DefaultValue("EditorStyles.foldout")] GUIStyle style) - { - return Foldout(foldout, content, false, style); - } - - [ExcludeFromDocs] - public static bool Foldout(bool foldout, string content, bool toggleOnLabelClick) - { - return Foldout(foldout, content, toggleOnLabelClick, EditorStyles.foldout); - } - - public static bool Foldout(bool foldout, string content, bool toggleOnLabelClick, [DefaultValue("EditorStyles.foldout")] GUIStyle style) - { - return Foldout(foldout, EditorGUIUtility.TempContent(content), toggleOnLabelClick, style); - } - - [ExcludeFromDocs] - public static bool Foldout(bool foldout, GUIContent content, bool toggleOnLabelClick) - { - return Foldout(foldout, content, toggleOnLabelClick, EditorStyles.foldout); - } - - public static bool Foldout(bool foldout, GUIContent content, bool toggleOnLabelClick, [DefaultValue("EditorStyles.foldout")] GUIStyle style) - { - return FoldoutInternal(foldout, content, toggleOnLabelClick, style); - } - - [ExcludeFromDocs] - public static void PrefixLabel(string label) - { - GUIStyle followingStyle = "Button"; - PrefixLabel(label, followingStyle); - } - - public static void PrefixLabel(string label, [DefaultValue("\"Button\"")] GUIStyle followingStyle) - { - PrefixLabel(EditorGUIUtility.TempContent(label), followingStyle, EditorStyles.label); - } - - public static void PrefixLabel(string label, GUIStyle followingStyle, GUIStyle labelStyle) - { - PrefixLabel(EditorGUIUtility.TempContent(label), followingStyle, labelStyle); - } - - [ExcludeFromDocs] - public static void PrefixLabel(GUIContent label) - { - GUIStyle followingStyle = "Button"; - PrefixLabel(label, followingStyle); - } - - public static void PrefixLabel(GUIContent label, [DefaultValue("\"Button\"")] GUIStyle followingStyle) - { - PrefixLabel(label, followingStyle, EditorStyles.label); - } - - // Make a label in front of some control. - public static void PrefixLabel(GUIContent label, GUIStyle followingStyle, GUIStyle labelStyle) - { - PrefixLabelInternal(label, followingStyle, labelStyle); - } - - public static void LabelField(string label, params GUILayoutOption[] options) - { - LabelField(GUIContent.none, EditorGUIUtility.TempContent(label), EditorStyles.label, options); - } - - public static void LabelField(string label, GUIStyle style, params GUILayoutOption[] options) - { - LabelField(GUIContent.none, EditorGUIUtility.TempContent(label), style, options); - } - - public static void LabelField(GUIContent label, params GUILayoutOption[] options) - { - LabelField(GUIContent.none, label, EditorStyles.label, options); - } - - public static void LabelField(GUIContent label, GUIStyle style, params GUILayoutOption[] options) - { - LabelField(GUIContent.none, label, style, options); - } - - public static void LabelField(string label, string label2, params GUILayoutOption[] options) - { - LabelField(new GUIContent(label), EditorGUIUtility.TempContent(label2), EditorStyles.label, options); - } - - public static void LabelField(string label, string label2, GUIStyle style, params GUILayoutOption[] options) - { - LabelField(new GUIContent(label), EditorGUIUtility.TempContent(label2), style, options); - } - - public static void LabelField(GUIContent label, GUIContent label2, params GUILayoutOption[] options) - { - LabelField(label, label2, EditorStyles.label, options); - } - - // Make a label field. (Useful for showing read-only info.) - public static void LabelField(GUIContent label, GUIContent label2, GUIStyle style, params GUILayoutOption[] options) - { - if (!style.wordWrap) - { - // If we don't need word wrapping, just allocate the standard space to avoid corner case layout issues - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, options); - EditorGUI.LabelField(r, label, label2, style); - } - else - { - BeginHorizontal(); - PrefixLabel(label, style); - Rect r = GUILayoutUtility.GetRect(label2, style, options); - int oldIndent = EditorGUI.indentLevel; - EditorGUI.indentLevel = 0; - EditorGUI.LabelField(r, label2, style); - EditorGUI.indentLevel = oldIndent; - EndHorizontal(); - } - } - - public static bool LinkButton(string label, params GUILayoutOption[] options) - { - return LinkButton(EditorGUIUtility.TempContent(label), options); - } - - public static bool LinkButton(GUIContent label, params GUILayoutOption[] options) - { - var position = s_LastRect = GUILayoutUtility.GetRect(label, EditorStyles.linkLabel, options); - - Handles.color = EditorStyles.linkLabel.normal.textColor; - Handles.DrawLine(new Vector3(position.xMin + EditorStyles.linkLabel.padding.left, position.yMax), new Vector3(position.xMax - EditorStyles.linkLabel.padding.right, position.yMax)); - Handles.color = Color.white; - - EditorGUIUtility.AddCursorRect(position, MouseCursor.Link); - - return GUI.Button(position, label, EditorStyles.linkLabel); - } - - public static bool Toggle(bool value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetToggleRect(false, options); - return EditorGUI.Toggle(r, value); - } - - public static bool Toggle(string label, bool value, params GUILayoutOption[] options) - { - return Toggle(EditorGUIUtility.TempContent(label), value, options); - } - - public static bool Toggle(GUIContent label, bool value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetToggleRect(true, options); - return EditorGUI.Toggle(r, label, value); - } - - public static bool Toggle(bool value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetToggleRect(false, options); - return EditorGUI.Toggle(r, value, style); - } - - public static bool Toggle(string label, bool value, GUIStyle style, params GUILayoutOption[] options) - { - return Toggle(EditorGUIUtility.TempContent(label), value, style, options); - } - - // Make a toggle. - public static bool Toggle(GUIContent label, bool value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetToggleRect(true, options); - return EditorGUI.Toggle(r, label, value, style); - } - - public static bool ToggleLeft(string label, bool value, params GUILayoutOption[] options) - { - return ToggleLeft(EditorGUIUtility.TempContent(label), value, options); - } - - public static bool ToggleLeft(GUIContent label, bool value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, options); - return EditorGUI.ToggleLeft(r, label, value); - } - - public static bool ToggleLeft(string label, bool value, GUIStyle labelStyle, params GUILayoutOption[] options) - { - return ToggleLeft(EditorGUIUtility.TempContent(label), value, labelStyle, options); - } - - // Make a toggle with the label on the right. - public static bool ToggleLeft(GUIContent label, bool value, GUIStyle labelStyle, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, options); - return EditorGUI.ToggleLeft(r, label, value, labelStyle); - } - - public static string TextField(string text, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.textField, options); - return EditorGUI.TextField(r, text); - } - - public static string TextField(string text, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.TextField(r, text, style); - } - - public static string TextField(string label, string text, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.textField, options); - return EditorGUI.TextField(r, label, text); - } - - public static string TextField(string label, string text, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.TextField(r, label, text, style); - } - - public static string TextField(GUIContent label, string text, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.textField, options); - return EditorGUI.TextField(r, label, text); - } - - // Make a text field. - public static string TextField(GUIContent label, string text, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.TextField(r, label, text, style); - } - - public static string DelayedTextField(string text, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.textField, options); - return EditorGUI.DelayedTextField(r, text); - } - - public static string DelayedTextField(string text, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.DelayedTextField(r, text, style); - } - - public static string DelayedTextField(string label, string text, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.textField, options); - return EditorGUI.DelayedTextField(r, label, text); - } - - public static string DelayedTextField(string label, string text, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.DelayedTextField(r, label, text, style); - } - - public static string DelayedTextField(GUIContent label, string text, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.textField, options); - return EditorGUI.DelayedTextField(r, label, text); - } - - // Make a delayed text field. - public static string DelayedTextField(GUIContent label, string text, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.DelayedTextField(r, label, text, style); - } - - public static void DelayedTextField(SerializedProperty property, params GUILayoutOption[] options) - { - DelayedTextField(property, null, options); - } - - // Make a delayed text field. - internal static void DelayedTextField(SerializedProperty property, GUIContent label, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(EditorGUI.LabelHasContent(label), EditorGUI.kSingleLineHeight, EditorStyles.textField, options); - EditorGUI.DelayedTextFieldHelper(r, property, label, style); - } - - public static void DelayedTextField(SerializedProperty property, GUIContent label, params GUILayoutOption[] options) - { - DelayedTextField(property, label, EditorStyles.textField, options); - } - - internal static string ToolbarSearchField(string text, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GUILayoutUtility.GetRect(0, kLabelFloatMaxW * 1.5f, EditorGUI.kSingleLineHeight, EditorGUI.kSingleLineHeight, EditorStyles.toolbarSearchField, options); - int i = 0; - return EditorGUI.ToolbarSearchField(r, null, ref i, text); - } - - internal static string ToolbarSearchField(string text, string[] searchModes, ref int searchMode, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GUILayoutUtility.GetRect(0, kLabelFloatMaxW * 1.5f, EditorGUI.kSingleLineHeight, EditorGUI.kSingleLineHeight, EditorStyles.toolbarSearchField, options); - return EditorGUI.ToolbarSearchField(r, searchModes, ref searchMode, text); - } - - public static string TextArea(string text, params GUILayoutOption[] options) - { return TextArea(text, EditorStyles.textField, options); } - // Make a text area. - public static string TextArea(string text, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GUILayoutUtility.GetRect(EditorGUIUtility.TempContent(text), style, options); - return EditorGUI.TextArea(r, text, style); - } - - public static void SelectableLabel(string text, params GUILayoutOption[] options) - { - SelectableLabel(text, EditorStyles.label, options); - } - - // Make a selectable label field. (Useful for showing read-only info that can be copy-pasted.) - public static void SelectableLabel(string text, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight * 2, style, options); - EditorGUI.SelectableLabel(r, text, style); - } - - internal static Event KeyEventField(Event e, params GUILayoutOption[] options) - { - Rect r = GUILayoutUtility.GetRect(EditorGUI.s_PleasePressAKey, GUI.skin.textField, options); - return EditorGUI.KeyEventField(r, e); - } - - public static string PasswordField(string password, params GUILayoutOption[] options) - { - return PasswordField(password, EditorStyles.textField, options); - } - - public static string PasswordField(string password, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.PasswordField(r, password, style); - } - - public static string PasswordField(string label, string password, params GUILayoutOption[] options) - { - return PasswordField(EditorGUIUtility.TempContent(label), password, EditorStyles.textField, options); - } - - public static string PasswordField(string label, string password, GUIStyle style, params GUILayoutOption[] options) - { - return PasswordField(EditorGUIUtility.TempContent(label), password, style, options); - } - - public static string PasswordField(GUIContent label, string password, params GUILayoutOption[] options) - { - return PasswordField(label, password, EditorStyles.textField, options); - } - - // Make a text field where the user can enter a password. - public static string PasswordField(GUIContent label, string password, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.PasswordField(r, label, password, style); - } - - // Peak smoothing should be handled by client. Input: value and peak is normalized values (0 - 1). - internal static void VUMeterHorizontal(float value, float peak, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); - EditorGUI.VUMeter.HorizontalMeter(r, value, peak, EditorGUI.VUMeter.horizontalVUTexture, Color.grey); - } - - // Auto-smoothing of peak - internal static void VUMeterHorizontal(float value, ref EditorGUI.VUMeter.SmoothingData data, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); - EditorGUI.VUMeter.HorizontalMeter(r, value, ref data, EditorGUI.VUMeter.horizontalVUTexture, Color.grey); - } - - public static float FloatField(float value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); - return EditorGUI.FloatField(r, value); - } - - public static float FloatField(float value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.FloatField(r, value, style); - } - - public static float FloatField(string label, float value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); - return EditorGUI.FloatField(r, label, value); - } - - public static float FloatField(string label, float value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.FloatField(r, label, value, style); - } - - public static float FloatField(GUIContent label, float value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); - return EditorGUI.FloatField(r, label, value); - } - - // Make a text field for entering float values. - public static float FloatField(GUIContent label, float value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.FloatField(r, label, value, style); - } - - public static float DelayedFloatField(float value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); - return EditorGUI.DelayedFloatField(r, value); - } - - public static float DelayedFloatField(float value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.DelayedFloatField(r, value, style); - } - - public static float DelayedFloatField(string label, float value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); - return EditorGUI.DelayedFloatField(r, label, value); - } - - public static float DelayedFloatField(string label, float value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.DelayedFloatField(r, label, value, style); - } - - public static float DelayedFloatField(GUIContent label, float value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); - return EditorGUI.DelayedFloatField(r, label, value); - } - - // Make a delayed text field for entering float values. - public static float DelayedFloatField(GUIContent label, float value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.DelayedFloatField(r, label, value, style); - } - - public static void DelayedFloatField(SerializedProperty property, params GUILayoutOption[] options) - { - DelayedFloatField(property, null, options); - } - - // Make a delayed text field for entering float values. - public static void DelayedFloatField(SerializedProperty property, GUIContent label, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(EditorGUI.LabelHasContent(label), EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); - EditorGUI.DelayedFloatField(r, property, label); - } - - public static double DoubleField(double value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); - return EditorGUI.DoubleField(r, value); - } - - public static double DoubleField(double value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.DoubleField(r, value, style); - } - - public static double DoubleField(string label, double value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); - return EditorGUI.DoubleField(r, label, value); - } - - public static double DoubleField(string label, double value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.DoubleField(r, label, value, style); - } - - public static double DoubleField(GUIContent label, double value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); - return EditorGUI.DoubleField(r, label, value); - } - - // Make a text field for entering double values. - public static double DoubleField(GUIContent label, double value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.DoubleField(r, label, value, style); - } - - public static double DelayedDoubleField(double value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); - return EditorGUI.DelayedDoubleField(r, value); - } - - public static double DelayedDoubleField(double value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.DelayedDoubleField(r, value, style); - } - - public static double DelayedDoubleField(string label, double value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); - return EditorGUI.DelayedDoubleField(r, label, value); - } - - public static double DelayedDoubleField(string label, double value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.DelayedDoubleField(r, label, value, style); - } - - public static double DelayedDoubleField(GUIContent label, double value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); - return EditorGUI.DelayedDoubleField(r, label, value); - } - - // Make a delayed text field for entering double values. - public static double DelayedDoubleField(GUIContent label, double value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.DelayedDoubleField(r, label, value, style); - } - - public static int IntField(int value, params GUILayoutOption[] options) - { - return IntField(value, EditorStyles.numberField, options); - } - - public static int IntField(int value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.IntField(r, value, style); - } - - public static int IntField(string label, int value, params GUILayoutOption[] options) - { - return IntField(label, value, EditorStyles.numberField, options); - } - - public static int IntField(string label, int value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.IntField(r, label, value, style); - } - - public static int IntField(GUIContent label, int value, params GUILayoutOption[] options) - { - return IntField(label, value, EditorStyles.numberField, options); - } - - // Make a text field for entering integers. - public static int IntField(GUIContent label, int value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.IntField(r, label, value, style); - } - - public static int DelayedIntField(int value, params GUILayoutOption[] options) - { - return DelayedIntField(value, EditorStyles.numberField, options); - } - - public static int DelayedIntField(int value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.DelayedIntField(r, value, style); - } - - public static int DelayedIntField(string label, int value, params GUILayoutOption[] options) - { - return DelayedIntField(label, value, EditorStyles.numberField, options); - } - - public static int DelayedIntField(string label, int value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.DelayedIntField(r, label, value, style); - } - - public static int DelayedIntField(GUIContent label, int value, params GUILayoutOption[] options) - { - return DelayedIntField(label, value, EditorStyles.numberField, options); - } - - // Make a text field for entering integers. - public static int DelayedIntField(GUIContent label, int value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.DelayedIntField(r, label, value, style); - } - - public static void DelayedIntField(SerializedProperty property, params GUILayoutOption[] options) - { - DelayedIntField(property, null, options); - } - - // Make a text field for entering integers. - public static void DelayedIntField(SerializedProperty property, GUIContent label, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(EditorGUI.LabelHasContent(label), EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); - EditorGUI.DelayedIntField(r, property, label); - } - - public static long LongField(long value, params GUILayoutOption[] options) - { - return LongField(value, EditorStyles.numberField, options); - } - - public static long LongField(long value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.LongField(r, value, style); - } - - public static long LongField(string label, long value, params GUILayoutOption[] options) - { - return LongField(label, value, EditorStyles.numberField, options); - } - - public static long LongField(string label, long value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.LongField(r, label, value, style); - } - - public static long LongField(GUIContent label, long value, params GUILayoutOption[] options) - { - return LongField(label, value, EditorStyles.numberField, options); - } - - // Make a text field for entering integers. - public static long LongField(GUIContent label, long value, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.LongField(r, label, value, style); - } - - public static float Slider(float value, float leftValue, float rightValue, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetSliderRect(false, options); - return EditorGUI.Slider(r, value, leftValue, rightValue); - } - - public static float Slider(string label, float value, float leftValue, float rightValue, params GUILayoutOption[] options) - { - return Slider(EditorGUIUtility.TempContent(label), value, leftValue, rightValue, options); - } - - // Make a slider the user can drag to change a value between a min and a max. - public static float Slider(GUIContent label, float value, float leftValue, float rightValue, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetSliderRect(true, options); - return EditorGUI.Slider(r, label, value, leftValue, rightValue); - } - - internal static float Slider(GUIContent label, float value, float sliderLeftValue, float sliderRightValue, float textLeftValue, float textRightValue, params GUILayoutOption[] options) - { - return Slider(label, value, sliderLeftValue, sliderRightValue, textLeftValue, textRightValue, EditorStyles.numberField, - GUI.skin.horizontalSlider, GUI.skin.horizontalSliderThumb, null, GUI.skin.horizontalSliderThumbExtent, options); - } - - static void GetSliderParts(GUIStyle baseStyle, ref GUIStyle textFieldStyle, ref GUIStyle thumbStyle, ref GUIStyle thumbExtentStyle) - { - string baseName = baseStyle.name; - thumbStyle = GUI.skin.FindStyle(baseName + "Thumb") ?? thumbStyle; - thumbExtentStyle = GUI.skin.FindStyle(baseName + "ThumbExtent") ?? thumbExtentStyle; - textFieldStyle = GUI.skin.FindStyle(baseName + "TextField") ?? textFieldStyle; - } - - static void GetHorizontalSliderParts(GUIStyle baseStyle, out GUIStyle textFieldStyle, out GUIStyle thumbStyle, out GUIStyle thumbExtentStyle) - { - thumbStyle = GUI.skin.horizontalSliderThumb; - thumbExtentStyle = GUI.skin.horizontalSliderThumbExtent; - textFieldStyle = EditorStyles.numberField; - - GetSliderParts(baseStyle, ref textFieldStyle, ref thumbStyle, ref thumbExtentStyle); - } - - static void GetVerticalSliderParts(GUIStyle baseStyle, out GUIStyle textFieldStyle, out GUIStyle thumbStyle, out GUIStyle thumbExtentStyle) - { - thumbStyle = GUI.skin.verticalSliderThumb; - thumbExtentStyle = GUI.skin.verticalSliderThumbExtent; - textFieldStyle = EditorStyles.numberField; - - GetSliderParts(baseStyle, ref textFieldStyle, ref thumbStyle, ref thumbExtentStyle); - } - - internal static float Slider(GUIContent label, float value, float sliderLeftValue, float sliderRightValue, float textLeftValue, float textRightValue, GUIStyle sliderStyle, params GUILayoutOption[] options) - { - GUIStyle sliderThumbStyle, sliderThumbStyleExtent, sliderTextFieldStyle; - - GetHorizontalSliderParts(sliderStyle, out sliderTextFieldStyle, out sliderThumbStyle, out sliderThumbStyleExtent); - - return Slider(label, value, sliderLeftValue, sliderRightValue, textLeftValue, textRightValue, sliderTextFieldStyle, sliderStyle - , sliderThumbStyle, null, sliderThumbStyleExtent); - } - - internal static float Slider(GUIContent label, float value, float sliderLeftValue, float sliderRightValue, float textLeftValue, float textRightValue - , GUIStyle sliderTextField, GUIStyle sliderStyle, GUIStyle sliderThumbStyle, Texture2D sliderBackground, GUIStyle sliderThumbStyleExtent, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetSliderRect(true, sliderStyle, options); - return EditorGUI.Slider(r, label, value, sliderLeftValue, sliderRightValue, textLeftValue, textRightValue, sliderTextField, sliderStyle, sliderThumbStyle, sliderBackground, sliderThumbStyleExtent); - } - - public static void Slider(SerializedProperty property, float leftValue, float rightValue, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetSliderRect(false, options); - EditorGUI.Slider(r, property, leftValue, rightValue); - } - - public static void Slider(SerializedProperty property, float leftValue, float rightValue, string label, params GUILayoutOption[] options) - { - Slider(property, leftValue, rightValue, EditorGUIUtility.TempContent(label), options); - } - - // Make a slider the user can drag to change a value between a min and a max. - public static void Slider(SerializedProperty property, float leftValue, float rightValue, GUIContent label, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetSliderRect(true, options); - EditorGUI.Slider(r, property, leftValue, rightValue, label); - } - - internal static void Slider(SerializedProperty property, float sliderLeftValue, float sliderRightValue, float textLeftValue, float textRightValue, GUIContent label, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetSliderRect(true, options); - EditorGUI.Slider(r, property, sliderLeftValue, sliderRightValue, textLeftValue, textRightValue, label); - } - - internal static float PowerSlider(string label, float value, float leftValue, float rightValue, float power, params GUILayoutOption[] options) - { - return PowerSlider(EditorGUIUtility.TempContent(label), value, leftValue, rightValue, power, options); - } - - // Make a power slider the user can drag to change a value between a min and a max. - internal static float PowerSlider(GUIContent label, float value, float leftValue, float rightValue, float power, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetSliderRect(true, options); - return EditorGUI.PowerSlider(r, label, value, leftValue, rightValue, power); - } - - public static int IntSlider(int value, int leftValue, int rightValue, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetSliderRect(false, options); - return EditorGUI.IntSlider(r, value, leftValue, rightValue); - } - - internal static int IntSlider(int value, int leftValue, int rightValue, float power, GUIStyle sliderStyle, params GUILayoutOption[] options) - { - GUIStyle sliderThumbStyle, sliderThumbStyleExtent, sliderTextFieldStyle; - - GetHorizontalSliderParts(sliderStyle, out sliderTextFieldStyle, out sliderThumbStyle, out sliderThumbStyleExtent); - - return IntSlider(value, leftValue, rightValue, power, sliderTextFieldStyle, sliderStyle, sliderThumbStyle, null, sliderThumbStyleExtent, options); - } - - internal static int IntSlider(int value, int leftValue, int rightValue, float power, - GUIStyle textfieldStyle, GUIStyle sliderStyle, GUIStyle thumbStyle, Texture2D sliderBackground, GUIStyle thumbStyleExtent, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetSliderRect(false, sliderStyle, options); - return EditorGUI.IntSlider(r, value, leftValue, rightValue, power, textfieldStyle, sliderStyle, thumbStyle, sliderBackground, thumbStyleExtent); - } - - public static int IntSlider(string label, int value, int leftValue, int rightValue, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetSliderRect(true, options); - return EditorGUI.IntSlider(r, label, value, leftValue, rightValue); - } - - // Make a slider the user can drag to change an integer value between a min and a max. - public static int IntSlider(GUIContent label, int value, int leftValue, int rightValue, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetSliderRect(true, options); - return EditorGUI.IntSlider(r, label, value, leftValue, rightValue); - } - - public static void IntSlider(SerializedProperty property, int leftValue, int rightValue, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetSliderRect(false, options); - EditorGUI.IntSlider(r, property, leftValue, rightValue, property.displayName); - } - - public static void IntSlider(SerializedProperty property, int leftValue, int rightValue, string label, params GUILayoutOption[] options) - { - IntSlider(property, leftValue, rightValue, EditorGUIUtility.TempContent(label), options); - } - - // Make a slider the user can drag to change an integer value between a min and a max. - public static void IntSlider(SerializedProperty property, int leftValue, int rightValue, GUIContent label, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetSliderRect(true, options); - EditorGUI.IntSlider(r, property, leftValue, rightValue, label); - } - - public static void MinMaxSlider(ref float minValue, ref float maxValue, float minLimit, float maxLimit, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetSliderRect(false, options); - EditorGUI.MinMaxSlider(r, ref minValue, ref maxValue, minLimit, maxLimit); - } - - public static void MinMaxSlider(string label, ref float minValue, ref float maxValue, float minLimit, float maxLimit, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetSliderRect(true, options); - EditorGUI.MinMaxSlider(r, label, ref minValue, ref maxValue, minLimit, maxLimit); - } - - // Make a special slider the user can use to specify a range between a min and a max. - public static void MinMaxSlider(GUIContent label, ref float minValue, ref float maxValue, float minLimit, float maxLimit, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetSliderRect(true, options); - EditorGUI.MinMaxSlider(r, label, ref minValue, ref maxValue, minLimit, maxLimit); - } - - public static int Popup(int selectedIndex, string[] displayedOptions, params GUILayoutOption[] options) - { - return Popup(selectedIndex, displayedOptions, EditorStyles.popup, options); - } - - public static int Popup(int selectedIndex, string[] displayedOptions, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.Popup(r, selectedIndex, displayedOptions, style); - } - - public static int Popup(int selectedIndex, GUIContent[] displayedOptions, params GUILayoutOption[] options) - { - return Popup(selectedIndex, displayedOptions, EditorStyles.popup, options); - } - - public static int Popup(int selectedIndex, GUIContent[] displayedOptions, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.Popup(r, selectedIndex, displayedOptions, style); - } - - public static int Popup(string label, int selectedIndex, string[] displayedOptions, params GUILayoutOption[] options) - { - return Popup(label, selectedIndex, displayedOptions, EditorStyles.popup, options); - } - - public static int Popup(GUIContent label, int selectedIndex, string[] displayedOptions, params GUILayoutOption[] options) - { - return Popup(label, selectedIndex, displayedOptions, EditorStyles.popup, options); - } - - public static int Popup(string label, int selectedIndex, string[] displayedOptions, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.Popup(r, label, selectedIndex, displayedOptions, style); - } - - public static int Popup(GUIContent label, int selectedIndex, GUIContent[] displayedOptions, params GUILayoutOption[] options) - { - return Popup(label, selectedIndex, displayedOptions, EditorStyles.popup, options); - } - - // Make a generic popup selection field. - public static int Popup(GUIContent label, int selectedIndex, GUIContent[] displayedOptions, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.Popup(r, label, selectedIndex, displayedOptions, style); - } - - internal static int Popup(GUIContent label, int selectedIndex, string[] displayedOptions, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.Popup(r, label, selectedIndex, displayedOptions, style); - } - - internal static void Popup(SerializedProperty property, GUIContent[] displayedOptions, params GUILayoutOption[] options) - { - Popup(property, displayedOptions, null, options); - } - - internal static void Popup(SerializedProperty property, GUIContent[] displayedOptions, GUIContent label, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.popup, options); - EditorGUI.Popup(r, property, displayedOptions, label); - } - - public static Enum EnumPopup(Enum selected, params GUILayoutOption[] options) - { - return EnumPopup(selected, EditorStyles.popup, options); - } - - public static Enum EnumPopup(Enum selected, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.EnumPopup(r, selected, style); - } - - public static Enum EnumPopup(string label, Enum selected, params GUILayoutOption[] options) - { - return EnumPopup(label, selected, EditorStyles.popup, options); - } - - public static Enum EnumPopup(string label, Enum selected, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.EnumPopup(r, GUIContent.Temp(label), selected, null, false, style); - } - - public static Enum EnumPopup(GUIContent label, Enum selected, params GUILayoutOption[] options) - { - return EnumPopup(label, selected, EditorStyles.popup, options); - } - - // Make an enum popup selection field. - public static Enum EnumPopup(GUIContent label, Enum selected, GUIStyle style, params GUILayoutOption[] options) - { - return EnumPopup(label, selected, null, false, style, options); - } - - public static Enum EnumPopup(GUIContent label, Enum selected, Func checkEnabled, bool includeObsolete, params GUILayoutOption[] options) - { - return EnumPopup(label, selected, checkEnabled, includeObsolete, EditorStyles.popup, options); - } - - public static Enum EnumPopup(GUIContent label, Enum selected, Func checkEnabled, bool includeObsolete, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.EnumPopup(r, label, selected, checkEnabled, includeObsolete, style); - } - - public static int IntPopup(int selectedValue, string[] displayedOptions, int[] optionValues, params GUILayoutOption[] options) - { - return IntPopup(selectedValue, displayedOptions, optionValues, EditorStyles.popup, options); - } - - public static int IntPopup(int selectedValue, string[] displayedOptions, int[] optionValues, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.IntPopup(r, selectedValue, displayedOptions, optionValues, style); - } - - public static int IntPopup(int selectedValue, GUIContent[] displayedOptions, int[] optionValues, params GUILayoutOption[] options) - { - return IntPopup(selectedValue, displayedOptions, optionValues, EditorStyles.popup, options); - } - - public static int IntPopup(int selectedValue, GUIContent[] displayedOptions, int[] optionValues, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.IntPopup(r, GUIContent.none, selectedValue, displayedOptions, optionValues, style); - } - - public static int IntPopup(string label, int selectedValue, string[] displayedOptions, int[] optionValues, params GUILayoutOption[] options) - { - return IntPopup(label, selectedValue, displayedOptions, optionValues, EditorStyles.popup, options); - } - - public static int IntPopup(string label, int selectedValue, string[] displayedOptions, int[] optionValues, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.IntPopup(r, label, selectedValue, displayedOptions, optionValues, style); - } - - public static int IntPopup(GUIContent label, int selectedValue, GUIContent[] displayedOptions, int[] optionValues, params GUILayoutOption[] options) - { - return IntPopup(label, selectedValue, displayedOptions, optionValues, EditorStyles.popup, options); - } - - // Make an integer popup selection field. - public static int IntPopup(GUIContent label, int selectedValue, GUIContent[] displayedOptions, int[] optionValues, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.IntPopup(r, label, selectedValue, displayedOptions, optionValues, style); - } - - public static void IntPopup(SerializedProperty property, GUIContent[] displayedOptions, int[] optionValues, params GUILayoutOption[] options) - { - IntPopup(property, displayedOptions, optionValues, null, options); - } - - // Make an integer popup selection field. - public static void IntPopup(SerializedProperty property, GUIContent[] displayedOptions, int[] optionValues, GUIContent label, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.popup, options); - EditorGUI.IntPopup(r, property, displayedOptions, optionValues, label); - } - - [Obsolete("This function is obsolete and the style is not used.")] - public static void IntPopup(SerializedProperty property, GUIContent[] displayedOptions, int[] optionValues, GUIContent label, GUIStyle style, params GUILayoutOption[] options) - { - IntPopup(property, displayedOptions, optionValues, label, options); - } - - public static string TagField(string tag, params GUILayoutOption[] options) - { - return TagField(tag, EditorStyles.popup, options); - } - - public static string TagField(string tag, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.TagField(r, tag, style); - } - - public static string TagField(string label, string tag, params GUILayoutOption[] options) - { - return TagField(EditorGUIUtility.TempContent(label), tag, EditorStyles.popup, options); - } - - public static string TagField(string label, string tag, GUIStyle style, params GUILayoutOption[] options) - { - return TagField(EditorGUIUtility.TempContent(label), tag, style, options); - } - - public static string TagField(GUIContent label, string tag, params GUILayoutOption[] options) - { - return TagField(label, tag, EditorStyles.popup, options); - } - - // Make a tag selection field. - public static string TagField(GUIContent label, string tag, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.TagField(r, label, tag, style); - } - - public static int LayerField(int layer, params GUILayoutOption[] options) - { - return LayerField(layer, EditorStyles.popup, options); - } - - public static int LayerField(int layer, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.LayerField(r, layer, style); - } - - public static int LayerField(string label, int layer, params GUILayoutOption[] options) - { - return LayerField(EditorGUIUtility.TempContent(label), layer, EditorStyles.popup, options); - } - - public static int LayerField(string label, int layer, GUIStyle style, params GUILayoutOption[] options) - { - return LayerField(EditorGUIUtility.TempContent(label), layer, style, options); - } - - public static int LayerField(GUIContent label, int layer, params GUILayoutOption[] options) - { - return LayerField(label, layer, EditorStyles.popup, options); - } - - // Make a layer selection field. - public static int LayerField(GUIContent label, int layer, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.LayerField(r, label, layer, style); - } - - public static int MaskField(GUIContent label, int mask, string[] displayedOptions, GUIStyle style, params GUILayoutOption[] options) - { - var r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.MaskField(r, label, mask, displayedOptions, style); - } - - public static int MaskField(string label, int mask, string[] displayedOptions, GUIStyle style, params GUILayoutOption[] options) - { - var r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.MaskField(r, label, mask, displayedOptions, style); - } - - public static int MaskField(GUIContent label, int mask, string[] displayedOptions, params GUILayoutOption[] options) - { - var r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.popup, options); - return EditorGUI.MaskField(r, label, mask, displayedOptions, EditorStyles.popup); - } - - public static int MaskField(string label, int mask, string[] displayedOptions, params GUILayoutOption[] options) - { - var r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.popup, options); - return EditorGUI.MaskField(r, label, mask, displayedOptions, EditorStyles.popup); - } - - public static int MaskField(int mask, string[] displayedOptions, GUIStyle style, params GUILayoutOption[] options) - { - var r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.MaskField(r, mask, displayedOptions, style); - } - - // Make a field for masks. - public static int MaskField(int mask, string[] displayedOptions, params GUILayoutOption[] options) - { - var r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.popup, options); - return EditorGUI.MaskField(r, mask, displayedOptions, EditorStyles.popup); - } - - public static Enum EnumFlagsField(Enum enumValue, params GUILayoutOption[] options) - { - return EnumFlagsField(enumValue, EditorStyles.popup, options); - } - - public static Enum EnumFlagsField(Enum enumValue, GUIStyle style, params GUILayoutOption[] options) - { - var position = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.EnumFlagsField(position, enumValue, style); - } - - public static Enum EnumFlagsField(string label, Enum enumValue, params GUILayoutOption[] options) - { - return EnumFlagsField(label, enumValue, EditorStyles.popup, options); - } - - public static Enum EnumFlagsField(string label, Enum enumValue, GUIStyle style, params GUILayoutOption[] options) - { - return EnumFlagsField(EditorGUIUtility.TempContent(label), enumValue, style, options); - } - - public static Enum EnumFlagsField(GUIContent label, Enum enumValue, params GUILayoutOption[] options) - { - return EnumFlagsField(label, enumValue, EditorStyles.popup, options); - } - - public static Enum EnumFlagsField(GUIContent label, Enum enumValue, GUIStyle style, params GUILayoutOption[] options) - { - var position = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.EnumFlagsField(position, label, enumValue, style); - } - - public static Enum EnumFlagsField(GUIContent label, Enum enumValue, bool includeObsolete, params GUILayoutOption[] options) - { - return EnumFlagsField(label, enumValue, includeObsolete, EditorStyles.popup, options); - } - - public static Enum EnumFlagsField(GUIContent label, Enum enumValue, bool includeObsolete, GUIStyle style, params GUILayoutOption[] options) - { - var position = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.EnumFlagsField(position, label, enumValue, includeObsolete, style); - } - - [Obsolete("Check the docs for the usage of the new parameter 'allowSceneObjects'.")] - public static Object ObjectField(Object obj, Type objType, params GUILayoutOption[] options) - { - return ObjectField(obj, objType, true, options); - } - - public static Object ObjectField(Object obj, Type objType, Object targetBeingEdited, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, options); - return EditorGUI.ObjectField(r, obj, objType, targetBeingEdited); - } - - public static Object ObjectField(Object obj, Type objType, bool allowSceneObjects, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, options); - return EditorGUI.ObjectField(r, obj, objType, allowSceneObjects); - } - - [Obsolete("Check the docs for the usage of the new parameter 'allowSceneObjects'.")] - public static Object ObjectField(string label, Object obj, Type objType, params GUILayoutOption[] options) - { - return ObjectField(label, obj, objType, true, options); - } - - public static Object ObjectField(string label, Object obj, Type objType, Object targetBeingEdited, params GUILayoutOption[] options) - { - return ObjectField(EditorGUIUtility.TempContent(label), obj, objType, targetBeingEdited, options); - } - - public static Object ObjectField(string label, Object obj, Type objType, bool allowSceneObjects, params GUILayoutOption[] options) - { - return ObjectField(EditorGUIUtility.TempContent(label), obj, objType, allowSceneObjects, options); - } - - [Obsolete("Check the docs for the usage of the new parameter 'allowSceneObjects'.")] - public static Object ObjectField(GUIContent label, Object obj, Type objType, params GUILayoutOption[] options) - { - return ObjectField(label, obj, objType, true, options); - } - - // Make an object field. You can assign objects either by drag'n drop objects or by selecting an object using the Object Picker. - public static Object ObjectField(GUIContent label, Object obj, Type objType, Object targetBeingEdited, params GUILayoutOption[] options) - { - var height = EditorGUIUtility.HasObjectThumbnail(objType) ? EditorGUI.kObjectFieldThumbnailHeight : EditorGUI.kSingleLineHeight; - Rect r = s_LastRect = GetControlRect(true, height, options); - return EditorGUI.ObjectField(r, label, obj, objType, targetBeingEdited); - } - - // Make an object field. You can assign objects either by drag'n drop objects or by selecting an object using the Object Picker. - public static Object ObjectField(GUIContent label, Object obj, Type objType, bool allowSceneObjects, params GUILayoutOption[] options) - { - var height = EditorGUIUtility.HasObjectThumbnail(objType) ? EditorGUI.kObjectFieldThumbnailHeight : EditorGUI.kSingleLineHeight; - Rect r = s_LastRect = GetControlRect(true, height, options); - return EditorGUI.ObjectField(r, label, obj, objType, allowSceneObjects); - } - - public static void ObjectField(SerializedProperty property, params GUILayoutOption[] options) - { - ObjectField(property, (GUIContent)null, options); - } - - public static void ObjectField(SerializedProperty property, GUIContent label, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.objectField, options); - EditorGUI.ObjectField(r, property, label); - } - - public static void ObjectField(SerializedProperty property, Type objType, params GUILayoutOption[] options) - { - ObjectField(property, objType, null, options); - } - - // Make an object field. You can assign objects either by drag'n drop objects or by selecting an object using the Object Picker. - public static void ObjectField(SerializedProperty property, Type objType, GUIContent label, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.objectField, options); - EditorGUI.ObjectField(r, property, objType, label); - } - - internal static void ObjectField(SerializedProperty property, Type objType, GUIContent label, EditorGUI.ObjectFieldValidator validator, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.objectField, options); - EditorGUI.ObjectField(r, property, objType, label, EditorStyles.objectField, validator); - } - - internal static Object MiniThumbnailObjectField(GUIContent label, Object obj, Type objType, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, options); - return EditorGUI.MiniThumbnailObjectField(r, label, obj, objType); - } - - public static Vector2 Vector2Field(string label, Vector2 value, params GUILayoutOption[] options) - { - return Vector2Field(EditorGUIUtility.TempContent(label), value, options); - } - - // Make an X & Y field for entering a [[Vector2]]. - public static Vector2 Vector2Field(GUIContent label, Vector2 value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.GetPropertyHeight(SerializedPropertyType.Vector2, label), EditorStyles.numberField, options); - return EditorGUI.Vector2Field(r, label, value); - } - - public static Vector3 Vector3Field(string label, Vector3 value, params GUILayoutOption[] options) - { - return Vector3Field(EditorGUIUtility.TempContent(label), value, options); - } - - // Make an X, Y & Z field for entering a [[Vector3]]. - public static Vector3 Vector3Field(GUIContent label, Vector3 value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.GetPropertyHeight(SerializedPropertyType.Vector3, label), EditorStyles.numberField, options); - return EditorGUI.Vector3Field(r, label, value); - } - - // Make an X, Y & Z field for entering a [[Vector3]], with a "lock" - internal static Vector3 LinkedVector3Field(GUIContent label, Vector3 value, ref bool proportionalScale, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.GetPropertyHeight(SerializedPropertyType.Vector3, label), EditorStyles.numberField, options); - return EditorGUI.LinkedVector3Field(r, label, value, ref proportionalScale); - } - - // Make an X, Y & Z field for entering a [[Vector3]], with a "lock" - internal static Vector3 LinkedVector3Field(GUIContent label, Vector3 value, Vector3 initialValue, ref bool proportionalScale, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.GetPropertyHeight(SerializedPropertyType.Vector3, label), EditorStyles.numberField, options); - int axisModified = 0;// Use X as default modified axis - return EditorGUI.LinkedVector3Field(r, label, GUIContent.none, value, ref proportionalScale, initialValue, 0, ref axisModified, null); - } - - // Make an X, Y, Z & W field for entering a [[Vector4]]. - public static Vector4 Vector4Field(string label, Vector4 value, params GUILayoutOption[] options) - { - return Vector4Field(EditorGUIUtility.TempContent(label), value, options); - } - - // Make an X, Y, Z & W field for entering a [[Vector4]]. - public static Vector4 Vector4Field(GUIContent label, Vector4 value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.GetPropertyHeight(SerializedPropertyType.Vector4, label), EditorStyles.numberField, options); - return EditorGUI.Vector4Field(r, label, value); - } - - public static Vector2Int Vector2IntField(string label, Vector2Int value, params GUILayoutOption[] options) - { - return Vector2IntField(EditorGUIUtility.TempContent(label), value, options); - } - - // Make an X & Y field for entering a [[Vector2Int]]. - public static Vector2Int Vector2IntField(GUIContent label, Vector2Int value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.GetPropertyHeight(SerializedPropertyType.Vector2Int, label), EditorStyles.numberField, options); - return EditorGUI.Vector2IntField(r, label, value); - } - - public static Vector3Int Vector3IntField(string label, Vector3Int value, params GUILayoutOption[] options) - { - return Vector3IntField(EditorGUIUtility.TempContent(label), value, options); - } - - // Make an X, Y & Z field for entering a [[Vector3Int]]. - public static Vector3Int Vector3IntField(GUIContent label, Vector3Int value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.GetPropertyHeight(SerializedPropertyType.Vector3Int, label), EditorStyles.numberField, options); - return EditorGUI.Vector3IntField(r, label, value); - } - - public static Rect RectField(Rect value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.GetPropertyHeight(SerializedPropertyType.Rect, GUIContent.none), EditorStyles.numberField, options); - return EditorGUI.RectField(r, value); - } - - public static Rect RectField(string label, Rect value, params GUILayoutOption[] options) - { - return RectField(EditorGUIUtility.TempContent(label), value, options); - } - - // Make an X, Y, W & H field for entering a [[Rect]]. - public static Rect RectField(GUIContent label, Rect value, params GUILayoutOption[] options) - { - bool hasLabel = EditorGUI.LabelHasContent(label); - float height = EditorGUI.GetPropertyHeight(SerializedPropertyType.Rect, label); - Rect r = s_LastRect = GetControlRect(hasLabel, height, EditorStyles.numberField, options); - return EditorGUI.RectField(r, label, value); - } - - public static RectInt RectIntField(RectInt value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.GetPropertyHeight(SerializedPropertyType.RectInt, GUIContent.none), EditorStyles.numberField, options); - return EditorGUI.RectIntField(r, value); - } - - public static RectInt RectIntField(string label, RectInt value, params GUILayoutOption[] options) - { - return RectIntField(EditorGUIUtility.TempContent(label), value, options); - } - - // Make an X, Y, W & H field for entering a [[RectInt]]. - public static RectInt RectIntField(GUIContent label, RectInt value, params GUILayoutOption[] options) - { - bool hasLabel = EditorGUI.LabelHasContent(label); - float height = EditorGUI.GetPropertyHeight(SerializedPropertyType.RectInt, label); - Rect r = s_LastRect = GetControlRect(hasLabel, height, EditorStyles.numberField, options); - return EditorGUI.RectIntField(r, label, value); - } - - public static Bounds BoundsField(Bounds value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.GetPropertyHeight(SerializedPropertyType.Bounds, GUIContent.none), EditorStyles.numberField, options); - return EditorGUI.BoundsField(r, value); - } - - public static Bounds BoundsField(string label, Bounds value, params GUILayoutOption[] options) - { - return BoundsField(EditorGUIUtility.TempContent(label), value, options); - } - - // Make Center & Extents field for entering a [[Bounds]]. - public static Bounds BoundsField(GUIContent label, Bounds value, params GUILayoutOption[] options) - { - bool hasLabel = EditorGUI.LabelHasContent(label); - float height = EditorGUI.GetPropertyHeight(SerializedPropertyType.Bounds, label); - Rect r = s_LastRect = GetControlRect(hasLabel, height, EditorStyles.numberField, options); - return EditorGUI.BoundsField(r, label, value); - } - - public static BoundsInt BoundsIntField(BoundsInt value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.GetPropertyHeight(SerializedPropertyType.BoundsInt, GUIContent.none), EditorStyles.numberField, options); - return EditorGUI.BoundsIntField(r, value); - } - - public static BoundsInt BoundsIntField(string label, BoundsInt value, params GUILayoutOption[] options) - { - return BoundsIntField(EditorGUIUtility.TempContent(label), value, options); - } - - // Make Center & Extents field for entering a [[BoundsInt]]. - public static BoundsInt BoundsIntField(GUIContent label, BoundsInt value, params GUILayoutOption[] options) - { - bool hasLabel = EditorGUI.LabelHasContent(label); - float height = EditorGUI.GetPropertyHeight(SerializedPropertyType.BoundsInt, label); - Rect r = s_LastRect = GetControlRect(hasLabel, height, EditorStyles.numberField, options); - return EditorGUI.BoundsIntField(r, label, value); - } - - // Make a property field that look like a multi property field (but is made up of individual properties) - internal static void PropertiesField(GUIContent label, SerializedProperty[] properties, GUIContent[] propertyLabels, float propertyLabelsWidth, params GUILayoutOption[] options) - { - bool hasLabel = EditorGUI.LabelHasContent(label); - float height = EditorGUI.kSingleLineHeight * properties.Length + EditorGUI.kVerticalSpacingMultiField * (properties.Length - 1); - Rect r = s_LastRect = GetControlRect(hasLabel, height, EditorStyles.numberField, options); - EditorGUI.PropertiesField(r, label, properties, propertyLabels, propertyLabelsWidth); - } - - internal static int CycleButton(int selected, GUIContent[] contents, GUIStyle style, params GUILayoutOption[] options) - { - if (GUILayout.Button(contents[selected], style, options)) - { - selected++; - if (selected >= contents.Length) - selected = 0; - } - return selected; - } - - public static Color ColorField(Color value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); - return EditorGUI.ColorField(r, value); - } - - public static Color ColorField(string label, Color value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); - return EditorGUI.ColorField(r, label, value); - } - - // Make a field for selecting a [[Color]]. - public static Color ColorField(GUIContent label, Color value, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); - return EditorGUI.ColorField(r, label, value); - } - -#pragma warning disable 612 - [Obsolete("Use EditorGUILayout.ColorField(GUIContent label, Color value, bool showEyedropper, bool showAlpha, bool hdr, params GUILayoutOption[] options)")] - public static Color ColorField( - GUIContent label, Color value, bool showEyedropper, bool showAlpha, bool hdr, ColorPickerHDRConfig hdrConfig, params GUILayoutOption[] options - ) - { - return ColorField(label, value, showEyedropper, showAlpha, hdr); - } - -#pragma warning restore 612 - - public static Color ColorField(GUIContent label, Color value, bool showEyedropper, bool showAlpha, bool hdr, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); - return EditorGUI.ColorField(r, label, value, showEyedropper, showAlpha, hdr); - } - - public static AnimationCurve CurveField(AnimationCurve value, params GUILayoutOption[] options) - { - // TODO Change style - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); - return EditorGUI.CurveField(r, value); - } - - public static AnimationCurve CurveField(string label, AnimationCurve value, params GUILayoutOption[] options) - { - // TODO Change style - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); - return EditorGUI.CurveField(r, label, value); - } - - public static AnimationCurve CurveField(GUIContent label, AnimationCurve value, params GUILayoutOption[] options) - { - // TODO Change style - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); - return EditorGUI.CurveField(r, label, value); - } - - // Variants with settings - public static AnimationCurve CurveField(AnimationCurve value, Color color, Rect ranges, params GUILayoutOption[] options) - { - // TODO Change style - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); - return EditorGUI.CurveField(r, value, color, ranges); - } - - public static AnimationCurve CurveField(string label, AnimationCurve value, Color color, Rect ranges, params GUILayoutOption[] options) - { - // TODO Change style - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); - return EditorGUI.CurveField(r, label, value, color, ranges); - } - - // Make a field for editing an [[AnimationCurve]]. - public static AnimationCurve CurveField(GUIContent label, AnimationCurve value, Color color, Rect ranges, params GUILayoutOption[] options) - { - // TODO Change style - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); - return EditorGUI.CurveField(r, label, value, color, ranges); - } - - public static void CurveField(SerializedProperty property, Color color, Rect ranges, params GUILayoutOption[] options) - { - CurveField(property, color, ranges, null, options); - } - - // Make a field for editing an [[AnimationCurve]]. - public static void CurveField(SerializedProperty property, Color color, Rect ranges, GUIContent label, params GUILayoutOption[] options) - { - // TODO Change style - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); - EditorGUI.CurveField(r, property, color, ranges, label); - } - - public static bool InspectorTitlebar(bool foldout, Object targetObj) - { - return InspectorTitlebar(foldout, targetObj, true); - } - - public static bool InspectorTitlebar(bool foldout, Object targetObj, bool expandable) - { - return EditorGUI.InspectorTitlebar(GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.inspectorTitlebar), foldout, - targetObj, expandable); - } - - // Make an inspector-window-like titlebar. - public static bool InspectorTitlebar(bool foldout, Object[] targetObjs) - { - return InspectorTitlebar(foldout, targetObjs, true); - } - - public static bool InspectorTitlebar(bool foldout, Object[] targetObjs, bool expandable) - { - return EditorGUI.InspectorTitlebar(GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.inspectorTitlebar), foldout, - targetObjs, expandable); - } - - public static bool InspectorTitlebar(bool foldout, Editor editor) - { - return EditorGUI.InspectorTitlebar(GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.inspectorTitlebar), foldout, - editor); - } - - public static void InspectorTitlebar(Object[] targetObjs) - { - EditorGUI.InspectorTitlebar(GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.inspectorTitlebar), targetObjs); - } - - // Make a foldout with a toggle and title - internal static bool ToggleTitlebar(bool foldout, GUIContent label, ref bool toggleValue) - { - return EditorGUI.ToggleTitlebar(GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.inspectorTitlebar), label, foldout, ref toggleValue); - } - - internal static bool ToggleTitlebar(bool foldout, GUIContent label, SerializedProperty property) - { - bool toggleValue = property.boolValue; - EditorGUI.BeginChangeCheck(); - foldout = EditorGUI.ToggleTitlebar(GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.inspectorTitlebar), label, foldout, ref toggleValue); - if (EditorGUI.EndChangeCheck()) - property.boolValue = toggleValue; - - return foldout; - } - - internal static bool FoldoutTitlebar(bool foldout, GUIContent label, bool skipIconSpacing) - { - return FoldoutTitlebar(foldout, label, skipIconSpacing, EditorStyles.inspectorTitlebar, EditorStyles.inspectorTitlebarText); - } - - internal static bool FoldoutTitlebar(bool foldout, GUIContent label, bool skipIconSpacing, GUIStyle baseStyle, GUIStyle textStyle) - { - return EditorGUI.FoldoutTitlebar(GUILayoutUtility.GetRect(GUIContent.none, baseStyle, GUILayout.ExpandWidth(true)), label, foldout, skipIconSpacing, baseStyle, textStyle); - } - - // Make a label with a foldout arrow to the left of it. - internal static bool FoldoutInternal(bool foldout, GUIContent content, bool toggleOnLabelClick, GUIStyle style) - { - Rect r = s_LastRect = GUILayoutUtility.GetRect(EditorGUIUtility.fieldWidth, EditorGUIUtility.fieldWidth, EditorGUI.kSingleLineHeight, EditorGUI.kSingleLineHeight, style); - return EditorGUI.Foldout(r, foldout, content, toggleOnLabelClick, style); - } - - internal static uint LayerMaskField(UInt32 layers, GUIContent label, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, options); - return EditorGUI.LayerMaskField(r, layers, label); - } - - internal static LayerMask LayerMaskField(LayerMask layers, GUIContent label, params GUILayoutOption[] options) - { - var rect = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, options); - return EditorGUI.LayerMaskField(rect, layers, label); - } - - internal static void LayerMaskField(SerializedProperty property, GUIContent label, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, options); - EditorGUI.LayerMaskField(r, property, label); - } - - public static void HelpBox(string message, MessageType type) - { - LabelField(GUIContent.none, EditorGUIUtility.TempContent(message, EditorGUIUtility.GetHelpIcon(type)), EditorStyles.helpBox); - } - - // Make a help box with a message to the user. - public static void HelpBox(string message, MessageType type, bool wide) - { - LabelField(wide ? GUIContent.none : EditorGUIUtility.blankContent, - EditorGUIUtility.TempContent(message, EditorGUIUtility.GetHelpIcon(type)), - EditorStyles.helpBox); - } - - // Make a help box with a message to the user. - public static void HelpBox(GUIContent content, bool wide = true) - { - LabelField(wide ? GUIContent.none : EditorGUIUtility.blankContent, - content, - EditorStyles.helpBox); - } - - // Make a label in front of some control. - internal static void PrefixLabelInternal(GUIContent label, GUIStyle followingStyle, GUIStyle labelStyle) - { - float p = followingStyle.margin.left; - if (!EditorGUI.LabelHasContent(label)) - { - GUILayoutUtility.GetRect(EditorGUI.indent - p, EditorGUI.kSingleLineHeight, followingStyle, GUILayout.ExpandWidth(false)); - return; - } - - Rect r = GUILayoutUtility.GetRect(EditorGUIUtility.labelWidth - p, EditorGUI.kSingleLineHeight, followingStyle, GUILayout.ExpandWidth(false)); - r.xMin += EditorGUI.indent; - EditorGUI.HandlePrefixLabel(r, r, label, 0, labelStyle); - } - - // Make a small space between the previous control and the following. - public static void Space() - { - Space(EditorGUI.kDefaultSpacing, true); - } - - public static void Space(float width) - { - Space(width, true); - } - - public static void Space(float width, bool expand) - { - GUILayoutUtility.GetRect(width, width, GUILayout.ExpandWidth(expand)); - } - - //[System.Obsolete ("Use Space() instead")] - // Make this function Obsolete when someone has time to _rename_ all - // the Standard Packages to Space(), as currently it shows tons of - // warnings. - // Same for the graphic tests. - // *undoc* - public static void Separator() - { - Space(); - } - - public class ToggleGroupScope : GUI.Scope - { - public bool enabled { get; protected set; } - - public ToggleGroupScope(string label, bool toggle) - { - enabled = BeginToggleGroup(label, toggle); - } - - public ToggleGroupScope(GUIContent label, bool toggle) - { - enabled = BeginToggleGroup(label, toggle); - } - - protected override void CloseScope() - { - EndToggleGroup(); - } - } - - public static bool BeginToggleGroup(string label, bool toggle) - { - return BeginToggleGroup(EditorGUIUtility.TempContent(label), toggle); - } - - // Begin a vertical group with a toggle to enable or disable all the controls within at once. - public static bool BeginToggleGroup(GUIContent label, bool toggle) - { - toggle = ToggleLeft(label, toggle, EditorStyles.boldLabel); - EditorGUI.BeginDisabled(!toggle); - GUILayout.BeginVertical(); - - return toggle; - } - - // Close a group started with ::ref::BeginToggleGroup - public static void EndToggleGroup() - { - GUILayout.EndVertical(); - EditorGUI.EndDisabled(); - } - - public class HorizontalScope : GUI.Scope - { - public Rect rect { get; protected set; } - - public HorizontalScope(params GUILayoutOption[] options) - { - rect = BeginHorizontal(options); - } - - public HorizontalScope(GUIStyle style, params GUILayoutOption[] options) - { - rect = BeginHorizontal(style, options); - } - - internal HorizontalScope(GUIContent content, GUIStyle style, params GUILayoutOption[] options) - { - rect = BeginHorizontal(content, style, options); - } - - protected override void CloseScope() - { - EndHorizontal(); - } - } - - public static Rect BeginHorizontal(params GUILayoutOption[] options) - { - return BeginHorizontal(GUIContent.none, GUIStyle.none, options); - } - - // Begin a horizontal group and get its rect back. - public static Rect BeginHorizontal(GUIStyle style, params GUILayoutOption[] options) - { - return BeginHorizontal(GUIContent.none, style, options); - } - - // public static Rect BeginHorizontal (string text, params GUILayoutOption[] options) { return BeginHorizontal (EditorGUIUtility.TempContent (text), GUIStyle.none, options); } - // public static Rect BeginHorizontal (Texture image, params GUILayoutOption[] options) { return BeginHorizontal (EditorGUIUtility.TempContent (image), GUIStyle.none, options); } - // public static Rect BeginHorizontal (GUIContent content, params GUILayoutOption[] options) { return BeginHorizontal (content, GUIStyle.none, options); } - // public static Rect BeginHorizontal (string text, GUIStyle style, params GUILayoutOption[] options) { return BeginHorizontal (EditorGUIUtility.TempContent (text), style, options); } - // public static Rect BeginHorizontal (Texture image, GUIStyle style, params GUILayoutOption[] options) { return BeginHorizontal (EditorGUIUtility.TempContent (image), style, options); } - internal static Rect BeginHorizontal(GUIContent content, GUIStyle style, params GUILayoutOption[] options) - { - GUILayoutGroup g = GUILayoutUtility.BeginLayoutGroup(style, options, typeof(GUILayoutGroup)); - g.isVertical = false; - if (style != GUIStyle.none || content != GUIContent.none) - { - GUI.Box(g.rect, GUIContent.none, style); - } - return g.rect; - } - - // Close a group started with BeginHorizontal - public static void EndHorizontal() - { - GUILayout.EndHorizontal(); - } - - public class VerticalScope : GUI.Scope - { - public Rect rect { get; protected set; } - - public VerticalScope(params GUILayoutOption[] options) - { - rect = BeginVertical(options); - } - - public VerticalScope(GUIStyle style, params GUILayoutOption[] options) - { - rect = BeginVertical(style, options); - } - - internal VerticalScope(GUIContent content, GUIStyle style, params GUILayoutOption[] options) - { - rect = BeginVertical(content, style, options); - } - - protected override void CloseScope() - { - EndVertical(); - } - } - - public static Rect BeginVertical(params GUILayoutOption[] options) - { - return BeginVertical(GUIContent.none, GUIStyle.none, options); - } - - // Begin a vertical group and get its rect back. - public static Rect BeginVertical(GUIStyle style, params GUILayoutOption[] options) - { - return BeginVertical(GUIContent.none, style, options); - } - - // public static Rect BeginVertical (string text, params GUILayoutOption[] options) { return BeginVertical (EditorGUIUtility.TempContent (text), GUIStyle.none, options); } - // public static Rect BeginVertical (Texture image, params GUILayoutOption[] options) { return BeginVertical (EditorGUIUtility.TempContent (image), GUIStyle.none, options); } - // public static Rect BeginVertical (GUIContent content, params GUILayoutOption[] options) { return BeginVertical (content, GUIStyle.none, options); } - // public static Rect BeginVertical (string text, GUIStyle style, params GUILayoutOption[] options) { return BeginVertical (EditorGUIUtility.TempContent (text), style, options); } - // public static Rect BeginVertical (Texture image, GUIStyle style, params GUILayoutOption[] options) { return BeginVertical (EditorGUIUtility.TempContent (image), style, options); } - internal static Rect BeginVertical(GUIContent content, GUIStyle style, params GUILayoutOption[] options) - { - GUILayoutGroup g = GUILayoutUtility.BeginLayoutGroup(style, options, typeof(GUILayoutGroup)); - g.isVertical = true; - if (style != GUIStyle.none || content != GUIContent.none) - { - GUI.Box(g.rect, GUIContent.none, style); - } - return g.rect; - } - - // Close a group started with BeginVertical - public static void EndVertical() - { - GUILayout.EndVertical(); - } - - public class ScrollViewScope : GUI.Scope - { - public Vector2 scrollPosition { get; protected set; } - public bool handleScrollWheel { get; set; } - - public ScrollViewScope(Vector2 scrollPosition, params GUILayoutOption[] options) - { - handleScrollWheel = true; - this.scrollPosition = BeginScrollView(scrollPosition, options); - } - - public ScrollViewScope(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical, params GUILayoutOption[] options) - { - handleScrollWheel = true; - this.scrollPosition = BeginScrollView(scrollPosition, alwaysShowHorizontal, alwaysShowVertical, options); - } - - public ScrollViewScope(Vector2 scrollPosition, GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, params GUILayoutOption[] options) - { - handleScrollWheel = true; - this.scrollPosition = BeginScrollView(scrollPosition, horizontalScrollbar, verticalScrollbar, options); - } - - public ScrollViewScope(Vector2 scrollPosition, GUIStyle style, params GUILayoutOption[] options) - { - handleScrollWheel = true; - this.scrollPosition = BeginScrollView(scrollPosition, style, options); - } - - public ScrollViewScope(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical, GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, GUIStyle background, params GUILayoutOption[] options) - { - handleScrollWheel = true; - this.scrollPosition = BeginScrollView(scrollPosition, alwaysShowHorizontal, alwaysShowVertical, horizontalScrollbar, verticalScrollbar, background, options); - } - - internal ScrollViewScope(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical, GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, params GUILayoutOption[] options) - { - handleScrollWheel = true; - this.scrollPosition = BeginScrollView(scrollPosition, alwaysShowHorizontal, alwaysShowVertical, horizontalScrollbar, verticalScrollbar, options); - } - - protected override void CloseScope() - { - EndScrollView(handleScrollWheel); - } - } - - public static Vector2 BeginScrollView(Vector2 scrollPosition, params GUILayoutOption[] options) - { - return BeginScrollView(scrollPosition, false, false, GUI.skin.horizontalScrollbar, GUI.skin.verticalScrollbar, GUI.skin.scrollView, options); - } - - public static Vector2 BeginScrollView(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical, params GUILayoutOption[] options) - { - return BeginScrollView(scrollPosition, alwaysShowHorizontal, alwaysShowVertical, GUI.skin.horizontalScrollbar, GUI.skin.verticalScrollbar, GUI.skin.scrollView, options); - } - - public static Vector2 BeginScrollView(Vector2 scrollPosition, GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, params GUILayoutOption[] options) - { - return BeginScrollView(scrollPosition, false, false, horizontalScrollbar, verticalScrollbar, GUI.skin.scrollView, options); - } - - public static Vector2 BeginScrollView(Vector2 scrollPosition, GUIStyle style, params GUILayoutOption[] options) - { - string name = style.name; - - GUIStyle vertical = GUI.skin.FindStyle(name + "VerticalScrollbar") ?? GUI.skin.verticalScrollbar; - GUIStyle horizontal = GUI.skin.FindStyle(name + "HorizontalScrollbar") ?? GUI.skin.horizontalScrollbar; - return BeginScrollView(scrollPosition, false, false, horizontal, vertical, style, options); - } - - internal static Vector2 BeginScrollView(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical, GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, params GUILayoutOption[] options) - { - return BeginScrollView(scrollPosition, alwaysShowHorizontal, alwaysShowVertical, horizontalScrollbar, verticalScrollbar, GUI.skin.scrollView, options); - } - - // Begin an automatically layouted scrollview. - public static Vector2 BeginScrollView(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical, GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, GUIStyle background, params GUILayoutOption[] options) - { - GUIScrollGroup g = (GUIScrollGroup)GUILayoutUtility.BeginLayoutGroup(background, null, typeof(GUIScrollGroup)); - if (Event.current.type == EventType.Layout) - { - g.resetCoords = true; - g.isVertical = true; - g.stretchWidth = 1; - g.stretchHeight = 1; - g.verticalScrollbar = verticalScrollbar; - g.horizontalScrollbar = horizontalScrollbar; - g.ApplyOptions(options); - } - return EditorGUIInternal.DoBeginScrollViewForward(g.rect, scrollPosition, new Rect(0, 0, g.clientWidth, g.clientHeight), alwaysShowHorizontal, alwaysShowVertical, horizontalScrollbar, verticalScrollbar, background); - } - - internal class VerticalScrollViewScope : GUI.Scope - { - public Vector2 scrollPosition { get; protected set; } - public bool handleScrollWheel { get; set; } - - public VerticalScrollViewScope(Vector2 scrollPosition, params GUILayoutOption[] options) - { - handleScrollWheel = true; - this.scrollPosition = BeginVerticalScrollView(scrollPosition, options); - } - - public VerticalScrollViewScope(Vector2 scrollPosition, bool alwaysShowVertical, GUIStyle verticalScrollbar, GUIStyle background, params GUILayoutOption[] options) - { - handleScrollWheel = true; - this.scrollPosition = BeginVerticalScrollView(scrollPosition, alwaysShowVertical, verticalScrollbar, background, options); - } - - protected override void CloseScope() - { - EndScrollView(handleScrollWheel); - } - } - - internal static Vector2 BeginVerticalScrollView(Vector2 scrollPosition, params GUILayoutOption[] options) - { - return BeginVerticalScrollView(scrollPosition, false, GUI.skin.verticalScrollbar, GUI.skin.scrollView, options); - } - - // Begin an automatically layouted scrollview. - internal static Vector2 BeginVerticalScrollView(Vector2 scrollPosition, bool alwaysShowVertical, GUIStyle verticalScrollbar, GUIStyle background, params GUILayoutOption[] options) - { - GUIScrollGroup g = (GUIScrollGroup)GUILayoutUtility.BeginLayoutGroup(background, null, typeof(GUIScrollGroup)); - if (Event.current.type == EventType.Layout) - { - g.resetCoords = true; - g.isVertical = true; - g.stretchWidth = 1; - g.stretchHeight = 1; - g.verticalScrollbar = verticalScrollbar; - g.horizontalScrollbar = GUIStyle.none; - g.allowHorizontalScroll = false; - g.ApplyOptions(options); - } - return EditorGUIInternal.DoBeginScrollViewForward(g.rect, scrollPosition, new Rect(0, 0, g.clientWidth, g.clientHeight), false, alwaysShowVertical, GUI.skin.horizontalScrollbar, verticalScrollbar, background); - } - - internal class HorizontalScrollViewScope : GUI.Scope - { - public Vector2 scrollPosition { get; protected set; } - public bool handleScrollWheel { get; set; } - - public HorizontalScrollViewScope(Vector2 scrollPosition, params GUILayoutOption[] options) - { - handleScrollWheel = true; - this.scrollPosition = BeginHorizontalScrollView(scrollPosition, options); - } - - public HorizontalScrollViewScope(Vector2 scrollPosition, bool alwaysShowHorizontal, GUIStyle horizontalScrollbar, GUIStyle background, params GUILayoutOption[] options) - { - handleScrollWheel = true; - this.scrollPosition = BeginHorizontalScrollView(scrollPosition, alwaysShowHorizontal, horizontalScrollbar, background, options); - } - - protected override void CloseScope() - { - EndScrollView(handleScrollWheel); - } - } - - internal static Vector2 BeginHorizontalScrollView(Vector2 scrollPosition, params GUILayoutOption[] options) - { - return BeginHorizontalScrollView(scrollPosition, false, GUI.skin.horizontalScrollbar, GUI.skin.scrollView, options); - } - - // Begin an automatically layouted scrollview. - - internal static Vector2 BeginHorizontalScrollView(Vector2 scrollPosition, bool alwaysShowHorizontal, GUIStyle horizontalScrollbar, GUIStyle background, params GUILayoutOption[] options) - { - GUIScrollGroup g = (GUIScrollGroup)GUILayoutUtility.BeginLayoutGroup(background, null, typeof(GUIScrollGroup)); - if (Event.current.type == EventType.Layout) - { - g.resetCoords = true; - g.isVertical = true; - g.stretchWidth = 1; - g.stretchHeight = 1; - g.verticalScrollbar = GUIStyle.none; - g.horizontalScrollbar = horizontalScrollbar; - g.allowHorizontalScroll = true; - g.allowVerticalScroll = false; - g.ApplyOptions(options); - } - return EditorGUIInternal.DoBeginScrollViewForward(g.rect, scrollPosition, new Rect(0, 0, g.clientWidth, g.clientHeight), alwaysShowHorizontal, false, horizontalScrollbar, GUI.skin.verticalScrollbar, background); - } - - // Ends a scrollview started with a call to BeginScrollView. - public static void EndScrollView() - { - GUILayout.EndScrollView(true); - } - - internal static void EndScrollView(bool handleScrollWheel) - { - GUILayout.EndScrollView(handleScrollWheel); - } - - public static bool PropertyField(SerializedProperty property, params GUILayoutOption[] options) - { - return PropertyField(property, null, IsChildrenIncluded(property), options); - } - - public static bool PropertyField(SerializedProperty property, GUIContent label, params GUILayoutOption[] options) - { - return PropertyField(property, label, IsChildrenIncluded(property), options); - } - - public static bool PropertyField(SerializedProperty property, bool includeChildren, params GUILayoutOption[] options) - { - return PropertyField(property, null, includeChildren, options); - } - - // Make a field for [[SerializedProperty]]. - public static bool PropertyField(SerializedProperty property, GUIContent label, bool includeChildren, params GUILayoutOption[] options) - { - return ScriptAttributeUtility.GetHandler(property).OnGUILayout(property, label, includeChildren, options); - } - - private static bool IsChildrenIncluded(SerializedProperty prop) - { - switch (prop.propertyType) - { - case SerializedPropertyType.Generic: - case SerializedPropertyType.Vector4: - return true; - default: - return false; - } - } - - public static Rect GetControlRect(params GUILayoutOption[] options) - { - return GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.layerMaskField, options); - } - - public static Rect GetControlRect(bool hasLabel, params GUILayoutOption[] options) - { - return GetControlRect(hasLabel, EditorGUI.kSingleLineHeight, EditorStyles.layerMaskField, options); - } - - public static Rect GetControlRect(bool hasLabel, float height, params GUILayoutOption[] options) - { - return GetControlRect(hasLabel, height, EditorStyles.layerMaskField, options); - } - - public static Rect GetControlRect(bool hasLabel, float height, GUIStyle style, params GUILayoutOption[] options) - { - return GUILayoutUtility.GetRect( - hasLabel ? kLabelFloatMinW : EditorGUIUtility.fieldWidth, - kLabelFloatMaxW, - height, height, style, options); - } - - internal static Rect GetSliderRect(bool hasLabel, params GUILayoutOption[] options) - { - return GetSliderRect(hasLabel, GUI.skin.horizontalSlider, options); - } - - internal static Rect GetSliderRect(bool hasLabel, GUIStyle sliderStyle, params GUILayoutOption[] options) - { - return GUILayoutUtility.GetRect( - hasLabel ? kLabelFloatMinW : EditorGUIUtility.fieldWidth, - kLabelFloatMaxW + EditorGUI.kSpacing + EditorGUI.kSliderMaxW, - EditorGUI.kSingleLineHeight, EditorGUI.kSingleLineHeight, sliderStyle, options); - } - - internal static Rect GetToggleRect(bool hasLabel, params GUILayoutOption[] options) - { - // Toggle is 14 pixels wide while float fields are EditorGUIUtility.fieldWidth pixels wide. - // Store difference in variable and add to min and max width values used for float fields. - float toggleAdjust = (14 - EditorGUIUtility.fieldWidth); - return GUILayoutUtility.GetRect( - hasLabel ? kLabelFloatMinW + toggleAdjust : EditorGUIUtility.fieldWidth + toggleAdjust, - kLabelFloatMaxW + toggleAdjust, - EditorGUI.kSingleLineHeight, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); - } - - public class FadeGroupScope : GUI.Scope - { - // when using the FadeGroupScope, make sure to only show the content when 'visible' is set to true, - // otherwise only the hide animation will run, and then the content will be visible again. - public bool visible { get; protected set; } - - public FadeGroupScope(float value) - { - visible = BeginFadeGroup(value); - } - - protected override void CloseScope() - { - EndFadeGroup(); - } - } - - public static bool BeginFadeGroup(float value) - { - GUILayoutFadeGroup g = (GUILayoutFadeGroup)GUILayoutUtility.BeginLayoutGroup(GUIStyle.none, null, typeof(GUILayoutFadeGroup)); - g.isVertical = true; - g.resetCoords = false; - g.fadeValue = value; - g.wasGUIEnabled = GUI.enabled; - g.guiColor = GUI.color; - g.consideredForMargin = value > 0; - - // We don't want the fade group gui clip to be used for calculating the label width of controls in this fade group, so we lock the context width. - EditorGUIUtility.LockContextWidth(); - - if (value != 0.0f && value != 1.0f) - { - g.resetCoords = true; - GUI.BeginGroup(g.rect); - - if (Event.current.type == EventType.MouseDown) - { - Event.current.Use(); - } - } - - return value != 0; - } - - public static void EndFadeGroup() - { - // If we're inside a fade group, end it here. - GUILayoutFadeGroup g = EditorGUILayoutUtilityInternal.topLevel as GUILayoutFadeGroup; - - // If there are no more FadeGroups to end, display a warning. - if (g == null) - { - Debug.LogWarning("Unexpected call to EndFadeGroup! Make sure to call EndFadeGroup the same number of times as BeginFadeGroup."); - return; - } - - if (g.fadeValue != 0.0f && g.fadeValue != 1.0f) - { - GUI.EndGroup(); - } - - EditorGUIUtility.UnlockContextWidth(); - GUI.enabled = g.wasGUIEnabled; - GUI.color = g.guiColor; - GUILayoutUtility.EndLayoutGroup(); - } - - public static BuildTargetGroup BeginBuildTargetSelectionGrouping() - { - BuildPlatform[] validPlatforms = BuildPlatforms.instance.GetValidPlatforms().ToArray(); - int selected = BeginPlatformGrouping(validPlatforms, null); - return validPlatforms[selected].namedBuildTarget.ToBuildTargetGroup(); - } - - public static void EndBuildTargetSelectionGrouping() - { - EndPlatformGrouping(); - } - - internal static int BeginPlatformGrouping(BuildPlatform[] platforms, GUIContent defaultTab) - { - return BeginPlatformGrouping(platforms, defaultTab, EditorStyles.frameBox); - } - - static Rect GetTabRect(Rect rect, int tabIndex, int tabCount, out GUIStyle tabStyle) - { - if (s_TabOnlyOne == null) - { - // Keep in sync with Tests/EditModeAndPlayModeTests/PlayerSettings/Assets/Editor/PlayerSettingsApplicationIdentifierTests.cs. - s_TabOnlyOne = "Tab onlyOne"; - s_TabFirst = "Tab first"; - s_TabMiddle = "Tab middle"; - s_TabLast = "Tab last"; - } - - tabStyle = s_TabMiddle; - - if (tabCount == 1) - { - tabStyle = s_TabOnlyOne; - } - else if (tabIndex == 0) - { - tabStyle = s_TabFirst; - } - else if (tabIndex == (tabCount - 1)) - { - tabStyle = s_TabLast; - } - - float tabWidth = rect.width / tabCount; - int left = Mathf.RoundToInt(tabIndex * tabWidth); - int right = Mathf.RoundToInt((tabIndex + 1) * tabWidth); - return new Rect(rect.x + left, rect.y, right - left, EditorGUI.kTabButtonHeight); - } - - internal static int BeginPlatformGrouping(BuildPlatform[] platforms, GUIContent defaultTab, GUIStyle style) - { - return BeginPlatformGrouping(platforms, defaultTab, style, null); - } - - internal static int BeginPlatformGrouping(BuildPlatform[] platforms, GUIContent defaultTab, GUIStyle style, Func showOverrideForPlatform) - { - int selectedPlatform = -1; - for (int i = 0; i < platforms.Length; i++) - { - if (platforms[i].IsSelected()) - { - selectedPlatform = i; - break; - } - } - if (selectedPlatform == -1) - { - s_SelectedDefault.value = true; - selectedPlatform = 0; - } - - int selected = defaultTab == null ? selectedPlatform : (s_SelectedDefault.value ? -1 : selectedPlatform); - - bool tempEnabled = GUI.enabled; - GUI.enabled = true; - EditorGUI.BeginChangeCheck(); - Rect r = BeginVertical(style); - int platformCount = platforms.Length; - int buttonCount = platformCount; - int startIndex = 0; - - if (defaultTab != null) - { - buttonCount++; - startIndex = -1; - } - - int buttonIndex = 0; - for (int i = startIndex; i < platformCount; i++, buttonIndex++) - { - GUIContent content = GUIContent.none; - - if (i == -1) - { - content = defaultTab; - } - else - { - content = new GUIContent(platforms[i].smallIcon, platforms[i].tooltip); - } - - GUIStyle buttonStyle = null; - Rect buttonRect = GetTabRect(r, buttonIndex, buttonCount, out buttonStyle); - - if (GUI.Toggle(buttonRect, selected == i, content, buttonStyle)) - selected = i; - if (showOverrideForPlatform != null) - { - if (showOverrideForPlatform(i)) - { - var prevMargin = EditorGUIUtility.leftMarginCoord; - var overrideRect = buttonRect; - const int margin = 3; - overrideRect.y += margin; - overrideRect.height -= margin * 2; - EditorGUIUtility.leftMarginCoord = overrideRect.x + margin; - EditorGUI.DrawOverrideBackgroundApplicable(overrideRect); - EditorGUIUtility.leftMarginCoord = prevMargin; - } - } - } - - // GUILayout.Space doesn't expand to available width, so use GetRect instead - GUILayoutUtility.GetRect(10, EditorGUI.kTabButtonHeight); - - GUI.enabled = tempEnabled; - - // Important that we only actually set the selectedBuildTargetGroup if the user clicked the button. - // If the current selectedBuildTargetGroup is one that is not among the tabs (because the build target - // is not supported), then this should not be changed unless the user explicitly does so. - // Otherwise, if the build window is open at the same time, the unsupported build target groups will - // not be selectable in the build window. - if (EditorGUI.EndChangeCheck()) - { - if (defaultTab == null) - { - platforms[selected].Select(); - } - else - { - if (selected < 0) - { - s_SelectedDefault.value = true; - } - else - { - platforms[selected].Select(); - s_SelectedDefault.value = false; - } - } - - // Repaint build window, if open. - Object[] buildWindows = Resources.FindObjectsOfTypeAll(typeof(BuildPlayerWindow)); - foreach (Object t in buildWindows) - { - BuildPlayerWindow buildWindow = t as BuildPlayerWindow; - if (buildWindow != null) - buildWindow.Repaint(); - } - } - - return selected; - } - - internal static void EndPlatformGrouping() - { - EndVertical(); - } - - internal static void MultiSelectionObjectTitleBar(Object[] objects) - { - string text = objects[0].name + " (" + ObjectNames.NicifyVariableName(ObjectNames.GetTypeName(objects[0])) + ")"; - if (objects.Length > 1) - { - text += " and " + (objects.Length - 1) + " other" + (objects.Length > 2 ? "s" : ""); - } - GUILayoutOption[] options = { GUILayout.Height(16f) }; - GUILayout.Label(EditorGUIUtility.TempContent(text, AssetPreview.GetMiniThumbnail(objects[0])), EditorStyles.boldLabel, options); - } - - // Returns true if specified bit is true for all targets - internal static bool BitToggleField(string label, SerializedProperty bitFieldProperty, int flag) - { - bool toggle = (bitFieldProperty.intValue & flag) != 0; - bool different = (bitFieldProperty.hasMultipleDifferentValuesBitwise & flag) != 0; - EditorGUI.showMixedValue = different; - EditorGUI.BeginChangeCheck(); - toggle = Toggle(label, toggle); - if (EditorGUI.EndChangeCheck()) - { - // If toggle has mixed values, always set all to true when clicking it - if (different) - { - toggle = true; - } - different = false; - int bitIndex = -1; - for (int i = 0; i < 32; i++) - { - if (((1 << i) & flag) != 0) - { - bitIndex = i; - break; - } - } - bitFieldProperty.SetBitAtIndexForAllTargetsImmediate(bitIndex, toggle); - } - EditorGUI.showMixedValue = false; - return toggle && !different; - } - - internal static void SortingLayerField(GUIContent label, SerializedProperty layerID, GUIStyle style) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style); - EditorGUI.SortingLayerField(r, label, layerID, style, EditorStyles.label); - } - - internal static string TextFieldDropDown(string text, string[] dropDownElement) - { - return TextFieldDropDown(GUIContent.none, text, dropDownElement); - } - - internal static string TextFieldDropDown(GUIContent label, string text, string[] dropDownElement) - { - Rect rect = GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.textField); - return EditorGUI.TextFieldDropDown(rect, label, text, dropDownElement); - } - - internal static string DelayedTextFieldDropDown(string text, string[] dropDownElement) - { - return DelayedTextFieldDropDown(GUIContent.none, text, dropDownElement); - } - - internal static string DelayedTextFieldDropDown(GUIContent label, string text, string[] dropDownElement) - { - Rect rect = GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.textFieldDropDownText); - return EditorGUI.DelayedTextFieldDropDown(rect, label, text, dropDownElement); - } - - // A button that returns true on mouse down - like a popup button - public static bool DropdownButton(GUIContent content, FocusType focusType, params GUILayoutOption[] options) - { - return DropdownButton(content, focusType, "MiniPullDown", options); - } - - // A button that returns true on mouse down - like a popup button - public static bool DropdownButton(GUIContent content, FocusType focusType, GUIStyle style, params GUILayoutOption[] options) - { - s_LastRect = GUILayoutUtility.GetRect(content, style, options); - return EditorGUI.DropdownButton(s_LastRect, content, focusType, style); - } - - // A toggle that returns true on mouse down - like a popup button and returns true if checked - internal static bool DropDownToggle(ref bool toggled, GUIContent content, GUIStyle toggleStyle) - { - GUIStyle buttonStyle = GUIStyle.none; - - // This is to be compatible with existing code - if (toggleStyle == EditorStyles.toolbarDropDownToggle || toggleStyle == EditorStyles.toolbarDropDownToggleRight) - buttonStyle = EditorStyles.toolbarDropDownToggleButton; - - return DropDownToggle(ref toggled, content, toggleStyle, buttonStyle); - } - - internal static bool DropDownToggle(ref bool toggled, GUIContent content, GUIStyle toggleStyle, GUIStyle toggleDropdownButtonStyle) - { - Rect toggleRect = GUILayoutUtility.GetRect(content, toggleStyle); - Rect arrowRightRect = Rect.zero; - - if (toggleDropdownButtonStyle != null) - { - arrowRightRect = new Rect(toggleRect.xMax - toggleDropdownButtonStyle.fixedWidth - toggleDropdownButtonStyle.margin.right, toggleRect.y, toggleDropdownButtonStyle.fixedWidth, toggleRect.height); - } - else - { - arrowRightRect = new Rect(toggleRect.xMax - toggleStyle.padding.right, toggleRect.y, toggleStyle.padding.right, toggleRect.height); - } - - - int dropdownButtonId = GUIUtility.GetControlID(EditorGUI.s_DropdownButtonHash, FocusType.Passive, arrowRightRect); - bool clicked = EditorGUI.DropdownButton(dropdownButtonId, arrowRightRect, GUIContent.none, GUIStyle.none); - - if (!clicked) - { - toggled = GUI.Toggle(toggleRect, toggled, content, toggleStyle); - } - - // Ensure that the dropdown button is rendered on top of the toggle - if (Event.current.type == EventType.Repaint && toggleDropdownButtonStyle != null && toggleDropdownButtonStyle != GUIStyle.none) - { - EditorGUI.DropdownButton(dropdownButtonId, arrowRightRect, GUIContent.none, toggleDropdownButtonStyle); - } - - return clicked; - } - - internal static int AdvancedPopup(int selectedIndex, string[] displayedOptions, params GUILayoutOption[] options) - { - return AdvancedPopup(selectedIndex, displayedOptions, "MiniPullDown", options); - } - - internal static int AdvancedPopup(int selectedIndex, string[] displayedOptions, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.AdvancedPopup(r, selectedIndex, displayedOptions, style); - } - - internal static int AdvancedLazyPopup(string displayedOption, int selectedIndex, Func> displayedOptionsFunc, GUIStyle style, params GUILayoutOption[] options) - { - Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - return EditorGUI.AdvancedLazyPopup(r, displayedOption, selectedIndex, displayedOptionsFunc, style); - } - - [Obsolete("(UnityUpgradable) -> UnityEditor.HyperLinkClickedEventArgs", true)] - internal class HyperLinkClickedEventArgs - { - [Obsolete("(UnityUpgradable) -> UnityEditor.HyperLinkClickedEventArgs.hyperLinkData", true)] - public Dictionary hyperlinkInfos { get; private set; } - internal HyperLinkClickedEventArgs(Dictionary hyperLinkData) {} - } - } } diff --git a/Editor/Mono/EditorGUILayout.cs b/Editor/Mono/EditorGUILayout.cs new file mode 100644 index 0000000000..0106fb93e7 --- /dev/null +++ b/Editor/Mono/EditorGUILayout.cs @@ -0,0 +1,2525 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using UnityEditor.Build; +using UnityEngine; +using UnityEngine.Internal; +using Object = UnityEngine.Object; + +namespace UnityEditor; + +// Auto-layouted version of [[EditorGUI]] +sealed partial class EditorGUILayout +{ + // @TODO: Make private (and rename to not claim it's a constant). Shouldn't really be used outside of EditorGUI. + // Places that use this directly should likely use GetControlRect instead. + internal static float kLabelFloatMinW => EditorGUIUtility.labelWidth + EditorGUIUtility.fieldWidth + EditorGUI.kSpacing; + + internal static float kLabelFloatMaxW => EditorGUIUtility.labelWidth + EditorGUIUtility.fieldWidth + EditorGUI.kSpacing; + + internal static Rect s_LastRect; + + internal const float kPlatformTabWidth = 30; + + internal static SavedBool s_SelectedDefault = new SavedBool("Platform.ShownDefaultTab", true); + + static GUIStyle s_TabOnlyOne; + static GUIStyle s_TabFirst; + static GUIStyle s_TabMiddle; + static GUIStyle s_TabLast; + + [ExcludeFromDocs] + public static bool Foldout(bool foldout, string content) + { + return Foldout(foldout, content, EditorStyles.foldout); + } + + public static bool Foldout(bool foldout, string content, [DefaultValue("EditorStyles.foldout")] GUIStyle style) + { + return Foldout(foldout, EditorGUIUtility.TempContent(content), false, style); + } + + [ExcludeFromDocs] + public static bool Foldout(bool foldout, GUIContent content) + { + return Foldout(foldout, content, EditorStyles.foldout); + } + + public static bool Foldout(bool foldout, GUIContent content, [DefaultValue("EditorStyles.foldout")] GUIStyle style) + { + return Foldout(foldout, content, false, style); + } + + [ExcludeFromDocs] + public static bool Foldout(bool foldout, string content, bool toggleOnLabelClick) + { + return Foldout(foldout, content, toggleOnLabelClick, EditorStyles.foldout); + } + + public static bool Foldout(bool foldout, string content, bool toggleOnLabelClick, [DefaultValue("EditorStyles.foldout")] GUIStyle style) + { + return Foldout(foldout, EditorGUIUtility.TempContent(content), toggleOnLabelClick, style); + } + + [ExcludeFromDocs] + public static bool Foldout(bool foldout, GUIContent content, bool toggleOnLabelClick) + { + return Foldout(foldout, content, toggleOnLabelClick, EditorStyles.foldout); + } + + public static bool Foldout(bool foldout, GUIContent content, bool toggleOnLabelClick, [DefaultValue("EditorStyles.foldout")] GUIStyle style) + { + return FoldoutInternal(foldout, content, toggleOnLabelClick, style); + } + + [ExcludeFromDocs] + public static void PrefixLabel(string label) + { + GUIStyle followingStyle = "Button"; + PrefixLabel(label, followingStyle); + } + + public static void PrefixLabel(string label, [DefaultValue("\"Button\"")] GUIStyle followingStyle) + { + PrefixLabel(EditorGUIUtility.TempContent(label), followingStyle, EditorStyles.label); + } + + public static void PrefixLabel(string label, GUIStyle followingStyle, GUIStyle labelStyle) + { + PrefixLabel(EditorGUIUtility.TempContent(label), followingStyle, labelStyle); + } + + [ExcludeFromDocs] + public static void PrefixLabel(GUIContent label) + { + GUIStyle followingStyle = "Button"; + PrefixLabel(label, followingStyle); + } + + public static void PrefixLabel(GUIContent label, [DefaultValue("\"Button\"")] GUIStyle followingStyle) + { + PrefixLabel(label, followingStyle, EditorStyles.label); + } + + // Make a label in front of some control. + public static void PrefixLabel(GUIContent label, GUIStyle followingStyle, GUIStyle labelStyle) + { + PrefixLabelInternal(label, followingStyle, labelStyle); + } + + public static void LabelField(string label, params GUILayoutOption[] options) + { + LabelField(GUIContent.none, EditorGUIUtility.TempContent(label), EditorStyles.label, options); + } + + public static void LabelField(string label, GUIStyle style, params GUILayoutOption[] options) + { + LabelField(GUIContent.none, EditorGUIUtility.TempContent(label), style, options); + } + + public static void LabelField(GUIContent label, params GUILayoutOption[] options) + { + LabelField(GUIContent.none, label, EditorStyles.label, options); + } + + public static void LabelField(GUIContent label, GUIStyle style, params GUILayoutOption[] options) + { + LabelField(GUIContent.none, label, style, options); + } + + public static void LabelField(string label, string label2, params GUILayoutOption[] options) + { + LabelField(new GUIContent(label), EditorGUIUtility.TempContent(label2), EditorStyles.label, options); + } + + public static void LabelField(string label, string label2, GUIStyle style, params GUILayoutOption[] options) + { + LabelField(new GUIContent(label), EditorGUIUtility.TempContent(label2), style, options); + } + + public static void LabelField(GUIContent label, GUIContent label2, params GUILayoutOption[] options) + { + LabelField(label, label2, EditorStyles.label, options); + } + + // Make a label field. (Useful for showing read-only info.) + public static void LabelField(GUIContent label, GUIContent label2, GUIStyle style, params GUILayoutOption[] options) + { + if (!style.wordWrap) + { + // If we don't need word wrapping, just allocate the standard space to avoid corner case layout issues + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, options); + EditorGUI.LabelField(r, label, label2, style); + } + else + { + BeginHorizontal(); + PrefixLabel(label, style); + Rect r = GUILayoutUtility.GetRect(label2, style, options); + int oldIndent = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + EditorGUI.LabelField(r, label2, style); + EditorGUI.indentLevel = oldIndent; + EndHorizontal(); + } + } + + public static bool LinkButton(string label, params GUILayoutOption[] options) + { + return LinkButton(EditorGUIUtility.TempContent(label), options); + } + + public static bool LinkButton(GUIContent label, params GUILayoutOption[] options) + { + var position = s_LastRect = GUILayoutUtility.GetRect(label, EditorStyles.linkLabel, options); + + Handles.color = EditorStyles.linkLabel.normal.textColor; + Handles.DrawLine(new Vector3(position.xMin + EditorStyles.linkLabel.padding.left, position.yMax), new Vector3(position.xMax - EditorStyles.linkLabel.padding.right, position.yMax)); + Handles.color = Color.white; + + EditorGUIUtility.AddCursorRect(position, MouseCursor.Link); + + return GUI.Button(position, label, EditorStyles.linkLabel); + } + + public static bool Toggle(bool value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetToggleRect(false, options); + return EditorGUI.Toggle(r, value); + } + + public static bool Toggle(string label, bool value, params GUILayoutOption[] options) + { + return Toggle(EditorGUIUtility.TempContent(label), value, options); + } + + public static bool Toggle(GUIContent label, bool value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetToggleRect(true, options); + return EditorGUI.Toggle(r, label, value); + } + + public static bool Toggle(bool value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetToggleRect(false, options); + return EditorGUI.Toggle(r, value, style); + } + + public static bool Toggle(string label, bool value, GUIStyle style, params GUILayoutOption[] options) + { + return Toggle(EditorGUIUtility.TempContent(label), value, style, options); + } + + // Make a toggle. + public static bool Toggle(GUIContent label, bool value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetToggleRect(true, options); + return EditorGUI.Toggle(r, label, value, style); + } + + public static bool ToggleLeft(string label, bool value, params GUILayoutOption[] options) + { + return ToggleLeft(EditorGUIUtility.TempContent(label), value, options); + } + + public static bool ToggleLeft(GUIContent label, bool value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, options); + return EditorGUI.ToggleLeft(r, label, value); + } + + public static bool ToggleLeft(string label, bool value, GUIStyle labelStyle, params GUILayoutOption[] options) + { + return ToggleLeft(EditorGUIUtility.TempContent(label), value, labelStyle, options); + } + + // Make a toggle with the label on the right. + public static bool ToggleLeft(GUIContent label, bool value, GUIStyle labelStyle, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, options); + return EditorGUI.ToggleLeft(r, label, value, labelStyle); + } + + public static string TextField(string text, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.textField, options); + return EditorGUI.TextField(r, text); + } + + public static string TextField(string text, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.TextField(r, text, style); + } + + public static string TextField(string label, string text, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.textField, options); + return EditorGUI.TextField(r, label, text); + } + + public static string TextField(string label, string text, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.TextField(r, label, text, style); + } + + public static string TextField(GUIContent label, string text, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.textField, options); + return EditorGUI.TextField(r, label, text); + } + + // Make a text field. + public static string TextField(GUIContent label, string text, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.TextField(r, label, text, style); + } + + public static string DelayedTextField(string text, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.textField, options); + return EditorGUI.DelayedTextField(r, text); + } + + public static string DelayedTextField(string text, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.DelayedTextField(r, text, style); + } + + public static string DelayedTextField(string label, string text, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.textField, options); + return EditorGUI.DelayedTextField(r, label, text); + } + + public static string DelayedTextField(string label, string text, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.DelayedTextField(r, label, text, style); + } + + public static string DelayedTextField(GUIContent label, string text, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.textField, options); + return EditorGUI.DelayedTextField(r, label, text); + } + + // Make a delayed text field. + public static string DelayedTextField(GUIContent label, string text, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.DelayedTextField(r, label, text, style); + } + + public static void DelayedTextField(SerializedProperty property, params GUILayoutOption[] options) + { + DelayedTextField(property, null, options); + } + + // Make a delayed text field. + internal static void DelayedTextField(SerializedProperty property, GUIContent label, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(EditorGUI.LabelHasContent(label), EditorGUI.kSingleLineHeight, EditorStyles.textField, options); + EditorGUI.DelayedTextFieldHelper(r, property, label, style); + } + + public static void DelayedTextField(SerializedProperty property, GUIContent label, params GUILayoutOption[] options) + { + DelayedTextField(property, label, EditorStyles.textField, options); + } + + internal static string ToolbarSearchField(string text, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GUILayoutUtility.GetRect(0, kLabelFloatMaxW * 1.5f, EditorGUI.kSingleLineHeight, EditorGUI.kSingleLineHeight, EditorStyles.toolbarSearchField, options); + int i = 0; + return EditorGUI.ToolbarSearchField(r, null, ref i, text); + } + + internal static string ToolbarSearchField(string text, string[] searchModes, ref int searchMode, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GUILayoutUtility.GetRect(0, kLabelFloatMaxW * 1.5f, EditorGUI.kSingleLineHeight, EditorGUI.kSingleLineHeight, EditorStyles.toolbarSearchField, options); + return EditorGUI.ToolbarSearchField(r, searchModes, ref searchMode, text); + } + + public static string TextArea(string text, params GUILayoutOption[] options) + { return TextArea(text, EditorStyles.textField, options); } + // Make a text area. + public static string TextArea(string text, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GUILayoutUtility.GetRect(EditorGUIUtility.TempContent(text), style, options); + return EditorGUI.TextArea(r, text, style); + } + + public static void SelectableLabel(string text, params GUILayoutOption[] options) + { + SelectableLabel(text, EditorStyles.label, options); + } + + // Make a selectable label field. (Useful for showing read-only info that can be copy-pasted.) + public static void SelectableLabel(string text, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight * 2, style, options); + EditorGUI.SelectableLabel(r, text, style); + } + + internal static Event KeyEventField(Event e, params GUILayoutOption[] options) + { + Rect r = GUILayoutUtility.GetRect(EditorGUI.s_PleasePressAKey, GUI.skin.textField, options); + return EditorGUI.KeyEventField(r, e); + } + + public static string PasswordField(string password, params GUILayoutOption[] options) + { + return PasswordField(password, EditorStyles.textField, options); + } + + public static string PasswordField(string password, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.PasswordField(r, password, style); + } + + public static string PasswordField(string label, string password, params GUILayoutOption[] options) + { + return PasswordField(EditorGUIUtility.TempContent(label), password, EditorStyles.textField, options); + } + + public static string PasswordField(string label, string password, GUIStyle style, params GUILayoutOption[] options) + { + return PasswordField(EditorGUIUtility.TempContent(label), password, style, options); + } + + public static string PasswordField(GUIContent label, string password, params GUILayoutOption[] options) + { + return PasswordField(label, password, EditorStyles.textField, options); + } + + // Make a text field where the user can enter a password. + public static string PasswordField(GUIContent label, string password, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.PasswordField(r, label, password, style); + } + + // Peak smoothing should be handled by client. Input: value and peak is normalized values (0 - 1). + internal static void VUMeterHorizontal(float value, float peak, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); + EditorGUI.VUMeter.HorizontalMeter(r, value, peak, EditorGUI.VUMeter.horizontalVUTexture, Color.grey); + } + + // Auto-smoothing of peak + internal static void VUMeterHorizontal(float value, ref EditorGUI.VUMeter.SmoothingData data, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); + EditorGUI.VUMeter.HorizontalMeter(r, value, ref data, EditorGUI.VUMeter.horizontalVUTexture, Color.grey); + } + + public static float FloatField(float value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); + return EditorGUI.FloatField(r, value); + } + + public static float FloatField(float value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.FloatField(r, value, style); + } + + public static float FloatField(string label, float value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); + return EditorGUI.FloatField(r, label, value); + } + + public static float FloatField(string label, float value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.FloatField(r, label, value, style); + } + + public static float FloatField(GUIContent label, float value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); + return EditorGUI.FloatField(r, label, value); + } + + // Make a text field for entering float values. + public static float FloatField(GUIContent label, float value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.FloatField(r, label, value, style); + } + + public static float DelayedFloatField(float value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); + return EditorGUI.DelayedFloatField(r, value); + } + + public static float DelayedFloatField(float value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.DelayedFloatField(r, value, style); + } + + public static float DelayedFloatField(string label, float value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); + return EditorGUI.DelayedFloatField(r, label, value); + } + + public static float DelayedFloatField(string label, float value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.DelayedFloatField(r, label, value, style); + } + + public static float DelayedFloatField(GUIContent label, float value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); + return EditorGUI.DelayedFloatField(r, label, value); + } + + // Make a delayed text field for entering float values. + public static float DelayedFloatField(GUIContent label, float value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.DelayedFloatField(r, label, value, style); + } + + public static void DelayedFloatField(SerializedProperty property, params GUILayoutOption[] options) + { + DelayedFloatField(property, null, options); + } + + // Make a delayed text field for entering float values. + public static void DelayedFloatField(SerializedProperty property, GUIContent label, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(EditorGUI.LabelHasContent(label), EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); + EditorGUI.DelayedFloatField(r, property, label); + } + + public static double DoubleField(double value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); + return EditorGUI.DoubleField(r, value); + } + + public static double DoubleField(double value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.DoubleField(r, value, style); + } + + public static double DoubleField(string label, double value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); + return EditorGUI.DoubleField(r, label, value); + } + + public static double DoubleField(string label, double value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.DoubleField(r, label, value, style); + } + + public static double DoubleField(GUIContent label, double value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); + return EditorGUI.DoubleField(r, label, value); + } + + // Make a text field for entering double values. + public static double DoubleField(GUIContent label, double value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.DoubleField(r, label, value, style); + } + + public static double DelayedDoubleField(double value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); + return EditorGUI.DelayedDoubleField(r, value); + } + + public static double DelayedDoubleField(double value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.DelayedDoubleField(r, value, style); + } + + public static double DelayedDoubleField(string label, double value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); + return EditorGUI.DelayedDoubleField(r, label, value); + } + + public static double DelayedDoubleField(string label, double value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.DelayedDoubleField(r, label, value, style); + } + + public static double DelayedDoubleField(GUIContent label, double value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); + return EditorGUI.DelayedDoubleField(r, label, value); + } + + // Make a delayed text field for entering double values. + public static double DelayedDoubleField(GUIContent label, double value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.DelayedDoubleField(r, label, value, style); + } + + public static int IntField(int value, params GUILayoutOption[] options) + { + return IntField(value, EditorStyles.numberField, options); + } + + public static int IntField(int value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.IntField(r, value, style); + } + + public static int IntField(string label, int value, params GUILayoutOption[] options) + { + return IntField(label, value, EditorStyles.numberField, options); + } + + public static int IntField(string label, int value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.IntField(r, label, value, style); + } + + public static int IntField(GUIContent label, int value, params GUILayoutOption[] options) + { + return IntField(label, value, EditorStyles.numberField, options); + } + + // Make a text field for entering integers. + public static int IntField(GUIContent label, int value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.IntField(r, label, value, style); + } + + public static int DelayedIntField(int value, params GUILayoutOption[] options) + { + return DelayedIntField(value, EditorStyles.numberField, options); + } + + public static int DelayedIntField(int value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.DelayedIntField(r, value, style); + } + + public static int DelayedIntField(string label, int value, params GUILayoutOption[] options) + { + return DelayedIntField(label, value, EditorStyles.numberField, options); + } + + public static int DelayedIntField(string label, int value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.DelayedIntField(r, label, value, style); + } + + public static int DelayedIntField(GUIContent label, int value, params GUILayoutOption[] options) + { + return DelayedIntField(label, value, EditorStyles.numberField, options); + } + + // Make a text field for entering integers. + public static int DelayedIntField(GUIContent label, int value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.DelayedIntField(r, label, value, style); + } + + public static void DelayedIntField(SerializedProperty property, params GUILayoutOption[] options) + { + DelayedIntField(property, null, options); + } + + // Make a text field for entering integers. + public static void DelayedIntField(SerializedProperty property, GUIContent label, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(EditorGUI.LabelHasContent(label), EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); + EditorGUI.DelayedIntField(r, property, label); + } + + public static long LongField(long value, params GUILayoutOption[] options) + { + return LongField(value, EditorStyles.numberField, options); + } + + public static long LongField(long value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.LongField(r, value, style); + } + + public static long LongField(string label, long value, params GUILayoutOption[] options) + { + return LongField(label, value, EditorStyles.numberField, options); + } + + public static long LongField(string label, long value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.LongField(r, label, value, style); + } + + public static long LongField(GUIContent label, long value, params GUILayoutOption[] options) + { + return LongField(label, value, EditorStyles.numberField, options); + } + + // Make a text field for entering integers. + public static long LongField(GUIContent label, long value, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.LongField(r, label, value, style); + } + + public static float Slider(float value, float leftValue, float rightValue, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetSliderRect(false, options); + return EditorGUI.Slider(r, value, leftValue, rightValue); + } + + public static float Slider(string label, float value, float leftValue, float rightValue, params GUILayoutOption[] options) + { + return Slider(EditorGUIUtility.TempContent(label), value, leftValue, rightValue, options); + } + + // Make a slider the user can drag to change a value between a min and a max. + public static float Slider(GUIContent label, float value, float leftValue, float rightValue, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetSliderRect(true, options); + return EditorGUI.Slider(r, label, value, leftValue, rightValue); + } + + internal static float Slider(GUIContent label, float value, float sliderLeftValue, float sliderRightValue, float textLeftValue, float textRightValue, params GUILayoutOption[] options) + { + return Slider(label, value, sliderLeftValue, sliderRightValue, textLeftValue, textRightValue, EditorStyles.numberField, + GUI.skin.horizontalSlider, GUI.skin.horizontalSliderThumb, null, GUI.skin.horizontalSliderThumbExtent, options); + } + + static void GetSliderParts(GUIStyle baseStyle, ref GUIStyle textFieldStyle, ref GUIStyle thumbStyle, ref GUIStyle thumbExtentStyle) + { + string baseName = baseStyle.name; + thumbStyle = GUI.skin.FindStyle(baseName + "Thumb") ?? thumbStyle; + thumbExtentStyle = GUI.skin.FindStyle(baseName + "ThumbExtent") ?? thumbExtentStyle; + textFieldStyle = GUI.skin.FindStyle(baseName + "TextField") ?? textFieldStyle; + } + + static void GetHorizontalSliderParts(GUIStyle baseStyle, out GUIStyle textFieldStyle, out GUIStyle thumbStyle, out GUIStyle thumbExtentStyle) + { + thumbStyle = GUI.skin.horizontalSliderThumb; + thumbExtentStyle = GUI.skin.horizontalSliderThumbExtent; + textFieldStyle = EditorStyles.numberField; + + GetSliderParts(baseStyle, ref textFieldStyle, ref thumbStyle, ref thumbExtentStyle); + } + + static void GetVerticalSliderParts(GUIStyle baseStyle, out GUIStyle textFieldStyle, out GUIStyle thumbStyle, out GUIStyle thumbExtentStyle) + { + thumbStyle = GUI.skin.verticalSliderThumb; + thumbExtentStyle = GUI.skin.verticalSliderThumbExtent; + textFieldStyle = EditorStyles.numberField; + + GetSliderParts(baseStyle, ref textFieldStyle, ref thumbStyle, ref thumbExtentStyle); + } + + internal static float Slider(GUIContent label, float value, float sliderLeftValue, float sliderRightValue, float textLeftValue, float textRightValue, GUIStyle sliderStyle, params GUILayoutOption[] options) + { + GUIStyle sliderThumbStyle, sliderThumbStyleExtent, sliderTextFieldStyle; + + GetHorizontalSliderParts(sliderStyle, out sliderTextFieldStyle, out sliderThumbStyle, out sliderThumbStyleExtent); + + return Slider(label, value, sliderLeftValue, sliderRightValue, textLeftValue, textRightValue, sliderTextFieldStyle, sliderStyle + , sliderThumbStyle, null, sliderThumbStyleExtent); + } + + internal static float Slider(GUIContent label, float value, float sliderLeftValue, float sliderRightValue, float textLeftValue, float textRightValue + , GUIStyle sliderTextField, GUIStyle sliderStyle, GUIStyle sliderThumbStyle, Texture2D sliderBackground, GUIStyle sliderThumbStyleExtent, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetSliderRect(true, sliderStyle, options); + return EditorGUI.Slider(r, label, value, sliderLeftValue, sliderRightValue, textLeftValue, textRightValue, sliderTextField, sliderStyle, sliderThumbStyle, sliderBackground, sliderThumbStyleExtent); + } + + public static void Slider(SerializedProperty property, float leftValue, float rightValue, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetSliderRect(false, options); + EditorGUI.Slider(r, property, leftValue, rightValue); + } + + public static void Slider(SerializedProperty property, float leftValue, float rightValue, string label, params GUILayoutOption[] options) + { + Slider(property, leftValue, rightValue, EditorGUIUtility.TempContent(label), options); + } + + // Make a slider the user can drag to change a value between a min and a max. + public static void Slider(SerializedProperty property, float leftValue, float rightValue, GUIContent label, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetSliderRect(true, options); + EditorGUI.Slider(r, property, leftValue, rightValue, label); + } + + internal static void Slider(SerializedProperty property, float sliderLeftValue, float sliderRightValue, float textLeftValue, float textRightValue, GUIContent label, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetSliderRect(true, options); + EditorGUI.Slider(r, property, sliderLeftValue, sliderRightValue, textLeftValue, textRightValue, label); + } + + internal static float PowerSlider(string label, float value, float leftValue, float rightValue, float power, params GUILayoutOption[] options) + { + return PowerSlider(EditorGUIUtility.TempContent(label), value, leftValue, rightValue, power, options); + } + + // Make a power slider the user can drag to change a value between a min and a max. + internal static float PowerSlider(GUIContent label, float value, float leftValue, float rightValue, float power, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetSliderRect(true, options); + return EditorGUI.PowerSlider(r, label, value, leftValue, rightValue, power); + } + + public static int IntSlider(int value, int leftValue, int rightValue, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetSliderRect(false, options); + return EditorGUI.IntSlider(r, value, leftValue, rightValue); + } + + internal static int IntSlider(int value, int leftValue, int rightValue, float power, GUIStyle sliderStyle, params GUILayoutOption[] options) + { + GUIStyle sliderThumbStyle, sliderThumbStyleExtent, sliderTextFieldStyle; + + GetHorizontalSliderParts(sliderStyle, out sliderTextFieldStyle, out sliderThumbStyle, out sliderThumbStyleExtent); + + return IntSlider(value, leftValue, rightValue, power, sliderTextFieldStyle, sliderStyle, sliderThumbStyle, null, sliderThumbStyleExtent, options); + } + + internal static int IntSlider(int value, int leftValue, int rightValue, float power, + GUIStyle textfieldStyle, GUIStyle sliderStyle, GUIStyle thumbStyle, Texture2D sliderBackground, GUIStyle thumbStyleExtent, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetSliderRect(false, sliderStyle, options); + return EditorGUI.IntSlider(r, value, leftValue, rightValue, power, textfieldStyle, sliderStyle, thumbStyle, sliderBackground, thumbStyleExtent); + } + + public static int IntSlider(string label, int value, int leftValue, int rightValue, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetSliderRect(true, options); + return EditorGUI.IntSlider(r, label, value, leftValue, rightValue); + } + + // Make a slider the user can drag to change an integer value between a min and a max. + public static int IntSlider(GUIContent label, int value, int leftValue, int rightValue, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetSliderRect(true, options); + return EditorGUI.IntSlider(r, label, value, leftValue, rightValue); + } + + public static void IntSlider(SerializedProperty property, int leftValue, int rightValue, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetSliderRect(false, options); + EditorGUI.IntSlider(r, property, leftValue, rightValue, property.displayName); + } + + public static void IntSlider(SerializedProperty property, int leftValue, int rightValue, string label, params GUILayoutOption[] options) + { + IntSlider(property, leftValue, rightValue, EditorGUIUtility.TempContent(label), options); + } + + // Make a slider the user can drag to change an integer value between a min and a max. + public static void IntSlider(SerializedProperty property, int leftValue, int rightValue, GUIContent label, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetSliderRect(true, options); + EditorGUI.IntSlider(r, property, leftValue, rightValue, label); + } + + public static void MinMaxSlider(ref float minValue, ref float maxValue, float minLimit, float maxLimit, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetSliderRect(false, options); + EditorGUI.MinMaxSlider(r, ref minValue, ref maxValue, minLimit, maxLimit); + } + + public static void MinMaxSlider(string label, ref float minValue, ref float maxValue, float minLimit, float maxLimit, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetSliderRect(true, options); + EditorGUI.MinMaxSlider(r, label, ref minValue, ref maxValue, minLimit, maxLimit); + } + + // Make a special slider the user can use to specify a range between a min and a max. + public static void MinMaxSlider(GUIContent label, ref float minValue, ref float maxValue, float minLimit, float maxLimit, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetSliderRect(true, options); + EditorGUI.MinMaxSlider(r, label, ref minValue, ref maxValue, minLimit, maxLimit); + } + + public static int Popup(int selectedIndex, string[] displayedOptions, params GUILayoutOption[] options) + { + return Popup(selectedIndex, displayedOptions, EditorStyles.popup, options); + } + + public static int Popup(int selectedIndex, string[] displayedOptions, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.Popup(r, selectedIndex, displayedOptions, style); + } + + public static int Popup(int selectedIndex, GUIContent[] displayedOptions, params GUILayoutOption[] options) + { + return Popup(selectedIndex, displayedOptions, EditorStyles.popup, options); + } + + public static int Popup(int selectedIndex, GUIContent[] displayedOptions, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.Popup(r, selectedIndex, displayedOptions, style); + } + + public static int Popup(string label, int selectedIndex, string[] displayedOptions, params GUILayoutOption[] options) + { + return Popup(label, selectedIndex, displayedOptions, EditorStyles.popup, options); + } + + public static int Popup(GUIContent label, int selectedIndex, string[] displayedOptions, params GUILayoutOption[] options) + { + return Popup(label, selectedIndex, displayedOptions, EditorStyles.popup, options); + } + + public static int Popup(string label, int selectedIndex, string[] displayedOptions, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.Popup(r, label, selectedIndex, displayedOptions, style); + } + + public static int Popup(GUIContent label, int selectedIndex, GUIContent[] displayedOptions, params GUILayoutOption[] options) + { + return Popup(label, selectedIndex, displayedOptions, EditorStyles.popup, options); + } + + // Make a generic popup selection field. + public static int Popup(GUIContent label, int selectedIndex, GUIContent[] displayedOptions, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.Popup(r, label, selectedIndex, displayedOptions, style); + } + + internal static int Popup(GUIContent label, int selectedIndex, string[] displayedOptions, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.Popup(r, label, selectedIndex, displayedOptions, style); + } + + internal static void Popup(SerializedProperty property, GUIContent[] displayedOptions, params GUILayoutOption[] options) + { + Popup(property, displayedOptions, null, options); + } + + internal static void Popup(SerializedProperty property, GUIContent[] displayedOptions, GUIContent label, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.popup, options); + EditorGUI.Popup(r, property, displayedOptions, label); + } + + public static Enum EnumPopup(Enum selected, params GUILayoutOption[] options) + { + return EnumPopup(selected, EditorStyles.popup, options); + } + + public static Enum EnumPopup(Enum selected, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.EnumPopup(r, selected, style); + } + + public static Enum EnumPopup(string label, Enum selected, params GUILayoutOption[] options) + { + return EnumPopup(label, selected, EditorStyles.popup, options); + } + + public static Enum EnumPopup(string label, Enum selected, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.EnumPopup(r, GUIContent.Temp(label), selected, null, false, style); + } + + public static Enum EnumPopup(GUIContent label, Enum selected, params GUILayoutOption[] options) + { + return EnumPopup(label, selected, EditorStyles.popup, options); + } + + // Make an enum popup selection field. + public static Enum EnumPopup(GUIContent label, Enum selected, GUIStyle style, params GUILayoutOption[] options) + { + return EnumPopup(label, selected, null, false, style, options); + } + + public static Enum EnumPopup(GUIContent label, Enum selected, Func checkEnabled, bool includeObsolete, params GUILayoutOption[] options) + { + return EnumPopup(label, selected, checkEnabled, includeObsolete, EditorStyles.popup, options); + } + + public static Enum EnumPopup(GUIContent label, Enum selected, Func checkEnabled, bool includeObsolete, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.EnumPopup(r, label, selected, checkEnabled, includeObsolete, style); + } + + public static int IntPopup(int selectedValue, string[] displayedOptions, int[] optionValues, params GUILayoutOption[] options) + { + return IntPopup(selectedValue, displayedOptions, optionValues, EditorStyles.popup, options); + } + + public static int IntPopup(int selectedValue, string[] displayedOptions, int[] optionValues, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.IntPopup(r, selectedValue, displayedOptions, optionValues, style); + } + + public static int IntPopup(int selectedValue, GUIContent[] displayedOptions, int[] optionValues, params GUILayoutOption[] options) + { + return IntPopup(selectedValue, displayedOptions, optionValues, EditorStyles.popup, options); + } + + public static int IntPopup(int selectedValue, GUIContent[] displayedOptions, int[] optionValues, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.IntPopup(r, GUIContent.none, selectedValue, displayedOptions, optionValues, style); + } + + public static int IntPopup(string label, int selectedValue, string[] displayedOptions, int[] optionValues, params GUILayoutOption[] options) + { + return IntPopup(label, selectedValue, displayedOptions, optionValues, EditorStyles.popup, options); + } + + public static int IntPopup(string label, int selectedValue, string[] displayedOptions, int[] optionValues, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.IntPopup(r, label, selectedValue, displayedOptions, optionValues, style); + } + + public static int IntPopup(GUIContent label, int selectedValue, GUIContent[] displayedOptions, int[] optionValues, params GUILayoutOption[] options) + { + return IntPopup(label, selectedValue, displayedOptions, optionValues, EditorStyles.popup, options); + } + + // Make an integer popup selection field. + public static int IntPopup(GUIContent label, int selectedValue, GUIContent[] displayedOptions, int[] optionValues, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.IntPopup(r, label, selectedValue, displayedOptions, optionValues, style); + } + + public static void IntPopup(SerializedProperty property, GUIContent[] displayedOptions, int[] optionValues, params GUILayoutOption[] options) + { + IntPopup(property, displayedOptions, optionValues, null, options); + } + + // Make an integer popup selection field. + public static void IntPopup(SerializedProperty property, GUIContent[] displayedOptions, int[] optionValues, GUIContent label, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.popup, options); + EditorGUI.IntPopup(r, property, displayedOptions, optionValues, label); + } + + [Obsolete("This function is obsolete and the style is not used.")] + public static void IntPopup(SerializedProperty property, GUIContent[] displayedOptions, int[] optionValues, GUIContent label, GUIStyle style, params GUILayoutOption[] options) + { + IntPopup(property, displayedOptions, optionValues, label, options); + } + + public static string TagField(string tag, params GUILayoutOption[] options) + { + return TagField(tag, EditorStyles.popup, options); + } + + public static string TagField(string tag, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.TagField(r, tag, style); + } + + public static string TagField(string label, string tag, params GUILayoutOption[] options) + { + return TagField(EditorGUIUtility.TempContent(label), tag, EditorStyles.popup, options); + } + + public static string TagField(string label, string tag, GUIStyle style, params GUILayoutOption[] options) + { + return TagField(EditorGUIUtility.TempContent(label), tag, style, options); + } + + public static string TagField(GUIContent label, string tag, params GUILayoutOption[] options) + { + return TagField(label, tag, EditorStyles.popup, options); + } + + // Make a tag selection field. + public static string TagField(GUIContent label, string tag, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.TagField(r, label, tag, style); + } + + public static int LayerField(int layer, params GUILayoutOption[] options) + { + return LayerField(layer, EditorStyles.popup, options); + } + + public static int LayerField(int layer, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.LayerField(r, layer, style); + } + + public static int LayerField(string label, int layer, params GUILayoutOption[] options) + { + return LayerField(EditorGUIUtility.TempContent(label), layer, EditorStyles.popup, options); + } + + public static int LayerField(string label, int layer, GUIStyle style, params GUILayoutOption[] options) + { + return LayerField(EditorGUIUtility.TempContent(label), layer, style, options); + } + + public static int LayerField(GUIContent label, int layer, params GUILayoutOption[] options) + { + return LayerField(label, layer, EditorStyles.popup, options); + } + + // Make a layer selection field. + public static int LayerField(GUIContent label, int layer, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.LayerField(r, label, layer, style); + } + + public static int MaskField(GUIContent label, int mask, string[] displayedOptions, GUIStyle style, params GUILayoutOption[] options) + { + var r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.MaskField(r, label, mask, displayedOptions, style); + } + + public static int MaskField(string label, int mask, string[] displayedOptions, GUIStyle style, params GUILayoutOption[] options) + { + var r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.MaskField(r, label, mask, displayedOptions, style); + } + + public static int MaskField(GUIContent label, int mask, string[] displayedOptions, params GUILayoutOption[] options) + { + var r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.popup, options); + return EditorGUI.MaskField(r, label, mask, displayedOptions, EditorStyles.popup); + } + + public static int MaskField(string label, int mask, string[] displayedOptions, params GUILayoutOption[] options) + { + var r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.popup, options); + return EditorGUI.MaskField(r, label, mask, displayedOptions, EditorStyles.popup); + } + + public static int MaskField(int mask, string[] displayedOptions, GUIStyle style, params GUILayoutOption[] options) + { + var r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.MaskField(r, mask, displayedOptions, style); + } + + // Make a field for masks. + public static int MaskField(int mask, string[] displayedOptions, params GUILayoutOption[] options) + { + var r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.popup, options); + return EditorGUI.MaskField(r, mask, displayedOptions, EditorStyles.popup); + } + + public static Enum EnumFlagsField(Enum enumValue, params GUILayoutOption[] options) + { + return EnumFlagsField(enumValue, EditorStyles.popup, options); + } + + public static Enum EnumFlagsField(Enum enumValue, GUIStyle style, params GUILayoutOption[] options) + { + var position = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.EnumFlagsField(position, enumValue, style); + } + + public static Enum EnumFlagsField(string label, Enum enumValue, params GUILayoutOption[] options) + { + return EnumFlagsField(label, enumValue, EditorStyles.popup, options); + } + + public static Enum EnumFlagsField(string label, Enum enumValue, GUIStyle style, params GUILayoutOption[] options) + { + return EnumFlagsField(EditorGUIUtility.TempContent(label), enumValue, style, options); + } + + public static Enum EnumFlagsField(GUIContent label, Enum enumValue, params GUILayoutOption[] options) + { + return EnumFlagsField(label, enumValue, EditorStyles.popup, options); + } + + public static Enum EnumFlagsField(GUIContent label, Enum enumValue, GUIStyle style, params GUILayoutOption[] options) + { + var position = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.EnumFlagsField(position, label, enumValue, style); + } + + public static Enum EnumFlagsField(GUIContent label, Enum enumValue, bool includeObsolete, params GUILayoutOption[] options) + { + return EnumFlagsField(label, enumValue, includeObsolete, EditorStyles.popup, options); + } + + public static Enum EnumFlagsField(GUIContent label, Enum enumValue, bool includeObsolete, GUIStyle style, params GUILayoutOption[] options) + { + var position = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.EnumFlagsField(position, label, enumValue, includeObsolete, style); + } + + [Obsolete("Check the docs for the usage of the new parameter 'allowSceneObjects'.")] + public static Object ObjectField(Object obj, Type objType, params GUILayoutOption[] options) + { + return ObjectField(obj, objType, true, options); + } + + public static Object ObjectField(Object obj, Type objType, Object targetBeingEdited, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, options); + return EditorGUI.ObjectField(r, obj, objType, targetBeingEdited); + } + + public static Object ObjectField(Object obj, Type objType, bool allowSceneObjects, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, options); + return EditorGUI.ObjectField(r, obj, objType, allowSceneObjects); + } + + [Obsolete("Check the docs for the usage of the new parameter 'allowSceneObjects'.")] + public static Object ObjectField(string label, Object obj, Type objType, params GUILayoutOption[] options) + { + return ObjectField(label, obj, objType, true, options); + } + + public static Object ObjectField(string label, Object obj, Type objType, Object targetBeingEdited, params GUILayoutOption[] options) + { + return ObjectField(EditorGUIUtility.TempContent(label), obj, objType, targetBeingEdited, options); + } + + public static Object ObjectField(string label, Object obj, Type objType, bool allowSceneObjects, params GUILayoutOption[] options) + { + return ObjectField(EditorGUIUtility.TempContent(label), obj, objType, allowSceneObjects, options); + } + + [Obsolete("Check the docs for the usage of the new parameter 'allowSceneObjects'.")] + public static Object ObjectField(GUIContent label, Object obj, Type objType, params GUILayoutOption[] options) + { + return ObjectField(label, obj, objType, true, options); + } + + // Make an object field. You can assign objects either by drag'n drop objects or by selecting an object using the Object Picker. + public static Object ObjectField(GUIContent label, Object obj, Type objType, Object targetBeingEdited, params GUILayoutOption[] options) + { + var height = EditorGUIUtility.HasObjectThumbnail(objType) ? EditorGUI.kObjectFieldThumbnailHeight : EditorGUI.kSingleLineHeight; + Rect r = s_LastRect = GetControlRect(true, height, options); + return EditorGUI.ObjectField(r, label, obj, objType, targetBeingEdited); + } + + // Make an object field. You can assign objects either by drag'n drop objects or by selecting an object using the Object Picker. + public static Object ObjectField(GUIContent label, Object obj, Type objType, bool allowSceneObjects, params GUILayoutOption[] options) + { + var height = EditorGUIUtility.HasObjectThumbnail(objType) ? EditorGUI.kObjectFieldThumbnailHeight : EditorGUI.kSingleLineHeight; + Rect r = s_LastRect = GetControlRect(true, height, options); + return EditorGUI.ObjectField(r, label, obj, objType, allowSceneObjects); + } + + public static void ObjectField(SerializedProperty property, params GUILayoutOption[] options) + { + ObjectField(property, (GUIContent)null, options); + } + + public static void ObjectField(SerializedProperty property, GUIContent label, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.objectField, options); + EditorGUI.ObjectField(r, property, label); + } + + public static void ObjectField(SerializedProperty property, Type objType, params GUILayoutOption[] options) + { + ObjectField(property, objType, null, options); + } + + // Make an object field. You can assign objects either by drag'n drop objects or by selecting an object using the Object Picker. + public static void ObjectField(SerializedProperty property, Type objType, GUIContent label, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.objectField, options); + EditorGUI.ObjectField(r, property, objType, label); + } + + internal static void ObjectField(SerializedProperty property, Type objType, GUIContent label, EditorGUI.ObjectFieldValidator validator, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.objectField, options); + EditorGUI.ObjectField(r, property, objType, label, EditorStyles.objectField, validator); + } + + internal static Object MiniThumbnailObjectField(GUIContent label, Object obj, Type objType, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, options); + return EditorGUI.MiniThumbnailObjectField(r, label, obj, objType); + } + + public static Vector2 Vector2Field(string label, Vector2 value, params GUILayoutOption[] options) + { + return Vector2Field(EditorGUIUtility.TempContent(label), value, options); + } + + // Make an X & Y field for entering a [[Vector2]]. + public static Vector2 Vector2Field(GUIContent label, Vector2 value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.GetPropertyHeight(SerializedPropertyType.Vector2, label), EditorStyles.numberField, options); + return EditorGUI.Vector2Field(r, label, value); + } + + public static Vector3 Vector3Field(string label, Vector3 value, params GUILayoutOption[] options) + { + return Vector3Field(EditorGUIUtility.TempContent(label), value, options); + } + + // Make an X, Y & Z field for entering a [[Vector3]]. + public static Vector3 Vector3Field(GUIContent label, Vector3 value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.GetPropertyHeight(SerializedPropertyType.Vector3, label), EditorStyles.numberField, options); + return EditorGUI.Vector3Field(r, label, value); + } + + // Make an X, Y & Z field for entering a [[Vector3]], with a "lock" + internal static Vector3 LinkedVector3Field(GUIContent label, Vector3 value, ref bool proportionalScale, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.GetPropertyHeight(SerializedPropertyType.Vector3, label), EditorStyles.numberField, options); + return EditorGUI.LinkedVector3Field(r, label, value, ref proportionalScale); + } + + // Make an X, Y & Z field for entering a [[Vector3]], with a "lock" + internal static Vector3 LinkedVector3Field(GUIContent label, Vector3 value, Vector3 initialValue, ref bool proportionalScale, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.GetPropertyHeight(SerializedPropertyType.Vector3, label), EditorStyles.numberField, options); + int axisModified = 0;// Use X as default modified axis + return EditorGUI.LinkedVector3Field(r, label, GUIContent.none, value, ref proportionalScale, initialValue, 0, ref axisModified, null); + } + + // Make an X, Y, Z & W field for entering a [[Vector4]]. + public static Vector4 Vector4Field(string label, Vector4 value, params GUILayoutOption[] options) + { + return Vector4Field(EditorGUIUtility.TempContent(label), value, options); + } + + // Make an X, Y, Z & W field for entering a [[Vector4]]. + public static Vector4 Vector4Field(GUIContent label, Vector4 value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.GetPropertyHeight(SerializedPropertyType.Vector4, label), EditorStyles.numberField, options); + return EditorGUI.Vector4Field(r, label, value); + } + + public static Vector2Int Vector2IntField(string label, Vector2Int value, params GUILayoutOption[] options) + { + return Vector2IntField(EditorGUIUtility.TempContent(label), value, options); + } + + // Make an X & Y field for entering a [[Vector2Int]]. + public static Vector2Int Vector2IntField(GUIContent label, Vector2Int value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.GetPropertyHeight(SerializedPropertyType.Vector2Int, label), EditorStyles.numberField, options); + return EditorGUI.Vector2IntField(r, label, value); + } + + public static Vector3Int Vector3IntField(string label, Vector3Int value, params GUILayoutOption[] options) + { + return Vector3IntField(EditorGUIUtility.TempContent(label), value, options); + } + + // Make an X, Y & Z field for entering a [[Vector3Int]]. + public static Vector3Int Vector3IntField(GUIContent label, Vector3Int value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.GetPropertyHeight(SerializedPropertyType.Vector3Int, label), EditorStyles.numberField, options); + return EditorGUI.Vector3IntField(r, label, value); + } + + public static Rect RectField(Rect value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.GetPropertyHeight(SerializedPropertyType.Rect, GUIContent.none), EditorStyles.numberField, options); + return EditorGUI.RectField(r, value); + } + + public static Rect RectField(string label, Rect value, params GUILayoutOption[] options) + { + return RectField(EditorGUIUtility.TempContent(label), value, options); + } + + // Make an X, Y, W & H field for entering a [[Rect]]. + public static Rect RectField(GUIContent label, Rect value, params GUILayoutOption[] options) + { + bool hasLabel = EditorGUI.LabelHasContent(label); + float height = EditorGUI.GetPropertyHeight(SerializedPropertyType.Rect, label); + Rect r = s_LastRect = GetControlRect(hasLabel, height, EditorStyles.numberField, options); + return EditorGUI.RectField(r, label, value); + } + + public static RectInt RectIntField(RectInt value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.GetPropertyHeight(SerializedPropertyType.RectInt, GUIContent.none), EditorStyles.numberField, options); + return EditorGUI.RectIntField(r, value); + } + + public static RectInt RectIntField(string label, RectInt value, params GUILayoutOption[] options) + { + return RectIntField(EditorGUIUtility.TempContent(label), value, options); + } + + // Make an X, Y, W & H field for entering a [[RectInt]]. + public static RectInt RectIntField(GUIContent label, RectInt value, params GUILayoutOption[] options) + { + bool hasLabel = EditorGUI.LabelHasContent(label); + float height = EditorGUI.GetPropertyHeight(SerializedPropertyType.RectInt, label); + Rect r = s_LastRect = GetControlRect(hasLabel, height, EditorStyles.numberField, options); + return EditorGUI.RectIntField(r, label, value); + } + + public static Bounds BoundsField(Bounds value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.GetPropertyHeight(SerializedPropertyType.Bounds, GUIContent.none), EditorStyles.numberField, options); + return EditorGUI.BoundsField(r, value); + } + + public static Bounds BoundsField(string label, Bounds value, params GUILayoutOption[] options) + { + return BoundsField(EditorGUIUtility.TempContent(label), value, options); + } + + // Make Center & Extents field for entering a [[Bounds]]. + public static Bounds BoundsField(GUIContent label, Bounds value, params GUILayoutOption[] options) + { + bool hasLabel = EditorGUI.LabelHasContent(label); + float height = EditorGUI.GetPropertyHeight(SerializedPropertyType.Bounds, label); + Rect r = s_LastRect = GetControlRect(hasLabel, height, EditorStyles.numberField, options); + return EditorGUI.BoundsField(r, label, value); + } + + public static BoundsInt BoundsIntField(BoundsInt value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.GetPropertyHeight(SerializedPropertyType.BoundsInt, GUIContent.none), EditorStyles.numberField, options); + return EditorGUI.BoundsIntField(r, value); + } + + public static BoundsInt BoundsIntField(string label, BoundsInt value, params GUILayoutOption[] options) + { + return BoundsIntField(EditorGUIUtility.TempContent(label), value, options); + } + + // Make Center & Extents field for entering a [[BoundsInt]]. + public static BoundsInt BoundsIntField(GUIContent label, BoundsInt value, params GUILayoutOption[] options) + { + bool hasLabel = EditorGUI.LabelHasContent(label); + float height = EditorGUI.GetPropertyHeight(SerializedPropertyType.BoundsInt, label); + Rect r = s_LastRect = GetControlRect(hasLabel, height, EditorStyles.numberField, options); + return EditorGUI.BoundsIntField(r, label, value); + } + + // Make a property field that look like a multi property field (but is made up of individual properties) + internal static void PropertiesField(GUIContent label, SerializedProperty[] properties, GUIContent[] propertyLabels, float propertyLabelsWidth, params GUILayoutOption[] options) + { + bool hasLabel = EditorGUI.LabelHasContent(label); + float height = EditorGUI.kSingleLineHeight * properties.Length + EditorGUI.kVerticalSpacingMultiField * (properties.Length - 1); + Rect r = s_LastRect = GetControlRect(hasLabel, height, EditorStyles.numberField, options); + EditorGUI.PropertiesField(r, label, properties, propertyLabels, propertyLabelsWidth); + } + + internal static int CycleButton(int selected, GUIContent[] contents, GUIStyle style, params GUILayoutOption[] options) + { + if (GUILayout.Button(contents[selected], style, options)) + { + selected++; + if (selected >= contents.Length) + selected = 0; + } + return selected; + } + + public static Color ColorField(Color value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); + return EditorGUI.ColorField(r, value); + } + + public static Color ColorField(string label, Color value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); + return EditorGUI.ColorField(r, label, value); + } + + // Make a field for selecting a [[Color]]. + public static Color ColorField(GUIContent label, Color value, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); + return EditorGUI.ColorField(r, label, value); + } + +#pragma warning disable 612 + [Obsolete("Use EditorGUILayout.ColorField(GUIContent label, Color value, bool showEyedropper, bool showAlpha, bool hdr, params GUILayoutOption[] options)")] + public static Color ColorField( + GUIContent label, Color value, bool showEyedropper, bool showAlpha, bool hdr, ColorPickerHDRConfig hdrConfig, params GUILayoutOption[] options + ) + { + return ColorField(label, value, showEyedropper, showAlpha, hdr); + } + +#pragma warning restore 612 + + public static Color ColorField(GUIContent label, Color value, bool showEyedropper, bool showAlpha, bool hdr, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); + return EditorGUI.ColorField(r, label, value, showEyedropper, showAlpha, hdr); + } + + public static AnimationCurve CurveField(AnimationCurve value, params GUILayoutOption[] options) + { + // TODO Change style + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); + return EditorGUI.CurveField(r, value); + } + + public static AnimationCurve CurveField(string label, AnimationCurve value, params GUILayoutOption[] options) + { + // TODO Change style + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); + return EditorGUI.CurveField(r, label, value); + } + + public static AnimationCurve CurveField(GUIContent label, AnimationCurve value, params GUILayoutOption[] options) + { + // TODO Change style + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); + return EditorGUI.CurveField(r, label, value); + } + + // Variants with settings + public static AnimationCurve CurveField(AnimationCurve value, Color color, Rect ranges, params GUILayoutOption[] options) + { + // TODO Change style + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); + return EditorGUI.CurveField(r, value, color, ranges); + } + + public static AnimationCurve CurveField(string label, AnimationCurve value, Color color, Rect ranges, params GUILayoutOption[] options) + { + // TODO Change style + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); + return EditorGUI.CurveField(r, label, value, color, ranges); + } + + // Make a field for editing an [[AnimationCurve]]. + public static AnimationCurve CurveField(GUIContent label, AnimationCurve value, Color color, Rect ranges, params GUILayoutOption[] options) + { + // TODO Change style + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); + return EditorGUI.CurveField(r, label, value, color, ranges); + } + + public static void CurveField(SerializedProperty property, Color color, Rect ranges, params GUILayoutOption[] options) + { + CurveField(property, color, ranges, null, options); + } + + // Make a field for editing an [[AnimationCurve]]. + public static void CurveField(SerializedProperty property, Color color, Rect ranges, GUIContent label, params GUILayoutOption[] options) + { + // TODO Change style + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, EditorStyles.colorField, options); + EditorGUI.CurveField(r, property, color, ranges, label); + } + + public static bool InspectorTitlebar(bool foldout, Object targetObj) + { + return InspectorTitlebar(foldout, targetObj, true); + } + + public static bool InspectorTitlebar(bool foldout, Object targetObj, bool expandable) + { + return EditorGUI.InspectorTitlebar(GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.inspectorTitlebar), foldout, + targetObj, expandable); + } + + // Make an inspector-window-like titlebar. + public static bool InspectorTitlebar(bool foldout, Object[] targetObjs) + { + return InspectorTitlebar(foldout, targetObjs, true); + } + + public static bool InspectorTitlebar(bool foldout, Object[] targetObjs, bool expandable) + { + return EditorGUI.InspectorTitlebar(GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.inspectorTitlebar), foldout, + targetObjs, expandable); + } + + public static bool InspectorTitlebar(bool foldout, Editor editor) + { + return EditorGUI.InspectorTitlebar(GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.inspectorTitlebar), foldout, + editor); + } + + public static void InspectorTitlebar(Object[] targetObjs) + { + EditorGUI.InspectorTitlebar(GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.inspectorTitlebar), targetObjs); + } + + // Make a foldout with a toggle and title + internal static bool ToggleTitlebar(bool foldout, GUIContent label, ref bool toggleValue) + { + return EditorGUI.ToggleTitlebar(GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.inspectorTitlebar), label, foldout, ref toggleValue); + } + + internal static bool ToggleTitlebar(bool foldout, GUIContent label, SerializedProperty property) + { + bool toggleValue = property.boolValue; + EditorGUI.BeginChangeCheck(); + foldout = EditorGUI.ToggleTitlebar(GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.inspectorTitlebar), label, foldout, ref toggleValue); + if (EditorGUI.EndChangeCheck()) + property.boolValue = toggleValue; + + return foldout; + } + + internal static bool FoldoutTitlebar(bool foldout, GUIContent label, bool skipIconSpacing) + { + return FoldoutTitlebar(foldout, label, skipIconSpacing, EditorStyles.inspectorTitlebar, EditorStyles.inspectorTitlebarText); + } + + internal static bool FoldoutTitlebar(bool foldout, GUIContent label, bool skipIconSpacing, GUIStyle baseStyle, GUIStyle textStyle) + { + return EditorGUI.FoldoutTitlebar(GUILayoutUtility.GetRect(GUIContent.none, baseStyle, GUILayout.ExpandWidth(true)), label, foldout, skipIconSpacing, baseStyle, textStyle); + } + + // Make a label with a foldout arrow to the left of it. + internal static bool FoldoutInternal(bool foldout, GUIContent content, bool toggleOnLabelClick, GUIStyle style) + { + Rect r = s_LastRect = GUILayoutUtility.GetRect(EditorGUIUtility.fieldWidth, EditorGUIUtility.fieldWidth, EditorGUI.kSingleLineHeight, EditorGUI.kSingleLineHeight, style); + return EditorGUI.Foldout(r, foldout, content, toggleOnLabelClick, style); + } + + internal static uint LayerMaskField(UInt32 layers, GUIContent label, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, options); + return EditorGUI.LayerMaskField(r, layers, label); + } + + internal static LayerMask LayerMaskField(LayerMask layers, GUIContent label, params GUILayoutOption[] options) + { + var rect = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, options); + return EditorGUI.LayerMaskField(rect, layers, label); + } + + internal static void LayerMaskField(SerializedProperty property, GUIContent label, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(true, EditorGUI.kSingleLineHeight, options); + EditorGUI.LayerMaskField(r, property, label); + } + + public static void HelpBox(string message, MessageType type) + { + LabelField(GUIContent.none, EditorGUIUtility.TempContent(message, EditorGUIUtility.GetHelpIcon(type)), EditorStyles.helpBox); + } + + // Make a help box with a message to the user. + public static void HelpBox(string message, MessageType type, bool wide) + { + LabelField(wide ? GUIContent.none : EditorGUIUtility.blankContent, + EditorGUIUtility.TempContent(message, EditorGUIUtility.GetHelpIcon(type)), + EditorStyles.helpBox); + } + + // Make a help box with a message to the user. + public static void HelpBox(GUIContent content, bool wide = true) + { + LabelField(wide ? GUIContent.none : EditorGUIUtility.blankContent, + content, + EditorStyles.helpBox); + } + + // Make a label in front of some control. + internal static void PrefixLabelInternal(GUIContent label, GUIStyle followingStyle, GUIStyle labelStyle) + { + float p = followingStyle.margin.left; + if (!EditorGUI.LabelHasContent(label)) + { + GUILayoutUtility.GetRect(EditorGUI.indent - p, EditorGUI.kSingleLineHeight, followingStyle, GUILayout.ExpandWidth(false)); + return; + } + + Rect r = GUILayoutUtility.GetRect(EditorGUIUtility.labelWidth - p, EditorGUI.kSingleLineHeight, followingStyle, GUILayout.ExpandWidth(false)); + r.xMin += EditorGUI.indent; + EditorGUI.HandlePrefixLabel(r, r, label, 0, labelStyle); + } + + // Make a small space between the previous control and the following. + public static void Space() + { + Space(EditorGUI.kDefaultSpacing, true); + } + + public static void Space(float width) + { + Space(width, true); + } + + public static void Space(float width, bool expand) + { + GUILayoutUtility.GetRect(width, width, GUILayout.ExpandWidth(expand)); + } + + //[System.Obsolete ("Use Space() instead")] + // Make this function Obsolete when someone has time to _rename_ all + // the Standard Packages to Space(), as currently it shows tons of + // warnings. + // Same for the graphic tests. + // *undoc* + public static void Separator() + { + Space(); + } + + public class ToggleGroupScope : GUI.Scope + { + public bool enabled { get; protected set; } + + public ToggleGroupScope(string label, bool toggle) + { + enabled = BeginToggleGroup(label, toggle); + } + + public ToggleGroupScope(GUIContent label, bool toggle) + { + enabled = BeginToggleGroup(label, toggle); + } + + protected override void CloseScope() + { + EndToggleGroup(); + } + } + + public static bool BeginToggleGroup(string label, bool toggle) + { + return BeginToggleGroup(EditorGUIUtility.TempContent(label), toggle); + } + + // Begin a vertical group with a toggle to enable or disable all the controls within at once. + public static bool BeginToggleGroup(GUIContent label, bool toggle) + { + toggle = ToggleLeft(label, toggle, EditorStyles.boldLabel); + EditorGUI.BeginDisabled(!toggle); + GUILayout.BeginVertical(); + + return toggle; + } + + // Close a group started with ::ref::BeginToggleGroup + public static void EndToggleGroup() + { + GUILayout.EndVertical(); + EditorGUI.EndDisabled(); + } + + public class HorizontalScope : GUI.Scope + { + public Rect rect { get; protected set; } + + public HorizontalScope(params GUILayoutOption[] options) + { + rect = BeginHorizontal(options); + } + + public HorizontalScope(GUIStyle style, params GUILayoutOption[] options) + { + rect = BeginHorizontal(style, options); + } + + internal HorizontalScope(GUIContent content, GUIStyle style, params GUILayoutOption[] options) + { + rect = BeginHorizontal(content, style, options); + } + + protected override void CloseScope() + { + EndHorizontal(); + } + } + + public static Rect BeginHorizontal(params GUILayoutOption[] options) + { + return BeginHorizontal(GUIContent.none, GUIStyle.none, options); + } + + // Begin a horizontal group and get its rect back. + public static Rect BeginHorizontal(GUIStyle style, params GUILayoutOption[] options) + { + return BeginHorizontal(GUIContent.none, style, options); + } + + // public static Rect BeginHorizontal (string text, params GUILayoutOption[] options) { return BeginHorizontal (EditorGUIUtility.TempContent (text), GUIStyle.none, options); } + // public static Rect BeginHorizontal (Texture image, params GUILayoutOption[] options) { return BeginHorizontal (EditorGUIUtility.TempContent (image), GUIStyle.none, options); } + // public static Rect BeginHorizontal (GUIContent content, params GUILayoutOption[] options) { return BeginHorizontal (content, GUIStyle.none, options); } + // public static Rect BeginHorizontal (string text, GUIStyle style, params GUILayoutOption[] options) { return BeginHorizontal (EditorGUIUtility.TempContent (text), style, options); } + // public static Rect BeginHorizontal (Texture image, GUIStyle style, params GUILayoutOption[] options) { return BeginHorizontal (EditorGUIUtility.TempContent (image), style, options); } + internal static Rect BeginHorizontal(GUIContent content, GUIStyle style, params GUILayoutOption[] options) + { + GUILayoutGroup g = GUILayoutUtility.BeginLayoutGroup(style, options, typeof(GUILayoutGroup)); + g.isVertical = false; + if (style != GUIStyle.none || content != GUIContent.none) + { + GUI.Box(g.rect, GUIContent.none, style); + } + return g.rect; + } + + // Close a group started with BeginHorizontal + public static void EndHorizontal() + { + GUILayout.EndHorizontal(); + } + + public class VerticalScope : GUI.Scope + { + public Rect rect { get; protected set; } + + public VerticalScope(params GUILayoutOption[] options) + { + rect = BeginVertical(options); + } + + public VerticalScope(GUIStyle style, params GUILayoutOption[] options) + { + rect = BeginVertical(style, options); + } + + internal VerticalScope(GUIContent content, GUIStyle style, params GUILayoutOption[] options) + { + rect = BeginVertical(content, style, options); + } + + protected override void CloseScope() + { + EndVertical(); + } + } + + public static Rect BeginVertical(params GUILayoutOption[] options) + { + return BeginVertical(GUIContent.none, GUIStyle.none, options); + } + + // Begin a vertical group and get its rect back. + public static Rect BeginVertical(GUIStyle style, params GUILayoutOption[] options) + { + return BeginVertical(GUIContent.none, style, options); + } + + // public static Rect BeginVertical (string text, params GUILayoutOption[] options) { return BeginVertical (EditorGUIUtility.TempContent (text), GUIStyle.none, options); } + // public static Rect BeginVertical (Texture image, params GUILayoutOption[] options) { return BeginVertical (EditorGUIUtility.TempContent (image), GUIStyle.none, options); } + // public static Rect BeginVertical (GUIContent content, params GUILayoutOption[] options) { return BeginVertical (content, GUIStyle.none, options); } + // public static Rect BeginVertical (string text, GUIStyle style, params GUILayoutOption[] options) { return BeginVertical (EditorGUIUtility.TempContent (text), style, options); } + // public static Rect BeginVertical (Texture image, GUIStyle style, params GUILayoutOption[] options) { return BeginVertical (EditorGUIUtility.TempContent (image), style, options); } + internal static Rect BeginVertical(GUIContent content, GUIStyle style, params GUILayoutOption[] options) + { + GUILayoutGroup g = GUILayoutUtility.BeginLayoutGroup(style, options, typeof(GUILayoutGroup)); + g.isVertical = true; + if (style != GUIStyle.none || content != GUIContent.none) + { + GUI.Box(g.rect, GUIContent.none, style); + } + return g.rect; + } + + // Close a group started with BeginVertical + public static void EndVertical() + { + GUILayout.EndVertical(); + } + + public class ScrollViewScope : GUI.Scope + { + public Vector2 scrollPosition { get; protected set; } + public bool handleScrollWheel { get; set; } + + public ScrollViewScope(Vector2 scrollPosition, params GUILayoutOption[] options) + { + handleScrollWheel = true; + this.scrollPosition = BeginScrollView(scrollPosition, options); + } + + public ScrollViewScope(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical, params GUILayoutOption[] options) + { + handleScrollWheel = true; + this.scrollPosition = BeginScrollView(scrollPosition, alwaysShowHorizontal, alwaysShowVertical, options); + } + + public ScrollViewScope(Vector2 scrollPosition, GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, params GUILayoutOption[] options) + { + handleScrollWheel = true; + this.scrollPosition = BeginScrollView(scrollPosition, horizontalScrollbar, verticalScrollbar, options); + } + + public ScrollViewScope(Vector2 scrollPosition, GUIStyle style, params GUILayoutOption[] options) + { + handleScrollWheel = true; + this.scrollPosition = BeginScrollView(scrollPosition, style, options); + } + + public ScrollViewScope(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical, GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, GUIStyle background, params GUILayoutOption[] options) + { + handleScrollWheel = true; + this.scrollPosition = BeginScrollView(scrollPosition, alwaysShowHorizontal, alwaysShowVertical, horizontalScrollbar, verticalScrollbar, background, options); + } + + internal ScrollViewScope(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical, GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, params GUILayoutOption[] options) + { + handleScrollWheel = true; + this.scrollPosition = BeginScrollView(scrollPosition, alwaysShowHorizontal, alwaysShowVertical, horizontalScrollbar, verticalScrollbar, options); + } + + protected override void CloseScope() + { + EndScrollView(handleScrollWheel); + } + } + + public static Vector2 BeginScrollView(Vector2 scrollPosition, params GUILayoutOption[] options) + { + return BeginScrollView(scrollPosition, false, false, GUI.skin.horizontalScrollbar, GUI.skin.verticalScrollbar, GUI.skin.scrollView, options); + } + + public static Vector2 BeginScrollView(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical, params GUILayoutOption[] options) + { + return BeginScrollView(scrollPosition, alwaysShowHorizontal, alwaysShowVertical, GUI.skin.horizontalScrollbar, GUI.skin.verticalScrollbar, GUI.skin.scrollView, options); + } + + public static Vector2 BeginScrollView(Vector2 scrollPosition, GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, params GUILayoutOption[] options) + { + return BeginScrollView(scrollPosition, false, false, horizontalScrollbar, verticalScrollbar, GUI.skin.scrollView, options); + } + + public static Vector2 BeginScrollView(Vector2 scrollPosition, GUIStyle style, params GUILayoutOption[] options) + { + string name = style.name; + + GUIStyle vertical = GUI.skin.FindStyle(name + "VerticalScrollbar") ?? GUI.skin.verticalScrollbar; + GUIStyle horizontal = GUI.skin.FindStyle(name + "HorizontalScrollbar") ?? GUI.skin.horizontalScrollbar; + return BeginScrollView(scrollPosition, false, false, horizontal, vertical, style, options); + } + + internal static Vector2 BeginScrollView(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical, GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, params GUILayoutOption[] options) + { + return BeginScrollView(scrollPosition, alwaysShowHorizontal, alwaysShowVertical, horizontalScrollbar, verticalScrollbar, GUI.skin.scrollView, options); + } + + // Begin an automatically layouted scrollview. + public static Vector2 BeginScrollView(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical, GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, GUIStyle background, params GUILayoutOption[] options) + { + GUIScrollGroup g = (GUIScrollGroup)GUILayoutUtility.BeginLayoutGroup(background, null, typeof(GUIScrollGroup)); + if (Event.current.type == EventType.Layout) + { + g.resetCoords = true; + g.isVertical = true; + g.stretchWidth = 1; + g.stretchHeight = 1; + g.verticalScrollbar = verticalScrollbar; + g.horizontalScrollbar = horizontalScrollbar; + g.ApplyOptions(options); + } + return EditorGUIInternal.DoBeginScrollViewForward(g.rect, scrollPosition, new Rect(0, 0, g.clientWidth, g.clientHeight), alwaysShowHorizontal, alwaysShowVertical, horizontalScrollbar, verticalScrollbar, background); + } + + internal class VerticalScrollViewScope : GUI.Scope + { + public Vector2 scrollPosition { get; protected set; } + public bool handleScrollWheel { get; set; } + + public VerticalScrollViewScope(Vector2 scrollPosition, params GUILayoutOption[] options) + { + handleScrollWheel = true; + this.scrollPosition = BeginVerticalScrollView(scrollPosition, options); + } + + public VerticalScrollViewScope(Vector2 scrollPosition, bool alwaysShowVertical, GUIStyle verticalScrollbar, GUIStyle background, params GUILayoutOption[] options) + { + handleScrollWheel = true; + this.scrollPosition = BeginVerticalScrollView(scrollPosition, alwaysShowVertical, verticalScrollbar, background, options); + } + + protected override void CloseScope() + { + EndScrollView(handleScrollWheel); + } + } + + internal static Vector2 BeginVerticalScrollView(Vector2 scrollPosition, params GUILayoutOption[] options) + { + return BeginVerticalScrollView(scrollPosition, false, GUI.skin.verticalScrollbar, GUI.skin.scrollView, options); + } + + // Begin an automatically layouted scrollview. + internal static Vector2 BeginVerticalScrollView(Vector2 scrollPosition, bool alwaysShowVertical, GUIStyle verticalScrollbar, GUIStyle background, params GUILayoutOption[] options) + { + GUIScrollGroup g = (GUIScrollGroup)GUILayoutUtility.BeginLayoutGroup(background, null, typeof(GUIScrollGroup)); + if (Event.current.type == EventType.Layout) + { + g.resetCoords = true; + g.isVertical = true; + g.stretchWidth = 1; + g.stretchHeight = 1; + g.verticalScrollbar = verticalScrollbar; + g.horizontalScrollbar = GUIStyle.none; + g.allowHorizontalScroll = false; + g.ApplyOptions(options); + } + return EditorGUIInternal.DoBeginScrollViewForward(g.rect, scrollPosition, new Rect(0, 0, g.clientWidth, g.clientHeight), false, alwaysShowVertical, GUI.skin.horizontalScrollbar, verticalScrollbar, background); + } + + internal class HorizontalScrollViewScope : GUI.Scope + { + public Vector2 scrollPosition { get; protected set; } + public bool handleScrollWheel { get; set; } + + public HorizontalScrollViewScope(Vector2 scrollPosition, params GUILayoutOption[] options) + { + handleScrollWheel = true; + this.scrollPosition = BeginHorizontalScrollView(scrollPosition, options); + } + + public HorizontalScrollViewScope(Vector2 scrollPosition, bool alwaysShowHorizontal, GUIStyle horizontalScrollbar, GUIStyle background, params GUILayoutOption[] options) + { + handleScrollWheel = true; + this.scrollPosition = BeginHorizontalScrollView(scrollPosition, alwaysShowHorizontal, horizontalScrollbar, background, options); + } + + protected override void CloseScope() + { + EndScrollView(handleScrollWheel); + } + } + + internal static Vector2 BeginHorizontalScrollView(Vector2 scrollPosition, params GUILayoutOption[] options) + { + return BeginHorizontalScrollView(scrollPosition, false, GUI.skin.horizontalScrollbar, GUI.skin.scrollView, options); + } + + // Begin an automatically layouted scrollview. + + internal static Vector2 BeginHorizontalScrollView(Vector2 scrollPosition, bool alwaysShowHorizontal, GUIStyle horizontalScrollbar, GUIStyle background, params GUILayoutOption[] options) + { + GUIScrollGroup g = (GUIScrollGroup)GUILayoutUtility.BeginLayoutGroup(background, null, typeof(GUIScrollGroup)); + if (Event.current.type == EventType.Layout) + { + g.resetCoords = true; + g.isVertical = true; + g.stretchWidth = 1; + g.stretchHeight = 1; + g.verticalScrollbar = GUIStyle.none; + g.horizontalScrollbar = horizontalScrollbar; + g.allowHorizontalScroll = true; + g.allowVerticalScroll = false; + g.ApplyOptions(options); + } + return EditorGUIInternal.DoBeginScrollViewForward(g.rect, scrollPosition, new Rect(0, 0, g.clientWidth, g.clientHeight), alwaysShowHorizontal, false, horizontalScrollbar, GUI.skin.verticalScrollbar, background); + } + + // Ends a scrollview started with a call to BeginScrollView. + public static void EndScrollView() + { + GUILayout.EndScrollView(true); + } + + internal static void EndScrollView(bool handleScrollWheel) + { + GUILayout.EndScrollView(handleScrollWheel); + } + + public static bool PropertyField(SerializedProperty property, params GUILayoutOption[] options) + { + return PropertyField(property, null, IsChildrenIncluded(property), options); + } + + public static bool PropertyField(SerializedProperty property, GUIContent label, params GUILayoutOption[] options) + { + return PropertyField(property, label, IsChildrenIncluded(property), options); + } + + public static bool PropertyField(SerializedProperty property, bool includeChildren, params GUILayoutOption[] options) + { + return PropertyField(property, null, includeChildren, options); + } + + // Make a field for [[SerializedProperty]]. + public static bool PropertyField(SerializedProperty property, GUIContent label, bool includeChildren, params GUILayoutOption[] options) + { + return ScriptAttributeUtility.GetHandler(property).OnGUILayout(property, label, includeChildren, options); + } + + private static bool IsChildrenIncluded(SerializedProperty prop) + { + switch (prop.propertyType) + { + case SerializedPropertyType.Generic: + case SerializedPropertyType.Vector4: + return true; + default: + return false; + } + } + + public static Rect GetControlRect(params GUILayoutOption[] options) + { + return GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.layerMaskField, options); + } + + public static Rect GetControlRect(bool hasLabel, params GUILayoutOption[] options) + { + return GetControlRect(hasLabel, EditorGUI.kSingleLineHeight, EditorStyles.layerMaskField, options); + } + + public static Rect GetControlRect(bool hasLabel, float height, params GUILayoutOption[] options) + { + return GetControlRect(hasLabel, height, EditorStyles.layerMaskField, options); + } + + public static Rect GetControlRect(bool hasLabel, float height, GUIStyle style, params GUILayoutOption[] options) + { + return GUILayoutUtility.GetRect( + hasLabel ? kLabelFloatMinW : EditorGUIUtility.fieldWidth, + kLabelFloatMaxW, + height, height, style, options); + } + + internal static Rect GetSliderRect(bool hasLabel, params GUILayoutOption[] options) + { + return GetSliderRect(hasLabel, GUI.skin.horizontalSlider, options); + } + + internal static Rect GetSliderRect(bool hasLabel, GUIStyle sliderStyle, params GUILayoutOption[] options) + { + return GUILayoutUtility.GetRect( + hasLabel ? kLabelFloatMinW : EditorGUIUtility.fieldWidth, + kLabelFloatMaxW + EditorGUI.kSpacing + EditorGUI.kSliderMaxW, + EditorGUI.kSingleLineHeight, EditorGUI.kSingleLineHeight, sliderStyle, options); + } + + internal static Rect GetToggleRect(bool hasLabel, params GUILayoutOption[] options) + { + // Toggle is 14 pixels wide while float fields are EditorGUIUtility.fieldWidth pixels wide. + // Store difference in variable and add to min and max width values used for float fields. + float toggleAdjust = (14 - EditorGUIUtility.fieldWidth); + return GUILayoutUtility.GetRect( + hasLabel ? kLabelFloatMinW + toggleAdjust : EditorGUIUtility.fieldWidth + toggleAdjust, + kLabelFloatMaxW + toggleAdjust, + EditorGUI.kSingleLineHeight, EditorGUI.kSingleLineHeight, EditorStyles.numberField, options); + } + + public class FadeGroupScope : GUI.Scope + { + // when using the FadeGroupScope, make sure to only show the content when 'visible' is set to true, + // otherwise only the hide animation will run, and then the content will be visible again. + public bool visible { get; protected set; } + + public FadeGroupScope(float value) + { + visible = BeginFadeGroup(value); + } + + protected override void CloseScope() + { + EndFadeGroup(); + } + } + + public static bool BeginFadeGroup(float value) + { + GUILayoutFadeGroup g = (GUILayoutFadeGroup)GUILayoutUtility.BeginLayoutGroup(GUIStyle.none, null, typeof(GUILayoutFadeGroup)); + g.isVertical = true; + g.resetCoords = false; + g.fadeValue = value; + g.wasGUIEnabled = GUI.enabled; + g.guiColor = GUI.color; + g.consideredForMargin = value > 0; + + // We don't want the fade group gui clip to be used for calculating the label width of controls in this fade group, so we lock the context width. + EditorGUIUtility.LockContextWidth(); + + if (value != 0.0f && value != 1.0f) + { + g.resetCoords = true; + GUI.BeginGroup(g.rect); + + if (Event.current.type == EventType.MouseDown) + { + Event.current.Use(); + } + } + + return value != 0; + } + + public static void EndFadeGroup() + { + // If we're inside a fade group, end it here. + GUILayoutFadeGroup g = EditorGUILayoutUtilityInternal.topLevel as GUILayoutFadeGroup; + + // If there are no more FadeGroups to end, display a warning. + if (g == null) + { + Debug.LogWarning("Unexpected call to EndFadeGroup! Make sure to call EndFadeGroup the same number of times as BeginFadeGroup."); + return; + } + + if (g.fadeValue != 0.0f && g.fadeValue != 1.0f) + { + GUI.EndGroup(); + } + + EditorGUIUtility.UnlockContextWidth(); + GUI.enabled = g.wasGUIEnabled; + GUI.color = g.guiColor; + GUILayoutUtility.EndLayoutGroup(); + } + + public static BuildTargetGroup BeginBuildTargetSelectionGrouping() + { + BuildPlatform[] validPlatforms = BuildPlatforms.instance.GetValidPlatforms().ToArray(); + int selected = BeginPlatformGrouping(validPlatforms, null); + return validPlatforms[selected].namedBuildTarget.ToBuildTargetGroup(); + } + + public static void EndBuildTargetSelectionGrouping() + { + EndPlatformGrouping(); + } + + internal static int BeginPlatformGrouping(BuildPlatform[] platforms, GUIContent defaultTab) + { + return BeginPlatformGrouping(platforms, defaultTab, EditorStyles.frameBox); + } + + static Rect GetTabRect(Rect rect, int tabIndex, int tabCount, out GUIStyle tabStyle) + { + if (s_TabOnlyOne == null) + { + // Keep in sync with Tests/EditModeAndPlayModeTests/PlayerSettings/Assets/Editor/PlayerSettingsApplicationIdentifierTests.cs. + s_TabOnlyOne = "Tab onlyOne"; + s_TabFirst = "Tab first"; + s_TabMiddle = "Tab middle"; + s_TabLast = "Tab last"; + } + + tabStyle = s_TabMiddle; + + if (tabCount == 1) + { + tabStyle = s_TabOnlyOne; + } + else if (tabIndex == 0) + { + tabStyle = s_TabFirst; + } + else if (tabIndex == (tabCount - 1)) + { + tabStyle = s_TabLast; + } + + float tabWidth = rect.width / tabCount; + int left = Mathf.RoundToInt(tabIndex * tabWidth); + int right = Mathf.RoundToInt((tabIndex + 1) * tabWidth); + return new Rect(rect.x + left, rect.y, right - left, EditorGUI.kTabButtonHeight); + } + + internal static int BeginPlatformGrouping(BuildPlatform[] platforms, GUIContent defaultTab, GUIStyle style) + { + return BeginPlatformGrouping(platforms, defaultTab, style, null); + } + + internal static int BeginPlatformGrouping(BuildPlatform[] platforms, GUIContent defaultTab, GUIStyle style, Func showOverrideForPlatform) + { + int selectedPlatform = -1; + for (int i = 0; i < platforms.Length; i++) + { + if (platforms[i].IsSelected()) + { + selectedPlatform = i; + break; + } + } + if (selectedPlatform == -1) + { + s_SelectedDefault.value = true; + selectedPlatform = 0; + } + + int selected = defaultTab == null ? selectedPlatform : (s_SelectedDefault.value ? -1 : selectedPlatform); + + bool tempEnabled = GUI.enabled; + GUI.enabled = true; + EditorGUI.BeginChangeCheck(); + Rect r = BeginVertical(style); + int platformCount = platforms.Length; + int buttonCount = platformCount; + int startIndex = 0; + + if (defaultTab != null) + { + buttonCount++; + startIndex = -1; + } + + int buttonIndex = 0; + for (int i = startIndex; i < platformCount; i++, buttonIndex++) + { + GUIContent content = GUIContent.none; + + if (i == -1) + { + content = defaultTab; + } + else + { + content = new GUIContent(platforms[i].smallIcon, platforms[i].tooltip); + } + + GUIStyle buttonStyle = null; + Rect buttonRect = GetTabRect(r, buttonIndex, buttonCount, out buttonStyle); + + if (GUI.Toggle(buttonRect, selected == i, content, buttonStyle)) + selected = i; + if (showOverrideForPlatform != null) + { + if (showOverrideForPlatform(i)) + { + var prevMargin = EditorGUIUtility.leftMarginCoord; + var overrideRect = buttonRect; + const int margin = 3; + overrideRect.y += margin; + overrideRect.height -= margin * 2; + EditorGUIUtility.leftMarginCoord = overrideRect.x + margin; + EditorGUI.DrawOverrideBackgroundApplicable(overrideRect); + EditorGUIUtility.leftMarginCoord = prevMargin; + } + } + } + + // GUILayout.Space doesn't expand to available width, so use GetRect instead + GUILayoutUtility.GetRect(10, EditorGUI.kTabButtonHeight); + + GUI.enabled = tempEnabled; + + // Important that we only actually set the selectedBuildTargetGroup if the user clicked the button. + // If the current selectedBuildTargetGroup is one that is not among the tabs (because the build target + // is not supported), then this should not be changed unless the user explicitly does so. + // Otherwise, if the build window is open at the same time, the unsupported build target groups will + // not be selectable in the build window. + if (EditorGUI.EndChangeCheck()) + { + if (defaultTab == null) + { + platforms[selected].Select(); + } + else + { + if (selected < 0) + { + s_SelectedDefault.value = true; + } + else + { + platforms[selected].Select(); + s_SelectedDefault.value = false; + } + } + + // Repaint build window, if open. + Object[] buildWindows = Resources.FindObjectsOfTypeAll(typeof(BuildPlayerWindow)); + foreach (Object t in buildWindows) + { + BuildPlayerWindow buildWindow = t as BuildPlayerWindow; + if (buildWindow != null) + buildWindow.Repaint(); + } + } + + return selected; + } + + internal static void EndPlatformGrouping() + { + EndVertical(); + } + + internal static void MultiSelectionObjectTitleBar(Object[] objects) + { + string text = objects[0].name + " (" + ObjectNames.NicifyVariableName(ObjectNames.GetTypeName(objects[0])) + ")"; + if (objects.Length > 1) + { + text += " and " + (objects.Length - 1) + " other" + (objects.Length > 2 ? "s" : ""); + } + GUILayoutOption[] options = { GUILayout.Height(16f) }; + GUILayout.Label(EditorGUIUtility.TempContent(text, AssetPreview.GetMiniThumbnail(objects[0])), EditorStyles.boldLabel, options); + } + + // Returns true if specified bit is true for all targets + internal static bool BitToggleField(string label, SerializedProperty bitFieldProperty, int flag) + { + bool toggle = (bitFieldProperty.intValue & flag) != 0; + bool different = (bitFieldProperty.hasMultipleDifferentValuesBitwise & flag) != 0; + EditorGUI.showMixedValue = different; + EditorGUI.BeginChangeCheck(); + toggle = Toggle(label, toggle); + if (EditorGUI.EndChangeCheck()) + { + // If toggle has mixed values, always set all to true when clicking it + if (different) + { + toggle = true; + } + different = false; + int bitIndex = -1; + for (int i = 0; i < 32; i++) + { + if (((1 << i) & flag) != 0) + { + bitIndex = i; + break; + } + } + bitFieldProperty.SetBitAtIndexForAllTargetsImmediate(bitIndex, toggle); + } + EditorGUI.showMixedValue = false; + return toggle && !different; + } + + internal static void SortingLayerField(GUIContent label, SerializedProperty layerID, GUIStyle style) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style); + EditorGUI.SortingLayerField(r, label, layerID, style, EditorStyles.label); + } + + internal static string TextFieldDropDown(string text, string[] dropDownElement) + { + return TextFieldDropDown(GUIContent.none, text, dropDownElement); + } + + internal static string TextFieldDropDown(GUIContent label, string text, string[] dropDownElement) + { + Rect rect = GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.textField); + return EditorGUI.TextFieldDropDown(rect, label, text, dropDownElement); + } + + internal static string DelayedTextFieldDropDown(string text, string[] dropDownElement) + { + return DelayedTextFieldDropDown(GUIContent.none, text, dropDownElement); + } + + internal static string DelayedTextFieldDropDown(GUIContent label, string text, string[] dropDownElement) + { + Rect rect = GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.textFieldDropDownText); + return EditorGUI.DelayedTextFieldDropDown(rect, label, text, dropDownElement); + } + + // A button that returns true on mouse down - like a popup button + public static bool DropdownButton(GUIContent content, FocusType focusType, params GUILayoutOption[] options) + { + return DropdownButton(content, focusType, "MiniPullDown", options); + } + + // A button that returns true on mouse down - like a popup button + public static bool DropdownButton(GUIContent content, FocusType focusType, GUIStyle style, params GUILayoutOption[] options) + { + s_LastRect = GUILayoutUtility.GetRect(content, style, options); + return EditorGUI.DropdownButton(s_LastRect, content, focusType, style); + } + + // A toggle that returns true on mouse down - like a popup button and returns true if checked + internal static bool DropDownToggle(ref bool toggled, GUIContent content, GUIStyle toggleStyle) + { + GUIStyle buttonStyle = GUIStyle.none; + + // This is to be compatible with existing code + if (toggleStyle == EditorStyles.toolbarDropDownToggle || toggleStyle == EditorStyles.toolbarDropDownToggleRight) + buttonStyle = EditorStyles.toolbarDropDownToggleButton; + + return DropDownToggle(ref toggled, content, toggleStyle, buttonStyle); + } + + internal static bool DropDownToggle(ref bool toggled, GUIContent content, GUIStyle toggleStyle, GUIStyle toggleDropdownButtonStyle) + { + Rect toggleRect = GUILayoutUtility.GetRect(content, toggleStyle); + Rect arrowRightRect = Rect.zero; + + if (toggleDropdownButtonStyle != null) + { + arrowRightRect = new Rect(toggleRect.xMax - toggleDropdownButtonStyle.fixedWidth - toggleDropdownButtonStyle.margin.right, toggleRect.y, toggleDropdownButtonStyle.fixedWidth, toggleRect.height); + } + else + { + arrowRightRect = new Rect(toggleRect.xMax - toggleStyle.padding.right, toggleRect.y, toggleStyle.padding.right, toggleRect.height); + } + + + int dropdownButtonId = GUIUtility.GetControlID(EditorGUI.s_DropdownButtonHash, FocusType.Passive, arrowRightRect); + bool clicked = EditorGUI.DropdownButton(dropdownButtonId, arrowRightRect, GUIContent.none, GUIStyle.none); + + if (!clicked) + { + toggled = GUI.Toggle(toggleRect, toggled, content, toggleStyle); + } + + // Ensure that the dropdown button is rendered on top of the toggle + if (Event.current.type == EventType.Repaint && toggleDropdownButtonStyle != null && toggleDropdownButtonStyle != GUIStyle.none) + { + EditorGUI.DropdownButton(dropdownButtonId, arrowRightRect, GUIContent.none, toggleDropdownButtonStyle); + } + + return clicked; + } + + internal static int AdvancedPopup(int selectedIndex, string[] displayedOptions, params GUILayoutOption[] options) + { + return AdvancedPopup(selectedIndex, displayedOptions, "MiniPullDown", options); + } + + internal static int AdvancedPopup(int selectedIndex, string[] displayedOptions, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.AdvancedPopup(r, selectedIndex, displayedOptions, style); + } + + internal static int AdvancedLazyPopup(string displayedOption, int selectedIndex, Func> displayedOptionsFunc, GUIStyle style, params GUILayoutOption[] options) + { + Rect r = s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); + return EditorGUI.AdvancedLazyPopup(r, displayedOption, selectedIndex, displayedOptionsFunc, style); + } + + [Obsolete("(UnityUpgradable) -> UnityEditor.HyperLinkClickedEventArgs", true)] + internal class HyperLinkClickedEventArgs + { + [Obsolete("(UnityUpgradable) -> UnityEditor.HyperLinkClickedEventArgs.hyperLinkData", true)] + public Dictionary hyperlinkInfos { get; private set; } + internal HyperLinkClickedEventArgs(Dictionary hyperLinkData) {} + } +} diff --git a/Editor/Mono/EditorGUIUtility.bindings.cs b/Editor/Mono/EditorGUIUtility.bindings.cs index 1d03dc2d7e..5df1992ebd 100644 --- a/Editor/Mono/EditorGUIUtility.bindings.cs +++ b/Editor/Mono/EditorGUIUtility.bindings.cs @@ -151,6 +151,10 @@ public static void RenderGameViewCameras(RenderTexture target, int targetDisplay internal static extern void RenderPlayModeViewCamerasInternal(RenderTexture target, int targetDisplay, Vector2 mousePosition, bool gizmos, bool renderIMGUI); internal static extern void SetupWindowSpaceAndVSyncInternal(Rect screenRect); + internal static extern void PerformTonemappingForGameView(); + internal static extern void DrawTextureHdrSupport(Rect screenRect, Texture texture, Rect sourceRect, int leftBorder, + int rightBorder, int topBorder, int bottomBorder, Color color, Material mat, int pass, bool resetLinearToSrgbIfHdrActive); + private static extern Texture2D FindTextureByName(string name); private static extern Texture2D FindTextureByType([NotNull] Type type); internal static extern string GetObjectNameWithInfo(Object obj); @@ -204,6 +208,6 @@ [ExcludeFromDocs] public SessionState() {} public static extern void EraseVector3(string key); public static extern void EraseIntArray(string key); public static extern void SetIntArray(string key, int[] value); - public static extern int[] GetIntArray(string key, int[] defaultValue); + public static extern int[] GetIntArray(string key, [Unmarshalled] int[] defaultValue); } } diff --git a/Editor/Mono/EditorGUIUtility.cs b/Editor/Mono/EditorGUIUtility.cs index dd1ccb1a5d..d94d54efec 100644 --- a/Editor/Mono/EditorGUIUtility.cs +++ b/Editor/Mono/EditorGUIUtility.cs @@ -18,6 +18,7 @@ using UnityEditor.StyleSheets; using UnityEditor.Experimental; using UnityEditor.SceneManagement; +using UnityEngine.Pool; using UnityEngine.UIElements; using UnityObject = UnityEngine.Object; @@ -513,7 +514,6 @@ internal static Texture2D FindTexture(Type type) return FindTextureByType(type); } - [ExcludeFromDocs] public static GUIContent TrTextContent(string key, string text, string tooltip, Texture icon) { GUIContent gc = (GUIContent)s_GUIContents[key]; @@ -533,14 +533,12 @@ public static GUIContent TrTextContent(string key, string text, string tooltip, return gc; } - [ExcludeFromDocs] public static GUIContent TrTextContent(string text, string tooltip = null, Texture icon = null) { string key = string.Format("{0}|{1}", text ?? "", tooltip ?? ""); return TrTextContent(key, text, tooltip, icon); } - [ExcludeFromDocs] public static GUIContent TrTextContent(string text, string tooltip, string iconName) { string key = iconName == null ? string.Format("{0}|{1}", text ?? "", tooltip ?? "") : @@ -548,43 +546,36 @@ public static GUIContent TrTextContent(string text, string tooltip, string iconN return TrTextContent(key, text, tooltip, LoadIconRequired(iconName)); } - [ExcludeFromDocs] public static GUIContent TrTextContent(string text, Texture icon) { return TrTextContent(text, null, icon); } - [ExcludeFromDocs] public static GUIContent TrTextContentWithIcon(string text, Texture icon) { return TrTextContent(text, null, icon); } - [ExcludeFromDocs] public static GUIContent TrTextContentWithIcon(string text, string iconName) { return TrTextContent(text, null, iconName); } - [ExcludeFromDocs] public static GUIContent TrTextContentWithIcon(string text, string tooltip, string iconName) { return TrTextContent(text, tooltip, iconName); } - [ExcludeFromDocs] public static GUIContent TrTextContentWithIcon(string text, string tooltip, Texture icon) { return TrTextContent(text, tooltip, icon); } - [ExcludeFromDocs] public static GUIContent TrTextContentWithIcon(string text, string tooltip, MessageType messageType) { return TrTextContent(text, tooltip, GetHelpIcon(messageType)); } - [ExcludeFromDocs] public static GUIContent TrTextContentWithIcon(string text, MessageType messageType) { return TrTextContentWithIcon(text, null, messageType); @@ -616,7 +607,6 @@ internal static Color LightenColor(Color color) return outColor; } - [ExcludeFromDocs] public static GUIContent TrIconContent(string iconName, string tooltip = null) { return TrIconContent(iconName, tooltip, false); @@ -644,7 +634,6 @@ internal static GUIContent TrIconContent(string iconName, string tooltip, bool l return gc; } - [ExcludeFromDocs] public static GUIContent TrIconContent(Texture icon, string tooltip = null) { GUIContent gc = (tooltip != null) ? (GUIContent)s_IconGUIContents[tooltip] : null; @@ -1319,6 +1308,12 @@ public static bool editingTextField set { EditorGUI.RecycledTextEditor.s_ActuallyEditing = value; } } + internal static bool renameWasCompleted + { + get { return EditorGUI.RecycledTextEditor.s_EditingWasCompleted; } + set { EditorGUI.RecycledTextEditor.s_EditingWasCompleted = value; } + } + public static bool textFieldHasSelection { get { return EditorGUI.s_RecycledEditor.hasSelection; } @@ -1416,7 +1411,7 @@ public static float labelWidth return s_LabelWidth; if (hierarchyMode) - return Mathf.Max(contextWidth * 0.45f - 40, 120); + return Mathf.Max(contextWidth * EditorGUI.kLabelWidthRatio - EditorGUI.kLabelWidthMargin, EditorGUI.kMinLabelWidth); return 150; } set { s_LabelWidth = value; } @@ -1468,6 +1463,58 @@ internal static Rect DragZoneRect(Rect position, bool hasLabel = true) return new Rect(position.x, position.y, hasLabel ? labelWidth : 0, position.height); } + internal static void MoveArrayExpandedState(SerializedProperty elements, int oldActiveElement, int newActiveElement) + { + SerializedProperty prop1 = elements.GetArrayElementAtIndex(oldActiveElement); + SerializedProperty prop2; + int depth; + List tempIsExpanded = ListPool.Get(); + var tempProp = prop1; + tempIsExpanded.Add(prop1.isExpanded); + bool clearGradientCache = false; + int next = (oldActiveElement < newActiveElement) ? 1 : -1; + + for (int i = oldActiveElement + next; + (oldActiveElement < newActiveElement) ? i <= newActiveElement : i >= newActiveElement; + i += next) + { + prop2 = elements.GetArrayElementAtIndex(i); + + var cprop1 = prop1.Copy(); + var cprop2 = prop2.Copy(); + depth = Math.Min(cprop1.depth, cprop2.depth); + while (cprop1.NextVisible(true) && cprop1.depth > depth && cprop2.NextVisible(true) && cprop2.depth > depth) + { + if (cprop1.hasVisibleChildren && cprop2.hasVisibleChildren) + { + tempIsExpanded.Add(cprop1.isExpanded); + cprop1.isExpanded = cprop2.isExpanded; + } + } + + prop1.isExpanded = prop2.isExpanded; + if (prop1.propertyType == SerializedPropertyType.Gradient) + clearGradientCache = true; + prop1 = prop2; + } + + prop1.isExpanded = tempIsExpanded[0]; + depth = Math.Min(prop1.depth, tempProp.depth); + int k = 1; + while (prop1.NextVisible(true) && prop1.depth > depth && tempProp.NextVisible(true) && tempProp.depth > depth) + { + if (prop1.hasVisibleChildren && tempProp.hasVisibleChildren && tempIsExpanded.Count > k) + { + prop1.isExpanded = tempIsExpanded[k]; + k++; + } + } + ListPool.Release(tempIsExpanded); + + if (clearGradientCache) + GradientPreviewCache.ClearCache(); + } + internal static void SetBoldDefaultFont(bool isBold) { int wantsBold = isBold ? 1 : 0; diff --git a/Editor/Mono/EditorHandles/ScaleHandle.cs b/Editor/Mono/EditorHandles/ScaleHandle.cs index e4384ad4f2..c31f782855 100644 --- a/Editor/Mono/EditorHandles/ScaleHandle.cs +++ b/Editor/Mono/EditorHandles/ScaleHandle.cs @@ -250,6 +250,8 @@ internal static Vector3 DoScaleHandle(ScaleHandleIds ids, Vector3 scale, Vector3 if (param.ShouldShow(ScaleHandleParam.Handle.XYZ) && (ids.xyz == GUIUtility.hotControl || !isHot)) { color = isProportionalScale ? constrainProportionsScaleHandleColor : ToActiveColorSpace(centerColor); + if (isDisabled) + color = Color.Lerp(color, staticColor, staticBlend); proportionalScale = false; EditorGUI.BeginChangeCheck(); s_CurrentMultiplier = ScaleValueHandle(ids.xyz, s_CurrentMultiplier, position, rotation, handleSize * param.xyzSize, CubeHandleCap, EditorSnapSettings.scale); diff --git a/Editor/Mono/EditorMode/MenuService.cs b/Editor/Mono/EditorMode/MenuService.cs index f837c1c58e..2eadd2797c 100644 --- a/Editor/Mono/EditorMode/MenuService.cs +++ b/Editor/Mono/EditorMode/MenuService.cs @@ -15,6 +15,7 @@ using static UnityEditor.ModeService; using UnityEditor.Scripting.ScriptCompilation; +using UnityEditor.ShortcutManagement; namespace UnityEditor { @@ -30,7 +31,22 @@ static class MenuService private static Dictionary> s_MenuItemsPerMode = null; // Contains menu from attributes for the default mode // The default mode menus are in a separate dictionary for performance reasons, in that case we don't need the costly MenuItemsTree structure because it won't be used when iterating through the .mode menus - private static Dictionary s_MenuItemsDefaultMode = null; + + + class GroupingMenuItemScriptCommand + { + public GroupingMenuItemScriptCommand(string menuName, MenuItemScriptCommand mi) + { + menuItem = mi; + menuPath = menuName.Substring(0, menuName.LastIndexOf('/') == -1 ? menuName.Length : menuName.LastIndexOf('/')); + } + + public MenuItemScriptCommand menuItem; + + public string menuPath; + } + + private static Dictionary s_MenuItemsDefaultMode = null; [UsedImplicitly, RequiredByNativeCode] internal static bool UseDefaultModeMenus() @@ -88,7 +104,7 @@ private static MenuItemsTree GetModeMenuTree(string mode { // if there are no menus in the mode file and we are on the default mode, we use the default modes menus directly foreach (var menuItem in s_MenuItemsDefaultMode.Values) - mit.AddChildSearch(new MenuItemOrderingNative(menuItem.name, menuItem.name, menuItem.priority, menuItem.priority)); + mit.AddChildSearch(new MenuItemOrderingNative(menuItem.menuItem.name, menuItem.menuItem.name, menuItem.menuItem.priority, menuItem.menuItem.priority, menuItem.menuItem.secondaryPriority)); return mit; } else @@ -132,6 +148,10 @@ private static void GetModeMenuTreeRecursive(MenuItemsTree menuItemsPerMode) + { + menuItemsPerMode = menuItemsPerMode + .OrderBy(m => m.Value.menuItem.Priority) + .ThenBy(m => m.Value.menuItem.SecondaryPriority) + .ThenBy(m => m.Value.menuItem.Name) + .ToDictionary(x => x.Key, x => x.Value); + } + private static void ExtractMenuItemsFromAttributes() { s_MenuItemsPerMode = new Dictionary>(); - s_MenuItemsDefaultMode = new Dictionary(); + s_MenuItemsDefaultMode = new Dictionary(); - var menuItems = TypeCache.GetMethodsWithAttribute(); + var methodInfos = TypeCache.GetMethodsWithAttribute() + .Where(m => ValidateMethodForMenuCommand(m)) + // Order the menu items to start with Unity menus before projects menus. That way if there is a duplicate, the project one is flagged as duplicate + .OrderBy(m => !Utility.FastStartsWith(m.DeclaringType.Assembly.FullName, "UnityEditor", "unityeditor")) + .ToList(); - // Order the menu items to start with Unity menus before projects menus. That way if there is a duplicate, the project one is flagged as duplicate - foreach (var methodInfo in menuItems.OrderBy(m => !Utility.FastStartsWith(m.DeclaringType.Assembly.FullName, "UnityEditor", "unityeditor"))) + foreach (var methodInfo in methodInfos) { - if (!ValidateMethodForMenuCommand(methodInfo)) - continue; - foreach (var attribute in methodInfo.GetCustomAttributes(typeof(MenuItem), false)) + foreach (var attribute in (MenuItem[])methodInfo.GetCustomAttributes(typeof(MenuItem), false)) { - string menuName = SanitizeMenuItemName(((MenuItem)attribute).menuItem); - string[] editorModes = ((MenuItem)attribute).editorModes; + string menuName = SanitizeMenuItemName(attribute.menuItem); + string[] editorModes = attribute.editorModes; foreach (var editorMode in editorModes) { if (editorMode == k_DefaultModeId) { if (s_MenuItemsDefaultMode.TryGetValue(menuName, out var menuItem)) - menuItem.Update((MenuItem)attribute, methodInfo); + menuItem.menuItem.Update(attribute, methodInfo); else - s_MenuItemsDefaultMode.Add(menuName, MenuItemScriptCommand.Initialize(menuName, (MenuItem)attribute, methodInfo)); + s_MenuItemsDefaultMode.Add(menuName, new GroupingMenuItemScriptCommand(menuName, MenuItemScriptCommand.Initialize(menuName, attribute, methodInfo))); } else { @@ -356,39 +386,42 @@ private static void ExtractMenuItemsFromAttributes() { MenuItemScriptCommand menuItem = menuItemsPerMode.FindItem(menuName); if (menuItem == null) - menuItemsPerMode.AddChildSearch(MenuItemScriptCommand.Initialize(menuName, (MenuItem)attribute, methodInfo)); + menuItemsPerMode.AddChildSearch(MenuItemScriptCommand.Initialize(menuName, attribute, methodInfo)); else - menuItem.Update((MenuItem)attribute, methodInfo); + menuItem.Update(attribute, methodInfo); } else { var newMenusPerMode = new MenuItemsTree(); - newMenusPerMode.AddChildSearch(MenuItemScriptCommand.Initialize(menuName, (MenuItem)attribute, methodInfo)); + newMenusPerMode.AddChildSearch(MenuItemScriptCommand.Initialize(menuName, attribute, methodInfo)); s_MenuItemsPerMode.Add(editorMode, newMenusPerMode); } } } } } - CleanUpInvalidMenuItems(); + + CleanUpInvalidMenuItems(ref s_MenuItemsPerMode, ref s_MenuItemsDefaultMode); + + SortMenuItems(ref s_MenuItemsDefaultMode); } - private static void CleanUpInvalidMenuItems() + private static void CleanUpInvalidMenuItems(ref Dictionary> menuItemsPerMode, ref Dictionary genericMenuItems) { - foreach (var menuItemPerMode in s_MenuItemsPerMode.Values) + foreach (var menuItemPerMode in menuItemsPerMode.Values) { menuItemPerMode.CleanUp(); } // Default mode items var itemsToDelete = new List(); - foreach (var menu in s_MenuItemsDefaultMode) + foreach (var menu in genericMenuItems) { - if (menu.Value.IsNotValid) + if (menu.Value.menuItem.IsNotValid) itemsToDelete.Add(menu.Key); } foreach (var itemToDelete in itemsToDelete) { - s_MenuItemsDefaultMode.Remove(itemToDelete); + genericMenuItems.Remove(itemToDelete); } } @@ -403,7 +436,7 @@ private static Dictionary CombineMenuItemsFromAtt menuItemsResult.Add(menuItem.name, menuItem); } // We always add the default mode menus (if not present) - AddMenuItemsFromMode(menuItemsResult, s_MenuItemsDefaultMode.Values); + AddMenuItemsFromMode(menuItemsResult, s_MenuItemsDefaultMode.Values.Select(x => x.menuItem)); // If the menus depend on the .mode file, we also add the other modes menus because the mode file can reference them if (menusDependOnModeFile) @@ -485,6 +518,7 @@ internal class MenuItemsTree private readonly string key; private T value; private readonly int m_Priority; + private readonly float m_SecondaryPriority; private readonly List> m_Children; public List menuItemChildren => GetChildrenRecursively(); @@ -500,7 +534,14 @@ private List GetChildrenRecursively(bool sorted = false, List result = nul result = new List(); if (m_Children.Any()) { - var children = sorted ? (IEnumerable>)m_Children.OrderBy(c => c.key).OrderBy(c => c.m_Priority) : m_Children; + var children = m_Children; + if (sorted) + { + children = m_Children.OrderBy(c => c.m_Priority) + .ThenBy(c => c.m_SecondaryPriority) + .ThenBy(c => c.key) + .ToList(); + } foreach (var child in children) child.GetChildrenRecursively(sorted, result); } @@ -509,14 +550,15 @@ private List GetChildrenRecursively(bool sorted = false, List result = nul return result; } - public MenuItemsTree(string key = "", int priority = 100) + public MenuItemsTree(string key = "", int priority = 100, float secondaryPriority = 0) { this.key = EditorUtility.ParseMenuName(key); m_Priority = priority; + m_SecondaryPriority = secondaryPriority == 0 ? float.MaxValue : secondaryPriority; m_Children = new List>(); } - public MenuItemsTree(T value) : this(value.Name, value.Priority) + public MenuItemsTree(T value) : this(value.Name, value.Priority, value.SecondaryPriority) { this.value = value; } @@ -529,12 +571,12 @@ private MenuItemsTree AddChildDirectly(T menuItem) return child; } - private MenuItemsTree AddIntermediateMenuItem(string pathPart, int priority) + private MenuItemsTree AddIntermediateMenuItem(string pathPart, int priority, float secondaryPriority) { string name = string.IsNullOrEmpty(key) ? pathPart : key + "/" + pathPart; - var child = new MenuItemsTree(name, priority); + var child = new MenuItemsTree(name, priority, secondaryPriority); m_Children.Add(child); return child; } @@ -563,7 +605,7 @@ public bool AddChildSearch(T menuItem) var currentMenu = this; for (int i = 0; i < pathSplit.Length - 1; ++i) { - currentMenu = currentMenu.AddIntermediateMenuItem(pathSplit[i], menuItem.Priority); + currentMenu = currentMenu.AddIntermediateMenuItem(pathSplit[i], menuItem.Priority, menuItem.SecondaryPriority); } // then add the menuItem currentMenu.AddChildDirectly(menuItem); diff --git a/Editor/Mono/EditorMode/ModeService.cs b/Editor/Mono/EditorMode/ModeService.cs index 89384e9293..def7247edb 100644 --- a/Editor/Mono/EditorMode/ModeService.cs +++ b/Editor/Mono/EditorMode/ModeService.cs @@ -97,6 +97,7 @@ public struct ModeChangedArgs public int nextIndex; } + internal const string k_ModePathsCache = "mode-paths-cache"; internal const string k_DefaultModeId = "default"; internal const string k_ModeCurrentIdKeyName = "mode-current-id"; internal const string k_MenuKeyChecked = "checked"; @@ -111,6 +112,7 @@ public struct ModeChangedArgs internal const string k_MenuKeyOriginalName = "original_name"; internal const string k_MenuKeyPlatform = "platform"; internal const string k_MenuKeyPriority = "priority"; + internal const string k_MenuKeySecondaryPriority = "secondary_priority"; internal const string k_MenuKeyShortcut = "shortcut"; internal const string k_MenuKeyValidateCommandId = "validate_command_id"; @@ -137,6 +139,7 @@ static ModeService() modeChanged += OnModeChangeMenus; modeChanged += OnModeChangeLayouts; + UnityEditor.PackageManager.Events.registeredPackages += OnRegisteredPackages; ModeDescriptorImporter.allowExplicitModeRefresh = true; } @@ -349,6 +352,12 @@ internal static bool HasStartupMode() return Application.HasARGV("editor-mode"); } + private static void OnRegisteredPackages(PackageManager.PackageRegistrationEventArgs evt) + { + SessionState.EraseString(k_ModePathsCache); + LoadModes(); + } + private static void LoadModes(bool checkStartupMode = false) { Log("LoadModes"); @@ -418,21 +427,38 @@ internal static void ScanModes() var builtinModeFile = Path.Combine(EditorApplication.applicationContentsPath, "Resources/default.mode"); FillModeData(builtinModeFile, modesData); - var modeDescriptors = AssetDatabase.EnumerateAllAssets(new SearchFilter + var modeFilePathCache = SessionState.GetString(k_ModePathsCache, ""); + + if (modeFilePathCache == "") { - searchArea = SearchFilter.SearchArea.InPackagesOnly, - classNames = new[] { nameof(ModeDescriptor) }, - showAllHits = true - }); + var modeDescriptors = AssetDatabase.EnumerateAllAssets(new SearchFilter + { + searchArea = SearchFilter.SearchArea.InPackagesOnly, + classNames = new[] { nameof(ModeDescriptor) }, + showAllHits = true + }); - while (modeDescriptors.MoveNext()) + while (modeDescriptors.MoveNext()) + { + var md = modeDescriptors.Current.pptrValue as ModeDescriptor; + if (md == null) + continue; + FillModeData(md.path, modesData); + modeFilePathCache += md.path + ";"; + } + SessionState.SetString(k_ModePathsCache, modeFilePathCache); + } + else { - var md = modeDescriptors.Current.pptrValue as ModeDescriptor; - if (md == null) - continue; - FillModeData(md.path, modesData); + var paths = modeFilePathCache.Split(";"); + foreach(var path in paths) + { + if (!File.Exists(path)) + continue; + FillModeData(path, modesData); + } } - + modes = new ModeEntry[modesData.Keys.Count]; modes[0] = CreateEntry(k_DefaultModeId, (JSONObject)modesData[k_DefaultModeId]); var modeIndex = 1; @@ -576,7 +602,6 @@ private static void BuildContextMenu(IList menus, string menuId, GenericMenu con var fullMenuName = prefix + menuName; var menuItemId = menuId + fullMenuName; var platform = JsonUtils.JsonReadString(menu, k_MenuKeyPlatform); - var hasExplicitPriority = menu.Contains(k_MenuKeyPriority); // Check the menu item platform if (!string.IsNullOrEmpty(platform) && !Application.platform.ToString().ToLowerInvariant().StartsWith(platform.ToLowerInvariant())) diff --git a/Editor/Mono/EditorResources.cs b/Editor/Mono/EditorResources.cs index 4110465c2b..2cb502c0cd 100644 --- a/Editor/Mono/EditorResources.cs +++ b/Editor/Mono/EditorResources.cs @@ -228,7 +228,7 @@ private static List GetDefaultStyleCatalogPaths() if (LocalizationDatabase.currentEditorLanguage == SystemLanguage.English) { - string currentFontStyleSheet = "StyleSheets/Extensions/fonts/" + currentFontName.ToLower() + ".uss"; + string currentFontStyleSheet = "StyleSheets/Extensions/fonts/" + currentFontName.ToLowerInvariant() + ".uss"; catalogFiles.Add(currentFontStyleSheet); } @@ -306,6 +306,10 @@ internal static void BuildCatalog() } } } + else + { + s_RefreshGlobalStyleCatalog = false; + } if (rebuildCatalog) { diff --git a/Editor/Mono/EditorSceneManager.bindings.cs b/Editor/Mono/EditorSceneManager.bindings.cs index 17b0b1a032..2b8899cdb0 100644 --- a/Editor/Mono/EditorSceneManager.bindings.cs +++ b/Editor/Mono/EditorSceneManager.bindings.cs @@ -141,6 +141,9 @@ internal static void RemapAssetReferencesInScene(UnityEngine.SceneManagement.Sce [NativeMethod("GetPreviewScenesVisibleInHierarchy")] internal extern static bool GetPreviewScenesVisibleInHierarchy(); + [StaticAccessor("EditorSceneManagerBindings", StaticAccessorType.DoubleColon)] + internal extern static Scene GetDontDestroyOnLoadScene(); + [StaticAccessor("EditorSceneManagerBindings", StaticAccessorType.DoubleColon)] [NativeMethod("IsPreviewSceneObject")] public extern static bool IsPreviewSceneObject(UnityEngine.Object obj); @@ -203,7 +206,7 @@ internal static void RemapAssetReferencesInScene(UnityEngine.SceneManagement.Sce [NativeThrows] [StaticAccessor("EditorSceneManagerBindings", StaticAccessorType.DoubleColon)] [NativeMethod("SaveScenes")] - public extern static bool SaveScenes(Scene[] scenes); + public extern static bool SaveScenes([Unmarshalled] Scene[] scenes); [NativeThrows] [StaticAccessor("EditorSceneManagerBindings", StaticAccessorType.DoubleColon)] @@ -213,7 +216,7 @@ internal static void RemapAssetReferencesInScene(UnityEngine.SceneManagement.Sce [NativeThrows] [StaticAccessor("EditorSceneManagerBindings", StaticAccessorType.DoubleColon)] [NativeMethod("SaveModifiedScenesIfUserWantsTo")] - public extern static bool SaveModifiedScenesIfUserWantsTo(Scene[] scenes); + public extern static bool SaveModifiedScenesIfUserWantsTo([Unmarshalled] Scene[] scenes); [StaticAccessor("GetSceneManager()", StaticAccessorType.Dot)] [NativeMethod("EnsureUntitledSceneHasBeenSaved")] @@ -241,7 +244,7 @@ internal static void RemapAssetReferencesInScene(UnityEngine.SceneManagement.Sce [NativeThrows] [StaticAccessor("EditorSceneManagerBindings", StaticAccessorType.DoubleColon)] [NativeMethod("RestoreSceneManagerSetup")] - public extern static void RestoreSceneManagerSetup(SceneSetup[] value); + public extern static void RestoreSceneManagerSetup([Unmarshalled] SceneSetup[] value); [StaticAccessor("EditorSceneManagerBindings", StaticAccessorType.DoubleColon)] [NativeMethod("LoadSceneManagerSetup")] diff --git a/Editor/Mono/EditorSettings.bindings.cs b/Editor/Mono/EditorSettings.bindings.cs index 8d94620a50..3c1b8354ff 100644 --- a/Editor/Mono/EditorSettings.bindings.cs +++ b/Editor/Mono/EditorSettings.bindings.cs @@ -69,6 +69,7 @@ public enum EnterPlayModeOptions None = 0, DisableDomainReload = 1 << 0, DisableSceneReload = 1 << 1, + [Obsolete("Option has no effect and is deprecated.")] DisableSceneBackupUnlessDirty = 1 << 2 } diff --git a/Editor/Mono/EditorUserBuildSettings.bindings.cs b/Editor/Mono/EditorUserBuildSettings.bindings.cs index 7bc3617bf1..9ba59f0e78 100644 --- a/Editor/Mono/EditorUserBuildSettings.bindings.cs +++ b/Editor/Mono/EditorUserBuildSettings.bindings.cs @@ -53,6 +53,7 @@ public enum PS4BuildSubtarget /// SA: EditorUserBuildSettings.ps4BuildSubtarget. Package = 1, Iso = 2, + GP4Project = 3, } @@ -373,9 +374,6 @@ private EditorUserBuildSettings() {} [NativeMethod("GetActiveSubTargetFor")] internal static extern int GetActiveSubtargetFor(BuildTarget target); - [NativeMethod("SetActiveSubTargetFor")] - internal static extern void SetActiveSubtargetFor(BuildTarget target, int subtarget); - // QNX OS Version public static extern QNXOsVersion selectedQnxOsVersion { @@ -670,7 +668,48 @@ public static extern string windowsDevicePortalAddress set; } - public static extern string windowsDevicePortalUsername + internal static string EncodeBase64(string plainText) + { + if (plainText == null) + { + plainText = string.Empty; + } + + var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText); + return Convert.ToBase64String(plainTextBytes); + } + + internal static string DecodeBase64(string base64Text) + { + if (base64Text == null) + { + base64Text = string.Empty; + } + + try + { + var base64EncodedBytes = System.Convert.FromBase64String(base64Text); + return System.Text.Encoding.UTF8.GetString(base64EncodedBytes); + } + catch (FormatException) + { + return string.Empty; + } + } + + public static string windowsDevicePortalUsername + { + get + { + return DecodeBase64(internal_windowsDevicePortalUsername); + } + set + { + internal_windowsDevicePortalUsername = EncodeBase64(value); + } + } + + private static extern string internal_windowsDevicePortalUsername { [NativeMethod("GetWindowsDevicePortalUsername")] get; @@ -679,7 +718,19 @@ public static extern string windowsDevicePortalUsername } // WDP password is not to be saved with other settings and only stored in memory until Editor is closed - public static string windowsDevicePortalPassword { get; set; } + private static string internal_windowsDevicePortalPassword; + + public static string windowsDevicePortalPassword + { + get + { + return DecodeBase64(internal_windowsDevicePortalPassword); + } + set + { + internal_windowsDevicePortalPassword = EncodeBase64(value); + } + } // *undocumented* public static extern WSABuildAndRunDeployTarget wsaBuildAndRunDeployTarget @@ -701,9 +752,14 @@ public static extern WSABuildAndRunDeployTarget wsaBuildAndRunDeployTarget internal static extern BuildTargetGroup activeBuildTargetGroup { get; } [NativeMethod("SwitchActiveBuildTargetSync")] - public static extern bool SwitchActiveBuildTarget(BuildTargetGroup targetGroup, BuildTarget target); + internal static extern bool SwitchActiveBuildTargetAndSubtarget(BuildTargetGroup targetGroup, BuildTarget target, int subtarget); + public static bool SwitchActiveBuildTarget(BuildTargetGroup targetGroup, BuildTarget target) + => SwitchActiveBuildTargetAndSubtarget(targetGroup, target, EditorUserBuildSettings.GetActiveSubtargetFor(target)); + [NativeMethod("SwitchActiveBuildTargetAsync")] - public static extern bool SwitchActiveBuildTargetAsync(BuildTargetGroup targetGroup, BuildTarget target); + internal static extern bool SwitchActiveBuildTargetAndSubtargetAsync(BuildTargetGroup targetGroup, BuildTarget target, int subtarget); + public static bool SwitchActiveBuildTargetAsync(BuildTargetGroup targetGroup, BuildTarget target) + => SwitchActiveBuildTargetAndSubtargetAsync(targetGroup, target, EditorUserBuildSettings.GetActiveSubtargetFor(target)); public static bool SwitchActiveBuildTarget(NamedBuildTarget namedBuildTarget, BuildTarget target) => BuildPlatforms.instance.BuildPlatformFromNamedBuildTarget(namedBuildTarget).SetActive(target); @@ -712,7 +768,9 @@ public static bool SwitchActiveBuildTarget(NamedBuildTarget namedBuildTarget, Bu // validating if support for it is installed. However it does not do things like script recompile // or domain reload -- generally only useful for asset import testing. [NativeMethod("SwitchActiveBuildTargetSyncNoCheck")] - internal static extern bool SwitchActiveBuildTargetNoCheck(BuildTargetGroup targetGroup, BuildTarget target); + internal static extern bool SwitchActiveBuildTargetAndSubtargetNoCheck(BuildTargetGroup targetGroup, BuildTarget target, int subtarget); + internal static bool SwitchActiveBuildTargetNoCheck(BuildTargetGroup targetGroup, BuildTarget target) + => SwitchActiveBuildTargetAndSubtargetNoCheck(targetGroup, target, EditorUserBuildSettings.GetActiveSubtargetFor(target)); // DEFINE directives for the compiler. public static extern string[] activeScriptCompilationDefines @@ -888,6 +946,15 @@ public static extern bool switchNVNShaderDebugging set; } + // Enable shader debugging using NVN Graphics Debugger + public static extern bool switchNVNAftermath + { + [NativeMethod("GetNVNAftermath")] + get; + [NativeMethod("SetNVNAftermath")] + set; + } + // Enable debug validation of NVN drawcalls [Obsolete("switchNVNDrawValidation is deprecated, use switchNVNDrawValidation_Heavy instead.")] public static bool switchNVNDrawValidation @@ -912,12 +979,21 @@ public static extern bool switchNVNDrawValidation_Heavy set; } - // Enable linkage of the Heap inspector tool for Nintendo Switch. - public static extern bool switchEnableHeapInspector + // Enable linkage of the Memory Tracker tool for Nintendo Switch. + public static extern bool switchEnableMemoryTracker { - [NativeMethod("GetEnableHeapInspectorForSwitch")] + [NativeMethod("GetEnableMemoryTrackerForSwitch")] get; - [NativeMethod("SetEnableHeapInspectorForSwitch")] + [NativeMethod("SetEnableMemoryTrackerForSwitch")] + set; + } + + // On startup the application waits for Memory Tracker to connect. + public static extern bool switchWaitForMemoryTrackerOnStartup + { + [NativeMethod("GetWaitForSwitchMemoryTrackerOnStartup")] + get; + [NativeMethod("SetWaitForSwitchMemoryTrackerOnStartup")] set; } @@ -956,6 +1032,14 @@ public static extern bool switchUseLegacyNvnPoolAllocator set; } + public static extern bool switchEnableUnpublishableErrors + { + [NativeMethod("GetEnableUnpublishableErrorsForSwitch")] + get; + [NativeMethod("SetEnableUnpublishableErrorsForSwitch")] + set; + } + internal static extern SwitchShaderCompilerConfig switchShaderCompilerConfig { [NativeMethod("GetSwitchShaderCompilerConfig")] diff --git a/Editor/Mono/EditorUserBuildSettings.deprecated.cs b/Editor/Mono/EditorUserBuildSettings.deprecated.cs index 7a39ebd89f..ccb773f466 100644 --- a/Editor/Mono/EditorUserBuildSettings.deprecated.cs +++ b/Editor/Mono/EditorUserBuildSettings.deprecated.cs @@ -32,39 +32,29 @@ internal static void Internal_ActiveBuildTargetChanged() [Obsolete("forceOptimizeScriptCompilation is obsolete - will always return false. Control script optimization using the 'IL2CPP optimization level' configuration in Player Settings / Other.")] public static bool forceOptimizeScriptCompilation { get { return false; } } - [Obsolete(@"androidDebugMinification is obsolete. Use PlayerSettings.Android.minifyDebug and PlayerSettings.Android.minifyWithR8.")] + [Obsolete(@"androidDebugMinification is obsolete. Use PlayerSettings.Android.minifyDebug")] public static AndroidMinification androidDebugMinification { get { - if (PlayerSettings.Android.minifyDebug) - { - return PlayerSettings.Android.minifyWithR8 ? AndroidMinification.Gradle : AndroidMinification.Proguard; - } - return AndroidMinification.None; + return PlayerSettings.Android.minifyDebug ? AndroidMinification.Gradle : AndroidMinification.None; } set { PlayerSettings.Android.minifyDebug = value != AndroidMinification.None; - PlayerSettings.Android.minifyWithR8 = value == AndroidMinification.Gradle; } } - [Obsolete(@"androidReleaseMinification is obsolete. Use PlayerSettings.Android.minifyRelease and PlayerSettings.Android.minifyWithR8.")] + [Obsolete(@"androidReleaseMinification is obsolete. Use PlayerSettings.Android.minifyRelease")] public static AndroidMinification androidReleaseMinification { get { - if (PlayerSettings.Android.minifyRelease) - { - return PlayerSettings.Android.minifyWithR8 ? AndroidMinification.Gradle : AndroidMinification.Proguard; - } - return AndroidMinification.None; + return PlayerSettings.Android.minifyRelease ? AndroidMinification.Gradle : AndroidMinification.None; } set { PlayerSettings.Android.minifyRelease = value != AndroidMinification.None; - PlayerSettings.Android.minifyWithR8 = value == AndroidMinification.Gradle; } } } diff --git a/Editor/Mono/EditorUserBuildSettingsUtils.cs b/Editor/Mono/EditorUserBuildSettingsUtils.cs index 16f66b64e6..3c493bc56b 100644 --- a/Editor/Mono/EditorUserBuildSettingsUtils.cs +++ b/Editor/Mono/EditorUserBuildSettingsUtils.cs @@ -108,7 +108,7 @@ public static bool SetActive(this BuildPlatform buildPlatform, BuildTarget targe } if (buildPlatform is BuildPlatformWithSubtarget) - EditorUserBuildSettings.SetActiveSubtargetFor(target, ((BuildPlatformWithSubtarget)buildPlatform).subtarget); + return EditorUserBuildSettings.SwitchActiveBuildTargetAndSubtarget(buildPlatform.namedBuildTarget.ToBuildTargetGroup(), target, ((BuildPlatformWithSubtarget)buildPlatform).subtarget); return EditorUserBuildSettings.SwitchActiveBuildTarget(buildPlatform.namedBuildTarget.ToBuildTargetGroup(), target); } diff --git a/Editor/Mono/EditorUserSettings.bindings.cs b/Editor/Mono/EditorUserSettings.bindings.cs index 0c964cee83..6b3db0efa7 100644 --- a/Editor/Mono/EditorUserSettings.bindings.cs +++ b/Editor/Mono/EditorUserSettings.bindings.cs @@ -61,6 +61,9 @@ public static string GetConfigValue(string name) [NativeProperty("VCAllowAsyncUpdate")] public static extern bool allowAsyncStatusUpdate { get; set; } + [NativeProperty("VCScanLocalPackagesOnConnect")] + public static extern bool scanLocalPackagesOnConnect { get; set; } + [NativeProperty("VCDebugCmd")] internal static extern bool DebugCmd { get; set; } diff --git a/Editor/Mono/EditorUtility.bindings.cs b/Editor/Mono/EditorUtility.bindings.cs index 2f50f701e5..a00261dc34 100644 --- a/Editor/Mono/EditorUtility.bindings.cs +++ b/Editor/Mono/EditorUtility.bindings.cs @@ -34,19 +34,26 @@ public static string OpenFilePanelWithFilters(string title, string directory, st public static extern void RevealInFinder(string path); [FreeFunction("DisplayDialog")] - static extern bool DoDisplayDialog(string title, string message, string ok, [DefaultValue("\"\"")] string cancel); + static extern bool DisplayDialogImpl(string title, string message, string ok, string cancel); public static bool DisplayDialog(string title, string message, string ok, [DefaultValue("\"\"")] string cancel) { + // i am not sure how picky should we be about the params + // for example, for the buttons we have code that ignores empty strings, + // to allow having "ok"-only dialogs (information panels, so to say) + // same with title+message: it sounds like we should allow skipping one of those + // hence we make sure that at least one button is present, and some message + // we can go more picky if we want in the future + if(string.IsNullOrEmpty(ok) && string.IsNullOrEmpty(cancel)) + throw new ArgumentException("Both 'ok' and 'cancel' strings are null or empty"); + if(string.IsNullOrEmpty(title) && string.IsNullOrEmpty(message)) + throw new ArgumentException("Both 'title' and 'message' strings are null or empty"); + using (new DisabledGuiViewInputScope(GUIView.current, true)) { - return DoDisplayDialog(title, message, ok, cancel); + return DisplayDialogImpl(title, message, ok, cancel); } } - - [FreeFunction("GetDialogResponse")] - internal static extern bool GetDialogResponse(InteractionContext interactionContext, string title, string message, string ok, [DefaultValue("\"\"")] string cancel); - [ExcludeFromDocs] public static bool DisplayDialog(string title, string message, string ok) { @@ -54,9 +61,22 @@ public static bool DisplayDialog(string title, string message, string ok) } [FreeFunction("DisplayDialogComplex")] - public static extern int DisplayDialogComplex(string title, string message, string ok, string cancel, string alt); - [FreeFunction("GetDialogResponseComplex")] - internal static extern int GetDialogResponseComplex(InteractionContext interactionContext, string title, string message, string ok, string cancel, string alt); + static extern int DisplayDialogComplexImpl(string title, string message, string ok, string cancel, string alt); + + public static int DisplayDialogComplex(string title, string message, string ok, string cancel, string alt) + { + // see the comment above in DisplayDialog + // our implementation allows setting some strings empty (the button will be skipped then) + // but we should totally ensure some buttons are set + + if(string.IsNullOrEmpty(ok) && string.IsNullOrEmpty(cancel) && string.IsNullOrEmpty(alt)) + throw new ArgumentException("All three 'ok', 'cancel' and 'alt' strings are null or empty"); + if(string.IsNullOrEmpty(title) && string.IsNullOrEmpty(message)) + throw new ArgumentException("Both 'title' and 'message' strings are null or empty"); + + return DisplayDialogComplexImpl(title, message, ok, cancel, alt); + } + [FreeFunction("RunOpenFolderPanel")] @@ -73,7 +93,9 @@ public static bool DisplayDialog(string title, string message, string ok) public extern static bool IsUnityExtensionsInitialized(); public static extern bool IsPersistent(Object target); + public static extern bool IsValidUnityYAML(string yaml); public static extern string SaveFilePanel(string title, string directory, string defaultName, string extension); + [ThreadSafe] public static extern int NaturalCompare(string a, string b); public static extern Object InstanceIDToObject(int instanceID); public static extern void CompressTexture([NotNull] Texture2D texture, TextureFormat format, int quality); @@ -114,8 +136,8 @@ internal static void RemapAssetReferences(UnityEngine.Object[] objects, Dictiona private static extern void InternalCopySerializedIfDifferent([NotNull("NullExceptionObject")] Object source, [NotNull("NullExceptionObject")] Object dest); [NativeThrows] - public static extern Object[] CollectDependencies(Object[] roots); - public static extern Object[] CollectDeepHierarchy(Object[] roots); + public static extern Object[] CollectDependencies([Unmarshalled] Object[] roots); + public static extern Object[] CollectDeepHierarchy([Unmarshalled] Object[] roots); [FreeFunction("InstantiateObjectRemoveAllNonAnimationComponents")] private static extern Object Internal_InstantiateRemoveAllNonAnimationComponentsSingle([NotNull("NullExceptionObject")] Object data, Vector3 pos, Quaternion rot); diff --git a/Editor/Mono/EditorUtility.cs b/Editor/Mono/EditorUtility.cs index a5b7a3fc5b..013ee38ba4 100644 --- a/Editor/Mono/EditorUtility.cs +++ b/Editor/Mono/EditorUtility.cs @@ -80,7 +80,7 @@ public static string GetDialogOptOutMessage(DialogOptOutDecisionType dialogOptOu public static bool LoadWindowLayout(string path) { - return WindowLayout.LoadWindowLayout(path, false); + return WindowLayout.TryLoadWindowLayout(path, false); } public static void CompressTexture(Texture2D texture, TextureFormat format, TextureCompressionQuality quality) @@ -327,11 +327,11 @@ internal static bool IsHiddenInInspector(Editor editor) if (!editor || editor.hideInspector) return true; - if (editor.inspectorMode != InspectorMode.Normal) + // Check for missing scripts or check is serializedObject can be created. + if (editor.target == null && editor.serializedObject?.FindProperty("m_Script") != null) return false; - // Check for missing scripts - if (editor.target == null && editor.serializedObject?.FindProperty("m_Script") != null) + if (editor.inspectorMode != InspectorMode.Normal) return false; return IsHiddenInInspector(editor.target); @@ -459,12 +459,19 @@ internal static void DisplayCustomMenuWithSeparators(Rect position, string[] opt DisplayCustomMenuWithSeparators(position, options, enabled, separator, selected, callback, userData, showHotkey, false); } + + //This method is only valid during onGUI callbacks, prefer DisplayCustomMenuWithSeparatorsWithScreenSpacePosition that work al the time. internal static void DisplayCustomMenuWithSeparators(Rect position, string[] options, bool[] enabled, bool[] separator, int[] selected, SelectMenuItemFunction callback, object userData, bool showHotkey, bool allowDisplayNames, bool shouldDiscardMenuOnSecondClick = false) { Vector2 temp = GUIUtility.GUIToScreenPoint(new Vector2(position.x, position.y)); position.x = temp.x; position.y = temp.y; + DisplayCustomMenuWithSeparatorsWithScreenSpacePosition(position, options, enabled, separator, selected, callback, userData, showHotkey, allowDisplayNames, shouldDiscardMenuOnSecondClick); + } + + internal static void DisplayCustomMenuWithSeparatorsWithScreenSpacePosition(Rect position, string[] options, bool[] enabled, bool[] separator, int[] selected, SelectMenuItemFunction callback, object userData, bool showHotkey, bool allowDisplayNames, bool shouldDiscardMenuOnSecondClick) + { Internal_DisplayCustomMenu(position, options, enabled, separator, selected, callback, userData, showHotkey, allowDisplayNames, shouldDiscardMenuOnSecondClick); ResetMouseDown(); } @@ -506,10 +513,17 @@ internal static void DisplayObjectContextMenu(Rect position, Object[] context, i info.instanceObject = targetComponent; info.assetPath = AssetDatabase.GetAssetPath(sourceGo); GameObject rootObject = PrefabUtility.GetRootGameObject(sourceGo); - if (!PrefabUtility.IsPartOfPrefabThatCanBeAppliedTo(rootObject) || EditorUtility.IsPersistent(instanceGo)) + + if (targetComponent.hideFlags.HasFlag(HideFlags.DontSaveInEditor) + || !PrefabUtility.IsPartOfPrefabThatCanBeAppliedTo(rootObject) + || EditorUtility.IsPersistent(instanceGo)) + { pm.AddDisabledItem(menuItemContent); + } else + { pm.AddItem(menuItemContent, false, TargetChoiceHandler.ApplyPrefabAddedComponent, info); + } }, (menuItemContent) => { @@ -567,7 +581,7 @@ internal static void DisplayObjectContextMenu(Rect position, Object[] context, i GameObject rootObject = PrefabUtility.GetRootGameObject(sourceObject); bool isPersistent = EditorUtility.IsPersistent(instanceOrAssetObject); - if (!PrefabUtility.IsPartOfPrefabThatCanBeAppliedTo(rootObject) || (!isPersistent && !PrefabUtility.HasApplicableObjectOverridesForTarget(instanceOrAssetObject, rootObject, false))) + if (isPersistent || !PrefabUtility.IsPartOfPrefabThatCanBeAppliedTo(rootObject) || !PrefabUtility.HasApplicableObjectOverridesForTarget(instanceOrAssetObject, rootObject, false)) pm.AddDisabledItem(menuItemContent); else pm.AddItem(menuItemContent, false, TargetChoiceHandler.ApplyPrefabObjectOverride, info); diff --git a/Editor/Mono/EditorWindow.cs b/Editor/Mono/EditorWindow.cs index d840b0221e..9b5ae596c1 100644 --- a/Editor/Mono/EditorWindow.cs +++ b/Editor/Mono/EditorWindow.cs @@ -40,10 +40,20 @@ public partial class EditorWindow : ScriptableObject [HideInInspector] int m_AntiAliasing = 1; + [HideInInspector] + bool m_ResetPanelRenderingOnAssetChange = true; + [SerializeField] [HideInInspector] internal Rect m_Pos = new Rect(0, 0, 320, 550); + [SerializeField] + [HideInInspector] + internal DataModeController m_SerializedDataModeController; + public IDataModeController dataModeController => GetDataModeController_Internal(); // For each editor window. + internal DataModeController GetDataModeController_Internal() // For HostView to use internally. + => m_SerializedDataModeController ??= new DataModeController(); + private VisualElement m_UIRootElement; internal VisualElement baseRootVisualElement => m_UIRootElement == null @@ -71,6 +81,7 @@ public VisualElement rootVisualElement } internal virtual bool liveReloadPreferenceDefault => false; + internal bool isUIToolkitWindow => m_UIRootElement != null && m_UIRootElement.childCount > 0; [HideInInspector] [SerializeField] @@ -105,6 +116,8 @@ internal SerializableJsonDictionary viewDataDictionary } } + internal static List activeEditorWindows { get; } = new List(); + internal void SaveViewData() { m_RequestedViewDataSave = true; @@ -140,6 +153,8 @@ internal void ClearPersistentViewData() m_ViewDataDictionary = null; } + [NonSerialized] internal bool m_IsPresented = false; + // The GameView rect is in GUI space of the view Rect m_GameViewRect; Rect m_GameViewClippedRect; @@ -232,6 +247,8 @@ internal void CheckForWindowRepaint() Repaint(); } + internal CustomYieldInstruction WaitUntilPresented() => new WaitUntil(() => m_IsPresented); + internal GUIContent GetLocalizedTitleContent() { return GetLocalizedTitleContentFromType(GetType()); @@ -248,7 +265,7 @@ internal static GUIContent GetLocalizedTitleContentFromType(Type t) else if (attr.useTypeNameAsIconName) iconName = t.ToString(); - if (!string.IsNullOrEmpty(iconName)) + if (!string.IsNullOrEmpty(iconName) && EditorGUIUtility.LoadIcon(iconName)) { // This should error msg if icon is not found since icon has been explicitly requested by the user return EditorGUIUtility.TrTextContentWithIcon(attr.title, iconName); @@ -420,8 +437,7 @@ static public EditorWindow mouseOverWindow return null; } } - - + internal int GetNumTabs() { DockArea da = m_Parent as DockArea; @@ -492,6 +508,7 @@ internal void MakeParentsSettingsMatchMe() bool parentChanged = m_Parent.depthBufferBits != m_DepthBufferBits || m_Parent.antiAliasing != m_AntiAliasing; m_Parent.depthBufferBits = m_DepthBufferBits; m_Parent.antiAliasing = m_AntiAliasing; + m_Parent.resetPanelRenderingOnAssetChange = m_ResetPanelRenderingOnAssetChange; m_Parent.SetInternalGameViewDimensions(m_GameViewRect, m_GameViewClippedRect, m_GameViewTargetSize); m_Parent.eventInterests = m_EventInterests; m_Parent.disableInputEvents = m_DisableInputEvents; @@ -694,6 +711,26 @@ public void ShowModal() } } + static void AssignTitle(EditorWindow win, string title) + { + if (title != null) + { + win.titleContent = new GUIContent(title); + return; + } + + // Do not assign anything new if the user has defined its own title. + var titleContent = GetLocalizedTitleContentFromType(win.GetType()); + if (win.titleContent.text == win.GetType().ToString()) + { + win.titleContent.text = titleContent.text; + } + if (win.titleContent.image == null) + { + win.titleContent.image = titleContent.image; + } + } + // Returns the first EditorWindow of type /t/ which is currently on the screen. static EditorWindow GetWindowPrivate(System.Type t, bool utility, string title, bool focus) { @@ -705,8 +742,7 @@ static EditorWindow GetWindowPrivate(System.Type t, bool utility, string title, try { win = ScriptableObject.CreateInstance(t) as EditorWindow; - if (title != null) - win.titleContent = new GUIContent(title); + AssignTitle(win, title); if (utility) win.ShowUtility(); else @@ -728,44 +764,44 @@ static EditorWindow GetWindowPrivate(System.Type t, bool utility, string title, return win; } - public static EditorWindow GetWindow(System.Type t, [DefaultValue("false")] bool utility, [DefaultValue("null")] string title, [DefaultValue("true")] bool focus) + public static EditorWindow GetWindow(System.Type windowType, [DefaultValue("false")] bool utility, [DefaultValue("null")] string title, [DefaultValue("true")] bool focus) { - return GetWindowPrivate(t, utility, title, focus); + return GetWindowPrivate(windowType, utility, title, focus); } [ExcludeFromDocs] - public static EditorWindow GetWindow(System.Type t, bool utility, string title) + public static EditorWindow GetWindow(System.Type windowType, bool utility, string title) { - return GetWindowPrivate(t, utility, title, true); + return GetWindowPrivate(windowType, utility, title, true); } [ExcludeFromDocs] - public static EditorWindow GetWindow(System.Type t, bool utility) + public static EditorWindow GetWindow(System.Type windowType, bool utility) { - return GetWindowPrivate(t, utility, null, true); + return GetWindowPrivate(windowType, utility, null, true); } [ExcludeFromDocs] - public static EditorWindow GetWindow(System.Type t) + public static EditorWindow GetWindow(System.Type windowType) { - return GetWindowPrivate(t, false, null, true); + return GetWindowPrivate(windowType, false, null, true); } - public static EditorWindow GetWindowWithRect(System.Type t, Rect rect, [DefaultValue("false")] bool utility, [DefaultValue("null")] string title) + public static EditorWindow GetWindowWithRect(System.Type windowType, Rect rect, [DefaultValue("false")] bool utility, [DefaultValue("null")] string title) { - return GetWindowWithRectPrivate(t, rect, utility, title); + return GetWindowWithRectPrivate(windowType, rect, utility, title); } [ExcludeFromDocs] - public static EditorWindow GetWindowWithRect(System.Type t, Rect rect, bool utility) + public static EditorWindow GetWindowWithRect(System.Type windowType, Rect rect, bool utility) { - return GetWindowWithRectPrivate(t, rect, utility, null); + return GetWindowWithRectPrivate(windowType, rect, utility, null); } [ExcludeFromDocs] - public static EditorWindow GetWindowWithRect(System.Type t, Rect rect) + public static EditorWindow GetWindowWithRect(System.Type windowType, Rect rect) { - return GetWindowWithRectPrivate(t, rect, false, null); + return GetWindowWithRectPrivate(windowType, rect, false, null); } public static T GetWindow() where T : EditorWindow @@ -835,8 +871,7 @@ public static T CreateWindow(string title, params System.Type[] desiredDockNe { T win = CreateInstance(); - if (title != null) - win.titleContent = new GUIContent(title); + AssignTitle(win, title); //Iterate the desired dock next to types... foreach (var desired in desiredDockNextTo) @@ -903,8 +938,7 @@ static EditorWindow GetWindowWithRectPrivate(System.Type t, Rect rect, bool util win.minSize = new Vector2(rect.width, rect.height); win.maxSize = new Vector2(rect.width, rect.height); win.position = rect; - if (title != null) - win.titleContent = new GUIContent(title); + AssignTitle(win, title); if (utility) win.ShowUtility(); else @@ -949,8 +983,7 @@ public static T GetWindowWithRect(Rect rect, bool utility, string title, bool window.minSize = new Vector2(rect.width, rect.height); window.maxSize = new Vector2(rect.width, rect.height); window.position = rect; - if (title != null) - window.titleContent = new GUIContent(title); + AssignTitle(window, title); if (utility) window.ShowUtility(); else @@ -1045,7 +1078,11 @@ public Vector2 minSize } set { - m_MinSize = value; MakeParentsSettingsMatchMe(); + if (!View.IsValidViewSize(value)) + throw new ArgumentException($"Invalid minSize: {value}"); + + m_MinSize = value; + MakeParentsSettingsMatchMe(); } } @@ -1058,6 +1095,9 @@ public Vector2 maxSize } set { + if (!View.IsValidViewSize(value)) + throw new ArgumentException($"Invalid minSize: {value}"); + m_MaxSize = value; MakeParentsSettingsMatchMe(); } @@ -1104,6 +1144,12 @@ internal int antiAliasing set { m_AntiAliasing = value; } } + internal bool resetPanelRenderingOnAssetChange + { + get => m_ResetPanelRenderingOnAssetChange; + set => m_ResetPanelRenderingOnAssetChange = value; + } + internal void SetParentGameViewDimensions(Rect rect, Rect clippedRect, Vector2 targetSize) { m_GameViewRect = rect; @@ -1217,10 +1263,23 @@ static void Initialize() EditorApplication.delayCall += () => ShortcutIntegration.instance.contextManager.RegisterToolContext(s_ShortcutContext); } - private void OnDisableINTERNAL() + void OnEnableINTERNAL() + { + activeEditorWindows.Add(this); + } + + void OnDisableINTERNAL() { m_OverlayCanvas.OnContainerWindowDisabled(); SaveViewDataToDisk(); + activeEditorWindows.Remove(this); + } + + internal void ReleaseViewData() + { + SaveViewDataToDisk(); + DestroyImmediate(m_ViewDataDictionary); + m_ViewDataDictionary = null; } // Internal stuff: diff --git a/Editor/Mono/EnumDataUtility.cs b/Editor/Mono/EnumDataUtility.cs index 1f0f5aa6a8..54864a2b07 100644 --- a/Editor/Mono/EnumDataUtility.cs +++ b/Editor/Mono/EnumDataUtility.cs @@ -4,6 +4,7 @@ using System; using UnityEngine; +using static UnityEngine.EnumDataUtility; namespace UnityEditor { @@ -11,7 +12,12 @@ internal static class EnumDataUtility { internal static EnumData GetCachedEnumData(Type enumType, bool excludeObsolete = true) { - return UnityEngine.EnumDataUtility.GetCachedEnumData(enumType, excludeObsolete, ObjectNames.NicifyVariableName); + return UnityEngine.EnumDataUtility.GetCachedEnumData(enumType, excludeObsolete ? CachedType.ExcludeObsolete : CachedType.IncludeObsoleteExceptErrors, ObjectNames.NicifyVariableName); + } + + internal static EnumData GetCachedEnumData(Type enumType, CachedType cachedType) + { + return UnityEngine.EnumDataUtility.GetCachedEnumData(enumType, cachedType, ObjectNames.NicifyVariableName); } internal static int EnumFlagsToInt(EnumData enumData, Enum enumValue) diff --git a/Editor/Mono/FileUtil.bindings.cs b/Editor/Mono/FileUtil.bindings.cs index 8673c66509..be31e80a2a 100644 --- a/Editor/Mono/FileUtil.bindings.cs +++ b/Editor/Mono/FileUtil.bindings.cs @@ -151,8 +151,12 @@ public static void ReplaceFile(string src, string dst) public static void ReplaceDirectory(string src, string dst) { if (Directory.Exists(dst)) - FileUtil.DeleteFileOrDirectory(dst); - + { + bool succesfullyDeletedDirectory = FileUtil.DeleteFileOrDirectory(dst); + if (succesfullyDeletedDirectory == false) + throw new System.IO.IOException(string.Format( + "Failed to delete directory '{0}'.", dst)); + } FileUtil.CopyFileOrDirectory(src, dst); } diff --git a/Editor/Mono/GI/BuiltinSkyManager.bindings.cs b/Editor/Mono/GI/BuiltinSkyManager.bindings.cs index 3488e362ff..126a274eac 100644 --- a/Editor/Mono/GI/BuiltinSkyManager.bindings.cs +++ b/Editor/Mono/GI/BuiltinSkyManager.bindings.cs @@ -12,5 +12,6 @@ internal class BuiltinSkyManager { private BuiltinSkyManager() {} extern public static bool StaticIsDone(); + extern public static bool enabled { get; set; } } } diff --git a/Editor/Mono/GI/LightmapEditorSettings.bindings.cs b/Editor/Mono/GI/LightmapEditorSettings.bindings.cs index 36e78ad5d2..d6124f7052 100644 --- a/Editor/Mono/GI/LightmapEditorSettings.bindings.cs +++ b/Editor/Mono/GI/LightmapEditorSettings.bindings.cs @@ -70,7 +70,7 @@ public enum FilterType } #pragma warning disable 0618 - [Obsolete("LightmapEditorSettings.lightmapper is obsolete, use Lightmapping.lightingSettings.lightmapper instead. ", false)] + [Obsolete("LightmapEditorSettings.lightmapper is obsolete, use LightingSettings.lightmapper instead. ", false)] public static Lightmapper lightmapper { get { return ConvertToOldLightmapperEnum(Lightmapping.GetLightingSettingsOrDefaultsFallback().lightmapper); } @@ -121,14 +121,14 @@ private static LightingSettings.Lightmapper ConvertToNewLightmapperEnum(Lightmap #pragma warning restore 0618 - [Obsolete("LightmapEditorSettings.lightmapsMode is obsolete, use Lightmapping.lightingSettings.directionalityMode instead. ", false)] + [Obsolete("LightmapEditorSettings.lightmapsMode is obsolete, use LightingSettings.directionalityMode instead. ", false)] public static LightmapsMode lightmapsMode { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().directionalityMode; } set { Lightmapping.GetOrCreateLightingsSettings().directionalityMode = value; } } - [Obsolete("LightmapEditorSettings.mixedBakeMode is obsolete, use Lightmapping.lightingSettings.mixedBakeMode instead. ", false)] + [Obsolete("LightmapEditorSettings.mixedBakeMode is obsolete, use LightingSettings.mixedBakeMode instead. ", false)] public static MixedLightingMode mixedBakeMode { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().mixedBakeMode; } @@ -136,7 +136,7 @@ public static MixedLightingMode mixedBakeMode } #pragma warning disable 0618 - [Obsolete("LightmapEditorSettings.sampling is obsolete, use Lightmapping.lightingSettings.sampling instead. ", false)] + [Obsolete("LightmapEditorSettings.sampling is obsolete, use LightingSettings.sampling instead. ", false)] public static Sampling sampling { get { return ConvertToOldSamplingEnum(Lightmapping.GetLightingSettingsOrDefaultsFallback().sampling); } @@ -181,28 +181,28 @@ private static LightingSettings.Sampling ConvertToNewSamplingEnum(Sampling light #pragma warning restore 0618 - [Obsolete("LightmapEditorSettings.directSampleCount is obsolete, use Lightmapping.lightingSettings.directSampleCount instead. ", false)] + [Obsolete("LightmapEditorSettings.directSampleCount is obsolete, use LightingSettings.directSampleCount instead. ", false)] public static int directSampleCount { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().directSampleCount; } set { Lightmapping.GetOrCreateLightingsSettings().directSampleCount = value; } } - [Obsolete("LightmapEditorSettings.indirectSampleCount is obsolete, use Lightmapping.lightingSettings.indirectSampleCount instead. ", false)] + [Obsolete("LightmapEditorSettings.indirectSampleCount is obsolete, use LightingSettings.indirectSampleCount instead. ", false)] public static int indirectSampleCount { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().indirectSampleCount; } set { Lightmapping.GetOrCreateLightingsSettings().indirectSampleCount = value; } } - [Obsolete("LightmapEditorSettings.bounces is obsolete, use Lightmapping.lightingSettings.maxBounces instead. ", false)] + [Obsolete("LightmapEditorSettings.bounces is obsolete, use LightingSettings.maxBounces instead. ", false)] public static int bounces { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().maxBounces; } set { Lightmapping.GetOrCreateLightingsSettings().maxBounces = value; } } - [Obsolete("LightmapEditorSettings.prioritizeView is obsolete, use Lightmapping.lightingSettings.prioritizeView instead. ", false)] + [Obsolete("LightmapEditorSettings.prioritizeView is obsolete, use LightingSettings.prioritizeView instead. ", false)] public static bool prioritizeView { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().prioritizeView; } @@ -210,7 +210,7 @@ public static bool prioritizeView } #pragma warning disable 0618 - [Obsolete("LightmapEditorSettings.filteringMode is obsolete, use Lightmapping.lightingSettings.filteringMode instead. ", false)] + [Obsolete("LightmapEditorSettings.filteringMode is obsolete, use LightingSettings.filteringMode instead. ", false)] public static FilterMode filteringMode { get { return ConvertToOldFilteringModeEnum(Lightmapping.GetLightingSettingsOrDefaultsFallback().filteringMode); } @@ -259,21 +259,21 @@ private static LightingSettings.FilterMode ConvertToNewFilteringModeEnum(FilterM } } - [Obsolete("LightmapEditorSettings.denoiserTypeDirect is obsolete, use Lightmapping.lightingSettings.denoiserTypeDirect instead. ", false)] + [Obsolete("LightmapEditorSettings.denoiserTypeDirect is obsolete, use LightingSettings.denoiserTypeDirect instead. ", false)] public static DenoiserType denoiserTypeDirect { get { return ConvertToOldDenoiserTypeEnum(Lightmapping.GetLightingSettingsOrDefaultsFallback().denoiserTypeDirect); } set { Lightmapping.GetOrCreateLightingsSettings().denoiserTypeDirect = ConvertToNewDenoiserTypeEnum(value); } } - [Obsolete("LightmapEditorSettings.denoiserTypeIndirect is obsolete, use Lightmapping.lightingSettings.denoiserTypeIndirect instead. ", false)] + [Obsolete("LightmapEditorSettings.denoiserTypeIndirect is obsolete, use LightingSettings.denoiserTypeIndirect instead. ", false)] public static DenoiserType denoiserTypeIndirect { get { return ConvertToOldDenoiserTypeEnum(Lightmapping.GetLightingSettingsOrDefaultsFallback().denoiserTypeIndirect); } set { Lightmapping.GetOrCreateLightingsSettings().denoiserTypeIndirect = ConvertToNewDenoiserTypeEnum(value); } } - [Obsolete("LightmapEditorSettings.denoiserTypeAO is obsolete, use Lightmapping.lightingSettings.denoiserTypeAO instead. ", false)] + [Obsolete("LightmapEditorSettings.denoiserTypeAO is obsolete, use LightingSettings.denoiserTypeAO instead. ", false)] public static DenoiserType denoiserTypeAO { get { return ConvertToOldDenoiserTypeEnum(Lightmapping.GetLightingSettingsOrDefaultsFallback().denoiserTypeAO); } @@ -328,21 +328,21 @@ private static LightingSettings.DenoiserType ConvertToNewDenoiserTypeEnum(Denois } } - [Obsolete("LightmapEditorSettings.filterTypeDirect is obsolete, use Lightmapping.lightingSettings.filterTypeDirect instead. ", false)] + [Obsolete("LightmapEditorSettings.filterTypeDirect is obsolete, use LightingSettings.filterTypeDirect instead. ", false)] public static FilterType filterTypeDirect { get { return ConvertToOldFilterTypeEnum(Lightmapping.GetLightingSettingsOrDefaultsFallback().filterTypeDirect); } set { Lightmapping.GetOrCreateLightingsSettings().filterTypeDirect = ConvertToNewFilterTypeEnum(value); } } - [Obsolete("LightmapEditorSettings.filterTypeIndirect is obsolete, use Lightmapping.lightingSettings.filterTypeIndirect instead. ", false)] + [Obsolete("LightmapEditorSettings.filterTypeIndirect is obsolete, use LightingSettings.filterTypeIndirect instead. ", false)] public static FilterType filterTypeIndirect { get { return ConvertToOldFilterTypeEnum(Lightmapping.GetLightingSettingsOrDefaultsFallback().filterTypeIndirect); } set { Lightmapping.GetOrCreateLightingsSettings().filterTypeIndirect = ConvertToNewFilterTypeEnum(value); } } - [Obsolete("LightmapEditorSettings.filterTypeAO is obsolete, use Lightmapping.lightingSettings.filterTypeAO instead. ", false)] + [Obsolete("LightmapEditorSettings.filterTypeAO is obsolete, use LightingSettings.filterTypeAO instead. ", false)] public static FilterType filterTypeAO { get { return ConvertToOldFilterTypeEnum(Lightmapping.GetLightingSettingsOrDefaultsFallback().filterTypeAO); } @@ -393,98 +393,98 @@ private static LightingSettings.FilterType ConvertToNewFilterTypeEnum(FilterType #pragma warning restore 0618 - [Obsolete("LightmapEditorSettings.filteringGaussRadiusDirect is obsolete, use Lightmapping.lightingSettings.filteringGaussRadiusDirect instead. ", false)] + [Obsolete("LightmapEditorSettings.filteringGaussRadiusDirect is obsolete, use LightingSettings.filteringGaussianRadiusDirect instead. ", false)] public static int filteringGaussRadiusDirect { - get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().filteringGaussRadiusDirect; } - set { Lightmapping.GetOrCreateLightingsSettings().filteringGaussRadiusDirect = value; } + get { return (int)Lightmapping.GetLightingSettingsOrDefaultsFallback().filteringGaussianRadiusDirect; } + set { Lightmapping.GetOrCreateLightingsSettings().filteringGaussianRadiusDirect = (float)value; } } - [Obsolete("LightmapEditorSettings.filteringGaussRadiusIndirect is obsolete, use Lightmapping.lightingSettings.filteringGaussRadiusIndirect instead. ", false)] + [Obsolete("LightmapEditorSettings.filteringGaussRadiusIndirect is obsolete, use LightingSettings.filteringGaussianRadiusIndirect instead. ", false)] public static int filteringGaussRadiusIndirect { - get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().filteringGaussRadiusIndirect; } - set { Lightmapping.GetOrCreateLightingsSettings().filteringGaussRadiusIndirect = value; } + get { return (int)Lightmapping.GetLightingSettingsOrDefaultsFallback().filteringGaussianRadiusIndirect; } + set { Lightmapping.GetOrCreateLightingsSettings().filteringGaussianRadiusIndirect = (float)value; } } - [Obsolete("LightmapEditorSettings.filteringGaussRadiusAO is obsolete, use Lightmapping.lightingSettings.filteringGaussRadiusAO instead. ", false)] + [Obsolete("LightmapEditorSettings.filteringGaussRadiusAO is obsolete, use LightingSettings.filteringGaussianRadiusAO instead. ", false)] public static int filteringGaussRadiusAO { - get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().filteringGaussRadiusAO; } - set { Lightmapping.GetOrCreateLightingsSettings().filteringGaussRadiusAO = value; } + get { return (int)Lightmapping.GetLightingSettingsOrDefaultsFallback().filteringGaussianRadiusAO; } + set { Lightmapping.GetOrCreateLightingsSettings().filteringGaussianRadiusAO = (float)value; } } - [Obsolete("LightmapEditorSettings.filteringAtrousPositionSigmaDirect is obsolete, use Lightmapping.lightingSettings.filteringAtrousPositionSigmaDirect instead. ", false)] + [Obsolete("LightmapEditorSettings.filteringAtrousPositionSigmaDirect is obsolete, use LightingSettings.filteringAtrousPositionSigmaDirect instead. ", false)] public static float filteringAtrousPositionSigmaDirect { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().filteringAtrousPositionSigmaDirect; } set { Lightmapping.GetOrCreateLightingsSettings().filteringAtrousPositionSigmaDirect = value; } } - [Obsolete("LightmapEditorSettings.filteringAtrousPositionSigmaIndirect is obsolete, use Lightmapping.lightingSettings.filteringAtrousPositionSigmaIndirect instead. ", false)] + [Obsolete("LightmapEditorSettings.filteringAtrousPositionSigmaIndirect is obsolete, use LightingSettings.filteringAtrousPositionSigmaIndirect instead. ", false)] public static float filteringAtrousPositionSigmaIndirect { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().filteringAtrousPositionSigmaIndirect; } set { Lightmapping.GetOrCreateLightingsSettings().filteringAtrousPositionSigmaIndirect = value; } } - [Obsolete("LightmapEditorSettings.filteringAtrousPositionSigmaIndirect is obsolete, use Lightmapping.lightingSettings.filteringAtrousPositionSigmaIndirect instead. ", false)] + [Obsolete("LightmapEditorSettings.filteringAtrousPositionSigmaIndirect is obsolete, use LightingSettings.filteringAtrousPositionSigmaIndirect instead. ", false)] public static float filteringAtrousPositionSigmaAO { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().filteringAtrousPositionSigmaIndirect; } set { Lightmapping.GetOrCreateLightingsSettings().filteringAtrousPositionSigmaIndirect = value; } } - [Obsolete("LightmapEditorSettings.environmentMIS is obsolete, use Lightmapping.lightingSettings.environmentImportanceSampling instead. ", false)] + [Obsolete("LightmapEditorSettings.environmentMIS is obsolete, use LightingSettings.environmentImportanceSampling instead. ", false)] internal static int environmentMIS { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().environmentImportanceSampling ? 1 : 0; } set { Lightmapping.GetOrCreateLightingsSettings().environmentImportanceSampling = value != 0; } } - [Obsolete("LightmapEditorSettings.environmentSampleCount is obsolete, use Lightmapping.lightingSettings.environmentSampleCount instead. ", false)] + [Obsolete("LightmapEditorSettings.environmentSampleCount is obsolete, use LightingSettings.environmentSampleCount instead. ", false)] public static int environmentSampleCount { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().environmentSampleCount; } set { Lightmapping.GetOrCreateLightingsSettings().environmentSampleCount = value; } } - [Obsolete("LightmapEditorSettings.environmentReferencePointCount is obsolete, use Lightmapping.lightingSettings.environmentReferencePointCount instead. ", false)] + [Obsolete("LightmapEditorSettings.environmentReferencePointCount is obsolete, use LightingSettings.environmentReferencePointCount instead. ", false)] internal static int environmentReferencePointCount { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().environmentReferencePointCount; } set { Lightmapping.GetOrCreateLightingsSettings().environmentReferencePointCount = value; } } - [Obsolete("LightmapEditorSettings.lightProbeSampleCountMultiplier is obsolete, use Lightmapping.lightingSettings.lightProbeSampleCountMultiplier instead. ", false)] + [Obsolete("LightmapEditorSettings.lightProbeSampleCountMultiplier is obsolete, use LightingSettings.lightProbeSampleCountMultiplier instead. ", false)] internal static float lightProbeSampleCountMultiplier { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().lightProbeSampleCountMultiplier; } set { Lightmapping.GetOrCreateLightingsSettings().lightProbeSampleCountMultiplier = value; } } - [Obsolete("LightmapEditorSettings.maxAtlasSize is obsolete, use Lightmapping.lightingSettings.lightmapMaxSize instead. ", false)] + [Obsolete("LightmapEditorSettings.maxAtlasSize is obsolete, use LightingSettings.lightmapMaxSize instead. ", false)] public static int maxAtlasSize { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().lightmapMaxSize; } set { Lightmapping.GetOrCreateLightingsSettings().lightmapMaxSize = value; } } - [Obsolete("LightmapEditorSettings.realtimeResolution is obsolete, use Lightmapping.lightingSettings.indirectResolution instead. ", false)] + [Obsolete("LightmapEditorSettings.realtimeResolution is obsolete, use LightingSettings.indirectResolution instead. ", false)] public static float realtimeResolution { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().indirectResolution; } set { Lightmapping.GetOrCreateLightingsSettings().indirectResolution = value; } } - [Obsolete("LightmapEditorSettings.bakeResolution is obsolete, use Lightmapping.lightingSettings.lightmapResolution instead. ", false)] + [Obsolete("LightmapEditorSettings.bakeResolution is obsolete, use LightingSettings.lightmapResolution instead. ", false)] public static float bakeResolution { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().lightmapResolution; } set { Lightmapping.GetOrCreateLightingsSettings().lightmapResolution = value; } } - [Obsolete("LightmapEditorSettings.textureCompression is obsolete, use Lightmapping.lightingSettings.compressLightmaps instead. ", false)] + [Obsolete("LightmapEditorSettings.textureCompression is obsolete, use LightingSettings.compressLightmaps instead. ", false)] public static bool textureCompression { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().compressLightmaps; } @@ -495,49 +495,49 @@ public static bool textureCompression [NativeName("ReflectionCompression")] public extern static ReflectionCubemapCompression reflectionCubemapCompression { get; set; } - [Obsolete("LightmapEditorSettings.enableAmbientOcclusion is obsolete, use Lightmapping.lightingSettings.ao instead. ", false)] + [Obsolete("LightmapEditorSettings.enableAmbientOcclusion is obsolete, use LightingSettings.ao instead. ", false)] public static bool enableAmbientOcclusion { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().ao; } set { Lightmapping.GetOrCreateLightingsSettings().ao = value; } } - [Obsolete("LightmapEditorSettings.aoMaxDistance is obsolete, use Lightmapping.lightingSettings.aoMaxDistance instead. ", false)] + [Obsolete("LightmapEditorSettings.aoMaxDistance is obsolete, use LightingSettings.aoMaxDistance instead. ", false)] public static float aoMaxDistance { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().aoMaxDistance; } set { Lightmapping.GetOrCreateLightingsSettings().aoMaxDistance = value; } } - [Obsolete("LightmapEditorSettings.aoExponentIndirect is obsolete, use Lightmapping.lightingSettings.aoExponentIndirect instead. ", false)] + [Obsolete("LightmapEditorSettings.aoExponentIndirect is obsolete, use LightingSettings.aoExponentIndirect instead. ", false)] public static float aoExponentIndirect { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().aoExponentIndirect; } set { Lightmapping.GetOrCreateLightingsSettings().aoExponentIndirect = value; } } - [Obsolete("LightmapEditorSettings.aoExponentDirect is obsolete, use Lightmapping.lightingSettings.aoExponentDirect instead. ", false)] + [Obsolete("LightmapEditorSettings.aoExponentDirect is obsolete, use LightingSettings.aoExponentDirect instead. ", false)] public static float aoExponentDirect { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().aoExponentDirect; } set { Lightmapping.GetOrCreateLightingsSettings().aoExponentDirect = value; } } - [Obsolete("LightmapEditorSettings.padding is obsolete, use Lightmapping.lightingSettings.lightmapPadding instead. ", false)] + [Obsolete("LightmapEditorSettings.padding is obsolete, use LightingSettings.lightmapPadding instead. ", false)] public static int padding { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().lightmapPadding; } set { Lightmapping.GetOrCreateLightingsSettings().lightmapPadding = value; } } - [Obsolete("LightmapEditorSettings.exportTrainingData is obsolete, use Lightmapping.lightingSettings.exportTrainingData instead. ", false)] + [Obsolete("LightmapEditorSettings.exportTrainingData is obsolete, use LightingSettings.exportTrainingData instead. ", false)] public static bool exportTrainingData { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().exportTrainingData; } set { Lightmapping.GetOrCreateLightingsSettings().exportTrainingData = value; } } - [Obsolete("LightmapEditorSettings.trainingDataDestination is obsolete, use Lightmapping.lightingSettings.trainingDataDestination instead. ", false)] + [Obsolete("LightmapEditorSettings.trainingDataDestination is obsolete, use LightingSettings.trainingDataDestination instead. ", false)] public static string trainingDataDestination { get { return Lightmapping.GetLightingSettingsOrDefaultsFallback().trainingDataDestination; } diff --git a/Editor/Mono/GI/Lightmapping.bindings.cs b/Editor/Mono/GI/Lightmapping.bindings.cs index 4f6b42be5e..2cd59f54c3 100644 --- a/Editor/Mono/GI/Lightmapping.bindings.cs +++ b/Editor/Mono/GI/Lightmapping.bindings.cs @@ -139,35 +139,35 @@ public enum GIWorkflowMode public delegate void OnStartedFunction(); public delegate void OnCompletedFunction(); -// [Obsolete("Lightmapping.giWorkflowMode is obsolete, use Lightmapping.lightingSettings.autoGenerate instead. ", false)] +// [Obsolete("Lightmapping.giWorkflowMode is obsolete, use LightingSettings.autoGenerate instead. ", false)] public static GIWorkflowMode giWorkflowMode { get { return GetLightingSettingsOrDefaultsFallback().autoGenerate ? GIWorkflowMode.Iterative : GIWorkflowMode.OnDemand; } set { GetOrCreateLightingsSettings().autoGenerate = (value == GIWorkflowMode.Iterative); } } -// [Obsolete("Lightmapping.realtimeGI is obsolete, use Lightmapping.lightingSettings.realtimeGI instead. ", false)] +// [Obsolete("Lightmapping.realtimeGI is obsolete, use LightingSettings.realtimeGI instead. ", false)] public static bool realtimeGI { get { return GetLightingSettingsOrDefaultsFallback().realtimeGI; } set { GetOrCreateLightingsSettings().realtimeGI = value; } } -// [Obsolete("Lightmapping.bakedGI is obsolete, use Lightmapping.lightingSettings.bakedGI instead. ", false)] +// [Obsolete("Lightmapping.bakedGI is obsolete, use LightingSettings.bakedGI instead. ", false)] public static bool bakedGI { get { return GetLightingSettingsOrDefaultsFallback().bakedGI; } set { GetOrCreateLightingsSettings().bakedGI = value; } } - [Obsolete("Lightmapping.indirectOutputScale is obsolete, use Lightmapping.lightingSettings.indirectScale instead. ", false)] + [Obsolete("Lightmapping.indirectOutputScale is obsolete, use LightingSettings.indirectScale instead. ", false)] public static float indirectOutputScale { get { return GetLightingSettingsOrDefaultsFallback().indirectScale; } set { GetOrCreateLightingsSettings().indirectScale = value; } } - [Obsolete("Lightmapping.bounceBoost is obsolete, use Lightmapping.lightingSettings.albedoBoost instead. ", false)] + [Obsolete("Lightmapping.bounceBoost is obsolete, use LightingSettings.albedoBoost instead. ", false)] public static float bounceBoost { get { return GetLightingSettingsOrDefaultsFallback().albedoBoost; } @@ -196,21 +196,21 @@ public static float bounceBoost [NativeName("CachePath")] internal static extern string diskCachePath { get; } - [Obsolete("Lightmapping.enlightenForceWhiteAlbedo is obsolete, use Lightmapping.lightingSettings.realtimeForceWhiteAlbedo instead. ", false)] + [Obsolete("Lightmapping.enlightenForceWhiteAlbedo is obsolete, use LightingSettings.realtimeForceWhiteAlbedo instead. ", false)] internal static bool enlightenForceWhiteAlbedo { get { return GetLightingSettingsOrDefaultsFallback().realtimeForceWhiteAlbedo; } set { GetOrCreateLightingsSettings().realtimeForceWhiteAlbedo = value; } } - [Obsolete("Lightmapping.enlightenForceUpdates is obsolete, use Lightmapping.lightingSettings.realtimeForceUpdates instead. ", false)] + [Obsolete("Lightmapping.enlightenForceUpdates is obsolete, use LightingSettings.realtimeForceUpdates instead. ", false)] internal static bool enlightenForceUpdates { get { return GetLightingSettingsOrDefaultsFallback().realtimeForceUpdates; } set { GetOrCreateLightingsSettings().realtimeForceUpdates = value; } } - [Obsolete("Lightmapping.filterMode is obsolete, use Lightmapping.lightingSettings.lightmapFilterMode instead. ", false)] + [Obsolete("Lightmapping.filterMode is obsolete, use LightingSettings.lightmapFilterMode instead. ", false)] internal static FilterMode filterMode { get { return GetLightingSettingsOrDefaultsFallback().lightmapFilterMode; } @@ -741,7 +741,7 @@ public static bool GetCustomBakeResults([Out] Vector4[] results) [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] public static extern ReadOnlySpan GetCustomBakeResultsNoCopy(); - [Obsolete("UnityEditor.Experimental.Lightmapping.extractAmbientOcclusion is obsolete, use Lightmapping.lightingSettings.extractAO instead. ", false)] + [Obsolete("UnityEditor.Experimental.Lightmapping.extractAmbientOcclusion is obsolete, use LightingSettings.extractAO instead. ", false)] public static bool extractAmbientOcclusion { get { return UnityEditor.Lightmapping.GetLightingSettingsOrDefaultsFallback().extractAO; } @@ -834,17 +834,21 @@ public unsafe static bool GetAdditionalBakedProbes(int id, NativeArray positions) + public static void SetAdditionalBakedProbes(int id, ReadOnlySpan positions) + { + SetAdditionalBakedProbes(id, positions, true); + } + public static unsafe void SetAdditionalBakedProbes(int id, ReadOnlySpan positions, bool dering) { fixed(Vector3* positionsPtr = positions) { - SetAdditionalBakedProbes(id, positionsPtr, positions.Length); + SetAdditionalBakedProbes(id, positionsPtr, positions.Length, dering); } } diff --git a/Editor/Mono/GUI/AnimatedValues.cs b/Editor/Mono/GUI/AnimatedValues.cs index 7567ca115e..2667b30fd4 100644 --- a/Editor/Mono/GUI/AnimatedValues.cs +++ b/Editor/Mono/GUI/AnimatedValues.cs @@ -101,7 +101,10 @@ public bool isAnimating void Update() { if (!m_Animating) + { + EditorApplication.update -= Update; return; + } // update the lerpPosition UpdateLerpPosition(); diff --git a/Editor/Mono/GUI/AppStatusBar.cs b/Editor/Mono/GUI/AppStatusBar.cs index 67cea790e6..f030972dd5 100644 --- a/Editor/Mono/GUI/AppStatusBar.cs +++ b/Editor/Mono/GUI/AppStatusBar.cs @@ -61,11 +61,15 @@ static Styles() const double k_CheckUnresponsiveFrequencyInSecond = 0.5; const float k_ShowProgressThreshold = 0.5f; private double m_LastUpdate; + private bool m_IsQuitting; private bool showBakeMode { get { + if (m_IsQuitting) + return false; + var settings = Lightmapping.GetLightingSettingsOrDefaultsFallback(); return settings.bakedGI || settings.realtimeGI; } @@ -93,6 +97,8 @@ protected override void OnEnable() Progress.added += RefreshProgressBar; Progress.removed += RefreshProgressBar; Progress.updated += RefreshProgressBar; + + EditorApplication.editorApplicationQuit += OnQuit; } protected override void OnDisable() @@ -100,6 +106,7 @@ protected override void OnDisable() Progress.added -= RefreshProgressBar; Progress.removed -= RefreshProgressBar; Progress.updated -= RefreshProgressBar; + EditorApplication.editorApplicationQuit -= OnQuit; EditorApplication.delayCall -= DelayRepaint; base.OnDisable(); } @@ -312,7 +319,11 @@ private void DrawBakeMode() if (GUILayout.Button(GetBakeModeIcon(m_autoLightBakingOn), Styles.statusIcon)) { Event.current.Use(); - LightingWindow.CreateLightingWindow(); + + if (LightingWindow.isShown) + LightingWindow.DestroyLightingWindow(); + else + LightingWindow.CreateLightingWindow(); } var buttonRect = GUILayoutUtility.GetLastRect(); @@ -466,6 +477,8 @@ private GUIContent GetBakeModeIcon(bool? bakeMode) return Lightmapping.GetLightingSettingsOrDefaultsFallback().autoGenerate; } + private void OnQuit() + => m_IsQuitting = true; [RequiredByNativeCode] public static void StatusChanged() diff --git a/Editor/Mono/GUI/ColorMutator.cs b/Editor/Mono/GUI/ColorMutator.cs index 8ddf00633e..ee764725e0 100644 --- a/Editor/Mono/GUI/ColorMutator.cs +++ b/Editor/Mono/GUI/ColorMutator.cs @@ -18,7 +18,7 @@ internal class ColorMutator { // specifies the max byte value to use when decomposing a float color into bytes with exposure // this is the value used by Photoshop - private const byte k_MaxByteForOverexposedColor = 191; + const byte k_MaxByteForOverexposedColor = 191; internal static void DecomposeHdrColor(Color linearColorHdr, out Color32 baseLinearColor, out float exposure) { @@ -46,23 +46,42 @@ internal static void DecomposeHdrColor(Color linearColorHdr, out Color32 baseLin } } - public Color originalColor => m_OriginalColor; [SerializeField] private Color m_OriginalColor; - [SerializeField] private Color m_HDRBaseColor; + [SerializeField] private Color m_HDRBaseColor; // This field is needed to compute the correct exposure value. Without it, the exposure would have rounding errors. + [SerializeField] private byte[] m_Color = new byte[4]; + [SerializeField] private float[] m_ColorHdr = new float[4]; + [SerializeField] private float[] m_Hsv = new float[3]; + [SerializeField] private float m_ExposureValue; + [SerializeField] private float m_BaseExposureValue; - public Color32 color + public Color originalColor => m_OriginalColor; + public Color32 color => new(m_Color[(int)RgbaChannel.R], m_Color[(int)RgbaChannel.G], m_Color[(int)RgbaChannel.B], m_Color[(int)RgbaChannel.A]); + public Vector3 colorHsv => new(m_Hsv[(int)HsvChannel.H], m_Hsv[(int)HsvChannel.S], m_Hsv[(int)HsvChannel.V]); + public Color exposureAdjustedColor => new(m_ColorHdr[(int)RgbaChannel.R], m_ColorHdr[(int)RgbaChannel.G], m_ColorHdr[(int)RgbaChannel.B], m_ColorHdr[(int)RgbaChannel.A]); + + public float exposureValue { - get + get => m_ExposureValue; + set { - return new Color32( - m_Color[(int)RgbaChannel.R], - m_Color[(int)RgbaChannel.G], - m_Color[(int)RgbaChannel.B], - m_Color[(int)RgbaChannel.A] - ); + if (Mathf.Approximately(m_ExposureValue, value)) + return; + m_ExposureValue = value; + var newRgbFloat = m_HDRBaseColor * Mathf.Pow(2f, m_ExposureValue - m_BaseExposureValue); + m_ColorHdr[(int)RgbaChannel.R] = FloatClampSafe(newRgbFloat.r); + m_ColorHdr[(int)RgbaChannel.G] = FloatClampSafe(newRgbFloat.g); + m_ColorHdr[(int)RgbaChannel.B] = FloatClampSafe(newRgbFloat.b); } } - [SerializeField] private byte[] m_Color = new byte[4]; + + static float FloatClampSafe(float value) + { + if (float.IsPositiveInfinity(value) || float.IsNaN(value)) + return float.MaxValue; + if (float.IsNegativeInfinity(value)) + return float.MinValue; + return value; + } public byte GetColorChannel(RgbaChannel channel) { @@ -76,18 +95,15 @@ public float GetColorChannelNormalized(RgbaChannel channel) public void SetColorChannel(RgbaChannel channel, byte value) { - if (m_Color[(int)channel] == value) + var channelIndex = (int)channel; + if (m_Color[channelIndex] == value) return; - m_Color[(int)channel] = value; - m_ColorHdr[(int)channel] = (value / 255f); + m_Color[channelIndex] = value; + m_ColorHdr[channelIndex] = value / 255f; if (channel != RgbaChannel.A) - m_ColorHdr[(int)channel] *= Mathf.Pow(2f, m_ExposureValue); - Color.RGBToHSV( - color, - out m_Hsv[(int)HsvChannel.H], - out m_Hsv[(int)HsvChannel.S], - out m_Hsv[(int)HsvChannel.V] - ); + m_ColorHdr[channelIndex] *= Mathf.Pow(2f, m_ExposureValue); + m_HDRBaseColor = new Color(m_ColorHdr[0], m_ColorHdr[1], m_ColorHdr[2], m_ColorHdr[3]); + Color.RGBToHSV(color, out m_Hsv[(int)HsvChannel.H], out m_Hsv[(int)HsvChannel.S], out m_Hsv[(int)HsvChannel.V]); } public void SetColorChannel(RgbaChannel channel, float normalizedValue) @@ -95,20 +111,6 @@ public void SetColorChannel(RgbaChannel channel, float normalizedValue) SetColorChannel(channel, (byte)Mathf.RoundToInt(Mathf.Clamp01(normalizedValue) * 255f)); } - public Color exposureAdjustedColor - { - get - { - return new Color( - m_ColorHdr[(int)RgbaChannel.R], - m_ColorHdr[(int)RgbaChannel.G], - m_ColorHdr[(int)RgbaChannel.B], - m_ColorHdr[(int)RgbaChannel.A] - ); - } - } - [SerializeField] private float[] m_ColorHdr = new float[4]; - public float GetColorChannelHdr(RgbaChannel channel) { return m_ColorHdr[(int)channel]; @@ -116,38 +118,21 @@ public float GetColorChannelHdr(RgbaChannel channel) public void SetColorChannelHdr(RgbaChannel channel, float value) { - if (m_ColorHdr[(int)channel] == value) + if (Mathf.Approximately(m_ColorHdr[(int)channel], value)) return; m_ColorHdr[(int)channel] = value; m_HDRBaseColor = new Color(m_ColorHdr[0], m_ColorHdr[1], m_ColorHdr[2], m_ColorHdr[3]); OnRgbaHdrChannelChanged((int)channel); + m_BaseExposureValue = m_ExposureValue; } - public Vector3 colorHsv - { - get - { - return new Vector3( - m_Hsv[(int)HsvChannel.H], - m_Hsv[(int)HsvChannel.S], - m_Hsv[(int)HsvChannel.V] - ); - } - } - [SerializeField] private float[] m_Hsv = new float[3]; - - public float GetColorChannel(HsvChannel channel) - { - return m_Hsv[(int)channel]; - } + public float GetColorChannel(HsvChannel channel) => m_Hsv[(int)channel]; public void SetColorChannel(HsvChannel channel, float value) { m_Hsv[(int)channel] = Mathf.Clamp01(value); - var newColor = Color.HSVToRGB( - m_Hsv[(int)HsvChannel.H], m_Hsv[(int)HsvChannel.S], m_Hsv[(int)HsvChannel.V] - ); + var newColor = Color.HSVToRGB(m_Hsv[(int)HsvChannel.H], m_Hsv[(int)HsvChannel.S], m_Hsv[(int)HsvChannel.V]); m_Color[(int)RgbaChannel.R] = (byte)Mathf.CeilToInt(newColor.r * 255f); m_Color[(int)RgbaChannel.G] = (byte)Mathf.CeilToInt(newColor.g * 255f); m_Color[(int)RgbaChannel.B] = (byte)Mathf.CeilToInt(newColor.b * 255f); @@ -159,33 +144,18 @@ public void SetColorChannel(HsvChannel channel, float value) m_HDRBaseColor = new Color(m_ColorHdr[0], m_ColorHdr[1], m_ColorHdr[2], m_ColorHdr[3]); } - public float exposureValue - { - get { return m_ExposureValue; } - set - { - if (m_ExposureValue == value) - return; - m_ExposureValue = value; - var newRgbFloat = m_HDRBaseColor * Mathf.Pow(2f, m_ExposureValue); - m_ColorHdr[(int)RgbaChannel.R] = newRgbFloat.r; - m_ColorHdr[(int)RgbaChannel.G] = newRgbFloat.g; - m_ColorHdr[(int)RgbaChannel.B] = newRgbFloat.b; - } - } - [SerializeField] private float m_ExposureValue; - public ColorMutator(Color originalColor) { m_OriginalColor = originalColor; - m_HDRBaseColor = originalColor; Reset(); } public void Reset() { - if (m_ColorHdr == null || m_ColorHdr.Length != 4) + if (m_ColorHdr is not { Length: 4 }) m_ColorHdr = new float[4]; + if (m_Color is not { Length: 4 }) + m_Color = new byte[4]; m_ColorHdr[(int)RgbaChannel.R] = m_OriginalColor.r; m_ColorHdr[(int)RgbaChannel.G] = m_OriginalColor.g; @@ -193,10 +163,8 @@ public void Reset() m_ColorHdr[(int)RgbaChannel.A] = m_OriginalColor.a; m_HDRBaseColor = new Color(m_ColorHdr[0], m_ColorHdr[1], m_ColorHdr[2], m_ColorHdr[3]); - if (m_Color == null || m_Color.Length != 4) - m_Color = new byte[4]; - OnRgbaHdrChannelChanged(-1); + m_BaseExposureValue = m_ExposureValue; } void OnRgbaHdrChannelChanged(int channel) @@ -205,17 +173,12 @@ void OnRgbaHdrChannelChanged(int channel) if (channel == (int)RgbaChannel.A) return; - Color32 baseColor; - DecomposeHdrColor(exposureAdjustedColor, out baseColor, out m_ExposureValue); + DecomposeHdrColor(exposureAdjustedColor, out var baseColor, out m_ExposureValue); + m_Color[(int)RgbaChannel.R] = baseColor.r; m_Color[(int)RgbaChannel.G] = baseColor.g; m_Color[(int)RgbaChannel.B] = baseColor.b; - Color.RGBToHSV( - color, - out m_Hsv[(int)HsvChannel.H], - out m_Hsv[(int)HsvChannel.S], - out m_Hsv[(int)HsvChannel.V] - ); + Color.RGBToHSV(color, out m_Hsv[(int)HsvChannel.H], out m_Hsv[(int)HsvChannel.S], out m_Hsv[(int)HsvChannel.V]); } } } diff --git a/Editor/Mono/GUI/ColorPicker.cs b/Editor/Mono/GUI/ColorPicker.cs index 418d39a9a1..a83f04a197 100644 --- a/Editor/Mono/GUI/ColorPicker.cs +++ b/Editor/Mono/GUI/ColorPicker.cs @@ -479,7 +479,7 @@ static class Styles public static readonly GUIStyle selectedExposureSwatchStroke = "ColorPickerCurrentExposureSwatchBorder"; public static readonly GUIContent eyeDropper = EditorGUIUtility.TrIconContent("EyeDropper.Large", "Pick a color from the screen."); - public static readonly GUIContent exposureValue = EditorGUIUtility.TrTextContent("Intensity", "Number of stops to over- or under-expose the color."); + public static readonly GUIContent exposureValue = EditorGUIUtility.TrTextContent("Intensity", "Number of stops to over- or under-expose the color. The intensity calculates each time based on the predefined max color component of 191 (0.749) when Color Picker opens."); public static readonly GUIContent hexLabel = EditorGUIUtility.TrTextContent("Hexadecimal"); public static readonly GUIContent presetsToggle = EditorGUIUtility.TrTextContent("Swatches"); @@ -493,11 +493,23 @@ static class Styles public static readonly Texture2D alphaSliderCheckerBackground = EditorGUIUtility.LoadRequired("Previews/Textures/textureChecker.png") as Texture2D; - public static readonly GUIContent[] sliderModeLabels = new[] + public static readonly GUIContent RGB0_255Mode = EditorGUIUtility.TrTextContent("RGB 0-255"); + public static readonly GUIContent RGB0_1Mode = EditorGUIUtility.TrTextContent("RGB 0-1.0"); + public static readonly GUIContent RGB0_InfMode = EditorGUIUtility.TrTextContent("RGB 0-Inf"); + public static readonly GUIContent RGBHSVMode = EditorGUIUtility.TrTextContent("HSV"); + + public static readonly GUIContent[] sliderLDRModeLabels = new[] + { + RGB0_255Mode, + RGB0_1Mode, + RGBHSVMode + }; + + public static readonly GUIContent[] sliderHDRModeLabels = new[] { - EditorGUIUtility.TrTextContent("RGB 0-255"), - EditorGUIUtility.TrTextContent("RGB 0-1.0"), - EditorGUIUtility.TrTextContent("HSV") + RGB0_255Mode, + RGB0_InfMode, + RGBHSVMode }; public static readonly int[] sliderModeValues = new[] { 0, 1, 2 }; @@ -699,9 +711,10 @@ void DoColorSliders(float availableWidth) float oldFieldWidth = EditorGUIUtility.fieldWidth; EditorGUIUtility.labelWidth = availableWidth - Styles.sliderModeFieldWidth; EditorGUIUtility.fieldWidth = Styles.sliderModeFieldWidth; - m_SliderMode = (SliderMode)EditorGUILayout.IntPopup( - GUIContent.Temp(" "), (int)m_SliderMode, Styles.sliderModeLabels, Styles.sliderModeValues - ); + if(m_HDR && m_Color.exposureAdjustedColor.maxColorComponent > 1f) + m_SliderMode = (SliderMode)EditorGUILayout.IntPopup(GUIContent.Temp(" "), (int)m_SliderMode, Styles.sliderHDRModeLabels, Styles.sliderModeValues); + else + m_SliderMode = (SliderMode)EditorGUILayout.IntPopup(GUIContent.Temp(" "), (int)m_SliderMode, Styles.sliderLDRModeLabels, Styles.sliderModeValues); GUILayout.Space(Styles.extraVerticalSpacing); @@ -874,7 +887,7 @@ void DoPresetsGUI() { GUILayout.Space(-(EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing)); // pull up to reuse space var presetsRect = GUILayoutUtility.GetRect(0, Mathf.Clamp(m_ColorLibraryEditor.contentHeight, 20f, 250f)); - m_ColorLibraryEditor.OnGUI(presetsRect, color); + m_ColorLibraryEditor.OnGUI(presetsRect, instance.GetGUIColor(color)); } } @@ -1236,6 +1249,9 @@ public static void Start(Action colorPickedCallback) static void Start(GUIView viewToUpdate, Action colorPickedCallback) { + if(!InternalEditorUtility.IsAllowedToReadPixelOutsideUnity(out var errorMessage)) + Debug.LogWarning(errorMessage); + instance.m_DelegateView = viewToUpdate; instance.m_ColorPickedCallback = colorPickedCallback; ContainerWindow win = CreateInstance(); @@ -1250,6 +1266,7 @@ static void Start(GUIView viewToUpdate, Action colorPickedCallback) Vector2 p = GUIUtility.GUIToScreenPoint(Event.current.mousePosition); win.position = new Rect(p.x - kDummyWindowSize / 2, p.y - kDummyWindowSize / 2, kDummyWindowSize, kDummyWindowSize); instance.wantsMouseMove = true; + instance.Focus(); instance.SetEyeDropperOpen(true); instance.StealMouseCapture(); instance.m_IsOpened = true; diff --git a/Editor/Mono/GUI/DockArea.cs b/Editor/Mono/GUI/DockArea.cs index 4d5e450d43..02addce8be 100644 --- a/Editor/Mono/GUI/DockArea.cs +++ b/Editor/Mono/GUI/DockArea.cs @@ -138,7 +138,7 @@ protected override void OnDestroy() m_IsBeingDestroyed = true; if (hasFocus) - m_OnLostFocus?.Invoke(); + OnLostFocus(); actualView = null; @@ -152,7 +152,6 @@ protected override void OnDestroy() continue; UnityEngine.Object.DestroyImmediate(w, true); - EditorWindow.UpdateWindowMenuListing(); } m_Panes.Clear(); @@ -244,7 +243,6 @@ public void RemoveTab(EditorWindow pane, bool killIfEmpty, bool sendEvents = tru pane.m_Parent = null; if (killIfEmpty) KillIfEmpty(); - RegisterSelectedPane(sendEvents: true); } private static void UpdateWindowTitle(EditorWindow w) @@ -354,8 +352,9 @@ protected override void OldOnGUI() Rect dockAreaRect = new Rect(0, 0, position.width, position.height); Rect containerWindowPosition = window.position; - containerWindowPosition.width = GUIUtility.RoundToPixelGrid(containerWindowPosition.width); - containerWindowPosition.height = GUIUtility.RoundToPixelGrid(containerWindowPosition.height); + float scale = GetBackingScaleFactor(); + containerWindowPosition.width = GUIUtility.RoundToPixelGrid(containerWindowPosition.width, scale); + containerWindowPosition.height = GUIUtility.RoundToPixelGrid(containerWindowPosition.height, scale); DrawDockAreaBackground(dockAreaRect); @@ -377,8 +376,8 @@ protected override void OldOnGUI() ShowGenericMenu(position.width - genericMenuLeftOffset, m_TabAreaRect.y + genericMenuTopOffset); } - DrawTabs(m_TabAreaRect); HandleSplitView(); //fogbugz 1169963: in order to easily use the splitter in the gameView, it must be prioritized over DrawView(). Side effect for touch is that splitter picking zones might overlap other controls but the tabs still have higher priority so the user can undock the window in that case + DrawTabs(m_TabAreaRect); DrawView(dockAreaRect); DrawTabScrollers(m_TabAreaRect); @@ -983,7 +982,7 @@ private float DragTab(Rect tabAreaRect, float scrollOffset, GUIStyle tabStyle, G // Don't call OnFocus on the tab when it is moved to the new window EditorWindow.CreateNewWindowForEditorWindow(w, loadPosition: false, showImmediately: false, setFocus: false); - w.position = w.m_Parent.window.FitWindowRectToScreen(wPos, true, true); + w.position = ContainerWindow.FitRectToMouseScreen(wPos, true, w.m_Parent.window); Invoke("OnTabNewWindow", w); GUIUtility.hotControl = 0; @@ -1097,8 +1096,10 @@ internal RectOffset GetBorderSizeInternal() return m_BorderSize; Rect containerWindowPosition = window.position; - containerWindowPosition.width = GUIUtility.RoundToPixelGrid(containerWindowPosition.width); - containerWindowPosition.height = GUIUtility.RoundToPixelGrid(containerWindowPosition.height); + float scale = GetBackingScaleFactor(); + + containerWindowPosition.width = GUIUtility.RoundToPixelGrid(containerWindowPosition.width, scale); + containerWindowPosition.height = GUIUtility.RoundToPixelGrid(containerWindowPosition.height, scale); bool customBorder = floatingWindow && windowPosition.y == 0; bool isBottomTab = Mathf.Abs(windowPosition.yMax - containerWindowPosition.height) < 0.02f; @@ -1109,7 +1110,7 @@ internal RectOffset GetBorderSizeInternal() Rect r = windowPosition; if (r.xMin != 0) m_BorderSize.left += (int)kSideBorders; - if (Mathf.Abs(r.xMax - GUIUtility.RoundToPixelGrid(window.position.width)) > 0.02f) + if (Mathf.Abs(r.xMax - GUIUtility.RoundToPixelGrid(window.position.width, scale)) > 0.02f) m_BorderSize.right += (int)kSideBorders; m_BorderSize.top = (int)kTabHeight + (customBorder ? kFloatingWindowTopBorderWidth : 0); diff --git a/Editor/Mono/GUI/EditorApplicationLayout.cs b/Editor/Mono/GUI/EditorApplicationLayout.cs index d893abcd75..f125a3fc99 100644 --- a/Editor/Mono/GUI/EditorApplicationLayout.cs +++ b/Editor/Mono/GUI/EditorApplicationLayout.cs @@ -23,12 +23,13 @@ namespace UnityEditor { internal class EditorApplicationLayout { + static private PlayModeView m_PlayModeView = null; static private bool m_MaximizePending = false; - static List m_PlayModeViewList = null; + static internal bool IsInitializingPlaymodeLayout() { - return m_PlayModeViewList != null && m_PlayModeViewList.Count > 0; + return m_PlayModeView != null; } static internal void SetPlaymodeLayout() @@ -39,22 +40,6 @@ static internal void SetPlaymodeLayout() static internal void SetStopmodeLayout() { - if (m_PlayModeViewList != null && m_PlayModeViewList.Count > 0) - { - var monitorNames = EditorFullscreenController.GetConnectedDisplayNames(); - foreach (var playModeView in m_PlayModeViewList) - { - if (playModeView.fullscreenMonitorIdx >= monitorNames.Length) - continue; - - EditorFullscreenController.SetSettingsForCurrentDisplay(playModeView.fullscreenMonitorIdx); - EditorFullscreenController.OnExitPlaymode(); - } - - m_PlayModeViewList.Clear(); - m_PlayModeViewList = null; - } - WindowLayout.ShowAppropriateViewOnEnterExitPlaymode(false); Toolbar.RepaintToolbar(); } @@ -65,101 +50,53 @@ static internal void SetPausemodeLayout() SetStopmodeLayout(); } - static internal void InitializePlaymodeViewList() - { - if (m_PlayModeViewList == null) - { - m_PlayModeViewList = new List(); - } - else - { - m_PlayModeViewList.Clear(); - } - } - static internal void InitPlaymodeLayout() { - InitializePlaymodeViewList(); - WindowLayout.ShowAppropriateViewOnEnterExitPlaymodeList(true, out m_PlayModeViewList); + m_PlayModeView = WindowLayout.ShowAppropriateViewOnEnterExitPlaymode(true) as PlayModeView; + if (m_PlayModeView == null) + return; - var fullscreenDetected = false; - var monitorNames = EditorFullscreenController.GetConnectedDisplayNames(); - - foreach (var playModeView in m_PlayModeViewList) + if (m_PlayModeView.enterPlayModeBehavior == PlayModeView.EnterPlayModeBehavior.PlayMaximized) { - if (playModeView == null) - continue; - - if (playModeView.fullscreenMonitorIdx >= monitorNames.Length) - continue; - - if (playModeView.enterPlayModeBehavior == PlayModeView.EnterPlayModeBehavior.PlayFullscreen) - { - EditorFullscreenController.SetSettingsForCurrentDisplay(playModeView.fullscreenMonitorIdx); - EditorFullscreenController.isFullscreenOnPlay = true; - EditorFullscreenController.fullscreenDisplayId = playModeView.fullscreenMonitorIdx; - EditorFullscreenController.isToolbarEnabledOnFullscreen = false; - EditorFullscreenController.targetDisplayID = playModeView.targetDisplay; - - if (playModeView.m_Parent is DockArea dockArea && dockArea.actualView is GameView gv) - { - playModeView.m_Parent.EnableVSync(gv.vSyncEnabled); - EditorFullscreenController.enableVSync = gv.vSyncEnabled; - EditorFullscreenController.selectedSizeIndex = gv.selectedSizeIndex; - } - fullscreenDetected = true; - } - else if (!fullscreenDetected) + if (m_PlayModeView.m_Parent is DockArea dockArea) { - EditorFullscreenController.isFullscreenOnPlay = false; - } + m_MaximizePending = WindowLayout.MaximizePrepare(dockArea.actualView); - if (playModeView.enterPlayModeBehavior == PlayModeView.EnterPlayModeBehavior.PlayMaximized) - { - if (playModeView.m_Parent is DockArea dockArea) - { - m_MaximizePending = WindowLayout.MaximizePrepare(dockArea.actualView); - var gv = dockArea.actualView as GameView; - if (gv != null) - { - playModeView.m_Parent.EnableVSync(gv.vSyncEnabled); - } - } + var gameView = dockArea.actualView as GameView; + if (gameView != null) + m_PlayModeView.m_Parent.EnableVSync(gameView.vSyncEnabled); } + } - EditorFullscreenController.OnEnterPlaymode(); + // Mark this PlayModeView window as the start view so the backend + // can set size and mouseoffset properly for this view + m_PlayModeView.m_Parent.SetAsStartView(); + m_PlayModeView.m_Parent.SetAsLastPlayModeView(); - if (!EditorFullscreenController.isFullscreenOnPlay) - { - playModeView.m_Parent.SetAsStartView(); - playModeView.m_Parent.SetAsLastPlayModeView(); - - if (playModeView is IGameViewOnPlayMenuUser) - { - if (((IGameViewOnPlayMenuUser)playModeView).playFocused) - { - playModeView.Focus(); - } - } - } - Toolbar.RepaintToolbar(); - } + //GameView should be actively focussed If Playmode is entered in maximized state - case 1252097 + if (m_PlayModeView.maximized) + m_PlayModeView.m_Parent.Focus(); + + Toolbar.RepaintToolbar(); } static internal void FinalizePlaymodeLayout() { - foreach (var playModeView in m_PlayModeViewList) + if (m_PlayModeView != null) { - if (playModeView != null) - { - if (m_MaximizePending) - WindowLayout.MaximizePresent(playModeView); + if (m_MaximizePending) + WindowLayout.MaximizePresent(m_PlayModeView); - // All StartView references on all play mode views must be cleared before play mode starts. Otherwise it may cause issues - // with input being routed to the correct game window. See case 1381985 - playModeView.m_Parent.ClearStartView(); - } + m_PlayModeView.m_Parent.ClearStartView(); } + + Clear(); + } + + static private void Clear() + { + m_MaximizePending = false; + m_PlayModeView = null; } } } // namespace diff --git a/Editor/Mono/GUI/EditorCache.cs b/Editor/Mono/GUI/EditorCache.cs index 03ffb9404a..7a94acc92e 100644 --- a/Editor/Mono/GUI/EditorCache.cs +++ b/Editor/Mono/GUI/EditorCache.cs @@ -67,10 +67,17 @@ private bool Init(Object obj, EditorFeatures requirements) // Are we dealing with a Root Editor? if (editor.GetType() != nonRootEditor.GetType()) { - // Try again, with a normal editor + // Destroy previous editor + UnityEngine.Object.DestroyImmediate(editor); + // Try again, with a non Root editor editor = nonRootEditor; onSceneDragMethodInfo = editor.GetType().GetMethod("OnSceneDrag", sceneDragReflectionFlags); } + else + { + // Destroy unused non Root editor + UnityEngine.Object.DestroyImmediate(nonRootEditor); + } } if (onSceneDragMethodInfo != null) diff --git a/Editor/Mono/GUI/EditorStyles.cs b/Editor/Mono/GUI/EditorStyles.cs index a07eb373b5..a51c377025 100644 --- a/Editor/Mono/GUI/EditorStyles.cs +++ b/Editor/Mono/GUI/EditorStyles.cs @@ -257,6 +257,9 @@ public sealed class EditorStyles public static GUIStyle inspectorDefaultMargins { get { return s_Current.m_InspectorDefaultMargins; } } private GUIStyle m_InspectorDefaultMargins; + internal static GUIStyle inspectorHorizontalDefaultMargins => s_Current.m_InspectorHorizontalDefaultMargins; + private GUIStyle m_InspectorHorizontalDefaultMargins; + public static GUIStyle inspectorFullWidthMargins { get { return s_Current.m_InspectorFullWidthMargins; } } private GUIStyle m_InspectorFullWidthMargins; @@ -475,13 +478,13 @@ private void InitSharedStyles() m_ToolbarCreateAddNewDropDown = GetStyle("ToolbarCreateAddNewDropDown"); m_ToolbarTextField = GetStyle("toolbarTextField"); m_ToolbarLabel = GetStyle("ToolbarLabel"); - m_ToolbarSearchField = GetStyle("ToolbarSeachTextField"); - m_ToolbarSearchFieldPopup = GetStyle("ToolbarSeachTextFieldPopup"); + m_ToolbarSearchField = GetStyle("ToolbarSearchTextField"); + m_ToolbarSearchFieldPopup = GetStyle("ToolbarSearchTextFieldPopup"); m_ToolbarSearchFieldWithJump = GetStyle("ToolbarSearchTextFieldWithJump"); m_ToolbarSearchFieldWithJumpPopup = GetStyle("ToolbarSearchTextFieldWithJumpPopup"); m_ToolbarSearchFieldJumpButton = GetStyle("ToolbarSearchTextFieldJumpButton"); - m_ToolbarSearchFieldCancelButton = GetStyle("ToolbarSeachCancelButton"); - m_ToolbarSearchFieldCancelButtonEmpty = GetStyle("ToolbarSeachCancelButtonEmpty"); + m_ToolbarSearchFieldCancelButton = GetStyle("ToolbarSearchCancelButton"); + m_ToolbarSearchFieldCancelButtonEmpty = GetStyle("ToolbarSearchCancelButtonEmpty"); m_ToolbarSearchFieldCancelButtonWithJump = GetStyle("ToolbarSearchCancelButtonWithJump"); m_ToolbarSearchFieldCancelButtonWithJumpEmpty = GetStyle("ToolbarSearchCancelButtonWithJumpEmpty"); m_ToolbarSearchFieldWithJumpSynced = GetStyle("ToolbarSearchTextFieldWithJumpSynced"); @@ -547,6 +550,11 @@ private void InitSharedStyles() padding = new RectOffset(kInspectorPaddingLeft, kInspectorPaddingRight, kInspectorPaddingTop, 0) }; + m_InspectorHorizontalDefaultMargins = new GUIStyle + { + padding = new RectOffset(kInspectorPaddingLeft, kInspectorPaddingRight, 0, 0) + }; + // For the full width margins, use padding from right side in both sides, // though adjust for overdraw by adding one in left side to get even margins. m_InspectorFullWidthMargins = new GUIStyle diff --git a/Editor/Mono/GUI/GenericMenu.cs b/Editor/Mono/GUI/GenericMenu.cs index 0c32fd1ff6..9d61160869 100644 --- a/Editor/Mono/GUI/GenericMenu.cs +++ b/Editor/Mono/GUI/GenericMenu.cs @@ -82,7 +82,9 @@ public MenuItem(GUIContent _content, bool _separator, bool _on, MenuFunction2 _f } } - // Show the menu under the mouse + /// + /// Show the menu under the mouse when used in an OnGUI callback. + /// public void ShowAsContext() { if (Event.current == null) @@ -90,13 +92,32 @@ public void ShowAsContext() DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); } + /// + /// Show the menu at the given rect relative to the current window in an OnGUI callback. + /// + /// public void DropDown(Rect position) { DropDown(position, false); } - // Show the menu at the given screen rect + /// + /// Show the menu at the given rect relative to the current window in an OnGUI callback. + /// + /// internal void DropDown(Rect position, bool shouldDiscardMenuOnSecondClick) + { + Vector2 temp = GUIUtility.GUIToScreenPoint(new Vector2(position.x, position.y)); + position.x = temp.x; + position.y = temp.y; + DropDownScreenSpace(position, shouldDiscardMenuOnSecondClick); + } + + /// + /// Show the menu at the given screen rect. + /// + /// + internal void DropDownScreenSpace(Rect position, bool shouldDiscardMenuOnSecondClick) { string[] titles = new string[m_MenuItems.Count]; bool[] enabled = new bool[m_MenuItems.Count]; @@ -112,8 +133,8 @@ internal void DropDown(Rect position, bool shouldDiscardMenuOnSecondClick) if (item.on) selected.Add(idx); } - - EditorUtility.DisplayCustomMenuWithSeparators(position, titles, enabled, separator, (int[])selected.ToArray(typeof(int)), CatchMenu, null, true, allowDuplicateNames, shouldDiscardMenuOnSecondClick); + + EditorUtility.DisplayCustomMenuWithSeparatorsWithScreenSpacePosition(position, titles, enabled, separator, (int[])selected.ToArray(typeof(int)), CatchMenu, null, true, allowDuplicateNames, shouldDiscardMenuOnSecondClick); } // Display as a popup with /selectedIndex/. How this behaves depends on the platform (on Mac, it'll try to scroll the menu to the right place) diff --git a/Editor/Mono/GUI/GradientEditor.cs b/Editor/Mono/GUI/GradientEditor.cs index 981dc0f236..790ea73264 100644 --- a/Editor/Mono/GUI/GradientEditor.cs +++ b/Editor/Mono/GUI/GradientEditor.cs @@ -87,6 +87,31 @@ public void Init(Gradient gradient, int numSteps, bool hdr, ColorSpace colorSpac m_SelectedSwatch = m_RGBSwatches[0]; } + /// + /// Called when the Gradient has changed and we need to update the swatches. + /// + public void RefreshGradientData() + { + // If we are already editing a Gradient we need to preserve the selected swatch or dragging may be corrupted. + int selectedSwatch = 0; + bool selectedIsAlpha = false; + if (m_SelectedSwatch != null) + { + selectedIsAlpha = m_SelectedSwatch.m_IsAlpha; + selectedSwatch = selectedIsAlpha ? m_AlphaSwatches.IndexOf(m_SelectedSwatch) : m_RGBSwatches.IndexOf(m_SelectedSwatch); + } + + BuildArrays(); + + var swatches = selectedIsAlpha ? m_AlphaSwatches : m_RGBSwatches; + if (swatches.Count > 0) + { + if (selectedSwatch > swatches.Count - 1 || selectedSwatch == -1) + selectedSwatch = 0; + m_SelectedSwatch = swatches[selectedSwatch]; + } + } + public Gradient target { get { return m_Gradient; } diff --git a/Editor/Mono/GUI/GradientPicker.cs b/Editor/Mono/GUI/GradientPicker.cs index 3919ba7850..f1fecc804e 100644 --- a/Editor/Mono/GUI/GradientPicker.cs +++ b/Editor/Mono/GUI/GradientPicker.cs @@ -2,9 +2,8 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License -using UnityEngine; -using UnityEditor; using UnityEditorInternal; +using UnityEngine; namespace UnityEditor { @@ -267,14 +266,22 @@ public static void SetCurrentGradient(Gradient gradient) GUI.changed = true; } - public static void CloseWindow() + public static void RefreshGradientData() + { + s_GradientPicker?.m_GradientEditor?.RefreshGradientData(); + RepaintWindow(); + } + + public static void CloseWindow(bool throwExitGUI = true) { if (s_GradientPicker == null) return; s_GradientPicker.UnregisterEvents(); s_GradientPicker.Close(); - GUIUtility.ExitGUI(); + + if (throwExitGUI) + GUIUtility.ExitGUI(); } public static void RepaintWindow() diff --git a/Editor/Mono/GUI/InternalEditorGUI.cs b/Editor/Mono/GUI/InternalEditorGUI.cs index 1dfc7f46b3..c495c3210f 100644 --- a/Editor/Mono/GUI/InternalEditorGUI.cs +++ b/Editor/Mono/GUI/InternalEditorGUI.cs @@ -243,18 +243,6 @@ internal static void GameViewSizePopup(Rect buttonRect, GameViewSizeGroupType gr } } - internal static void GameViewOnPlayPopup(Rect buttonRect, int selectedIndex, IGameViewOnPlayMenuUser gameView, GUIStyle guiStyle) - { - var text = GameViewOnPlayMenu.GetOnPlayBehaviorName(selectedIndex); - - if (EditorGUI.DropdownButton(buttonRect, GUIContent.Temp(text), FocusType.Passive, guiStyle)) - { - var menuData = new GameViewOnPlayMenuItemProvider(); - var flexibleMenu = new GameViewOnPlayMenu(menuData, selectedIndex, null, gameView); - PopupWindow.Show(buttonRect, flexibleMenu); - } - } - public static void DrawRect(Rect rect, Color color) { if (Event.current.type != EventType.Repaint) diff --git a/Editor/Mono/GUI/InternalEditorGUILayout.cs b/Editor/Mono/GUI/InternalEditorGUILayout.cs index db0e20fb68..dba74acbd3 100644 --- a/Editor/Mono/GUI/InternalEditorGUILayout.cs +++ b/Editor/Mono/GUI/InternalEditorGUILayout.cs @@ -25,12 +25,6 @@ internal static void GameViewSizePopup(GameViewSizeGroupType groupType, int sele EditorGUI.GameViewSizePopup(s_LastRect, groupType, selectedIndex, gameView, style); } - internal static void GameViewOnPlayPopup(int selectedIndex, IGameViewOnPlayMenuUser gameView, GUIStyle style, params GUILayoutOption[] options) - { - s_LastRect = GetControlRect(false, EditorGUI.kSingleLineHeight, style, options); - EditorGUI.GameViewOnPlayPopup(s_LastRect, selectedIndex, gameView, style); - } - internal static void SortingLayerField(GUIContent label, SerializedProperty layerID, GUIStyle style, GUIStyle labelStyle) { s_LastRect = EditorGUILayout.GetControlRect(false, EditorGUI.kSingleLineHeight, style); diff --git a/Editor/Mono/GUI/LazyLoadReferenceField.cs b/Editor/Mono/GUI/LazyLoadReferenceField.cs index 31fed6affb..abb202fea9 100644 --- a/Editor/Mono/GUI/LazyLoadReferenceField.cs +++ b/Editor/Mono/GUI/LazyLoadReferenceField.cs @@ -2,6 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using UnityEditor.UIElements; using UnityEngine; using UnityEngine.UIElements; @@ -35,12 +36,14 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) { ScriptAttributeUtility.GetFieldInfoFromProperty(property, out var fieldType); - var objectField = new UnityEditor.UIElements.ObjectField(property.displayName); + var objectField = new ObjectField(preferredLabel); var genericType = fieldType.GetGenericArguments()[0]; objectField.objectType = genericType; objectField.value = property.objectReferenceValue; objectField.bindingPath = property.propertyPath; + PropertyField.ConfigureFieldStyles(objectField); + return objectField; } } diff --git a/Editor/Mono/GUI/MainView.cs b/Editor/Mono/GUI/MainView.cs index a6b27109ac..75edc0647f 100644 --- a/Editor/Mono/GUI/MainView.cs +++ b/Editor/Mono/GUI/MainView.cs @@ -40,6 +40,7 @@ void OnEnable() protected override void SetPosition(Rect newPos) { base.SetPosition(newPos); + newPos = position; // base.SetPosition does some sanitization so we have to use the position property instead of the newPos we received if (children.Length == 0) return; if (children.Length > 2) diff --git a/Editor/Mono/GUI/MaskFieldGUI.cs b/Editor/Mono/GUI/MaskFieldGUI.cs index 0c4ca9c1e8..652cd7bff4 100644 --- a/Editor/Mono/GUI/MaskFieldGUI.cs +++ b/Editor/Mono/GUI/MaskFieldGUI.cs @@ -33,6 +33,12 @@ private class MaskCallbackInfo // Which view should we send it to. private readonly GUIView m_SourceView; + // Current drop-down reference + public MaskFieldDropDown m_DropDown; + + // validation flag for masks that are changed externally + private bool m_Validate = false; + public MaskCallbackInfo(int controlID) { m_ControlID = controlID; @@ -77,6 +83,31 @@ internal void SetMaskValueDelegate(object userData, string[] options, int select if (m_SourceView) m_SourceView.SendEvent(EditorGUIUtility.CommandEvent(kMaskMenuChangedMessage)); } + + public void UpdateFlagChanges(int controlID, int mask, int[] optionMaskValues) + { + var evt = Event.current; + + if (evt.type == EventType.ExecuteCommand) + { + m_Validate = true; + } + // This code is responsible for verifying whether the incoming mask value differs from the one that is currently selected in the dropdown menu. + // When these values do not match, it serves as confirmation that the incoming value has been modified. + // Subsequently, we proceed to update the dropdown menu to reflect these changes. + else if (evt.type == EventType.Repaint && m_Validate) + { + if (m_DropDown == null) + { + return; + } + + if (mask != m_NewMask && m_ControlID == controlID) + m_DropDown.UpdateMaskValues(mask, optionMaskValues); + + m_Validate = false; + } + } } /// Make a field for a generic mask. @@ -117,6 +148,10 @@ internal static int DoMaskField(Rect position, int controlID, int mask, string[] GetMenuOptions(mask, flagNames, flagValues, out var buttonText, out var buttonTextMixed, out var optionNames, out var optionMaskValues, out _, enumType); + // This checks and update flags changes that are modified out of dropdown menu + if (MaskCallbackInfo.m_Instance != null) + MaskCallbackInfo.m_Instance.UpdateFlagChanges(controlID, mask, optionMaskValues); + Event evt = Event.current; if (evt.type == EventType.Repaint) { @@ -126,7 +161,8 @@ internal static int DoMaskField(Rect position, int controlID, int mask, string[] else if ((evt.type == EventType.MouseDown && position.Contains(evt.mousePosition)) || evt.MainActionKeyForControl(controlID)) { MaskCallbackInfo.m_Instance = new MaskCallbackInfo(controlID); - PopupWindowWithoutFocus.Show(position, new MaskFieldDropDown(optionNames, flagValues, optionMaskValues, mask, MaskCallbackInfo.m_Instance.SetMaskValueDelegate)); + MaskCallbackInfo.m_Instance.m_DropDown = new MaskFieldDropDown(optionNames, flagValues, optionMaskValues, mask, MaskCallbackInfo.m_Instance.SetMaskValueDelegate); + PopupWindow.Show(position, MaskCallbackInfo.m_Instance.m_DropDown); } return mask; @@ -234,6 +270,38 @@ internal static GUIContent DoMixedLabel(string label, string mixedLabel, Rect re return content; } + internal static void CalculateMaskValues(int mask, int[] flagValues, ref int[] optionMaskValues) + { + uint selectedValue = (uint)mask; + + var flagStartIndex = 0; + if (flagValues[0] == 0) + flagStartIndex++; + if (flagValues.Length > 1 && flagValues[1] == -1) + flagStartIndex++; + + if (mask == ~0) + { + uint allLayersMask = 0; + for (var flagIndex = flagStartIndex; flagIndex < flagValues.Length; flagIndex++) + { + allLayersMask |= (uint)flagValues[flagIndex]; + } + + selectedValue = allLayersMask; + } + + var flagEndIndex = flagStartIndex + optionMaskValues.Length - 2; + + for (var flagIndex = flagStartIndex; flagIndex < flagEndIndex; flagIndex++) + { + uint flagValue = (uint)flagValues[flagIndex]; + + bool flagSet = ((selectedValue & flagValue) == flagValue); + + optionMaskValues[flagIndex - flagStartIndex + 2] = (int)(flagSet ? selectedValue & ~flagValue : selectedValue | flagValue); + } + } internal static void GetMenuOptions(int mask, string[] flagNames, int[] flagValues, out string buttonText, out string buttonMixedValuesText, out string[] optionNames, out int[] optionMaskValues, out int[] selectedOptions, Type enumType = null) @@ -342,15 +410,8 @@ internal static void GetMenuOptions(int mask, string[] flagNames, int[] flagValu optionMaskValues[1] = everythingValue; if (EditorGUI.showMixedValue) intermediateMask = 0; - for (var flagIndex = flagStartIndex; flagIndex < flagEndIndex; flagIndex++) - { - var optionIndex = flagIndex - flagStartIndex + 2; - var flagValue = flagValues[flagIndex]; - var flagSet = ((intermediateMask & flagValue) == flagValue); - var newMask = (flagSet ? intermediateMask & ~flagValue : intermediateMask | flagValue); - optionMaskValues[optionIndex] = newMask; - } + CalculateMaskValues(intermediateMask, flagValues, ref optionMaskValues); } } } diff --git a/Editor/Mono/GUI/ObjectField.cs b/Editor/Mono/GUI/ObjectField.cs index f357a41c98..bf54e1da63 100644 --- a/Editor/Mono/GUI/ObjectField.cs +++ b/Editor/Mono/GUI/ObjectField.cs @@ -248,7 +248,9 @@ static Object DoObjectField(Rect position, Rect dropRect, int id, Object obj, Ob if (validatedObject != null) { - DragAndDrop.visualMode = DragAndDropVisualMode.Generic; + if (DragAndDrop.visualMode == DragAndDropVisualMode.None) + DragAndDrop.visualMode = DragAndDropVisualMode.Generic; + if (eventType == EventType.DragPerform) { if (property != null) diff --git a/Editor/Mono/GUI/PackageExport.cs b/Editor/Mono/GUI/PackageExport.cs index af6f7d45d6..dd30b3326d 100644 --- a/Editor/Mono/GUI/PackageExport.cs +++ b/Editor/Mono/GUI/PackageExport.cs @@ -8,29 +8,35 @@ using System.Linq; using UnityEditor.IMGUI.Controls; using UnityEngine; +using UnityEngine.Analytics; +using UnityEngine.Scripting; +using UnityEngine.UIElements; namespace UnityEditor { internal class PackageExport : EditorWindow { - [SerializeField] private ExportPackageItem[] m_ExportPackageItems; - [SerializeField] private bool m_IncludeDependencies = true; - [SerializeField] private TreeViewState m_TreeViewState; - [NonSerialized] private PackageExportTreeView m_Tree; - [NonSerialized] private bool m_DidScheduleUpdate = false; + [SerializeField] private ExportPackageItem[] m_ExportPackageItems; + [SerializeField] private bool m_IncludeDependencies = true; + [SerializeField] private bool m_IncludeScripts = true; + [SerializeField] private TreeViewState m_TreeViewState; + [SerializeField] private string[] m_ProjectBrowserSelection; + [NonSerialized] private PackageExportTreeView m_Tree; + [NonSerialized] private bool m_DidScheduleUpdate = false; public ExportPackageItem[] items { get { return m_ExportPackageItems; } } internal static class Styles { - public static GUIStyle title = "LargeBoldLabel"; - public static GUIStyle bottomBarBg = "ProjectBrowserBottomBarBg"; - public static GUIStyle topBarBg = "OT TopBar"; - public static GUIStyle loadingTextStyle = "CenteredLabel"; - public static GUIContent allText = EditorGUIUtility.TrTextContent("All"); - public static GUIContent noneText = EditorGUIUtility.TrTextContent("None"); - public static GUIContent includeDependenciesText = EditorGUIUtility.TrTextContent("Include dependencies"); - public static GUIContent header = EditorGUIUtility.TrTextContent("Items to Export"); + public static GUIStyle title = "LargeBoldLabel"; + public static GUIStyle bottomBarBg = "ProjectBrowserBottomBarBg"; + public static GUIStyle topBarBg = "OT TopBar"; + public static GUIStyle loadingTextStyle = "CenteredLabel"; + public static GUIContent allText = EditorGUIUtility.TrTextContent("All"); + public static GUIContent noneText = EditorGUIUtility.TrTextContent("None"); + public static GUIContent includeDependenciesText = EditorGUIUtility.TrTextContent("Include dependencies", "Include all dependencies required for the selected items in the export list."); + public static GUIContent includeScriptsText = EditorGUIUtility.TrTextContent("Include all scripts", "Include all project scripts in the export list to avoid potential compilation errors."); + public static GUIContent header = EditorGUIUtility.TrTextContent("Items to Export"); } public PackageExport() @@ -40,13 +46,13 @@ public PackageExport() minSize = new Vector2(350, 350); } - // Called from from menu + // Called from menu static internal void ShowExportPackage() { GetWindow(true, "Exporting package").RefreshAssetList(); } - internal static IEnumerable GetAssetItemsForExport(ICollection guids, bool includeDependencies) + internal static IEnumerable GetAssetItemsForExport(ICollection guids, bool includeDependencies, bool includeScripts) { // if nothing is selected, export all if (0 == guids.Count) @@ -56,9 +62,8 @@ internal static IEnumerable GetAssetItemsForExport(ICollectio } ExportPackageItem[] assets = PackageUtility.BuildExportPackageItemsListWithPackageManagerWarning(guids.ToArray(), includeDependencies, true); - - // If any scripts are included, add all scripts with dependencies - if (includeDependencies && assets.Any(asset => UnityEditorInternal.InternalEditorUtility.IsScriptOrAssembly(asset.assetPath))) + + if (includeScripts) { assets = PackageUtility.BuildExportPackageItemsListWithPackageManagerWarning( guids.Union(UnityEditorInternal.InternalEditorUtility.GetAllScriptGUIDs()).ToArray(), includeDependencies, true); @@ -158,11 +163,13 @@ void TopButtonsArea() if (GUILayout.Button(Styles.allText, GUILayout.Width(50))) { m_Tree.SetAllEnabled(PackageExportTreeView.EnabledState.All); + SendAnalyticsEvent("selectAll"); } if (GUILayout.Button(Styles.noneText, GUILayout.Width(50))) { m_Tree.SetAllEnabled(PackageExportTreeView.EnabledState.None); + SendAnalyticsEvent("selectNone"); } GUILayout.Space(10); @@ -180,7 +187,21 @@ void BottomArea() GUILayout.Space(10); EditorGUI.BeginChangeCheck(); - m_IncludeDependencies = GUILayout.Toggle(m_IncludeDependencies, Styles.includeDependenciesText); + var includeDependenciesNewValue = GUILayout.Toggle(m_IncludeDependencies, Styles.includeDependenciesText); + if (m_IncludeDependencies != includeDependenciesNewValue) + { + m_IncludeDependencies = includeDependenciesNewValue; + SendAnalyticsEvent("toggleIncludeDependencies"); + } + + GUILayout.Space(5); + var includeScriptsNewValue = GUILayout.Toggle(m_IncludeScripts, Styles.includeScriptsText); + if (m_IncludeScripts != includeScriptsNewValue) + { + m_IncludeScripts = includeScriptsNewValue; + SendAnalyticsEvent("toggleIncludeScripts"); + } + if (EditorGUI.EndChangeCheck()) { RefreshAssetList(); @@ -195,6 +216,7 @@ void BottomArea() if (selectedItemWithInvalidChar != null && !EditorUtility.DisplayDialog(L10n.Tr("Cross platform incompatibility"), L10n.Tr($"The asset “{Path.GetFileNameWithoutExtension(selectedItemWithInvalidChar.assetPath)}” contains one or more characters that are not compatible across platforms: {invalidChars}"), L10n.Tr("I understand"), L10n.Tr("Cancel"))) { GUIUtility.ExitGUI(); + SendAnalyticsEvent("exportErrorInvalidCharInAssetName"); return; } @@ -245,10 +267,15 @@ private void Export() } PackageUtility.ExportPackage(guids.ToArray(), fileName); + SendAnalyticsEvent("exportSuccess"); Close(); GUIUtility.ExitGUI(); } + else + { + SendAnalyticsEvent("exportCancelledAtFileSelection"); + } } private void ScheduleBuildAssetList() @@ -273,7 +300,8 @@ private void BuildAssetList() { UnscheduleBuildAssetList(); - m_ExportPackageItems = GetAssetItemsForExport(Selection.assetGUIDsDeepSelection, m_IncludeDependencies).ToArray(); + m_ProjectBrowserSelection ??= Selection.assetGUIDsDeepSelection; + m_ExportPackageItems = GetAssetItemsForExport(m_ProjectBrowserSelection, m_IncludeDependencies, m_IncludeScripts).ToArray(); // GUI is reconstructed in OnGUI (when needed) m_Tree = null; @@ -281,5 +309,41 @@ private void BuildAssetList() Repaint(); } + + private void SendAnalyticsEvent(string action) + { + var numSelectedAssets = 0; + var numTotalAssets = 0; + foreach (var i in m_ExportPackageItems) + { + if (i.isFolder) + continue; + numTotalAssets++; + if (i.enabledStatus > 0) + numSelectedAssets++; + } + AssetExportWindowAnalytics.SendEvent(action, numSelectedAssets, numTotalAssets, m_IncludeDependencies); + } + + [Serializable] + internal struct AssetExportWindowAnalytics + { + public string action; + public int num_selected_assets; + public int num_total_assets; + public bool include_dependencies; + + public static void SendEvent(string action, int numSelectedAsset, int numTotalAssets, bool includeDependencies) + { + var parameters = new AssetExportWindowAnalytics + { + action = action, + num_selected_assets = numSelectedAsset, + num_total_assets = numTotalAssets, + include_dependencies = includeDependencies + }; + EditorAnalytics.SendEventWithLimit("assetExportWindow", parameters); + } + } } } diff --git a/Editor/Mono/GUI/PackageImport.cs b/Editor/Mono/GUI/PackageImport.cs index 93f6f23ff0..cca4dcd487 100644 --- a/Editor/Mono/GUI/PackageImport.cs +++ b/Editor/Mono/GUI/PackageImport.cs @@ -243,12 +243,14 @@ void BottomArea() { PackageImportWizard.instance.CancelImport(); } - if (PackageImportWizard.instance.IsProjectSettingStep && GUILayout.Button(EditorGUIUtility.TrTextContent("Back"))) + + var isSecondStep = PackageImportWizard.instance.IsMultiStepWizard && + PackageImportWizard.instance.IsProjectSettingStep; + if (isSecondStep && GUILayout.Button(EditorGUIUtility.TrTextContent("Back"))) { PackageImportWizard.instance.DoPreviousStep(m_ImportPackageItems); } - var buttonText = PackageImportWizard.instance.IsMultiStepWizard - && !PackageImportWizard.instance.IsProjectSettingStep ? "Next" : "Import"; + var buttonText = isSecondStep || !PackageImportWizard.instance.IsMultiStepWizard ? "Import" : "Next"; if (GUILayout.Button(EditorGUIUtility.TrTextContent(buttonText))) { if (m_ImportPackageItems != null) @@ -430,14 +432,24 @@ public void StartImport(string packagePath, ImportPackageItem[] items, string pa m_InitialImportItems = items; foreach (var item in items) { + // We don't want to add `ProjectVersion.txt` since it would override the project Editor version and if it's a lower version, it would be downgraded + if (item.destinationAssetPath == "ProjectSettings/ProjectVersion.txt") + continue; + if (item.destinationAssetPath.StartsWith("ProjectSettings/")) m_ProjectSettingItems.Add(item); else m_AssetContentItems.Add(item); } - m_IsMultiStepWizard = m_ProjectSettingItems.Any(); - ShowImportWindow(m_AssetContentItems.ToArray()); + m_IsMultiStepWizard = m_AssetContentItems.Any() && m_ProjectSettingItems.Any(); + if (m_AssetContentItems.Any()) + ShowImportWindow(m_AssetContentItems.ToArray()); + else if (m_ProjectSettingItems.Any()) + { + m_IsProjectSettingStep = true; + ShowImportWindow(m_ProjectSettingItems.ToArray()); + } } public void DoNextStep(ImportPackageItem[] importPackageItems) @@ -459,12 +471,12 @@ public void DoNextStep(ImportPackageItem[] importPackageItems) public void DoPreviousStep(ImportPackageItem[] importPackageItems) { - if (IsProjectSettingStep) - { - m_ProjectSettingItems = new List(importPackageItems); - m_IsProjectSettingStep = false; - ShowImportWindow(m_AssetContentItems.ToArray()); - } + if (!IsProjectSettingStep || !IsMultiStepWizard) + return; + + m_ProjectSettingItems = new List(importPackageItems); + m_IsProjectSettingStep = false; + ShowImportWindow(m_AssetContentItems.ToArray()); } public void CancelImport() @@ -491,8 +503,7 @@ private void ShowImportWindow(ImportPackageItem[] items) private void FinishImport() { - var completeItemList = IsMultiStepWizard ? m_AssetContentItems.Concat(m_ProjectSettingItems) : m_AssetContentItems; - PackageUtility.ImportPackageAssets(m_PackageName, completeItemList.ToArray()); + PackageUtility.ImportPackageAssets(m_PackageName, m_AssetContentItems.Concat(m_ProjectSettingItems).ToArray()); CloseImportWindow(); } diff --git a/Editor/Mono/GUI/PackageImportTreeView.cs b/Editor/Mono/GUI/PackageImportTreeView.cs index 5c48ee85c4..45225ed672 100644 --- a/Editor/Mono/GUI/PackageImportTreeView.cs +++ b/Editor/Mono/GUI/PackageImportTreeView.cs @@ -534,6 +534,7 @@ protected override void RenameEnded() private class PackageImportTreeViewDataSource : TreeViewDataSource { private PackageImportTreeView m_PackageImportView; + private const string k_RootTreeItemName = "InvisibleAssetsFolder"; public PackageImportTreeViewDataSource(TreeViewController treeView, PackageImportTreeView view) : base(treeView) @@ -559,7 +560,7 @@ public override bool IsExpandable(TreeViewItem item) public override void FetchData() { int rootDepth = -1; // -1 so its children will have 0 depth - m_RootItem = new PackageImportTreeViewItem(null, "Assets".GetHashCode(), rootDepth, null, "InvisibleAssetsFolder"); + m_RootItem = new PackageImportTreeViewItem(null, k_RootTreeItemName.GetHashCode(), rootDepth, null, k_RootTreeItemName); bool initExpandedState = true; if (initExpandedState) diff --git a/Editor/Mono/GUI/PopupLocationHelper.cs b/Editor/Mono/GUI/PopupLocationHelper.cs index 287f99c426..886d66d416 100644 --- a/Editor/Mono/GUI/PopupLocationHelper.cs +++ b/Editor/Mono/GUI/PopupLocationHelper.cs @@ -74,12 +74,9 @@ public static Rect GetDropDownRect(Rect buttonRect, Vector2 minSize, Vector2 max return GetLargestRect(croppedRects); } - private static Rect FitRect(Rect rect, ContainerWindow popupContainerWindow) + private static Rect FitRect(Rect rect, Vector2 uiPositionToSelectScreen, ContainerWindow popupContainerWindow) { - if (popupContainerWindow) - return popupContainerWindow.FitWindowRectToScreen(rect, true, true); - else - return ContainerWindow.FitRectToScreen(rect, true, true); + return ContainerWindow.FitRectToScreen(rect, uiPositionToSelectScreen, true, popupContainerWindow); } private static bool PopupRight(Rect buttonRect, Vector2 minSize, Vector2 maxSize, ContainerWindow popupContainerWindow, out Rect resultRect) @@ -90,12 +87,12 @@ private static bool PopupRight(Rect buttonRect, Vector2 minSize, Vector2 maxSize dropDownRectRight.xMax += spaceFromRight; dropDownRectRight.height += k_SpaceFromBottom; - dropDownRectRight = FitRect(dropDownRectRight, popupContainerWindow); + dropDownRectRight = FitRect(dropDownRectRight, buttonRect.center, popupContainerWindow); float availableWidthRight = Mathf.Max(dropDownRectRight.xMax - buttonRect.xMax - spaceFromRight, 0); float windowWidth = Mathf.Min(availableWidthRight, maxSize.x); resultRect = new Rect(dropDownRectRight.x, dropDownRectRight.y, windowWidth, dropDownRectRight.height - k_SpaceFromBottom); - if (availableWidthRight >= minSize.x) + if (Mathf.Ceil(availableWidthRight) >= minSize.x) return true; return false; } @@ -109,12 +106,12 @@ private static bool PopupLeft(Rect buttonRect, Vector2 minSize, Vector2 maxSize, dropDownRectLeft.xMin -= spaceFromLeft; dropDownRectLeft.height += k_SpaceFromBottom; - dropDownRectLeft = FitRect(dropDownRectLeft, popupContainerWindow); + dropDownRectLeft = FitRect(dropDownRectLeft, buttonRect.center, popupContainerWindow); float availableWidthLeft = Mathf.Max(buttonRect.x - dropDownRectLeft.x - spaceFromLeft, 0); float windowWidth = Mathf.Min(availableWidthLeft, maxSize.x); resultRect = new Rect(dropDownRectLeft.x, dropDownRectLeft.y, windowWidth, dropDownRectLeft.height - k_SpaceFromBottom); - if (availableWidthLeft >= minSize.x) + if (Mathf.Ceil(availableWidthLeft) >= minSize.x) return true; return false; } @@ -141,12 +138,12 @@ private static bool PopupAbove(Rect dropDownRectAbove, Rect buttonRect, Vector2 // Expand dropdown height to include empty space above dropDownRectAbove.yMin -= spaceFromTop; // Fit rect to screen - dropDownRectAbove = FitRect(dropDownRectAbove, popupContainerWindow); + dropDownRectAbove = FitRect(dropDownRectAbove, buttonRect.center, popupContainerWindow); // Calculate how much space is available for the window above the button float availableHeightAbove = Mathf.Max(buttonRect.y - dropDownRectAbove.y - spaceFromTop, 0); // If there's room for the window at its minimum size above the button, then place it there - if (availableHeightAbove >= minSize.y) + if (Mathf.Ceil(availableHeightAbove) >= minSize.y) { float windowHeight = Mathf.Min(availableHeightAbove, maxSize.y); { @@ -178,12 +175,12 @@ private static bool PopupBelow(Rect dropDownRectBelow, Rect buttonRect, Vector2 // Expand dropdown height to include empty space below dropDownRectBelow.height += k_SpaceFromBottom; // Fit rect to screen - dropDownRectBelow = FitRect(dropDownRectBelow, popupContainerWindow); + dropDownRectBelow = FitRect(dropDownRectBelow, buttonRect.center, popupContainerWindow); // Calculate how much space is available for the window below the button float availableHeightBelow = Mathf.Max(dropDownRectBelow.yMax - buttonRect.yMax - k_SpaceFromBottom, 0); // If there's room for the window at its minimum size below the button, then place it there - if (availableHeightBelow >= minSize.y) + if (Mathf.Ceil(availableHeightBelow) >= minSize.y) { float windowHeight = Mathf.Min(availableHeightBelow, maxSize.y); resultRect = new Rect(dropDownRectBelow.x, buttonRect.yMax, dropDownRectBelow.width, windowHeight); @@ -203,7 +200,7 @@ private static bool PopupOverlay(Rect buttonRect, Vector2 minSize, Vector2 maxSi { //Stretch the popup over the button Rect if it doesn't fit Rect dropDownRectBelow = new Rect(buttonRect.x, buttonRect.yMax, maxSize.x, maxSize.y); - resultRect = FitRect(dropDownRectBelow, popupContainerWindow); + resultRect = FitRect(dropDownRectBelow, buttonRect.center, popupContainerWindow); return true; } diff --git a/Editor/Mono/GUI/PopupWindow.cs b/Editor/Mono/GUI/PopupWindow.cs index 26d03d32fa..de025f1989 100644 --- a/Editor/Mono/GUI/PopupWindow.cs +++ b/Editor/Mono/GUI/PopupWindow.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using UnityEngine; +using UnityEngine.UIElements; namespace UnityEditor { @@ -14,7 +15,9 @@ public abstract class PopupWindowContent { public EditorWindow editorWindow { get; internal set; } - public abstract void OnGUI(Rect rect); + public virtual void OnGUI(Rect rect) { } + internal virtual VisualElement CreateGUI() => null; + public virtual Vector2 GetWindowSize() { return new Vector2(200, 200); @@ -26,12 +29,22 @@ public virtual void OnClose() {} public class PopupWindow : EditorWindow { + const string k_UssClassName = "unity-popup-window-root"; + const string k_InvalidSizeTemplate = "Invalid content size: {0}. Specify dimensions greater than zero in CreateGUI."; + + internal static readonly string invalidSizeLabelUssClassName = "popup-window__invalid-size-label"; + static readonly Vector2 k_DefaultWindowSize = new(10, 10); + PopupWindowContent m_WindowContent; Vector2 m_LastWantedSize; Rect m_ActivatorRect; PopupLocation[] m_LocationPriorityOrder; static double s_LastClosedTime; static Rect s_LastActivatorRect; + VisualElement m_UserContent; + + bool UseIMGUI => !UseUIToolkit; + bool UseUIToolkit => m_UserContent != null; internal PopupWindow() { @@ -52,18 +65,11 @@ internal static void Show(Rect activatorRect, PopupWindowContent windowContent, { // If we already have a popup window showing this type of content, then just close // the existing one. - var existingWindows = Resources.FindObjectsOfTypeAll(typeof(PopupWindow)); - if (existingWindows != null && existingWindows.Length > 0) + var existingPopup = FindExistingPopupWindow(windowContent); + if (existingPopup != null) { - var existingPopup = existingWindows[0] as PopupWindow; - if (existingPopup != null && existingPopup.m_WindowContent != null && windowContent != null) - { - if (existingPopup.m_WindowContent.GetType() == windowContent.GetType()) - { - existingPopup.CloseWindow(); - return; - } - } + existingPopup.CloseWindow(); + return; } if (ShouldShowWindow(activatorRect)) @@ -103,32 +109,96 @@ internal void Init(Rect activatorRect, PopupWindowContent windowContent, PopupLo m_LastWantedSize = Vector2.zero; m_LocationPriorityOrder = locationPriorityOrder; - ShowAsDropDown(m_ActivatorRect, m_WindowContent.GetWindowSize(), locationPriorityOrder, showMode, giveFocus); + ShowAsDropDown(m_ActivatorRect, k_DefaultWindowSize, locationPriorityOrder, showMode, giveFocus); + + if (UseUIToolkit) + SetupPopupAutoSize(); + else + FitWindowToContent(); } internal void OnGUI() { - FitWindowToContent(); - Rect windowRect = new Rect(0, 0, position.width, position.height); - m_WindowContent.OnGUI(windowRect); - GUI.Label(windowRect, GUIContent.none, "grey_border"); - FitWindowToContent(); + if (UseIMGUI) + { + FitWindowToContent(); + Rect windowRect = new Rect(0, 0, position.width, position.height); + m_WindowContent.OnGUI(windowRect); + GUI.Label(windowRect, GUIContent.none, "grey_border"); + FitWindowToContent(); + } + } + + void CreateGUI() + { + m_UserContent = m_WindowContent?.CreateGUI(); + if (m_UserContent != null) + { + var infiniteCanvas = new VisualElement { style = { position = Position.Absolute } }; + infiniteCanvas.AddToClassList(k_UssClassName); + rootVisualElement.Add(infiniteCanvas); + infiniteCanvas.Add(m_UserContent); + } } - private void FitWindowToContent() + void FitWindowToContent() { if (m_WindowContent == null) return; Vector2 wantedSize = m_WindowContent.GetWindowSize(); - if (m_LastWantedSize != wantedSize) + SetWindowSize(wantedSize); + } + + void SetWindowSize(Vector2 size) + { + if (this && m_LastWantedSize != size) { - m_LastWantedSize = wantedSize; - Rect screenRect = m_Parent.window.GetDropDownRect(m_ActivatorRect, wantedSize, wantedSize, m_LocationPriorityOrder); - minSize = maxSize = new Vector2(screenRect.width, screenRect.height); + m_LastWantedSize = size; + Rect screenRect = m_Parent.window.GetDropDownRect(m_ActivatorRect, size, size, m_LocationPriorityOrder); position = screenRect; + minSize = maxSize = new Vector2(screenRect.width, screenRect.height); + } + } + + void SetupPopupAutoSize() + { + if (UseUIToolkit && rootVisualElement.panel is Panel panel) + { + panel.pixelsPerPoint = m_Parent.GetBackingScaleFactor(); + panel.ValidateLayout(); + var contentSize = m_UserContent.layout.size; + if (contentSize.x <= 0f || contentSize.y <= 0f) + { + var invalidSizeMessage = string.Format(k_InvalidSizeTemplate, contentSize); + AddInvalidSizeLabel(invalidSizeMessage); + panel.ValidateLayout(); // measure again to get the size of the label + } + + var canvasSize = m_UserContent.parent.layout.size; + SetWindowSize(canvasSize); + m_UserContent.RegisterCallback(OnContentGeometryChanged); } } + void AddInvalidSizeLabel(string text) + { + var invalidSizeMessage = new TextElement + { + text = text, + enableRichText = true, + }; + invalidSizeMessage.AddToClassList(invalidSizeLabelUssClassName); + m_UserContent.Add(invalidSizeMessage); + } + + void OnContentGeometryChanged(GeometryChangedEvent evt) + { + var size = evt.newRect.size; + + // Schedule the size change to avoid multiple layout events in the same frame + rootVisualElement.schedule.Execute(() => { SetWindowSize(size); }); + } + void CloseWindow() { Close(); @@ -147,11 +217,29 @@ protected virtual void OnDisable() CloseContent(); } - // Change to private protected once available in C#. - internal void CloseContent() + private protected void CloseContent() { - if (m_WindowContent != null) - m_WindowContent.OnClose(); + m_WindowContent?.OnClose(); + m_UserContent?.UnregisterCallback(OnContentGeometryChanged); + m_UserContent = null; + } + + static PopupWindow FindExistingPopupWindow(PopupWindowContent windowContent) + { + var existingWindows = Resources.FindObjectsOfTypeAll(typeof(PopupWindow)); + if (existingWindows != null && existingWindows.Length > 0) + { + var existingPopup = existingWindows[0] as PopupWindow; + if (existingPopup != null && existingPopup.m_WindowContent != null && windowContent != null) + { + if (existingPopup.m_WindowContent.GetType() == windowContent.GetType()) + { + return existingPopup; + } + } + } + + return null; } } } diff --git a/Editor/Mono/GUI/PopupWindowWithoutFocus.cs b/Editor/Mono/GUI/PopupWindowWithoutFocus.cs index 5d129e8426..fcd8e6e606 100644 --- a/Editor/Mono/GUI/PopupWindowWithoutFocus.cs +++ b/Editor/Mono/GUI/PopupWindowWithoutFocus.cs @@ -27,7 +27,8 @@ class PopupWindowWithoutFocus : PopupWindow if (s_PopupWindowWithoutFocus != null) { - s_PopupWindowWithoutFocus.CloseContent(); + s_PopupWindowWithoutFocus.Close(); + s_PopupWindowWithoutFocus = null; } if (ShouldShowWindow(activatorRect)) diff --git a/Editor/Mono/GUI/RenameOverlay.cs b/Editor/Mono/GUI/RenameOverlay.cs index 85b8ab136e..184965b515 100644 --- a/Editor/Mono/GUI/RenameOverlay.cs +++ b/Editor/Mono/GUI/RenameOverlay.cs @@ -105,6 +105,8 @@ public void EndRename(bool acceptChanges) if (!m_IsRenaming) return; + EditorGUIUtility.renameWasCompleted = true; + Undo.undoRedoEvent -= UndoRedoWasPerformed; EditorApplication.update -= BeginRenameInternalCallback; diff --git a/Editor/Mono/GUI/ReorderableList.cs b/Editor/Mono/GUI/ReorderableList.cs index 3490912021..ae01c675cd 100644 --- a/Editor/Mono/GUI/ReorderableList.cs +++ b/Editor/Mono/GUI/ReorderableList.cs @@ -538,6 +538,8 @@ public int index private float listElementTopPadding => headerHeight > 5 ? 4 : 1; // headerHeight is usually set to 3px when there is no header content. Therefore, we add a 1px top margin to match the 4px bottom margin private const float kListElementBottomPadding = 4; + bool useCulling => GUI.matrix.rotation == Quaternion.identity && GUI.matrix.lossyScale == Vector3.one; + // draggable accessor public bool draggable { @@ -794,7 +796,7 @@ private float GetListElementHeight() if (m_CacheCount == 0) CacheIfNeeded(); - if (m_Count <= 0 || isOverMaxMultiEditLimit) + if (count <= 0 || isOverMaxMultiEditLimit) height = elementHeight * (isOverMaxMultiEditLimit ? 2 : 1) + listElementPadding; else height = GetElementYOffset(m_Count - 1) + GetElementHeight(m_Count - 1) + listElementPadding; @@ -874,8 +876,11 @@ private void DoListElements(Rect listRect, Rect visibleRect) var next = Mathf.Min(i + 1, m_Count - 1); var previous = Mathf.Max(i - 1, 0); - if (visibleRect.y > GetElementYOffset(next) + GetElementHeight(next)) continue; - if (visibleRect.y + visibleRect.height < GetElementYOffset(previous)) break; + if (useCulling) + { + if (visibleRect.y > GetElementYOffset(next) + GetElementHeight(next)) continue; + if (visibleRect.y + visibleRect.height < GetElementYOffset(previous)) break; + } var nonDragTargetIndex = m_NonDragTargetIndices[i]; if (nonDragTargetIndex != -1) @@ -953,8 +958,11 @@ private void DoListElements(Rect listRect, Rect visibleRect) // if we aren't dragging, we just draw all of the elements in order for (int i = 0; i < m_Count; i++) { - if (visibleRect.y > GetElementYOffset(i) + GetElementHeight(i)) continue; - if (visibleRect.y + visibleRect.height < GetElementYOffset(i > 0 ? i - 1 : i)) break; + if (useCulling) + { + if (visibleRect.y > GetElementYOffset(i) + GetElementHeight(i)) continue; + if (visibleRect.y + visibleRect.height < GetElementYOffset(i > 0 ? i - 1 : i)) break; + } bool activeElement = m_Selection.Any(id => id == i); bool focusedElement = (activeElement && HasKeyboardControl()); @@ -1311,54 +1319,7 @@ private void DoDraggingAndSelection(Rect listRect) // Retain expanded state after reordering properties if (m_SerializedObject != null && m_Elements != null && !isOverMaxMultiEditLimit) { - SerializedProperty prop1 = m_Elements.GetArrayElementAtIndex(oldActiveElement); - SerializedProperty prop2; - int depth; - List tempIsExpanded = ListPool.Get(); - var tempProp = prop1; - tempIsExpanded.Add(prop1.isExpanded); - bool clearGradientCache = false; - int next = (oldActiveElement < newActiveElement) ? 1 : -1; - - for (int i = oldActiveElement + next; - (oldActiveElement < newActiveElement) ? i <= newActiveElement : i >= newActiveElement; - i += next) - { - prop2 = m_Elements.GetArrayElementAtIndex(i); - - var cprop1 = prop1.Copy(); - var cprop2 = prop2.Copy(); - depth = Math.Min(cprop1.depth, cprop2.depth); - while (cprop1.NextVisible(true) && cprop1.depth > depth && cprop2.NextVisible(true) && cprop2.depth > depth) - { - if (cprop1.hasVisibleChildren && cprop2.hasVisibleChildren) - { - tempIsExpanded.Add(cprop1.isExpanded); - cprop1.isExpanded = cprop2.isExpanded; - } - } - - prop1.isExpanded = prop2.isExpanded; - if (prop1.propertyType == SerializedPropertyType.Gradient) - clearGradientCache = true; - prop1 = prop2; - } - - prop1.isExpanded = tempIsExpanded[0]; - depth = Math.Min(prop1.depth, tempProp.depth); - int k = 1; - while (prop1.NextVisible(true) && prop1.depth > depth && tempProp.NextVisible(true) && tempProp.depth > depth) - { - if (prop1.hasVisibleChildren && tempProp.hasVisibleChildren && tempIsExpanded.Count > k) - { - prop1.isExpanded = tempIsExpanded[k]; - k++; - } - } - ListPool.Release(tempIsExpanded); - - if (clearGradientCache) - GradientPreviewCache.ClearCache(); + EditorGUIUtility.MoveArrayExpandedState(m_Elements, oldActiveElement, newActiveElement); } // update the active element, now that we've moved it diff --git a/Editor/Mono/GUI/ScreenShotting.cs b/Editor/Mono/GUI/ScreenShotting.cs index aadab58455..9b17479dbd 100644 --- a/Editor/Mono/GUI/ScreenShotting.cs +++ b/Editor/Mono/GUI/ScreenShotting.cs @@ -14,21 +14,21 @@ internal class ScreenShots public static Color kWindowBorderColor = new Color(0.51f, 0.51f, 0.51f, 1f); public static bool s_TakeComponentScreenshot = false; - [MenuItem("Window/Internal/Screenshot/Set Window Size %&l", false, 1000, true)] + [MenuItem("Window/Internal/Screenshot/Set Window Size %&l", false, 1000, true, secondaryPriority = 1)] public static void SetMainWindowSize() { var main = Resources.FindObjectsOfTypeAll()[0]; main.window.position = new Rect(0, 0, 1024, 768); } - [MenuItem("Window/Internal/Screenshot/Set Window Size Small", false, 1000, true)] + [MenuItem("Window/Internal/Screenshot/Set Window Size Small", false, 1000, true, secondaryPriority = 2)] public static void SetMainWindowSizeSmall() { var main = Resources.FindObjectsOfTypeAll()[0]; main.window.position = new Rect(0, 0, 800 - 38, 600); } - [MenuItem("Window/Internal/Screenshot/Snap View %&j", false, 1000, true)] + [MenuItem("Window/Internal/Screenshot/Snap View %&j", false, 1000, true, secondaryPriority = 3)] public static void Screenshot() { GUIView v = GetMouseOverView(); @@ -42,7 +42,7 @@ public static void Screenshot() } } - [MenuItem("Window/Internal/Screenshot/Snap View Toolbar", false, 1000, true)] + [MenuItem("Window/Internal/Screenshot/Snap View Toolbar", false, 1000, true, secondaryPriority = 4)] public static void ScreenshotToolbar() { GUIView v = GetMouseOverView(); @@ -57,7 +57,7 @@ public static void ScreenshotToolbar() } } - [MenuItem("Window/Internal/Screenshot/Snap View Extended Right %&k", false, 1000, true)] + [MenuItem("Window/Internal/Screenshot/Snap View Extended Right %&k", false, 1000, true, secondaryPriority = 5)] public static void ScreenshotExtendedRight() { GUIView v = GetMouseOverView(); @@ -73,7 +73,7 @@ public static void ScreenshotExtendedRight() } } - [MenuItem("Window/Internal/Screenshot/Snap Component", false, 1000, true)] + [MenuItem("Window/Internal/Screenshot/Snap Component", false, 1000, true, secondaryPriority = 6)] public static void ScreenShotComponent() { s_TakeComponentScreenshot = true; @@ -88,7 +88,7 @@ public static void ScreenShotComponent(Rect contentRect, Object target) ScreenShots.SaveScreenShotWithBorder(contentRect, kWindowBorderColor, target.GetType().Name + "Inspector"); } - [MenuItem("Window/Internal/Screenshot/Snap Game View Content", false, 1000, true)] + [MenuItem("Window/Internal/Screenshot/Snap Game View Content", false, 1000, true, secondaryPriority = 7)] public static void ScreenGameViewContent() { string path = GetUniquePathForName("ContentExample"); diff --git a/Editor/Mono/GUI/SplitView.cs b/Editor/Mono/GUI/SplitView.cs index 7e2c8acbe4..74c2c2c93d 100644 --- a/Editor/Mono/GUI/SplitView.cs +++ b/Editor/Mono/GUI/SplitView.cs @@ -19,6 +19,7 @@ class SplitView : View, ICleanuppable, IDropArea public bool vertical = false; public int controlID = 0; + public int draggingID = 0; [Flags] internal enum ViewEdge { @@ -209,7 +210,6 @@ public void RemoveChildNice(View child) Mathf.Lerp(child.position.yMin, child.position.yMax, moveToPos) : Mathf.Lerp(child.position.xMin, child.position.xMax, moveToPos); - if (idx > 0) { View c = (View)children[idx - 1]; @@ -579,7 +579,6 @@ public void Cleanup() parent.RemoveChild(this); if (sp) sp.Cleanup(); - c.position = position; if (!Unsupported.IsDestroyScriptableObject(this)) DestroyImmediate(this); return; @@ -596,11 +595,12 @@ public void Cleanup() } } - if (sp) + if (sp != null) { sp.Cleanup(); // the parent might have moved US up and gotten rid of itself sp = parent as SplitView; + if (sp) { // If the parent has the same orientation as us, we can move our views up and kill ourselves @@ -612,9 +612,21 @@ public void Cleanup() sp.AddChild(child, idx++); child.position = new Rect(position.x + child.position.x, position.y + child.position.y, child.position.width, child.position.height); } + + // don't let this fall through to the `children == 0` case because we don't want to be removed + // "nicely." our children have already been merged to the parent with correct positions, so + // there is no need to recalculate sibling dimensions (and may incorrectly resize views that + // have been recursively cleaned up). + sp.RemoveChild(this); + if (!Unsupported.IsDestroyScriptableObject(this)) + DestroyImmediate(this, true); + sp.Cleanup(); + + return; } } } + if (children.Length == 0) { if (parent == null && window != null) @@ -698,6 +710,7 @@ public void SplitGUI(Event evt) splitState.splitterInitialOffset = GUIUtility.RoundToPixelGrid(pos); splitState.currentActiveSplitter = i; GUIUtility.hotControl = id; + draggingID = id; evt.Use(); break; } @@ -707,6 +720,14 @@ public void SplitGUI(Event evt) } break; case EventType.MouseDrag: + // NOTE: if we were Drag initiator and our id was changed, update hotcontrol to keep allowing drag. + // Entities (or other package) could be modifying ControlID list when drag starts (see https://jira.unity3d.com/browse/UUM-67862) + if (draggingID != 0 && id != draggingID && draggingID == GUIUtility.hotControl) + { + draggingID = id; + GUIUtility.hotControl = id; + } + if (children.Length > 1 && (GUIUtility.hotControl == id) && (splitState.currentActiveSplitter >= 0)) { float diff = GUIUtility.RoundToPixelGrid(pos) - splitState.splitterInitialOffset; @@ -717,13 +738,12 @@ public void SplitGUI(Event evt) } SetupRectsFromSplitter(); - - evt.Use(); } break; case EventType.MouseUp: + draggingID = 0; if (GUIUtility.hotControl == id) GUIUtility.hotControl = 0; break; diff --git a/Editor/Mono/GUI/Toolbar.cs b/Editor/Mono/GUI/Toolbar.cs index 1df88833c6..f960fddecd 100644 --- a/Editor/Mono/GUI/Toolbar.cs +++ b/Editor/Mono/GUI/Toolbar.cs @@ -100,6 +100,7 @@ void CreateContents() protected override void OnDisable() { + m_Root?.RemoveFromHierarchy(); base.OnDisable(); EditorApplication.modifierKeysChanged -= Repaint; } @@ -113,9 +114,6 @@ protected override void OldOnGUI() { if (Event.current.type == EventType.Repaint) Styles.appToolbar.Draw(new Rect(0, 0, position.width, position.height), false, false, false, false); - - BeginOffsetArea(GetToolbarPosition(), GUIContent.none, GUIStyle.none); - EndOffsetArea(); } static VisualElement CreateRoot() diff --git a/Editor/Mono/GUI/Toolbars/Core/EditorToolbar.cs b/Editor/Mono/GUI/Toolbars/Core/EditorToolbar.cs index 88b56addcc..253ddc6e40 100644 --- a/Editor/Mono/GUI/Toolbars/Core/EditorToolbar.cs +++ b/Editor/Mono/GUI/Toolbars/Core/EditorToolbar.cs @@ -75,9 +75,6 @@ static bool TryCreateElement(string id, EditorWindow ctx, out VisualElement ve) return true; } - Debug.LogError($"Failed to load EditorToolbar ID '{id}' for window '{ctx}'. " - + "No element with that ID and target toolbar was registered using the EditorToolbarElement attribute."); - return false; } } diff --git a/Editor/Mono/GUI/Tools/BuiltinTools.cs b/Editor/Mono/GUI/Tools/BuiltinTools.cs index 75849a8acc..d0298964d5 100644 --- a/Editor/Mono/GUI/Tools/BuiltinTools.cs +++ b/Editor/Mono/GUI/Tools/BuiltinTools.cs @@ -44,7 +44,7 @@ public override void OnToolGUI(EditorWindow window) if (!view || !Selection.activeTransform || Tools.s_Hidden) return; - if (!StageUtility.IsGameObjectRenderedByCameraAndPartOfEditableScene(Selection.activeTransform.gameObject, Camera.current)) + if (StageUtility.IsGizmoCulledBySceneCullingMasksOrFocusedScene(Selection.activeTransform.gameObject, Camera.current)) return; GUIContent disabledLabel; @@ -81,6 +81,14 @@ protected bool IsDisabledByPrefabPropertyPatching(string partialPropertyName, ou return prefabStage.ContainsTransformPrefabPropertyPatchingFor(Selection.gameObjects, partialPropertyName); } + + protected void ResetGlobalHandleRotationIfNeeded() + { + if (Tools.pivotRotation == PivotRotation.Global && Event.current.GetTypeForControl(GUIUtility.hotControl) == EventType.MouseUp) + { + Tools.ResetGlobalHandleRotation(); + } + } } static class ManipulationToolUtility @@ -195,6 +203,8 @@ protected override void ToolGUI(SceneView view, Vector3 handlePosition, bool isS if (view.camera.transform.position == handlePosition) return; + ResetGlobalHandleRotationIfNeeded(); + var ids = Handles.TransformHandleIds.Default; TransformManipulator.BeginManipulationHandling(false); @@ -331,10 +341,7 @@ protected override bool ShouldToolGUIBeDisabled(out GUIContent disabledLabel) protected override void ToolGUI(SceneView view, Vector3 handlePosition, bool isStatic) { - if (Tools.pivotRotation == PivotRotation.Global && Event.current.GetTypeForControl(GUIUtility.hotControl) == EventType.MouseUp) - { - Tools.ResetGlobalHandleRotation(); - } + ResetGlobalHandleRotationIfNeeded(); Quaternion before = Tools.handleRotation; @@ -714,6 +721,23 @@ static Vector3 ResizeHandlesGUI(Rect rect, Vector3 pivot, Quaternion rotation, o Vector3 sizeBefore = inverseRotation * (origPos - scalePivot); Vector3 sizeAfter = inverseRotation * (newPos - scalePivot); + + // Fix for UUM-102690. If difference is zero, we don't want to scale on that axis + if (scaleFromPivot) + { + Vector3 diff = origPos - scalePivot; + if (Mathf.Approximately(diff.x, 0f)) + { + sizeBefore.x = 1f; + sizeAfter.x = 1f; + } + if (Mathf.Approximately(diff.y, 0f)) + { + sizeBefore.y = 1f; + sizeAfter.y = 1f; + } + } + if (xHandle != 1) scale.x = sizeAfter.x / sizeBefore.x; if (yHandle != 1) @@ -724,12 +748,6 @@ static Vector3 ResizeHandlesGUI(Rect rect, Vector3 pivot, Quaternion rotation, o float refScale = (xHandle == 1 ? scale.y : scale.x); scale = Vector3.one * refScale; } - - if (uniformScaling) - { - float refScale = (xHandle == 1 ? scale.y : scale.x); - scale = Vector3.one * refScale; - } } if (xHandle == 0) @@ -931,10 +949,13 @@ static Vector3 MoveHandlesGUI(Rect rect, Vector3 pivot, Quaternion rotation) } else { + // https://jira.unity3d.com/browse/UUM-30232 + // https://jira.unity3d.com/browse/UUM-18037 + var repaintPivot = rotation * rect.center + pivot; Handles.color = Handles.secondaryColor * new Color(1, 1, 1, 1.5f * discOpacity); - Handles.CircleHandleCap(id, pivot, rotation, discSize, EventType.Repaint); + Handles.CircleHandleCap(id, repaintPivot, rotation, discSize, EventType.Repaint); Handles.color = Handles.secondaryColor * new Color(1, 1, 1, 0.3f * discOpacity); - Handles.DrawSolidDisc(pivot, rotation * Vector3.forward, discSize); + Handles.DrawSolidDisc(repaintPivot, rotation * Vector3.forward, discSize); } break; } diff --git a/Editor/Mono/GUI/Tools/EditorToolAttributes.cs b/Editor/Mono/GUI/Tools/EditorToolAttributes.cs index 4089057dee..6065f5e4c9 100644 --- a/Editor/Mono/GUI/Tools/EditorToolAttributes.cs +++ b/Editor/Mono/GUI/Tools/EditorToolAttributes.cs @@ -31,7 +31,9 @@ protected ToolAttribute(string displayName, Type targetType = null, Type editorT [AttributeUsage(AttributeTargets.Class, Inherited = false)] public sealed class EditorToolAttribute : ToolAttribute { - public EditorToolAttribute(string displayName, Type componentToolTarget = null, Type editorToolContext = null) + public EditorToolAttribute(string displayName, Type componentToolTarget = null) + : base(displayName, componentToolTarget) {} + public EditorToolAttribute(string displayName, Type componentToolTarget, Type editorToolContext) : base(displayName, componentToolTarget, editorToolContext) {} } diff --git a/Editor/Mono/GUI/Tools/EditorToolCache.cs b/Editor/Mono/GUI/Tools/EditorToolCache.cs index 0e9458f3ff..d680564dc2 100644 --- a/Editor/Mono/GUI/Tools/EditorToolCache.cs +++ b/Editor/Mono/GUI/Tools/EditorToolCache.cs @@ -110,8 +110,10 @@ public UnityObject[] targets if (additionalEditors == null) return inspector.targets; List objects = new List(inspector.targets); - foreach (var insp in additionalEditors) - objects.AddRange(insp.targets); + foreach (var additionalInspector in additionalEditors) + foreach (var additionalTarget in additionalInspector.targets) + if (!objects.Contains(additionalTarget)) + objects.Add(additionalTarget); return objects.ToArray(); } } @@ -159,20 +161,6 @@ struct NullTargetKey {} EditorTypeAssociation[] s_AvailableEditorTypeAssociations = null; Dictionary> s_EditorTargetCache = new Dictionary>(); - // Static fields in generic classes result in multiple static field instances. In this case that's fine. - // ReSharper disable once StaticMemberInGenericType - static ActiveEditorTracker m_Tracker; - - static ActiveEditorTracker sharedTracker - { - get - { - if (m_Tracker == null) - m_Tracker = new ActiveEditorTracker(); - return m_Tracker; - } - } - public EditorToolCache(Type attributeType) { if (!typeof(ToolAttribute).IsAssignableFrom(attributeType)) @@ -287,21 +275,14 @@ public void InstantiateEditors(EditorToolContext ctx, List edit { editors.Clear(); - // If the shared tracker is locked, use our own tracker instance so that the current selection is always + // If the shared tracker is locked, use fallback tracker instance so that the active selection is always // represented. Addresses case where a single locked inspector is open. var shared = ActiveEditorTracker.sharedTracker; - var activeTracker = shared.isLocked ? sharedTracker : shared; - var inspectors = InspectorWindow.GetInspectors(); + var activeTracker = shared.isLocked ? ActiveEditorTracker.fallbackTracker : shared; - // Collect editor tools for the shared tracker first, then any locked inspectors + // We collect editors only for the tracker that represents the active selection. CollectEditorsForTracker(ctx, activeTracker, editors); - foreach (var inspector in inspectors) - { - if (inspector.isLocked) - CollectEditorsForTracker(ctx, inspector.tracker, editors); - } - foreach (var editor in editors) editor.InstantiateEditor(); } diff --git a/Editor/Mono/GUI/Tools/EditorToolManager.cs b/Editor/Mono/GUI/Tools/EditorToolManager.cs index 2b972ad231..115d2df6a7 100644 --- a/Editor/Mono/GUI/Tools/EditorToolManager.cs +++ b/Editor/Mono/GUI/Tools/EditorToolManager.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using UnityEditor.SceneManagement; using UnityEngine; using UnityObject = UnityEngine.Object; @@ -495,14 +496,26 @@ internal static bool TryPopToolState() return false; } + static bool IsGizmoCulledBySceneCullingMasksOrFocusedScene(UnityObject uobject) + { + var cmp = uobject as UnityEngine.Component; + if (cmp == null) + return false; + + return StageUtility.IsGizmoCulledBySceneCullingMasksOrFocusedScene(cmp.gameObject, Camera.current); + } + internal static void OnToolGUI(EditorWindow window) { - activeToolContext.OnToolGUI(window); + if (!IsGizmoCulledBySceneCullingMasksOrFocusedScene(activeToolContext.target)) + activeToolContext.OnToolGUI(window); if (Tools.s_Hidden || instance.m_ActiveTool == null) return; var current = instance.m_ActiveTool; + if (IsGizmoCulledBySceneCullingMasksOrFocusedScene(current.target)) + return; using (new EditorGUI.DisabledScope(!current.IsAvailable())) { @@ -671,6 +684,9 @@ internal static void InvokeOnSceneGUICustomEditorTools() { foreach (var context in instance.m_ComponentContexts) { + if (IsGizmoCulledBySceneCullingMasksOrFocusedScene(context.target)) + continue; + // ReSharper disable once SuspiciousTypeConversion.Global if (context.editor is IDrawSelectedHandles handle) handle.OnDrawHandles(); @@ -678,6 +694,9 @@ internal static void InvokeOnSceneGUICustomEditorTools() foreach (var tool in instance.m_ComponentTools) { + if (IsGizmoCulledBySceneCullingMasksOrFocusedScene(tool.target)) + continue; + // ReSharper disable once SuspiciousTypeConversion.Global if (tool.editor is IDrawSelectedHandles handle) handle.OnDrawHandles(); diff --git a/Editor/Mono/GUI/Tools/EditorToolUtility.cs b/Editor/Mono/GUI/Tools/EditorToolUtility.cs index c7b74a0836..43cb5b810c 100644 --- a/Editor/Mono/GUI/Tools/EditorToolUtility.cs +++ b/Editor/Mono/GUI/Tools/EditorToolUtility.cs @@ -135,7 +135,7 @@ internal static EditorTool GetEditorToolWithEnum(Tool type, EditorToolContext ct // Tool types can resolve to either global or instance tools if (IsComponentTool(resolved)) { - var instance = EditorToolManager.GetComponentTool(resolved); + var instance = EditorToolManager.GetComponentTool(resolved, true); if (instance == null) { Debug.LogError($"{context} resolved Tool.{type} to a Component tool of type `{resolved}`, but " + @@ -212,8 +212,10 @@ internal static bool IsGlobalTool(EditorTool tool) if(GetEnumWithEditorTool(tool) == Tool.Custom) { var type = tool.GetType(); - return !IsComponentTool(type) - && EditorToolManager.additionalContextToolTypesCache.All(t => t != type); + return !IsComponentTool(type) // Component tool? + && !IsManipulationTool(GetEnumWithEditorTool(tool, EditorToolManager.GetSingleton())) // Built-in tool? + && !IsBuiltinOverride(tool) // Built-in tool override? + && EditorToolManager.additionalContextToolTypesCache.All(t => t != type); // Additional/Extra tool? } return false; diff --git a/Editor/Mono/GUI/Tools/ToolManager.cs b/Editor/Mono/GUI/Tools/ToolManager.cs index 991df48130..c3605a87cb 100644 --- a/Editor/Mono/GUI/Tools/ToolManager.cs +++ b/Editor/Mono/GUI/Tools/ToolManager.cs @@ -24,7 +24,7 @@ public static void SetActiveContext(Type context) if (EditorToolUtility.IsComponentEditor(ctx)) { - var instance = EditorToolManager.GetComponentContext(ctx); + var instance = EditorToolManager.GetComponentContext(ctx, true); if (instance == null) throw new InvalidOperationException("The current selection does not contain any objects editable " + $"by the component tool of type: {context}"); diff --git a/Editor/Mono/GUI/Tools/Tools.cs b/Editor/Mono/GUI/Tools/Tools.cs index a324df9cb1..3bda59edae 100644 --- a/Editor/Mono/GUI/Tools/Tools.cs +++ b/Editor/Mono/GUI/Tools/Tools.cs @@ -110,7 +110,7 @@ public static ViewTool viewTool } internal static ViewTool s_LockedViewTool = ViewTool.None; internal static int s_ButtonDown = -1; - public static bool viewToolActive => SceneViewMotion.viewToolActive; + public static bool viewToolActive => SceneViewMotion.viewToolIsActive; static Vector3 s_HandlePosition; static bool s_HandlePositionComputed; diff --git a/Editor/Mono/GUI/Tools/TransformManipulator.cs b/Editor/Mono/GUI/Tools/TransformManipulator.cs index 1fafc9a632..b92e990f63 100644 --- a/Editor/Mono/GUI/Tools/TransformManipulator.cs +++ b/Editor/Mono/GUI/Tools/TransformManipulator.cs @@ -22,6 +22,7 @@ private struct TransformData }; public Transform transform; + public Transform parent; public Vector3 position; public Vector3 localPosition; public Quaternion rotation; @@ -75,12 +76,19 @@ private Quaternion GetRefAlignment(Quaternion targetRotation, Quaternion ownRota private void SetupTransformValues(Transform t) { transform = t; + parent = t.parent; position = t.position; localPosition = t.localPosition; rotation = t.rotation; scale = t.localScale; } + void UpdateTransformValues() + { + parent = transform.parent; + localPosition = parent != null ? parent.InverseTransformPoint(position) : position; + } + private void SetScaleValue(Vector3 scale) { transform.localScale = scale; @@ -139,13 +147,16 @@ private void SetPosition(Vector3 newPosition) public void SetPositionDelta(Vector3 positionDelta, bool applySmartRounding) { + if(transform.parent != parent) + UpdateTransformValues(); + Vector3 localPositionDelta = positionDelta; - if (transform.parent != null) + if (parent != null) { - localPositionDelta = transform.parent.InverseTransformVector(localPositionDelta); + localPositionDelta = parent.InverseTransformVector(localPositionDelta); if (!applySmartRounding) - applySmartRounding = !transform.parent.localRotation.Equals(Quaternion.identity); + applySmartRounding = !parent.localRotation.Equals(Quaternion.identity); } //If we are snapping, disable the smart rounding. If not the case, the transform will have the wrong snap value based on distance to screen. @@ -361,7 +372,6 @@ public static void SetPositionDelta(Vector3 newPosition, Vector3 oldPosition) s_PreviousHandlePosition = newPosition; Vector3 positionDelta = newPosition - oldPosition; - Object[] undoObjects = new Object[s_MouseDownState.Length]; string undoName = ""; diff --git a/Editor/Mono/GUI/TreeView/AssetOrGameObjectTreeViewDragging.cs b/Editor/Mono/GUI/TreeView/AssetOrGameObjectTreeViewDragging.cs index 38722d2e08..8c7c1bdafe 100644 --- a/Editor/Mono/GUI/TreeView/AssetOrGameObjectTreeViewDragging.cs +++ b/Editor/Mono/GUI/TreeView/AssetOrGameObjectTreeViewDragging.cs @@ -64,7 +64,6 @@ internal class GameObjectsTreeViewDragging : TreeViewDragging { public delegate DragAndDropVisualMode CustomDraggingDelegate(GameObjectTreeViewItem parentItem, GameObjectTreeViewItem targetItem, DropPosition dropPos, bool perform); CustomDraggingDelegate m_CustomDragHandling; - const string kSceneHeaderDragString = "SceneHeaderList"; public Transform parentForDraggedObjectsOutsideItems { get; set; } @@ -214,6 +213,16 @@ public override DragAndDropVisualMode DoDrag(TreeViewItem parentItem, TreeViewIt if (perform && SubSceneGUI.IsUsingSubScenes() && !IsValidSubSceneDropTarget(gameObjectOrSceneInstanceID, dropPos, DragAndDrop.objectReferences)) return DragAndDropVisualMode.Rejected; + GameObject go = EditorUtility.InstanceIDToObject(gameObjectOrSceneInstanceID) as GameObject; + if (go != null) + { + DragAndDropVisualMode visualMode; + if (PrefabReplaceUtility.GetDragVisualModeAndShowMenuWithReplaceMenuItemsWhenNeeded(go, draggingUpon, perform, true, true, out visualMode)) + { + return visualMode; + } + } + return DragAndDrop.DropOnHierarchyWindow(gameObjectOrSceneInstanceID, option, null, perform); } diff --git a/Editor/Mono/GUI/TreeView/GameObjectTreeViewGUI.cs b/Editor/Mono/GUI/TreeView/GameObjectTreeViewGUI.cs index 194e82dac3..c84038c285 100644 --- a/Editor/Mono/GUI/TreeView/GameObjectTreeViewGUI.cs +++ b/Editor/Mono/GUI/TreeView/GameObjectTreeViewGUI.cs @@ -413,7 +413,7 @@ override protected void DoItemGUI(Rect rect, int row, TreeViewItem item, bool se if (goItem == null) return; - EnsureLazyInitialization(goItem); + EnsureLazyInitialization(goItem); // Needed to ensure item is ready for all ui controls if (goItem.isSceneHeader) { @@ -679,13 +679,15 @@ void SetPrefabModeButtonVisibility(GameObjectTreeViewItem item) protected override void OnContentGUI(Rect rect, int row, TreeViewItem item, string label, bool selected, bool focused, bool useBoldFont, bool isPinging) { - if (Event.current.type != EventType.Repaint) - return; - GameObjectTreeViewItem goItem = item as GameObjectTreeViewItem; if (goItem == null) return; + if (Event.current.type != EventType.Repaint) + return; + + EnsureLazyInitialization(goItem); // Needed to ensure icon is initialized if reload happens during DoItemGUI + rect.xMax = m_ContentRectRight; if (goItem.isSceneHeader) diff --git a/Editor/Mono/GUI/TreeView/TreeViewController.cs b/Editor/Mono/GUI/TreeView/TreeViewController.cs index cf15aec1d4..a41d620416 100644 --- a/Editor/Mono/GUI/TreeView/TreeViewController.cs +++ b/Editor/Mono/GUI/TreeView/TreeViewController.cs @@ -743,7 +743,7 @@ public void OnGUI(Rect rect, int keyboardControlID) KeyboardGUI(); if (m_UseScrollView) - GUI.EndScrollView(showingVerticalScrollBar); + GUI.EndScrollView(showingVerticalScrollBar || showingHorizontalScrollBar); else GUI.EndClip(); diff --git a/Editor/Mono/GUI/TypeSelectionList.cs b/Editor/Mono/GUI/TypeSelectionList.cs index d02cec9b74..6971ac0480 100644 --- a/Editor/Mono/GUI/TypeSelectionList.cs +++ b/Editor/Mono/GUI/TypeSelectionList.cs @@ -3,8 +3,6 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using UnityEngine; -using UnityEditor; -using UnityEditorInternal; using System; using System.Collections.Generic; using Object = UnityEngine.Object; @@ -19,12 +17,20 @@ class TypeSelectionList public TypeSelectionList(Object[] objects) { // Create dictionary of lists of objects indexed by their type. - Dictionary> types = new Dictionary>(); + var types = new Dictionary>(); foreach (Object o in objects) { - string typeName = ObjectNames.GetTypeName(o); - if (o is GameObject && EditorUtility.IsPersistent(o)) - typeName = "Prefab"; + var typeName = ObjectNames.GetTypeName(o) + "{0}"; + + if (EditorUtility.IsPersistent(o)) + { + if (o is GameObject) + typeName = "Prefab{0}"; + + var importerType = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(o))?.GetType(); + if (importerType is not null && importerType != typeof(AssetImporter)) + typeName = $"{typeName} ({importerType.Name})"; + } if (!types.ContainsKey(typeName)) types[typeName] = new List(); @@ -33,7 +39,7 @@ public TypeSelectionList(Object[] objects) // Create and store a TypeSelection per type. m_TypeSelections = new List(); - foreach (KeyValuePair> kvp in types) + foreach (var kvp in types) m_TypeSelections.Add(new TypeSelection(kvp.Key, kvp.Value.ToArray())); // Sort the TypeSelections @@ -50,7 +56,11 @@ public TypeSelection(string typeName, Object[] objects) { System.Diagnostics.Debug.Assert(objects != null && objects.Length >= 1); this.objects = objects; - label = new GUIContent(objects.Length + " " + ObjectNames.NicifyVariableName(typeName) + (objects.Length > 1 ? "s" : "")); + + label = new GUIContent( + $"{objects.Length} " + + $"{ObjectNames.NicifyVariableName(string.Format(typeName, objects.Length > 1 ? "s" : ""))}"); + if (objects[0] is GameObject) label.image = EditorUtility.IsPersistent(objects[0]) ? PrefabUtility.GameObjectStyles.prefabIcon : PrefabUtility.GameObjectStyles.gameObjectIcon; else diff --git a/Editor/Mono/GUI/WindowLayout.cs b/Editor/Mono/GUI/WindowLayout.cs index 51a2f5f8d1..3928e4921f 100644 --- a/Editor/Mono/GUI/WindowLayout.cs +++ b/Editor/Mono/GUI/WindowLayout.cs @@ -72,14 +72,18 @@ public float size const string verticalLayoutKey = "vertical"; const string horizontalLayoutKey = "horizontal"; - private const string kMaximizeRestoreFile = "CurrentMaximizeLayout.dwlt"; + + // used by tests + internal const string kMaximizeRestoreFile = "CurrentMaximizeLayout.dwlt"; private const string kDefaultLayoutName = "Default.wlt"; internal static string layoutResourcesPath => Path.Combine(EditorApplication.applicationContentsPath, "Resources/Layouts"); internal static string layoutsPreferencesPath => FileUtil.CombinePaths(InternalEditorUtility.unityPreferencesFolder, "Layouts"); internal static string layoutsModePreferencesPath => FileUtil.CombinePaths(layoutsPreferencesPath, ModeService.currentId); internal static string layoutsDefaultModePreferencesPath => FileUtil.CombinePaths(layoutsPreferencesPath, "default"); + internal static string layoutsCurrentModePreferencesPath => FileUtil.CombinePaths(layoutsPreferencesPath, "current"); internal static string layoutsProjectPath => FileUtil.CombinePaths("UserSettings", "Layouts"); internal static string ProjectLayoutPath => GetProjectLayoutPerMode(ModeService.currentId); + internal static string currentLayoutName => GetLayoutFileName(ModeService.currentId, Application.unityVersionVer); [UsedImplicitly, RequiredByNativeCode] public static void LoadDefaultWindowPreferences() @@ -93,14 +97,13 @@ public static void LoadCurrentModeLayout(bool keepMainWindow) InitializeLayoutPreferencesFolder(); var dynamicLayout = ModeService.GetDynamicLayout(); if (dynamicLayout == null) - LoadProjectLayout(keepMainWindow); + LoadLastUsedLayoutForCurrentMode(keepMainWindow); else { - var projectLayoutExists = File.Exists(ProjectLayoutPath); if ((projectLayoutExists && Convert.ToBoolean(dynamicLayout["restore_saved_layout"])) || !LoadModeDynamicLayout(keepMainWindow, dynamicLayout)) - LoadProjectLayout(keepMainWindow); + LoadLastUsedLayoutForCurrentMode(keepMainWindow); } } @@ -123,6 +126,7 @@ private static bool LoadModeDynamicLayout(bool keepMainWindow, JSONObject layout GetLayoutViewInfo(layoutData, availableEditorWindowTypes, ref bottomViewInfo); var mainWindow = GenerateLayout(keepMainWindow, availableEditorWindowTypes, centerViewInfo, topViewInfo, bottomViewInfo, layoutData); + if (mainWindow) mainWindow.m_DontSaveToLayout = !Convert.ToBoolean(layoutData["restore_saved_layout"]); @@ -202,10 +206,12 @@ private static View LoadLayoutView(Type[] availableEditorWindowTypes, LayoutV internal static ContainerWindow FindMainWindow() { var containers = Resources.FindObjectsOfTypeAll(typeof(ContainerWindow)); + foreach (ContainerWindow window in containers) { if (window.showMode == ShowMode.MainWindow) return window; + } return null; @@ -223,11 +229,13 @@ private static ContainerWindow GenerateLayout(bool keepMainWindow, Type[] availa try { ContainerWindow.SetFreezeDisplay(true); + var loadInitialWindowGeometry = Convert.ToBoolean(layoutData["restore_layout_dimension"]); if (!mainContainerWindow) { mainContainerWindow = ScriptableObject.CreateInstance(); var mainWindowMinSize = new Vector2(120, 80); var mainWindowMaxSize = new Vector2(8192, 8192); + if (layoutData.Contains("min_width")) { mainWindowMinSize.x = Convert.ToSingle(layoutData["min_width"]); @@ -245,14 +253,17 @@ private static ContainerWindow GenerateLayout(bool keepMainWindow, Type[] availa mainWindowMaxSize.y = Convert.ToSingle(layoutData["max_height"]); } mainContainerWindow.SetMinMaxSizes(mainWindowMinSize, mainWindowMaxSize); + loadInitialWindowGeometry = true; } var mainViewID = $"MainView_{ModeService.currentId}"; var hasMainViewGeometrySettings = EditorPrefs.HasKey($"{mainViewID}h"); mainContainerWindow.windowID = mainViewID; - if (hasMainViewGeometrySettings) + if (loadInitialWindowGeometry && hasMainViewGeometrySettings) + { mainContainerWindow.LoadGeometry(true); - + } + var width = mainContainerWindow.position.width; var height = mainContainerWindow.position.height; @@ -339,6 +350,7 @@ private static bool ParseViewData(Type[] availableEditorWindowTypes, object view if (viewExpandedData.Contains("size")) viewInfo.defaultSize = Convert.ToSingle(viewExpandedData["size"]); viewInfo.extendedData = viewExpandedData; + } else { @@ -368,23 +380,73 @@ private static bool ParseViewData(Type[] availableEditorWindowTypes, object view return true; } - private static void LoadProjectLayout(bool keepMainWindow) + // Used by tests + internal static string GetLayoutFileName(string mode, int version) => $"{mode}-{version}.dwlt"; + + static IEnumerable GetCurrentModeLayouts() { - var projectLayoutExists = File.Exists(ProjectLayoutPath); - if (!projectLayoutExists) + var layouts = ModeService.GetModeDataSection(ModeService.currentIndex, ModeDescriptor.LayoutsKey); + + if (layouts is IList modeLayoutPaths) { - var currentLayoutPath = GetCurrentLayoutPath(); - if (EnsureDirectoryCreated(ProjectLayoutPath)) + foreach (var layoutPath in modeLayoutPaths.Cast()) { - Console.WriteLine($"[LAYOUT] LoadProjectLayout: Copying Project Current Layout: {ProjectLayoutPath} from {currentLayoutPath}"); - FileUtil.CopyFileOrDirectory(currentLayoutPath, ProjectLayoutPath); + if (!File.Exists(layoutPath)) + continue; + yield return layoutPath; } } + } - Debug.Assert(File.Exists(ProjectLayoutPath)); + // Iterate through potential layouts in descending order of precedence. + // 1. Last loaded layout in project for matching Unity version + // 2. Last loaded layout in project for any Unity version, in descending alphabetical order + // 3. Last loaded layout in global preferences for matching Unity version + // 4. Last loaded layout in global preferences for any Unity version, in descending alphabetical order + // 5. Any available layouts specified by the EditorMode, if EditorMode supplies layouts + // 6. The factory default layout + private static void LoadLastUsedLayoutForCurrentMode(bool keepMainWindow) + { + // steps 1-4 + foreach (var layout in GetLastLayout()) + if (LoadWindowLayout(layout, layout != ProjectLayoutPath, false, keepMainWindow)) + return; + + // step 5 + foreach (var layout in GetCurrentModeLayouts()) + if (LoadWindowLayout(layout, layout != ProjectLayoutPath, false, keepMainWindow)) + return; + + // step 6 + var defaultLayout = Path.Combine(layoutsDefaultModePreferencesPath, kDefaultLayoutName); + + // If all else fails, load the default layout that ships with the editor. If that fails, prompt the user to + // restore the default layouts. + if (!LoadWindowLayout(defaultLayout, true, false, keepMainWindow)) + { + int option = 0; - // Load the current project layout - LoadWindowLayout(ProjectLayoutPath, !projectLayoutExists, false, keepMainWindow); + if (!Application.isTestRun && Application.isHumanControllingUs) + { + option = EditorUtility.DisplayDialogComplex("Missing Default Layout", "No valid user created or " + + "default window layout found. Please revert factory settings to restore the default layouts.", + "Quit", "Revert Factory Settings", ""); + } + else + { + ResetUserLayouts(); + } + + switch (option) + { + case 0: + EditorApplication.Exit(0); + break; + case 1: + ResetFactorySettings(); + break; + } + } } [UsedImplicitly, RequiredByNativeCode] @@ -399,8 +461,49 @@ public static void SaveDefaultWindowPreferences() internal static void SaveCurrentLayoutPerMode(string modeId) { - // Save Project Current Layout + // Save the layout in two places. Once in the Project/UserSettings directory, then again the global + // preferences. The latter is used when opening a new project (or any case where UserSettings/Layouts/ does + // not exist). SaveWindowLayout(FileUtil.CombinePaths(Directory.GetCurrentDirectory(), GetProjectLayoutPerMode(modeId))); + SaveWindowLayout(Path.Combine(layoutsCurrentModePreferencesPath, GetLayoutFileName(modeId, Application.unityVersionVer))); + } + + // Iterate through potential layout files, prioritizing exact match followed by descending unity version. + // IMPORTANT: This function is "dumb" in that it does not do any kind of sophisticated version comparison. If the + // naming scheme for current layouts is changed, or this function is called on to sort user saved layouts, you will + // need to add more sophisticated filtering. + public static IEnumerable GetLastLayout(string directory, string mode, int version) + { + var currentModeAndVersionLayout = GetLayoutFileName(mode, version); + string layoutSearchPattern = $"{mode}-*.*wlt"; + + // first try the exact match + var preferred = Path.Combine(directory, currentModeAndVersionLayout); + + if(File.Exists(preferred)) + yield return preferred; + + // if that fails, fall back to layouts for this mode from other unity versions in descending order + if (Directory.Exists(directory)) + { + var paths = Directory.GetFiles(directory, layoutSearchPattern) + .Where(p => string.Compare(p, preferred, StringComparison.OrdinalIgnoreCase) != 0) + .OrderByDescending(p => p, StringComparer.OrdinalIgnoreCase); + + foreach (var path in paths) + yield return path; + } + } + + // used by Tests/EditModeAndPlayModeTests/EditorModes + internal static IEnumerable GetLastLayout() + { + var mode = ModeService.currentId; + var version = Application.unityVersionVer; + foreach (var layout in GetLastLayout(layoutsProjectPath, mode, version)) + yield return layout; + foreach (var layout in GetLastLayout(layoutsCurrentModePreferencesPath, mode, version)) + yield return layout; } internal static string GetCurrentLayoutPath() @@ -421,7 +524,7 @@ internal static string GetDefaultLayoutPath() internal static string GetProjectLayoutPerMode(string modeId) { - return FileUtil.CombinePaths(layoutsProjectPath, $"{modeId}-{Application.unityVersionVer}.dwlt"); + return FileUtil.CombinePaths(layoutsProjectPath, GetLayoutFileName(modeId, Application.unityVersionVer)); } private static void InitializeLayoutPreferencesFolder() @@ -558,7 +661,7 @@ private static void AddLegacyLayoutMenuItems(ref int layoutMenuItemPriority) name = ObjectNames.NicifyVariableName(names[0]); menuName = $"{legacyRootMenu}/{name} ({names[1]})"; } - Menu.AddMenuItem(menuName, "", false, layoutMenuItemPriority++, () => LoadWindowLayout(layoutPath, false, true, false), null); + Menu.AddMenuItem(menuName, "", false, layoutMenuItemPriority++, () => TryLoadWindowLayout(layoutPath, false), null); } } @@ -686,19 +789,8 @@ internal static EditorWindow GetMaximizedWindow() return null; } - internal static EditorWindow ShowAppropriateViewOnEnterExitPlaymodeList(bool entering, out List allWindows) + internal static EditorWindow ShowAppropriateViewOnEnterExitPlaymode(bool entering) { - allWindows = new List(); - - int[] map = new[] { 4, 3, 1, 2 }; - var allWindowsBase = PlayModeView.GetAllPlayModeViewWindows() - .OrderBy(c => map[(int)(c.enterPlayModeBehavior)]).ToList(); - - foreach (var w in allWindowsBase) - { - allWindows.Add(w); - } - // Prevent trying to go into the same state as we're already in, as it will break things if (WindowFocusState.instance.m_CurrentlyInPlayMode == entering) return null; @@ -719,100 +811,31 @@ internal static EditorWindow ShowAppropriateViewOnEnterExitPlaymodeList(bool ent // just keep that maximized view, no matter if it's the game view or some other. // Trust that user has a good reason (desire by Ethan etc.) if (maximized != null) - { return maximized; - } } else { // If a view was already maximized before entering play mode, // then it was kept when switching to play mode, and can simply still be kept when exiting if (WindowFocusState.instance.m_WasMaximizedBeforePlay) - { return maximized; - } } // Unmaximize if maximized if (maximized) Unmaximize(maximized); - // Try finding and focusing appropriate window/tab - window = TryFocusAppropriateWindow(entering); - if (window) - { - return window; - } - - // If we are entering Play more and no Game View was found, create one - if (entering && PlayModeView.openWindowOnEnteringPlayMode) + // When the playmode behaviour is set to Play Unfocused + if (entering) { - - // Try to create and focus a Game View tab docked together with the Scene View tab - EditorWindow sceneView = FindEditorWindowOfType(typeof(SceneView)); - GameView gameView; - if (sceneView && sceneView.m_Parent is DockArea) + var playmodeView = PlayModeView.GetCorrectPlayModeViewToFocus(); + if (playmodeView != null && playmodeView.enterPlayModeBehavior == PlayModeView.EnterPlayModeBehavior.PlayUnfocused) { - DockArea dock = sceneView.m_Parent as DockArea; - if (dock) - { - WindowFocusState.instance.m_LastWindowTypeInSameDock = sceneView.GetType().ToString(); - gameView = ScriptableObject.CreateInstance(); - dock.AddTab(gameView); - - allWindows.Add(gameView); - return gameView; - } + playmodeView.m_Parent.OnLostFocus(); + return playmodeView; } - - // If no Scene View was found at all, just create a floating Game View - gameView = ScriptableObject.CreateInstance(); - gameView.Show(true); - gameView.Focus(); - - allWindows.Add(gameView); - return gameView; - } - - return window; - } - - internal static EditorWindow ShowAppropriateViewOnEnterExitPlaymode(bool entering) - { - // Prevent trying to go into the same state as we're already in, as it will break things - if (WindowFocusState.instance.m_CurrentlyInPlayMode == entering) - return null; - - WindowFocusState.instance.m_CurrentlyInPlayMode = entering; - - EditorWindow window = null; - EditorWindow maximized = GetMaximizedWindow(); - - if (entering) - { - if (!GameView.openWindowOnEnteringPlayMode && !(PlayModeView.GetCorrectPlayModeViewToFocus() is PlayModeView)) - return null; - - WindowFocusState.instance.m_WasMaximizedBeforePlay = (maximized != null); - - // If a view is already maximized before entering play mode, - // just keep that maximized view, no matter if it's the game view or some other. - // Trust that user has a good reason (desire by Ethan etc.) - if (maximized != null) - return maximized; - } - else - { - // If a view was already maximized before entering play mode, - // then it was kept when switching to play mode, and can simply still be kept when exiting - if (WindowFocusState.instance.m_WasMaximizedBeforePlay) - return maximized; } - // Unmaximize if maximized - if (maximized) - Unmaximize(maximized); - // Try finding and focusing appropriate window/tab window = TryFocusAppropriateWindow(entering); if (window) @@ -862,11 +885,12 @@ public static void Unmaximize(EditorWindow win) return; } - UnityObject[] newWindows = InternalEditorUtility.LoadSerializedFileAndForget(Path.Combine(layoutsProjectPath, kMaximizeRestoreFile)); + var restoreLayout = Path.Combine(layoutsProjectPath, kMaximizeRestoreFile); + UnityObject[] newWindows = InternalEditorUtility.LoadSerializedFileAndForget(restoreLayout); if (newWindows.Length < 2) { - Debug.Log("Maximized serialized file backup not found"); + Debug.LogError("Maximized serialized file backup not found."); ResetAllLayouts(); return; } @@ -876,7 +900,7 @@ public static void Unmaximize(EditorWindow win) if (oldRoot == null) { - Debug.Log("Maximization failed because the root split view was not found"); + Debug.LogError("Maximization failed because the root split view was not found."); ResetAllLayouts(); return; } @@ -884,7 +908,7 @@ public static void Unmaximize(EditorWindow win) ContainerWindow parentWindow = win.m_Parent.window; if (parentWindow == null) { - Debug.Log("Maximization failed because the root split view has no container window"); + Debug.LogError("Maximization failed because the root split view has no container window."); ResetAllLayouts(); return; } @@ -1065,6 +1089,9 @@ public static void Maximize(EditorWindow win) public static bool MaximizePrepare(EditorWindow win) { View rootSplit = FindRootSplitView(win); + //Some windows such as pop up windows might not have a split view + if (rootSplit == null) + return false; View itor = rootSplit.parent; // Make sure it has a dockarea @@ -1163,6 +1190,15 @@ static void DeleteWindowLayoutImpl(string name, string path) ShortcutIntegration.instance.RebuildShortcuts(); } + + public static bool TryLoadWindowLayout(string path, bool newProjectLayoutWasCreated) + { + if (LoadWindowLayout(path, newProjectLayoutWasCreated, true, false)) + return true; + LoadCurrentModeLayout(FindMainWindow()); + return false; + } + public static bool LoadWindowLayout(string path, bool newProjectLayoutWasCreated) { return LoadWindowLayout(path, newProjectLayoutWasCreated, true, false); @@ -1203,7 +1239,7 @@ public static bool LoadWindowLayout(string path, bool newProjectLayoutWasCreated UnityObject[] loadedWindows = InternalEditorUtility.LoadSerializedFileAndForget(path); if (loadedWindows == null || loadedWindows.Length == 0) - throw new LayoutException($"Window layout at {path} could not be loaded."); + throw new LayoutException("No windows found in layout."); List newWindows = new List(); @@ -1219,8 +1255,8 @@ public static bool LoadWindowLayout(string path, bool newProjectLayoutWasCreated { if (!editorWin || !editorWin.m_Parent || !editorWin.m_Parent.window) { - Console.WriteLine("[LAYOUT] Removed unparented EditorWindow while reading window layout: window #" + i + ", type=" + - o.GetType() + ", instanceID=" + o.GetInstanceID()); + Console.WriteLine($"[LAYOUT] Removed un-parented EditorWindow while reading window layout" + + $" window #{i}, type={o.GetType()} instanceID={o.GetInstanceID()}"); UnityObject.DestroyImmediate(editorWin, true); layoutLoadingIssue = true; continue; @@ -1270,6 +1306,8 @@ public static bool LoadWindowLayout(string path, bool newProjectLayoutWasCreated if (mainWindowPosition.width != 0.0) { mainWindowToSetSize = cur; + // This is the same reference as the mainwindow, so need to freeze it too on for Linux during reload. + mainWindowToSetSize.SetFreeze(true); mainWindowToSetSize.position = mainWindowPosition; } @@ -1291,17 +1329,25 @@ public static bool LoadWindowLayout(string path, bool newProjectLayoutWasCreated } if (mainWindowToSetSize) + { + mainWindowToSetSize.SetFreeze(true); mainWindowToSetSize.position = mainWindowPosition; + } // Always show main window before other windows. So that other windows can // get their parent/owner. if (!mainWindow) throw new LayoutException("Error while reading window layout: no main window found"); + // Don't adjust height to prevent main window shrink during layout on Linux. + mainWindow.SetFreeze(true); mainWindow.Show(mainWindow.showMode, loadPosition: true, displayImmediately: true, setFocus: true); if (mainWindowToSetSize && mainWindow.maximized != mainWindowMaximized) mainWindow.ToggleMaximize(); + // Unfreeze to make sure resize work properly. + mainWindow.SetFreeze(false); + // Make sure to restore the save to layout flag when loading a layout from a file. if (keepMainWindow) mainWindow.m_DontSaveToLayout = false; @@ -1313,48 +1359,24 @@ public static bool LoadWindowLayout(string path, bool newProjectLayoutWasCreated continue; EditorWindow win = newWindows[i] as EditorWindow; + if (win) win.minSize = win.minSize; // Causes minSize to be propagated upwards to parents! ContainerWindow containerWindow = newWindows[i] as ContainerWindow; + if (containerWindow && containerWindow != mainWindow) containerWindow.Show(containerWindow.showMode, loadPosition: false, displayImmediately: true, setFocus: true); } - // Unmaximize maximized PlayModeView window if maximize on play is enabled + // Un-maximize maximized PlayModeView window if maximize on play is enabled PlayModeView playModeView = GetMaximizedWindow() as PlayModeView; if (playModeView != null && playModeView.enterPlayModeBehavior == PlayModeView.EnterPlayModeBehavior.PlayMaximized) Unmaximize(playModeView); } catch (Exception ex) { - Debug.LogError("Failed to load window layout: " + ex); - - int option = 0; - if (!Application.isTestRun && Application.isHumanControllingUs) - { - option = EditorUtility.DisplayDialogComplex("Failed to load window layout", - $"This can happen if layout contains custom windows and there are compile errors in the project.\r\n\r\n{ex.Message}", - "Load Default Layout", "Quit", "Revert Factory Settings"); - } - else - { - ResetUserLayouts(); - } - - switch (option) - { - case 0: - LoadDefaultLayout(); - break; - case 1: - EditorApplication.Exit(0); - break; - case 2: - ResetFactorySettings(); - break; - } - + Debug.LogError($"Failed to load window layout \"{path}\": {ex}"); return false; } finally @@ -1368,7 +1390,7 @@ public static bool LoadWindowLayout(string path, bool newProjectLayoutWasCreated } if (layoutLoadingIssue) - Debug.Log("The editor layout could not be fully loaded, this can happen when the layout contains EditorWindows not available in this project"); + Debug.LogWarning($"The layout \"{path}\" could not be fully loaded, this can happen when the layout contains EditorWindows not available in this project."); return true; } @@ -1496,6 +1518,7 @@ public static void SaveWindowLayout(string path) all.Add(w); } + InternalEditorUtility.SaveToSerializedFileAndForget(all.Where(o => o).ToArray(), path, true); } @@ -1508,11 +1531,9 @@ internal static void SaveGUI() public static void LoadFromFile() { var layoutFilePath = EditorUtility.OpenFilePanelWithFilters("Load layout from disk...", "", new[] {"Layout", "wlt"}); - if (String.IsNullOrEmpty(layoutFilePath)) + if (string.IsNullOrEmpty(layoutFilePath)) return; - - if (LoadWindowLayout(layoutFilePath, false)) - Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, "Loaded layout from " + layoutFilePath); + TryLoadWindowLayout(layoutFilePath, false); } public static void SaveToFile() @@ -1536,14 +1557,19 @@ private static void ResetUserLayouts() FileUtil.CopyFileIfExists(installationLayoutPath, userLayoutDstPath, overwrite: true); } - // Delete any current project layouts + // delete per-project layouts if (Directory.Exists(layoutsProjectPath)) Directory.Delete(layoutsProjectPath, true); + + // delete per-user layouts + if (Directory.Exists(layoutsCurrentModePreferencesPath)) + Directory.Delete(layoutsCurrentModePreferencesPath, true); } + public static void ResetAllLayouts(bool quitOnCancel = true) { - if (!EditorUtility.DisplayDialog("Revert All Window Layouts", + if (!Application.isTestRun && !EditorUtility.DisplayDialog("Revert All Window Layouts", "Unity is about to delete all window layouts and restore them to the default settings.", "Continue", quitOnCancel ? "Quit" : "Cancel")) { @@ -1552,6 +1578,9 @@ public static void ResetAllLayouts(bool quitOnCancel = true) return; } + if (!ContainerWindow.InternalRequestCloseAll(false)) + return; + ResetFactorySettings(); } @@ -1577,6 +1606,7 @@ internal class SaveWindowLayout : EditorWindow const int k_Width = 200; const int k_Height = 48; const int k_HelpBoxHeight = 40; + const int k_MaxLayoutNameLength = 128; static readonly string k_InvalidChars = EditorUtility.GetInvalidFilenameChars(); static readonly string s_InvalidCharsFormatString = L10n.Tr("Invalid characters: {0}"); @@ -1615,9 +1645,13 @@ void OnGUI() GUI.SetNextControlName("m_PreferencesName"); EditorGUI.BeginChangeCheck(); m_LayoutName = EditorGUILayout.TextField(m_LayoutName); - m_LayoutName = m_LayoutName.TrimEnd(); if (EditorGUI.EndChangeCheck()) { + if (m_LayoutName.Length > k_MaxLayoutNameLength) + { + m_LayoutName = m_LayoutName.Substring(0, k_MaxLayoutNameLength); + } + m_LayoutName = m_LayoutName.TrimEnd(); UpdateCurrentInvalidChars(); } @@ -1711,6 +1745,7 @@ static void ShowAnimationWindow() } [MenuItem("Window/Audio/Audio Mixer %8", false, 1)] + static void ShowAudioMixer() { AudioMixerWindow.CreateAudioMixerWindow(); diff --git a/Editor/Mono/GUIView.bindings.cs b/Editor/Mono/GUIView.bindings.cs index aac0a2d304..f6eb3649c5 100644 --- a/Editor/Mono/GUIView.bindings.cs +++ b/Editor/Mono/GUIView.bindings.cs @@ -55,6 +55,7 @@ internal partial class GUIView internal extern void SetKeyboardControl(int id); internal extern int GetKeyboardControl(); internal extern void GrabPixels(RenderTexture rd, Rect rect); + internal extern float GetBackingScaleFactor(); internal extern void MarkHotRegion(Rect hotRegionRect); internal extern void EnableVSync(bool value); internal extern void SetActualViewName(string viewName); diff --git a/Editor/Mono/GUIView.cs b/Editor/Mono/GUIView.cs index a1c000a816..b7afe19e5e 100644 --- a/Editor/Mono/GUIView.cs +++ b/Editor/Mono/GUIView.cs @@ -21,6 +21,7 @@ internal partial class GUIView : View, IWindowModel int m_DepthBufferBits = 0; int m_AntiAliasing = 1; + bool m_ResetPanelRenderingOnAssetChange = true; bool m_AutoRepaintOnSceneChange = false; private IWindowBackend m_WindowBackend; @@ -149,6 +150,19 @@ public int antiAlias set { throw new NotSupportedException("AA is not supported on GUIViews"); } } + public bool resetPanelRenderingOnAssetChange + { + get => m_ResetPanelRenderingOnAssetChange; + set + { + if (m_ResetPanelRenderingOnAssetChange != value) + { + m_ResetPanelRenderingOnAssetChange = value; + windowBackend?.ResetPanelRenderingOnAssetChangeChanged(); + } + } + } + internal IWindowBackend windowBackend { get { return m_WindowBackend; } @@ -272,10 +286,8 @@ public static void BeginOffsetArea(Rect screenRect, GUIContent content, GUIStyle public static void EndOffsetArea() { - if (Event.current.type == EventType.Used) - return; - GUILayoutUtility.EndLayoutGroup(); GUI.EndGroup(); + GUILayoutUtility.EndLayoutGroup(); } // we already have renderdoc integration done in GUIView but in cpp @@ -302,5 +314,14 @@ internal void CaptureMetalScene() System.Diagnostics.Process.Start(System.IO.Path.GetDirectoryName(path)); } } + + [RequiredByNativeCode] + private void SetActiveWindowAsPresented() + { + if ((this is HostView h) && h.actualView) + { + h.actualView.m_IsPresented = true; + } + } } } //namespace diff --git a/Editor/Mono/GameView/GameView.cs b/Editor/Mono/GameView/GameView.cs index b4a8429f30..b2808c67c0 100644 --- a/Editor/Mono/GameView/GameView.cs +++ b/Editor/Mono/GameView/GameView.cs @@ -38,7 +38,7 @@ Floating GameView in separate window namespace UnityEditor { [EditorWindowTitle(title = "Game", useTypeNameAsIconName = true)] - internal class GameView : PlayModeView, IHasCustomMenu, IGameViewSizeMenuUser, IGameViewOnPlayMenuUser + internal class GameView : PlayModeView, IHasCustomMenu, IGameViewSizeMenuUser { const int kScaleSliderMinWidth = 30; const int kScaleSliderMaxWidth = 150; @@ -58,7 +58,7 @@ float minScale { var clampedMinScale = Mathf.Min(kMinScale, ScaleThatFitsTargetInView(targetRenderSize, viewInWindow.size)); if (m_LowResolutionForAspectRatios[(int)currentSizeGroupType] && currentGameViewSize.sizeType == GameViewSizeType.AspectRatio) - clampedMinScale = Mathf.Max(clampedMinScale, Mathf.Floor(EditorGUIUtility.pixelsPerPoint)); + clampedMinScale = Mathf.Max(clampedMinScale, EditorGUIUtility.pixelsPerPoint); return clampedMinScale; } } @@ -68,7 +68,6 @@ float maxScale } [SerializeField] bool m_VSyncEnabled; - [SerializeField] bool m_PlayFocused; [SerializeField] bool m_Gizmos; [SerializeField] bool m_Stats; [SerializeField] int[] m_SelectedSizes = new int[0]; // We have a selection for each game view size group (e.g standalone, android etc) @@ -85,7 +84,6 @@ float maxScale [SerializeField] int m_XRRenderMode = 0; [SerializeField] RenderTexture m_RenderTexture; [SerializeField] bool m_showToolbar = true; - [SerializeField] bool m_showToolbarOnFullscreen = false; int m_SizeChangeID = int.MinValue; @@ -110,8 +108,6 @@ internal static class Styles public static GUIContent lowResAspectRatiosContextMenuContent = EditorGUIUtility.TrTextContent("Low Resolution Aspect Ratios"); public static GUIContent metalFrameCaptureContent = EditorGUIUtility.TrIconContent("FrameCapture", "Capture the current view and open in Xcode frame debugger"); public static GUIContent frameDebuggerContent = EditorGUIUtility.TrIconContent("Debug_Frame_d", "Opens the Frame Debugger"); - public static GUIContent suppressMessage = EditorGUIUtility.TrTextContent("This GameView is suppressed from rendering during Fullscreen."); - public static GUIContent disableFullscreenMainDisplayFormatContent = EditorGUIUtility.TrTextContent("Press {0} to exit fullscreen."); public static GUIContent renderdocContent; public static GUIStyle gameViewBackgroundStyle; @@ -124,35 +120,10 @@ static Styles() gameViewBackgroundStyle = "GameViewBackground"; renderdocContent = EditorGUIUtility.TrIconContent("FrameCapture", RenderDocUtil.openInRenderDocTooltip); } - - public static readonly GUIStyle largeCenteredText = new GUIStyle(EditorStyles.label) - { - name = "large-centered-text", - richText = true, - wordWrap = true, - alignment = TextAnchor.MiddleCenter, - margin = new RectOffset(4, 4, 1, 4), - padding = new RectOffset(4, 4, 4, 4), - fontSize = 42 - }; - public static readonly GUIStyle smallCenteredText = new GUIStyle(EditorStyles.label) - { - name = "small-centered-text", - richText = true, - wordWrap = true, - alignment = TextAnchor.MiddleCenter, - margin = new RectOffset(4, 4, 1, 4), - padding = new RectOffset(4, 4, 4, 4), - fontSize = 12 - }; } - static double s_LastScrollTime; - [FreeFunction] - extern private static bool NeedToPerformRendering(); - public GameView() { autoRepaintOnSceneChange = true; @@ -166,81 +137,6 @@ public GameView() textureHideFlags = HideFlags.HideAndDontSave; } - internal override void ApplyEditorDisplayFullscreenSetting(IPlayModeViewFullscreenSettings settings) - { - var gameviewSetting = settings as GameViewFullscreenSettings; - if (gameviewSetting == null) - { - return; - } - - isFullscreen = true; - - var changed = false; - - if (ModuleManager.ShouldShowMultiDisplayOption()) - { - if (targetDisplay != gameviewSetting.DisplayNumber) - { - targetDisplay = gameviewSetting.DisplayNumber; - changed = true; - } - } - - if (m_showToolbarOnFullscreen != gameviewSetting.ShowToolbar) - { - m_showToolbarOnFullscreen = gameviewSetting.ShowToolbar; - changed = true; - } - - if (m_Stats != gameviewSetting.ShowStats) - { - m_Stats = gameviewSetting.ShowStats; - changed = true; - } - - if (m_Gizmos != gameviewSetting.ShowGizmos) - { - m_Gizmos = gameviewSetting.ShowGizmos; - changed = true; - } - - if (canVSync) - { - if (m_VSyncEnabled != gameviewSetting.VsyncEnabled) - { - m_VSyncEnabled = gameviewSetting.VsyncEnabled; - SetVSync(m_VSyncEnabled); - changed = true; - } - } - - if (selectedSizeIndex != gameviewSetting.SelectedSizeIndex) - { - selectedSizeIndex = gameviewSetting.SelectedSizeIndex; - changed = true; - } - - if (changed) - { - UpdateZoomAreaAndParent(); - } - } - - private bool canVSync - { - get - { - var gfxDeviceType = SystemInfo.graphicsDeviceType; - return - gfxDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Metal || - gfxDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Vulkan || - gfxDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Direct3D11 || - gfxDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Direct3D12 || - gfxDeviceType == UnityEngine.Rendering.GraphicsDeviceType.OpenGLCore; - } - } - public bool lowResolutionForAspectRatios { get @@ -255,6 +151,9 @@ public bool lowResolutionForAspectRatios { m_LowResolutionForAspectRatios[(int)currentSizeGroupType] = value; UpdateZoomAreaAndParent(); + + if (currentGameViewSize.sizeType == GameViewSizeType.AspectRatio) + SnapZoom(minScale); } } } @@ -274,12 +173,6 @@ public bool vSyncEnabled } } - public bool playFocused - { - get { return m_PlayFocused; } - set { m_PlayFocused = value; } - } - public int selectedSizeIndex { get @@ -311,6 +204,7 @@ Rect GetViewInWindow(Rect pos) { if (showToolbar) return new Rect(0, EditorGUI.kWindowToolbarHeight, pos.width, pos.height - EditorGUI.kWindowToolbarHeight); + return new Rect(0, 0, pos.width, pos.height); } @@ -406,10 +300,10 @@ Rect targetInParent // Area of the render target in parent view space Vector2 gameMouseOffset { get { return -viewInWindow.position - targetInView.position; } } float gameMouseScale { get { return EditorGUIUtility.pixelsPerPoint / m_ZoomArea.scale.y; } } - + private bool showToolbar { - get => isFullscreen ? m_showToolbarOnFullscreen : m_showToolbar; + get => m_showToolbar; set => m_showToolbar = value; } @@ -441,9 +335,7 @@ public void OnEnable() EditorApplication.playModeStateChanged += OnPlayModeStateChanged; Undo.undoRedoEvent += UndoRedoPerformed; - if (!suppressRenderingForFullscreen) - targetSize = targetRenderSize; - + targetSize = targetRenderSize; PlayModeAnalytics.GameViewEnableEvent(); } @@ -469,9 +361,6 @@ internal static Vector2 GetSizeOfMainGameView() private void UpdateZoomAreaAndParent() { - if (suppressRenderingForFullscreen) - return; - // Configure ZoomableArea for new resolution so that old resolution doesn't restrict scale bool oldScaleWasDefault = Mathf.Approximately(m_ZoomArea.scale.y, m_defaultScale); ConfigureZoomArea(); @@ -498,8 +387,7 @@ protected void AllowCursorLockAndHide(bool enable) private void OnFocus() { SetFocus(true); - if (!suppressRenderingForFullscreen) - targetSize = targetRenderSize; + targetSize = targetRenderSize; } private void OnLostFocus() @@ -517,18 +405,21 @@ private void OnLostFocus() internal override void OnResized() { - if (!suppressRenderingForFullscreen) - targetSize = targetRenderSize; + targetSize = targetRenderSize; } internal override void OnBackgroundViewResized(Rect pos) { - // Should only update the game view size if it's in Aspect Ratio mode, otherwise - // we keep the static size - - if (suppressRenderingForFullscreen) + // If we are switching from GameView to Simulator, this call will overwrite the value already written + // by the SimulatorView since both tabs exist for a brief period of time. Don't do anything here + // if this view is the one being switched out. + if (m_SwitchingPlayModeViewType) + { return; + } + // Should only update the game view size if it's in Aspect Ratio mode, otherwise + // we keep the static size Rect viewInWindow = GetViewInWindow(pos); Rect viewPixelRect = GetViewPixelRect(viewInWindow); var newTargetSize = @@ -668,31 +559,6 @@ private bool ShouldShowMetalFrameCaptureGUI() || FrameCapture.IsDestinationSupported(FrameCaptureDestination.GPUTraceDocument); } - private void MirrorFullscreenToolbarSettings() - { - if (enterPlayModeBehavior != EnterPlayModeBehavior.PlayFullscreen) - return; - - // Even though there is a 1:1 relationship between a fullscreen game view, and the "suppressed" game view that - // spawned it, it's not easy to distingush which game view controls which sub game view. Rather than brutally - // enforce the 1:1 relationship allow any game view with the selected fullscreen idx, to control the settings - // on that fullscreen container window. - int displayIdx = fullscreenMonitorIdx; - List fullscreenContainers = EditorFullscreenController.GetFullscreenContainersForDisplayIndex(displayIdx); - foreach (var cw in fullscreenContainers) - { - var fullscreenGameView = (cw.rootView as HostView).actualView as GameView; - - fullscreenGameView.targetDisplay = this.targetDisplay; - fullscreenGameView.m_ZoomArea = this.m_ZoomArea; - fullscreenGameView.UpdateZoomAreaAndParent(); - fullscreenGameView.m_Stats = this.m_Stats; - fullscreenGameView.m_Gizmos = this.m_Gizmos; - - } - - } - private void DoToolbarGUI() { if (Event.current.isKey || Event.current.type == EventType.Used) @@ -702,24 +568,22 @@ private void DoToolbarGUI() GUILayout.BeginHorizontal(EditorStyles.toolbar); { - if (!isFullscreen) + var availableTypes = GetAvailableWindowTypes(); + if (availableTypes.Count > 1) { - EditorGUI.BeginDisabled(enterPlayModeBehavior == EnterPlayModeBehavior.PlayFullscreen); - var availableTypes = GetAvailableWindowTypes(); - if (availableTypes.Count > 1) + var typeNames = availableTypes.Values.ToList(); + var types = availableTypes.Keys.ToList(); + int viewIndex = EditorGUILayout.Popup(typeNames.IndexOf(titleContent.text), typeNames.ToArray(), + EditorStyles.toolbarPopup, + GUILayout.Width(90)); + if (viewIndex == -1) + viewIndex = 0; + + EditorGUILayout.Space(); + if (types[viewIndex] != typeof(GameView)) { - var typeNames = availableTypes.Values.ToList(); - var types = availableTypes.Keys.ToList(); - int viewIndex = EditorGUILayout.Popup(typeNames.IndexOf(titleContent.text), typeNames.ToArray(), - EditorStyles.toolbarPopup, - GUILayout.Width(90)); - EditorGUILayout.Space(); - if (types[viewIndex] != typeof(GameView)) - { - SwapMainWindow(types[viewIndex]); - } + SwapMainWindow(types[viewIndex]); } - EditorGUI.EndDisabled(); } if (ModuleManager.ShouldShowMultiDisplayOption()) @@ -759,6 +623,10 @@ private void DoToolbarGUI() } GUILayout.FlexibleSpace(); + using (new EditorGUI.DisabledScope(EditorApplication.isPlaying && !EditorApplication.isPaused)) + { + enterPlayModeBehavior = (EnterPlayModeBehavior)EditorGUILayout.EnumPopup(enterPlayModeBehavior, EditorStyles.toolbarDropDown, GUILayout.Width(110)); + } if (ShouldShowMetalFrameCaptureGUI()) { @@ -767,14 +635,17 @@ private void DoToolbarGUI() } if (GUILayout.Button(Styles.frameDebuggerContent, EditorStyles.toolbarButton)) - FrameDebuggerWindow.ShowFrameDebuggerWindow(); + FrameDebuggerWindow.OpenWindowAndToggleEnabled(); if (RenderDoc.IsLoaded()) { using (new EditorGUI.DisabledScope(!RenderDoc.IsSupported())) { if (GUILayout.Button(Styles.renderdocContent, EditorStyles.toolbarButton)) + { RenderDoc.CaptureRenderDoc(); + GUIUtility.ExitGUI(); + } } } @@ -809,14 +680,6 @@ private void DoToolbarGUI() } GUILayout.FlexibleSpace(); - // Don't display the fullscreen selection dropdown on gameview toolbars that are already fullscreen. - if (!isFullscreen) - { - EditorGUI.BeginDisabled(EditorApplication.isPlaying && !EditorApplication.isPaused); - EditorGUILayout.GameViewOnPlayPopup(playModeBehaviorIdx, this, EditorStyles.toolbarDropDown); - EditorGUI.EndDisabled(); - } - EditorUtility.audioMasterMute = GUILayout.Toggle(EditorUtility.audioMasterMute, EditorUtility.audioMasterMute ? Styles.muteOnContent : Styles.muteOffContent, EditorStyles.toolbarButton); @@ -835,7 +698,6 @@ private void DoToolbarGUI() } } GUILayout.EndHorizontal(); - MirrorFullscreenToolbarSettings(); } private int XRTranslateMirrorViewBlitModeToRenderMode(int mirrorViewBlitMode) @@ -985,6 +847,14 @@ private void OnPlayModeStateChanged(PlayModeStateChange state) { m_Parent.EnableVSync(false); } + else if (state == PlayModeStateChange.EnteredEditMode) + { + // UUM-76326 - Ensure CursorLock is disabled in EditMode in case a script locks it while + // exiting PlayMode. In this scenario, the cursor lock state is maintained and will re-engage + // in the next PlayMode session. + // NOTE: For safety the user must still click the GameView before cursor lock is allowed again. + AllowCursorLockAndHide(false); + } } void OnBecameVisible() @@ -1006,7 +876,6 @@ void RepaintIfNeeded() private void OnEditorModeChanged(ModeService.ModeChangedArgs args) { showToolbar = ModeService.HasCapability(ModeCapability.GameViewToolbar, true); - Repaint(); } @@ -1040,12 +909,6 @@ private void OnGUI() if (HandleCommand(evt)) return; - if (suppressRenderingForFullscreen && latestState == PlayModeStateChange.EnteredPlayMode) - { - DrawDuringFullscreenBackground(); - return; - } - if (position.size * EditorGUIUtility.pixelsPerPoint != m_LastWindowPixelSize) // pixelsPerPoint only reliable in OnGUI() { UpdateZoomAreaAndParent(); @@ -1063,8 +926,10 @@ private void OnGUI() if (EditorApplication.isPlayingOrWillChangePlaymode) EditorGUIUtility.AddCursorRect(viewInWindow, MouseCursor.CustomCursor); + var playing = EditorApplication.isPlaying && !EditorApplication.isPaused; + // Gain mouse lock when clicking on game view content, unless game is paused - if (!EditorApplication.isPaused && type == EventType.MouseDown && viewInWindow.Contains(Event.current.mousePosition)) + if (playing && type == EventType.MouseDown && viewInWindow.Contains(Event.current.mousePosition)) { AllowCursorLockAndHide(true); } @@ -1075,7 +940,6 @@ private void OnGUI() } // We hide sliders when playing, and also when we are zoomed out beyond canvas edges - var playing = EditorApplication.isPlaying && !EditorApplication.isPaused; var targetInContentCached = targetInContent; m_ZoomArea.hSlider = !playing && m_ZoomArea.shownArea.width < targetInContentCached.width; m_ZoomArea.vSlider = !playing && m_ZoomArea.shownArea.height < targetInContentCached.height; @@ -1094,7 +958,7 @@ private void OnGUI() m_ZoomArea.BeginViewGUI(); // Window size might change on Layout event - if (type == EventType.Layout && !suppressRenderingForFullscreen) + if (type == EventType.Layout) targetSize = targetRenderSize; // Setup game view dimensions, so that player loop can use it for input @@ -1102,23 +966,19 @@ private void OnGUI() if (m_Parent) { var zoomedTarget = new Rect(targetInView.position + gameViewTarget.position, targetInView.size); - if (!isFullscreen) - { - SetParentGameViewDimensions(zoomedTarget, gameViewTarget, targetRenderSize); - } - else - { - SetParentGameViewDimensions(zoomedTarget, gameViewTarget, EditorFullscreenController.fullscreenDisplayRenderSize); - } + SetParentGameViewDimensions(zoomedTarget, gameViewTarget, targetRenderSize); } var editorMousePosition = Event.current.mousePosition; var gameMousePosition = (editorMousePosition + gameMouseOffset) * gameMouseScale; - if (type == EventType.Repaint && !suppressRenderingForFullscreen) + if (type == EventType.Repaint) { GUI.Box(m_ZoomArea.drawRect, GUIContent.none, Styles.gameViewBackgroundStyle); + // Tonemapping for HDR targets + EditorGUIUtility.PerformTonemappingForGameView(); + Vector2 oldOffset = GUIUtility.s_EditorScreenPointOffset; GUIUtility.s_EditorScreenPointOffset = Vector2.zero; SavedGUIState oldState = SavedGUIState.Create(); @@ -1141,8 +1001,7 @@ private void OnGUI() viewPadding = targetInParent.position; viewMouseScale = gameMouseScale; - if (!EditorApplication.isPlaying || - (EditorApplication.isPlaying && NeedToPerformRendering() && !Unsupported.IsEditorPlayerLoopWaiting())) + if (renderViewCallNeededInOnGUI) m_RenderTexture = RenderView(gameMousePosition, clearTexture); if (m_TargetClamped) @@ -1159,7 +1018,7 @@ private void OnGUI() Rect drawRect = deviceFlippedTargetInView; drawRect.x = Mathf.Round(drawRect.x); drawRect.y = Mathf.Round(drawRect.y); - Graphics.DrawTexture(drawRect, m_RenderTexture, new Rect(0, 0, 1, 1), 0, 0, 0, 0, GUI.color, GUI.blitMaterial); + EditorGUIUtility.DrawTextureHdrSupport(drawRect, m_RenderTexture, new Rect(0, 0, 1, 1), 0, 0, 0, 0, GUI.color, GUI.blitMaterial, -1, true); GUI.EndGroup(); } } @@ -1168,13 +1027,6 @@ private void OnGUI() if (Event.current.isKey && (!EditorApplication.isPlaying || EditorApplication.isPaused)) return; - if (isFullscreen) - { - // Let the global eventhandler handle toggling fullscreen after this event (do not use the current event) - // We support fullscreen in both edit mode and play mode - return; - } - bool mousePosInGameViewRect = viewInWindow.Contains(Event.current.mousePosition); // MouseDown events outside game view rect are not send to scripts but MouseUp events are (see below) @@ -1242,15 +1094,6 @@ private void OnGUI() GameViewGUI.GameViewStatsGUI(); } - private void DrawDuringFullscreenBackground() - { - DoToolbarGUI(); - EditorGUILayout.LabelField(Styles.suppressMessage, Styles.largeCenteredText); - var binding = ShortcutManager.instance.GetShortcutBinding(EditorFullscreenController.kFullscreenToggle); - string content = string.Format(Styles.disableFullscreenMainDisplayFormatContent.text, binding); - EditorGUILayout.LabelField(content, Styles.smallCenteredText); - } - internal void SetCustomResolution(Vector2 res, string baseName) { GameViewSize customSize = null; @@ -1283,34 +1126,5 @@ internal void SetCustomResolution(Vector2 res, string baseName) targetSize = targetRenderSize; SceneView.RepaintAll(); } - - void IGameViewOnPlayMenuUser.OnPlayPopupSelection(int indexClicked, object objectSelected) - { - playModeBehaviorIdx = indexClicked; - if (playModeBehaviorIdx == 0) - { - if (playFocused) - enterPlayModeBehavior = EnterPlayModeBehavior.PlayFocused; - else - enterPlayModeBehavior = EnterPlayModeBehavior.PlayUnfocused; - fullscreenMonitorIdx = PlayModeView.kFullscreenNone; - } - else if (playModeBehaviorIdx == 1) - { - enterPlayModeBehavior = EnterPlayModeBehavior.PlayMaximized; - fullscreenMonitorIdx = PlayModeView.kFullscreenNone; - GameViewOnPlayMenu.SetFocusedToggle(this, true); - } - else - { - enterPlayModeBehavior = EnterPlayModeBehavior.PlayFullscreen; - fullscreenMonitorIdx = GameViewOnPlayMenu.SelectedIndexToDisplayIndex(indexClicked); - if (fullscreenMonitorIdx == PlayModeView.kFullscreenInvalidIdx) - { - Debug.LogError("Invalid display index(" + fullscreenMonitorIdx + ") for selected index(" + indexClicked + ")"); - fullscreenMonitorIdx = PlayModeView.kFullscreenNone; - } - } - } } } diff --git a/Editor/Mono/GameView/GameViewOnPlayMenu.cs b/Editor/Mono/GameView/GameViewOnPlayMenu.cs deleted file mode 100644 index 572b2aba15..0000000000 --- a/Editor/Mono/GameView/GameViewOnPlayMenu.cs +++ /dev/null @@ -1,164 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using UnityEngine; -using System; -using System.Collections.Generic; - -namespace UnityEditor -{ - internal class GameViewOnPlayMenu : FlexibleMenu - { - private static class Styles - { - public static readonly GUIContent gamePlayNormalContent = EditorGUIUtility.TrTextContent("Play in Window", "Play inside a game view docked inside unity"); - public static readonly GUIContent gamePlayMaximizedContent = EditorGUIUtility.TrTextContent("Maximized", "Maximize the game view before entering play mode."); - public static readonly GUIContent gamePlayFullscreenContent = EditorGUIUtility.TrTextContent("Fullscreen on ", "Play the game view on a fullscreen monitor"); - public static readonly GUIContent playFocusedToggleContent = EditorGUIUtility.TrTextContent("Focused", "Forcilby focus the game view when entering play mode."); - public static readonly GUIContent playFocusedToggleDisabledContent = EditorGUIUtility.TrTextContent("Focused", "Focus toggle is currently disabled because a view will play maximized."); - public static GUIContent vSyncToggleContent = EditorGUIUtility.TrTextContent("VSync", "Enable VSync only for the game view while in playmode."); - public static GUIContent vSyncUnsupportedContent = EditorGUIUtility.TrTextContent("No VSync", "VSync is not available because it is not supported by this device"); - public static GUIContent gamePlayModeBehaviorLabelContent = EditorGUIUtility.TrTextContent("Enter Play Mode:"); - - public const float kMargin = 9f; - public const float kTopMargin = 7f; - public const int kNumberOfTogglesOnTop = 2; - public static float frameHeight => (kTopMargin * kNumberOfTogglesOnTop) + EditorGUI.kSingleLineHeight; - public static float contentOffset => frameHeight + EditorGUI.kControlVerticalSpacing; - } - - // Number of on play behaviors that should always be visible. Fullscreen may or may not be supported and there may be multiple monitors. - // The base is 2, "Play Normally" (with focus as a checkbox) and "Play Maximized" - public const int kPlayModeBaseOptionCount = 2; - private readonly IGameViewOnPlayMenuUser m_GameView; - private bool m_ShowFullscreenOptions = true; - private IFlexibleMenuItemProvider m_ItemProvider; - - public GameViewOnPlayMenu(IFlexibleMenuItemProvider itemProvider, int selectionIndex, FlexibleMenuModifyItemUI modifyItemUi, IGameViewOnPlayMenuUser gameView, bool showFullscreenOptions = true) - : base(itemProvider, selectionIndex, modifyItemUi, gameView.OnPlayPopupSelection) - { - m_GameView = gameView; - m_ShowFullscreenOptions = showFullscreenOptions; - m_ItemProvider = itemProvider; - } - - public override Vector2 GetWindowSize() - { - var playFocusedToggleSize = EditorStyles.toggle.CalcSize(Styles.playFocusedToggleContent); - var size = CalcSize(); - - size.x = Mathf.Max(size.x, playFocusedToggleSize.x + Styles.kMargin * 2); - size.y += Styles.frameHeight + EditorGUI.kControlVerticalSpacing + EditorGUI.kSingleLineHeight; - return size; - } - - private bool IsVSyncToggleVisible() - { - // Only show the vsync toggle for editor supported gfx device backend. - var gfxDeviceType = SystemInfo.graphicsDeviceType; - return gfxDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Metal || - gfxDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Vulkan || - gfxDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Direct3D11 || - gfxDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Direct3D12 || - gfxDeviceType == UnityEngine.Rendering.GraphicsDeviceType.OpenGLCore; - } - - private void DoVSyncToggle(Rect rect) - { - if (IsVSyncToggleVisible()) - { - m_GameView.vSyncEnabled = GUI.Toggle(rect, m_GameView.vSyncEnabled, Styles.vSyncToggleContent); - } - else - { - m_GameView.vSyncEnabled = false; - GUI.Label(rect, Styles.vSyncUnsupportedContent, EditorStyles.miniLabel); - } - } - - private static void UncheckFocusToggleOnAllViews() - { - List playViewList; - WindowLayout.ShowAppropriateViewOnEnterExitPlaymodeList(true, out playViewList); - foreach (PlayModeView playView in playViewList) - { - if (playView is IGameViewOnPlayMenuUser) - { - ((IGameViewOnPlayMenuUser)playView).playFocused = false; - } - } - } - - public static void SetFocusedToggle(IGameViewOnPlayMenuUser view, bool newValue) - { - UncheckFocusToggleOnAllViews(); - view.playFocused = newValue; - } - - private bool IsAnyViewInMaximizeMode() - { - List playViewList; - WindowLayout.ShowAppropriateViewOnEnterExitPlaymodeList(true, out playViewList); - foreach (PlayModeView playView in playViewList) - { - if (playView.enterPlayModeBehavior == PlayModeView.EnterPlayModeBehavior.PlayMaximized) - { - SetFocusedToggle(playView as IGameViewOnPlayMenuUser, true); - return true; - } - } - return false; - } - - public override void OnGUI(Rect rect) - { - var frameRect = new Rect(rect.x, rect.y, rect.width, rect.height); - GUI.Label(frameRect, "", EditorStyles.viewBackground); - - var focusTextSize = EditorStyles.label.CalcSize(Styles.playFocusedToggleContent); - GUI.enabled = !IsAnyViewInMaximizeMode(); - var focusToggleRect = new Rect(Styles.kMargin, Styles.kTopMargin, focusTextSize.x, EditorGUI.kSingleLineHeight); - bool playFocusedToggle = GUI.Toggle(focusToggleRect, m_GameView.playFocused, GUI.enabled ? Styles.playFocusedToggleContent : Styles.playFocusedToggleDisabledContent); - GUI.enabled = true; - if (playFocusedToggle != m_GameView.playFocused) - { - SetFocusedToggle(m_GameView, playFocusedToggle); - } - var vsyncToggleRect = new Rect(focusTextSize.x + Styles.kMargin*2, Styles.kTopMargin, rect.width, EditorGUI.kSingleLineHeight); - DoVSyncToggle(vsyncToggleRect); - - var labelSize = EditorStyles.boldLabel.CalcSize(Styles.gamePlayModeBehaviorLabelContent); - var labelRect = new Rect(Styles.kMargin, Styles.kTopMargin + EditorGUI.kSingleLineHeight, labelSize.x, labelSize.y + EditorGUI.kSingleLineHeight); - GUI.Label(labelRect, Styles.gamePlayModeBehaviorLabelContent, EditorStyles.boldLabel); - - rect.y = rect.y + Styles.contentOffset + EditorGUI.kSingleLineHeight; - - base.OnGUI(rect); - } - - public static string GetOnPlayBehaviorName(int selectedIndex) - { - if (selectedIndex == 0) - return Styles.gamePlayNormalContent.text; - if (selectedIndex == 1) - return Styles.gamePlayMaximizedContent.text; - - int displayIdx = SelectedIndexToDisplayIndex(selectedIndex); - var connectedDisplay = EditorFullscreenController.GetConnectedDisplayNames(); - - var displayName = (displayIdx >= connectedDisplay.Length) - ? "Invalid monitor" - : connectedDisplay[displayIdx]; - - return Styles.gamePlayFullscreenContent.text + displayIdx + ":" + displayName; - } - - public static int SelectedIndexToDisplayIndex(int selectedIndex) - { - if (selectedIndex <= 1) - return -1; //Invalid fullscreen selection - return selectedIndex - kPlayModeBaseOptionCount; - } - } -} diff --git a/Editor/Mono/GameView/GameViewOnPlayMenuItemProvider.cs b/Editor/Mono/GameView/GameViewOnPlayMenuItemProvider.cs deleted file mode 100644 index 40102b6cac..0000000000 --- a/Editor/Mono/GameView/GameViewOnPlayMenuItemProvider.cs +++ /dev/null @@ -1,122 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using System; -using UnityEngine; - -namespace UnityEditor -{ - internal class SimulationOnPlayMenuItemProvider : IFlexibleMenuItemProvider - { - public int Count() - { - // The simulator window only supports "play normally (focuced/unfocused) and play maximized - // fullscreen support may be added in the future. - int optionCount = GameViewOnPlayMenu.kPlayModeBaseOptionCount; - return optionCount; - } - - public object GetItem(int index) - { - return null; - } - - public int Add(object obj) - { - throw new NotImplementedException("Operation not supported"); - } - - public void Replace(int index, object obj) - { - throw new NotImplementedException("Operation not supported"); - } - - public void Remove(int index) - { - throw new NotImplementedException("Operation not supported"); - } - - public object Create() - { - throw new NotImplementedException("Operation not supported"); - } - - public void Move(int index, int destIndex, bool insertAfterDestIndex) - { - throw new NotImplementedException("Operation not supported"); - } - - public string GetName(int index) - { - return GameViewOnPlayMenu.GetOnPlayBehaviorName(index); - } - - public bool IsModificationAllowed(int index) - { - return false; - } - - public int[] GetSeperatorIndices() - { - return null; - } - } - - internal class GameViewOnPlayMenuItemProvider : IFlexibleMenuItemProvider - { - public int Count() - { - // "Play Normal" and "Play Maximized" should always be options. - // The play fullscreen option will be added per-display. - int optionCount = GameViewOnPlayMenu.kPlayModeBaseOptionCount; - optionCount += EditorFullscreenController.GetConnectedDisplayNames().Length; - return optionCount; - } - - public object GetItem(int index) - { - return null; - } - - public int Add(object obj) - { - throw new NotImplementedException("Operation not supported"); - } - - public void Replace(int index, object obj) - { - throw new NotImplementedException("Operation not supported"); - } - - public void Remove(int index) - { - throw new NotImplementedException("Operation not supported"); - } - - public object Create() - { - throw new NotImplementedException("Operation not supported"); - } - - public void Move(int index, int destIndex, bool insertAfterDestIndex) - { - throw new NotImplementedException("Operation not supported"); - } - - public string GetName(int index) - { - return GameViewOnPlayMenu.GetOnPlayBehaviorName(index); - } - - public bool IsModificationAllowed(int index) - { - return false; - } - - public int[] GetSeperatorIndices() - { - return null; - } - } -} diff --git a/Editor/Mono/GameView/GameViewSizeMenu.cs b/Editor/Mono/GameView/GameViewSizeMenu.cs index b518ce473d..588b92fdae 100644 --- a/Editor/Mono/GameView/GameViewSizeMenu.cs +++ b/Editor/Mono/GameView/GameViewSizeMenu.cs @@ -10,11 +10,15 @@ namespace UnityEditor // Resolution/Aspect ratio menu for the GameView, with an optional toggle for low-resolution aspect ratios internal class GameViewSizeMenu : FlexibleMenu { + static class Styles + { + public static GUIContent vSyncToggleContent = EditorGUIUtility.TrTextContent("VSync (Game view only)", "Enable VSync only for the game view while in playmode."); + } const float kTopMargin = 7f; const float kMargin = 9f; IGameViewSizeMenuUser m_GameView; - float frameHeight { get { return kTopMargin * 2 + EditorGUI.kSingleLineHeight; } } + float frameHeight { get { return kTopMargin * 2 + EditorGUI.kSingleLineHeight * (IsVSyncToggleVisible() ? 2 : 1);}} float contentOffset { get { return frameHeight + EditorGUI.kControlVerticalSpacing; } } public GameViewSizeMenu(IFlexibleMenuItemProvider itemProvider, int selectionIndex, FlexibleMenuModifyItemUI modifyItemUi, IGameViewSizeMenuUser gameView) @@ -32,6 +36,26 @@ public override Vector2 GetWindowSize() return size; } + private bool IsVSyncToggleVisible() + { + // Only show the vsync toggle for editor supported gfx device backend. + var gfxDeviceType = SystemInfo.graphicsDeviceType; + return gfxDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Metal || + gfxDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Vulkan || + gfxDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Direct3D11 || + gfxDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Direct3D12 || + gfxDeviceType == UnityEngine.Rendering.GraphicsDeviceType.OpenGLCore; + } + + + private void DoVSyncToggle(Rect rect) + { + if (!IsVSyncToggleVisible()) + return; + var toggleRect = new Rect(rect.xMin, rect.yMax + 2, rect.width, EditorGUI.kSingleLineHeight); + m_GameView.vSyncEnabled = GUI.Toggle(toggleRect, m_GameView.vSyncEnabled, Styles.vSyncToggleContent); + } + public override void OnGUI(Rect rect) { var frameRect = new Rect(rect.x, rect.y, rect.width, frameHeight); @@ -43,7 +67,7 @@ public override void OnGUI(Rect rect) m_GameView.lowResolutionForAspectRatios = GUI.Toggle(toggleRect, m_GameView.forceLowResolutionAspectRatios || m_GameView.lowResolutionForAspectRatios, GameView.Styles.lowResAspectRatiosContextMenuContent); GUI.enabled = true; - + DoVSyncToggle(toggleRect); rect.height = rect.height - contentOffset; rect.y = rect.y + contentOffset; diff --git a/Editor/Mono/GameView/IGameViewSizeMenuUser.cs b/Editor/Mono/GameView/IGameViewSizeMenuUser.cs index d2ed188a87..ff43698b8a 100644 --- a/Editor/Mono/GameView/IGameViewSizeMenuUser.cs +++ b/Editor/Mono/GameView/IGameViewSizeMenuUser.cs @@ -9,5 +9,6 @@ internal interface IGameViewSizeMenuUser void SizeSelectionCallback(int indexClicked, object objectSelected); bool lowResolutionForAspectRatios { get; set; } bool forceLowResolutionAspectRatios { get; } + bool vSyncEnabled { get; set; } } } diff --git a/Editor/Mono/HandleUtility.cs b/Editor/Mono/HandleUtility.cs index 3e99ddb33d..ffd3af6994 100644 --- a/Editor/Mono/HandleUtility.cs +++ b/Editor/Mono/HandleUtility.cs @@ -871,41 +871,40 @@ public static Rect WorldPointToSizedRect(Vector3 position, GUIContent content, G { Vector2 screenpos = WorldToGUIPoint(position); Vector2 size = style.CalcSize(content); - Rect r = new Rect(screenpos.x, screenpos.y, size.x, size.y); + Rect rect = new Rect(screenpos.x, screenpos.y, size.x, size.y); switch (style.alignment) { - case TextAnchor.UpperLeft: - break; case TextAnchor.UpperCenter: - r.xMin -= r.width * .5f; + rect.x -= rect.width * 0.5f; break; case TextAnchor.UpperRight: - r.xMin -= r.width; + rect.x -= rect.width; break; case TextAnchor.MiddleLeft: - r.yMin -= r.height * .5f; + rect.y -= rect.height * 0.5f; break; case TextAnchor.MiddleCenter: - r.xMin -= r.width * .5f; - r.yMin -= r.height * .5f; + rect.x -= rect.width * 0.5f; + rect.y -= rect.height * 0.5f; break; case TextAnchor.MiddleRight: - r.xMin -= r.width; - r.yMin -= r.height * .5f; + rect.x -= rect.width; + rect.y -= rect.height * 0.5f; break; case TextAnchor.LowerLeft: - r.yMin -= r.height * .5f; + rect.y -= rect.height; break; case TextAnchor.LowerCenter: - r.xMin -= r.width * .5f; - r.yMin -= r.height; + rect.x -= rect.width * 0.5f; + rect.y -= rect.height; break; case TextAnchor.LowerRight: - r.xMin -= r.width; - r.yMin -= r.height; + rect.x -= rect.width; + rect.y -= rect.height; break; } - return style.padding.Add(r); + + return style.padding.Add(rect); } // Pick game object in specified rectangle @@ -1144,10 +1143,8 @@ internal static PickingObject PickObject(Vector2 guiPosition, if (picked == PickingObject.Empty && pickGameObjectCustomPasses != null) { - if(s_PickingGameObjectIgnore == null) - s_PickingGameObjectIgnore = CreatePickingObjectArray(ignore); - if(s_PickingGameObjectFilter == null) - s_PickingGameObjectFilter = CreatePickingObjectArray(filter); + s_PickingGameObjectIgnore = CreatePickingObjectArray(ignore); + s_PickingGameObjectFilter = CreatePickingObjectArray(filter); foreach (var method in pickGameObjectCustomPasses.GetInvocationList()) { @@ -1200,10 +1197,13 @@ static void GetPickingIds(List objects, List ren, List if (objects[i] == null) continue; + // If the object is represented by entities, they take priority + if (GetEntitiesForAuthoringObject(objects[i].target, ent) > 0) + continue; + + // Otherwise, use the Renderer component, if any if (objects[i].target is GameObject gameObject && gameObject.TryGetComponent(out var renderer)) ren.Add(renderer.GetInstanceID()); - else - GetEntitiesForAuthoringObject(objects[i].target, ent); } } @@ -1217,10 +1217,13 @@ static void GetPickingIds(UnityEngine.Object[] objects, List ren, List if (objects[i] == null) continue; + // If the object is represented by entities, they take priority + if (GetEntitiesForAuthoringObject(objects[i], ent) > 0) + continue; + + // Otherwise, use the Renderer component, if any if (objects[i] is GameObject gameObject && gameObject.TryGetComponent(out var renderer)) ren.Add(renderer.GetInstanceID()); - else - GetEntitiesForAuthoringObject(objects[i], ent); } } @@ -1245,16 +1248,24 @@ static UnityEngine.Object GetAuthoringObjectForEntity(int entityIndex) return null; } - static void GetEntitiesForAuthoringObject(UnityEngine.Object authoring, List entities) + static int GetEntitiesForAuthoringObject(UnityEngine.Object authoring, List entities) { + int numEntities = 0; var delegates = getEntitiesForAuthoringObject?.GetInvocationList(); if (delegates == null) - return; + return 0; foreach (var del in delegates) + { foreach (var ent in ((Func>)del)(authoring)) + { entities.Add(ent); + ++numEntities; + } + } + + return numEntities; } // Get the selection base object, taking into account user enabled picking filter @@ -1293,14 +1304,19 @@ internal static GameObject FindSelectionBaseForPicking(GameObject go) // alternatively a GameObject with the SelectionBaseAttribute assigned. Transform tr = go.transform; GameObject outerMostSelectableRoot = null; + GameObject prefabBaseGo = null; while (tr != null) { if (!SceneVisibilityState.IsGameObjectPickingDisabled(tr.gameObject)) { - // If we come across the prefab base, no need to search further - if (tr == prefabBase) - return tr.gameObject; + // If we come across the prefab base, we still need to make sure there are no + // go with the SelectionBaseAttribute assigned before returning prefabBaseGo. + if (tr == prefabBase && prefabBaseGo == null) + { + prefabBaseGo = tr.gameObject; + continue; + } // Only test again nested prefab if go is already part of a prefab to avoid selecting a // parent prefab is the current go is only child of this prefab but does not belong to it @@ -1313,7 +1329,8 @@ internal static GameObject FindSelectionBaseForPicking(GameObject go) outerMostSelectableRoot = tr.gameObject; } - // If a SelectionBaseAttribute is found, select the nearest to the picked GameObject + // If a SelectionBaseAttribute is found, select the nearest to the picked GameObject. + // GameObjects with the SelectionBaseAttribute should have priority over all others. if (AttributeHelper.GameObjectContainsAttribute(tr.gameObject)) return tr.gameObject; } @@ -1321,6 +1338,9 @@ internal static GameObject FindSelectionBaseForPicking(GameObject go) tr = tr.parent; } + if (prefabBaseGo != null) + return prefabBaseGo; + return outerMostSelectableRoot; } diff --git a/Editor/Mono/Handles.bindings.cs b/Editor/Mono/Handles.bindings.cs index e93ce171f6..8cbc9dd5c1 100644 --- a/Editor/Mono/Handles.bindings.cs +++ b/Editor/Mono/Handles.bindings.cs @@ -97,16 +97,16 @@ public static extern Matrix4x4 matrix static extern void Internal_SetupCamera([NotNull("NullExceptionObject")] Camera cam); [FreeFunction] - static extern void Internal_DrawAAPolyLine(Color[] colors, Vector3[] points, Color defaultColor, int actualNumberOfPoints, Texture2D texture, float width, Matrix4x4 toWorld); + static extern void Internal_DrawAAPolyLine([Unmarshalled] Color[] colors, [Unmarshalled] Vector3[] points, Color defaultColor, int actualNumberOfPoints, Texture2D texture, float width, Matrix4x4 toWorld); [FreeFunction] - static extern void Internal_DrawAAConvexPolygon(Vector3[] points, Color defaultColor, int actualNumberOfPoints, Matrix4x4 toWorld); + static extern void Internal_DrawAAConvexPolygon([Unmarshalled] Vector3[] points, Color defaultColor, int actualNumberOfPoints, Matrix4x4 toWorld); [FreeFunction] static extern void Internal_DrawBezier(Vector3 startPosition, Vector3 endPosition, Vector3 startTangent, Vector3 endTangent, Color color, Texture2D texture, float width, Matrix4x4 toWorld); [FreeFunction("Internal_SetDiscSectionPoints")] - internal static extern void SetDiscSectionPoints(Vector3[] dest, Vector3 center, Vector3 normal, Vector3 from, float angle, float radius); + internal static extern void SetDiscSectionPoints([Unmarshalled] Vector3[] dest, Vector3 center, Vector3 normal, Vector3 from, float angle, float radius); [FreeFunction("Internal_EmitGUIGeometryForCamera")] internal static extern void EmitGUIGeometryForCamera([NotNull("NullExceptionObject")] Camera source, Camera dest); diff --git a/Editor/Mono/Handles.cs b/Editor/Mono/Handles.cs index a3072ce008..82e65dcbab 100644 --- a/Editor/Mono/Handles.cs +++ b/Editor/Mono/Handles.cs @@ -50,9 +50,11 @@ public sealed partial class Handles // internal blend ratio for static colors internal static float staticBlend = 0.6f; - static PrefColor s_ElementPreselectionColor => new PrefColor("Scene/Element Preselection", .29f, .93f, .88f, 1f); - static PrefColor s_ElementSelectionColor => new PrefColor("Scene/Element Selection", .93f, 1f, .99f, 1f); + static PrefColor s_ElementColor => new PrefColor("Scene/Element Default", 0f, 224f / 255f, 1f, 1f); + static PrefColor s_ElementPreselectionColor => new PrefColor("Scene/Element Preselection", 1f, 207f / 255f, 112f / 255f, 1f); + static PrefColor s_ElementSelectionColor => new PrefColor("Scene/Element Selection", 1f, 182f / 255f, 40f / 255f, 1f); + public static Color elementColor => s_ElementColor; public static Color elementPreselectionColor => s_ElementPreselectionColor; public static Color elementSelectionColor => s_ElementSelectionColor; @@ -240,6 +242,12 @@ static Vector3 GetAxisVector(int axis) internal static bool IsHovering(int controlID, Event evt) { + // If the mouse position is over an overlay, return false since we don't want to indicate interactivity with any handles. + if (SceneView.lastActiveSceneView == null || SceneView.lastActiveSceneView.sceneViewMotion == null) + return false; + if (!SceneView.lastActiveSceneView.sceneViewMotion.viewportsUnderMouse) + return false; + return controlID == HandleUtility.nearestControl && GUIUtility.hotControl == 0 && !Tools.viewToolActive; } @@ -377,28 +385,40 @@ public static void DrawLines(Vector3[] lineSegments) { if (!BeginLineDrawing(matrix, false, GL.LINES)) return; - for (int i = 0; i < lineSegments.Length; i += 2) + try { - var p1 = lineSegments[i + 0]; - var p2 = lineSegments[i + 1]; - GL.Vertex(p1); - GL.Vertex(p2); + for (int i = 0; i < lineSegments.Length; i += 2) + { + var p1 = lineSegments[i + 0]; + var p2 = lineSegments[i + 1]; + GL.Vertex(p1); + GL.Vertex(p2); + } + } + finally + { + EndLineDrawing(); } - EndLineDrawing(); } public static void DrawLines(Vector3[] points, int[] segmentIndices) { if (!BeginLineDrawing(matrix, false, GL.LINES)) return; - for (int i = 0; i < segmentIndices.Length; i += 2) + try { - var p1 = points[segmentIndices[i + 0]]; - var p2 = points[segmentIndices[i + 1]]; - GL.Vertex(p1); - GL.Vertex(p2); + for (int i = 0; i < segmentIndices.Length; i += 2) + { + var p1 = points[segmentIndices[i + 0]]; + var p2 = points[segmentIndices[i + 1]]; + GL.Vertex(p1); + GL.Vertex(p2); + } + } + finally + { + EndLineDrawing(); } - EndLineDrawing(); } public static void DrawDottedLine(Vector3 p1, Vector3 p2, float screenSpaceSize) @@ -416,14 +436,20 @@ public static void DrawDottedLines(Vector3[] lineSegments, float screenSpaceSize if (!BeginLineDrawing(matrix, true, GL.LINES)) return; var dashSize = screenSpaceSize * EditorGUIUtility.pixelsPerPoint; - for (int i = 0; i < lineSegments.Length; i += 2) + try { - var p1 = lineSegments[i + 0]; - var p2 = lineSegments[i + 1]; - GL.MultiTexCoord(1, p1); GL.MultiTexCoord2(2, dashSize, 0); GL.Vertex(p1); - GL.MultiTexCoord(1, p1); GL.MultiTexCoord2(2, dashSize, 0); GL.Vertex(p2); + for (int i = 0; i < lineSegments.Length; i += 2) + { + var p1 = lineSegments[i + 0]; + var p2 = lineSegments[i + 1]; + GL.MultiTexCoord(1, p1); GL.MultiTexCoord2(2, dashSize, 0); GL.Vertex(p1); + GL.MultiTexCoord(1, p1); GL.MultiTexCoord2(2, dashSize, 0); GL.Vertex(p2); + } + } + finally + { + EndLineDrawing(); } - EndLineDrawing(); } public static void DrawDottedLines(Vector3[] points, int[] segmentIndices, float screenSpaceSize) @@ -431,14 +457,20 @@ public static void DrawDottedLines(Vector3[] points, int[] segmentIndices, float if (!BeginLineDrawing(matrix, true, GL.LINES)) return; var dashSize = screenSpaceSize * EditorGUIUtility.pixelsPerPoint; - for (int i = 0; i < segmentIndices.Length; i += 2) + try { - var p1 = points[segmentIndices[i + 0]]; - var p2 = points[segmentIndices[i + 1]]; - GL.MultiTexCoord(1, p1); GL.MultiTexCoord2(2, dashSize, 0); GL.Vertex(p1); - GL.MultiTexCoord(1, p1); GL.MultiTexCoord2(2, dashSize, 0); GL.Vertex(p2); + for (int i = 0; i < segmentIndices.Length; i += 2) + { + var p1 = points[segmentIndices[i + 0]]; + var p2 = points[segmentIndices[i + 1]]; + GL.MultiTexCoord(1, p1); GL.MultiTexCoord2(2, dashSize, 0); GL.Vertex(p1); + GL.MultiTexCoord(1, p1); GL.MultiTexCoord2(2, dashSize, 0); GL.Vertex(p2); + } + } + finally + { + EndLineDrawing(); } - EndLineDrawing(); } public static void DrawWireCube(Vector3 center, Vector3 size) @@ -447,12 +479,11 @@ public static void DrawWireCube(Vector3 center, Vector3 size) return; HandleUtility.ApplyWireMaterial(zTest); - GL.Color(color * lineTransparency); - GL.PushMatrix(); GL.MultMatrix(matrix); GL.Begin(GL.LINE_STRIP); + GL.Color(color * lineTransparency); Vector3 p1, p2, p3, p6, p7, p8; { Vector3 halfsize = size * 0.5f; @@ -476,22 +507,13 @@ public static void DrawWireCube(Vector3 center, Vector3 size) } GL.End(); GL.Begin(GL.LINES); - { - GL.Vertex(p1); - GL.Vertex(p6); - } - GL.End(); - GL.Begin(GL.LINES); - { - GL.Vertex(p2); - GL.Vertex(p7); - } - GL.End(); - GL.Begin(GL.LINES); - { - GL.Vertex(p3); - GL.Vertex(p8); - } + GL.Color(color * lineTransparency); + GL.Vertex(p1); + GL.Vertex(p6); + GL.Vertex(p2); + GL.Vertex(p7); + GL.Vertex(p3); + GL.Vertex(p8); GL.End(); GL.PopMatrix(); @@ -1130,8 +1152,10 @@ public static void SelectionFrame(int controlID, Vector3 position, Quaternion ro Handles.DrawLine(point4, point1); } - internal static void DrawAAPolyLine(Color[] colors, Vector3[] points) { DoDrawAAPolyLine(colors, points, -1, null, 2, 0.75f); } - internal static void DrawAAPolyLine(float width, Color[] colors, Vector3[] points) { DoDrawAAPolyLine(colors, points, -1, null, width, 0.75f); } + /// *listonly* + public static void DrawAAPolyLine(Color[] colors, Vector3[] points) { DoDrawAAPolyLine(colors, points, -1, null, 2, 0.75f); } + /// *listonly* + public static void DrawAAPolyLine(float width, Color[] colors, Vector3[] points) { DoDrawAAPolyLine(colors, points, -1, null, width, 0.75f); } /// *listonly* public static void DrawAAPolyLine(params Vector3[] points) { DoDrawAAPolyLine(null, points, -1, null, 2, 0.75f); } /// *listonly* @@ -1152,15 +1176,6 @@ static void DoDrawAAPolyLine(Color[] colors, Vector3[] points, int actualNumberO HandleUtility.ApplyWireMaterial(zTest); Color defaultColor = new Color(1, 1, 1, alpha); - - if (colors != null) - { - for (int i = 0; i < colors.Length; i++) - colors[i] *= defaultColor; - } - else - defaultColor *= color; - Internal_DrawAAPolyLine(colors, points, defaultColor, actualNumberOfPoints, lineTex, width, matrix); } diff --git a/Editor/Mono/Help.cs b/Editor/Mono/Help.cs index 2a02a216f5..870a663b74 100644 --- a/Editor/Mono/Help.cs +++ b/Editor/Mono/Help.cs @@ -11,6 +11,7 @@ using UnityEditorInternal; using UnityEngine.Networking; using System.Text.RegularExpressions; +using UnityEditor.Presets; namespace UnityEditor { @@ -169,7 +170,7 @@ internal static string GetNiceHelpNameForObject(Object obj, bool defaultToMonoBe } } - return ""; + return helpTopic; } internal static string GetHelpURLForObject(Object obj, bool defaultToMonoBehaviour) @@ -181,9 +182,6 @@ internal static string GetHelpURLForObject(Object obj, bool defaultToMonoBehavio if (attrs.Length > 0) { var attr = (HelpURLAttribute)attrs[0]; - if (attrs.Length > 1) - Debug.LogWarningFormat("Multiple HelpURL attributes detected on {0}; only one is supported per class. {1} will be used.", obj.GetType().Name, attr.GetType().Name); - var url = attr.URL; if (!string.IsNullOrEmpty(attr.m_DispatchingFieldName)) { @@ -206,7 +204,11 @@ internal static string GetHelpURLForObject(Object obj, bool defaultToMonoBehavio } } - return url; + if (IsURL(url)) + return url; + + // Assume url is a topic that needs to be properly formatted: + return FindHelpNamed(url); } var topicForObject = HelpFileNameForObject(obj); @@ -325,30 +327,21 @@ internal static string HelpFileNameForObject(Object obj) return $"script-{obj.GetType().Name}"; } - if (obj is Terrain) - { - return "script-Terrain"; - } - - if (obj is AudioMixerController || obj is AudioMixerGroupController) + return obj switch { - return "class-AudioMixer"; - } - - if (obj is EditorSettings) - { - return "class-EditorManager"; - } - - if (obj is SceneAsset) - { - return "CreatingScenes"; - } - - if (obj is DefaultAsset) - return ""; - - return $"class-{obj.GetType().Name}"; + Terrain => "script-Terrain", + AudioMixerController or AudioMixerGroupController => "class-AudioMixer", + AudioImporter => "class-AudioClip", // UUM-96832: We don't have an entry in the manual for the audio importer. + VideoClipImporter => "class-VideoClip", // We don't have an entry in the manual for the video clip importer. + EditorSettings => "class-EditorManager", + SceneAsset => "CreatingScenes", + PrefabImporter => "Prefabs", + Preset => "Presets", + MonoImporter => "ScriptedImporters", + MonoScript => "class-TextAsset", + DefaultAsset => "", + _ => $"class-{obj.GetType().Name}", + }; } [UnityEngine.Scripting.RequiredByNativeCode] @@ -419,6 +412,11 @@ private static void InitDocumentation() } } + private static bool IsURL(string path) + { + return path.StartsWith("http://") || path.StartsWith("https://") || path.StartsWith(k_AbsoluteFileRef); + } + private static bool IsLocalPath(string path) { return !path.StartsWith("http://") && !path.StartsWith("https://"); diff --git a/Editor/Mono/HierarchyProperty.bindings.cs b/Editor/Mono/HierarchyProperty.bindings.cs index 5cf7a9f9ea..22097beb89 100644 --- a/Editor/Mono/HierarchyProperty.bindings.cs +++ b/Editor/Mono/HierarchyProperty.bindings.cs @@ -153,7 +153,7 @@ void Dispose() public extern int colorCode { get; } [FreeFunction("HierarchyPropertyBindings::IsExpanded", HasExplicitThis = true)] - public extern bool IsExpanded(int[] expanded); + public extern bool IsExpanded([Unmarshalled] int[] expanded); public extern string guid { [FreeFunction("HierarchyPropertyBindings::GetGuid", HasExplicitThis = true)] get; } public extern bool alphaSorted { [NativeName("IsAlphaSorted")] get; set; } @@ -167,18 +167,18 @@ void Dispose() public extern GUID[] dynamicDependencies { get; } [FreeFunction("HierarchyPropertyBindings::Next", HasExplicitThis = true)] - public extern bool Next(int[] expanded); + public extern bool Next([Unmarshalled] int[] expanded); [FreeFunction("HierarchyPropertyBindings::NextWithDepthCheck", HasExplicitThis = true)] - public extern bool NextWithDepthCheck(int[] expanded, int minDepth); + public extern bool NextWithDepthCheck([Unmarshalled] int[] expanded, int minDepth); [FreeFunction("HierarchyPropertyBindings::Previous", HasExplicitThis = true)] - public extern bool Previous(int[] expanded); + public extern bool Previous([Unmarshalled] int[] expanded); public extern bool Parent(); [FreeFunction("HierarchyPropertyBindings::Find", HasExplicitThis = true)] - public extern bool Find(int instanceID, int[] expanded); + public extern bool Find(int instanceID, [Unmarshalled] int[] expanded); [FreeFunction("HierarchyPropertyBindings::Skip", HasExplicitThis = true)] - public extern bool Skip(int count, int[] expanded); + public extern bool Skip(int count, [Unmarshalled] int[] expanded); [FreeFunction("HierarchyPropertyBindings::CountRemaining", HasExplicitThis = true)] - public extern int CountRemaining(int[] expanded); + public extern int CountRemaining([Unmarshalled] int[] expanded); public extern int GetInstanceIDIfImported(); @@ -202,11 +202,22 @@ public void SetSearchFilter(string searchString, int mode) // 4.0 interface (made internal for now) internal void SetSearchFilter(SearchFilter filter) { - SetSearchFilterImpl(SearchFilter.Split(filter.nameFilter), filter.classNames, filter.assetLabels, filter.assetBundleNames, filter.versionControlStates, filter.softLockControlStates, filter.referencingInstanceIDs, filter.sceneHandles, filter.GlobToRegex().ToArray(), filter.showAllHits, filter.importLogFlags); + SetSearchFilterImpl(SearchFilter.Split(filter.nameFilter), filter.classNames, filter.assetLabels, filter.assetBundleNames, new string[0], new string[0], filter.referencingInstanceIDs, filter.sceneHandles, filter.GlobToRegex().ToArray(), filter.showAllHits, filter.importLogFlags, filter.filterByTypeIntersection); + } + + internal void CopySearchFilterFrom(HierarchyProperty other) + { + if (other == null) + throw new ArgumentNullException(nameof(other)); + CopySearchFilterImpl(other); } [FreeFunction("HierarchyPropertyBindings::SetSearchFilterImpl", HasExplicitThis = true)] - extern void SetSearchFilterImpl(string[] nameFilters, string[] classNames, string[] assetLabels, string[] assetBundleNames, string[] versionControlStates, string[] softLockControlStates, int[] referencingInstanceIDs, int[] sceneHandles, string[] regex, bool showAllHits, ImportLogFlags importLogFlags); + extern void SetSearchFilterImpl(string[] nameFilters, string[] classNames, string[] assetLabels, string[] assetBundleNames, string[] versionControlStates, string[] softLockControlStates, int[] referencingInstanceIDs, int[] sceneHandles, string[] regex, bool showAllHits, ImportLogFlags importLogFlags, bool filterByTypeIntersection); + + [FreeFunction("HierarchyPropertyBindings::CopySearchFilterImpl", HasExplicitThis = true)] + extern void CopySearchFilterImpl(HierarchyProperty other); + [FreeFunction("HierarchyPropertyBindings::FindAllAncestors", HasExplicitThis = true)] public extern int[] FindAllAncestors(int[] instanceIDs); [FreeFunction("HierarchyPropertyBindings::ClearSceneObjectsFilter")] diff --git a/Editor/Mono/HomeWindow.bindings.cs b/Editor/Mono/HomeWindow.bindings.cs index 66cc9c124f..78fb00a984 100644 --- a/Editor/Mono/HomeWindow.bindings.cs +++ b/Editor/Mono/HomeWindow.bindings.cs @@ -20,6 +20,7 @@ public enum HomeMode ManageLicense, Welcome, Tutorial, + Logout } [NativeMethod("StaticShow")] diff --git a/Editor/Mono/HostView.cs b/Editor/Mono/HostView.cs index 19c7a3d439..c1933d48e1 100644 --- a/Editor/Mono/HostView.cs +++ b/Editor/Mono/HostView.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Data.Common; using System.IO; using System.Linq; using System.Reflection; @@ -31,13 +32,31 @@ public static class DataModes { public const float switchButtonWidth = 16.0f; + static readonly string k_AutomaticAuthoringString = L10n.Tr("Automatic (Authoring)"); + static readonly string k_AutomaticMixedString = L10n.Tr("Automatic (Mixed)"); + static readonly string k_AutomaticRuntimeString = L10n.Tr("Automatic (Runtime)"); + static readonly string k_AuthoringString = L10n.Tr("Authoring"); + static readonly string k_MixedString = L10n.Tr("Mixed"); + static readonly string k_RuntimeString = L10n.Tr("Runtime"); + static readonly string k_DisabledString = L10n.Tr("Disabled"); + + // Auto data mode icons static readonly Texture2D k_AuthoringModeIcon = EditorGUIUtility.LoadIcon("DataMode.Authoring"); static readonly Texture2D k_MixedModeIcon = EditorGUIUtility.LoadIcon("DataMode.Mixed"); static readonly Texture2D k_RuntimeModeIcon = EditorGUIUtility.LoadIcon("DataMode.Runtime"); - public static readonly GUIContent authoringModeContent = EditorGUIUtility.TrIconContent(k_AuthoringModeIcon, "Data Mode: Authoring"); - public static readonly GUIContent mixedModeContent = EditorGUIUtility.TrIconContent(k_MixedModeIcon, "Data Mode: Mixed"); - public static readonly GUIContent runtimeModeContent = EditorGUIUtility.TrIconContent(k_RuntimeModeIcon, "Data Mode: Runtime"); + public static readonly GUIContent authoringModeContent = EditorGUIUtility.TrIconContent(k_AuthoringModeIcon, k_AutomaticAuthoringString); + public static readonly GUIContent mixedModeContent = EditorGUIUtility.TrIconContent(k_MixedModeIcon, k_AutomaticMixedString); + public static readonly GUIContent runtimeModeContent = EditorGUIUtility.TrIconContent(k_RuntimeModeIcon, k_AutomaticRuntimeString); + + // Sticky data mode icons + static readonly Texture2D k_StickyAuthoringModeIcon = EditorGUIUtility.LoadIcon("DataMode.Authoring.Sticky"); + static readonly Texture2D k_StickyMixedModeIcon = EditorGUIUtility.LoadIcon("DataMode.Mixed.Sticky"); + static readonly Texture2D k_StickyRuntimeModeIcon = EditorGUIUtility.LoadIcon("DataMode.Runtime.Sticky"); + + public static readonly GUIContent stickyAuthoringModeContent = EditorGUIUtility.TrIconContent(k_StickyAuthoringModeIcon, k_AuthoringString); + public static readonly GUIContent stickyMixedModeContent = EditorGUIUtility.TrIconContent(k_StickyMixedModeIcon, k_MixedString); + public static readonly GUIContent stickyRuntimeModeContent = EditorGUIUtility.TrIconContent(k_StickyRuntimeModeIcon, k_RuntimeString); // Use an empty style to avoid the hover effect of normal buttons public static readonly GUIStyle switchStyle = new GUIStyle(); @@ -45,10 +64,10 @@ public static class DataModes public static readonly Dictionary dataModeNameLabels = new Dictionary { - { DataMode.Disabled, EditorGUIUtility.TrTextContent("Disabled") }, - { DataMode.Authoring, EditorGUIUtility.TrTextContent("Authoring Mode") }, - { DataMode.Mixed, EditorGUIUtility.TrTextContent("Mixed Mode") }, - { DataMode.Runtime, EditorGUIUtility.TrTextContent("Runtime Mode") } + { DataMode.Disabled, EditorGUIUtility.TextContent(k_DisabledString) }, + { DataMode.Authoring, EditorGUIUtility.TextContent(k_AuthoringString) }, + { DataMode.Mixed, EditorGUIUtility.TextContent(k_MixedString) }, + { DataMode.Runtime, EditorGUIUtility.TextContent(k_RuntimeString) } }; } @@ -85,6 +104,8 @@ static Styles() protected EditorWindowDelegate m_ModifierKeysChanged; protected EditorWindowShowButtonDelegate m_ShowButton; + private bool m_IsLosingFocus = false; + internal EditorWindow actualView { get { return m_ActualView; } @@ -101,7 +122,7 @@ internal void SetActualViewInternal(EditorWindow value, bool sendEvents) if (m_ActualView == value) return; - DeregisterSelectedPane(clearActualView: true, sendEvents: true); + DeregisterSelectedPane(clearActualView: true, sendEvents: true, isSwitchingTab: value != null); m_ActualView = value; m_ActualViewName = null; @@ -320,8 +341,22 @@ protected override bool OnFocus() internal void OnLostFocus() { + // Avoid calling OnLostFocus script callback if we are already processing the focus lost. + // This can happen when user scripts call Close() in OnLostFocus() callback. + if (m_IsLosingFocus) + return; + EditorGUI.EndEditingActiveTextField(); - m_OnLostFocus?.Invoke(); + + try + { + m_IsLosingFocus = true; + m_OnLostFocus?.Invoke(); + } + finally + { + m_IsLosingFocus = false; + } // Callback could have killed us if (!this) @@ -466,6 +501,11 @@ public void InvokeOnGUI(Rect onGUIPosition) return; DoWindowDecorationStart(); + + //We backup and restore the group count to properly identify the ongui method where the unbalance occurs. + int backup = GUILayoutUtility.unbalancedgroupscount; + + GUILayoutUtility.unbalancedgroupscount = 0; BeginOffsetArea(m_ActualView.rootVisualElement.worldBound, GUIContent.none, Styles.tabWindowBackground); EditorGUIUtility.ResetGUIState(); @@ -473,28 +513,39 @@ public void InvokeOnGUI(Rect onGUIPosition) bool isExitGUIException = false; try { - GUILayoutUtility.unbalancedgroupscount = 0; m_OnGUI?.Invoke(); - if (GUILayoutUtility.unbalancedgroupscount > 0) - { - Debug.LogError("GUI Error: Invalid GUILayout state in " + GetActualViewName() + " view. Verify that all layout Begin/End calls match"); - GUILayoutUtility.unbalancedgroupscount = 0; } - } catch (TargetInvocationException e) { - if (e.InnerException is ExitGUIException) + Exception exception = e; + while (exception is TargetInvocationException && exception.InnerException != null) + exception = exception.InnerException; + + isExitGUIException = exception is ExitGUIException; + + throw; + } + catch(ExitGUIException) + { isExitGUIException = true; throw; } finally { + // We explicitly called BeginOffsetArea outside this try-catch scope, + // so we need to always explicitly end it as well just like all other + // GUI scopes are responsible for dealing with ExitGUI properly. + EndOffsetArea(); + // We can't reset gui state after ExitGUI we just want to bail completely - if (!isExitGUIException) + if (!(isExitGUIException || GUIUtility.guiIsExiting)) { CheckNotificationStatus(); - EndOffsetArea(); + if (GUILayoutUtility.unbalancedgroupscount != 0) + { + Debug.LogError("GUI Error: Invalid GUILayout state in " + GetActualViewName() + " view. Verify that all layout Begin/End calls match"); + } EditorGUIUtility.ResetGUIState(); @@ -503,6 +554,8 @@ public void InvokeOnGUI(Rect onGUIPosition) if (Event.current != null && Event.current.type == EventType.Repaint) Styles.overlay.Draw(onGUIPosition, GUIContent.none, 0); } + + GUILayoutUtility.unbalancedgroupscount = backup; } } @@ -526,9 +579,6 @@ public IEditorWindowBackend editorWindowBackend set { windowBackend = value; } } - DataMode m_CachedDataMode; - bool m_ShouldDrawDataModeSwitch; - protected void RegisterSelectedPane(bool sendEvents) { if (!m_ActualView) @@ -560,19 +610,6 @@ protected void RegisterSelectedPane(bool sendEvents) EditorApplication.update += m_ActualView.CheckForWindowRepaint; } - if (m_ActualView is IDataModeHandler dataModeHandler) - { - UpdateDataMode(dataModeHandler.dataMode, false); - - if (m_ActualView is IDataModeHandlerAndDispatcher dataModesDispatcher) - dataModesDispatcher.dataModeChanged += OnViewDataModeChanged; - } - else - { - m_CachedDataMode = DataMode.Disabled; - m_ShouldDrawDataModeSwitch = false; - } - if (sendEvents) { try @@ -591,13 +628,11 @@ protected void RegisterSelectedPane(bool sendEvents) UpdateViewMargins(m_ActualView); } - protected void DeregisterSelectedPane(bool clearActualView, bool sendEvents) + protected void DeregisterSelectedPane(bool clearActualView, bool sendEvents, bool isSwitchingTab = false) { if (!m_ActualView) return; - editorWindowBackend?.OnUnregisterWindow(); - if (m_Update != null) EditorApplication.update -= SendUpdate; @@ -609,23 +644,36 @@ protected void DeregisterSelectedPane(bool clearActualView, bool sendEvents) EditorApplication.update -= m_ActualView.CheckForWindowRepaint; } - if (m_ActualView is IDataModeHandlerAndDispatcher dataModesDispatcher) - dataModesDispatcher.dataModeChanged -= OnViewDataModeChanged; + // UUM-57083. Allow Blur events to take effect when switching tab. + if (isSwitchingTab) + { + UnityEngine.UIElements.EventDispatcher.editorDispatcher.PushDispatcherContext(); + } if (clearActualView) { - var onLostFocus = m_OnLostFocus; var onBecameInvisible = m_OnBecameInvisible; - m_ActualView = null; - if (sendEvents) { - onLostFocus?.Invoke(); + OnLostFocus(); onBecameInvisible?.Invoke(); } + ClearDelegates(); } + + editorWindowBackend?.OnUnregisterWindow(); + + if (isSwitchingTab) + { + UnityEngine.UIElements.EventDispatcher.editorDispatcher.PopDispatcherContext(); + } + + if (clearActualView) + { + m_ActualView = null; + } } private bool m_NotificationIsVisible; @@ -694,7 +742,7 @@ internal float GetExtraButtonsWidth() if (m_ShowButton != null) extraWidth += ContainerWindow.kButtonWidth; - if (m_ShouldDrawDataModeSwitch) + if (m_ActualView.GetDataModeController_Internal().ShouldDrawDataModesSwitch()) extraWidth += Styles.DataModes.switchButtonWidth + Styles.iconMargin; foreach (var item in windowActions) @@ -721,13 +769,15 @@ protected void ShowGenericMenu(float leftOffset, float topOffset) if (m_ShowButton != null) m_ShowButton.Invoke(new Rect(leftOffset, topOffset, ContainerWindow.kButtonWidth, ContainerWindow.kButtonHeight)); - if (m_ShouldDrawDataModeSwitch) + var dataModeControllerInternal = m_ActualView.GetDataModeController_Internal(); + if (dataModeControllerInternal.ShouldDrawDataModesSwitch()) { - var switchContent = m_CachedDataMode switch + var isClientInAutomaticDataMode = dataModeControllerInternal.isAutomatic; + var switchContent = dataModeControllerInternal.dataMode switch { - DataMode.Authoring => Styles.DataModes.authoringModeContent, - DataMode.Mixed => Styles.DataModes.mixedModeContent, - DataMode.Runtime => Styles.DataModes.runtimeModeContent, + DataMode.Authoring => isClientInAutomaticDataMode? Styles.DataModes.authoringModeContent : Styles.DataModes.stickyAuthoringModeContent, + DataMode.Mixed => isClientInAutomaticDataMode? Styles.DataModes.mixedModeContent : Styles.DataModes.stickyMixedModeContent, + DataMode.Runtime => isClientInAutomaticDataMode? Styles.DataModes.runtimeModeContent : Styles.DataModes.stickyRuntimeModeContent, _ => default }; @@ -739,11 +789,14 @@ protected void ShowGenericMenu(float leftOffset, float topOffset) if (EditorGUI.Button(switchRect, switchContent, Styles.DataModes.switchStyle)) { - // This cast is guaranteed to work by m_ShouldDrawDataModeSwitch - var dataModesClient = (IDataModeHandler) m_ActualView; + var evt = Event.current; - dataModesClient.SwitchToNextDataMode(); - UpdateDataMode(dataModesClient.dataMode, true); + if (evt.button == 0 || evt.button == 1) // left click or right click + { + var menu = new GenericMenu(); + PopulateDataModeDropdown(dataModeControllerInternal, menu); + menu.DropDown(switchRect); + } } } } @@ -768,39 +821,23 @@ protected void ShowGenericMenu(float leftOffset, float topOffset) } } - bool ShouldDrawDataModesSwitch() - { - return m_ActualView is IDataModeHandler dataModesHandler - && dataModesHandler.dataMode != DataMode.Disabled - // We don't want to show this switch if there are not - // at least 2 modes supported at the current moment. - && dataModesHandler.supportedDataModes.Count > 1; - } - - void SelectDataMode(object dataMode) + void PopulateDataModeDropdown(DataModeController clientDataModeController, GenericMenu menu) { - if (m_ActualView is not IDataModeHandler dataModeHandler) - return; // Something very weird has happened... + var autoLabel = !clientDataModeController.isAutomatic + ? L10n.Tr($"Automatic ({clientDataModeController.preferredDataMode})") + : L10n.Tr($"Automatic ({clientDataModeController.dataMode})"); - if (dataMode is DataMode mode && dataModeHandler.IsDataModeSupported(mode)) - dataModeHandler.SwitchToDataMode(mode); - else - dataModeHandler.SwitchToDefaultDataMode(); + menu.AddItem(new GUIContent(autoLabel), + clientDataModeController.isAutomatic, + () => clientDataModeController.SwitchToAutomatic()); - UpdateDataMode(dataModeHandler.dataMode, true); - } + menu.AddSeparator(""); - void OnViewDataModeChanged(DataMode newDataMode) => UpdateDataMode(newDataMode, true); - - void UpdateDataMode(DataMode newDataMode, bool needsRepaint) - { - m_CachedDataMode = newDataMode; - m_ShouldDrawDataModeSwitch = ShouldDrawDataModesSwitch(); - - if (needsRepaint) + foreach (var mode in clientDataModeController.supportedDataModes) { - m_ActualView.Repaint(); - RepaintImmediately(); + menu.AddItem(Styles.DataModes.dataModeNameLabels[mode], + !clientDataModeController.isAutomatic && clientDataModeController.dataMode == mode, + () => clientDataModeController.SwitchToStickyDataMode(mode)); } } @@ -918,54 +955,12 @@ internal void Reload(object userData) File.Delete(saveWindowPath); } - readonly List m_DataModeSanitizationCache = new List(3); // Number of modes, minus `Disabled` - - static void SanitizeSupportedDataModesList(IReadOnlyList originalList, List sanitizedList) - { - sanitizedList.Clear(); - - foreach (var mode in originalList) - { - if (mode == DataMode.Disabled) - continue; // Never list `DataMode.Disabled` - - if (sanitizedList.Contains(mode)) - continue; // Prevent duplicate entries - - sanitizedList.Add(mode); - } - - // Ensure we are displaying the data modes in a predefined order, regardless of - // the order in which the user defined their list. - sanitizedList.Sort(); - } protected virtual void AddDefaultItemsToMenu(GenericMenu menu, EditorWindow window) { if (menu.GetItemCount() != 0) menu.AddSeparator(""); - if (m_ShouldDrawDataModeSwitch) - { - // This cast is guaranteed to work by m_ShouldDrawDataModeSwitch - var dataModesHandler = (IDataModeHandler) window; - SanitizeSupportedDataModesList(dataModesHandler.supportedDataModes, m_DataModeSanitizationCache); - - // Don't show anything if only one mode is supported - if (m_DataModeSanitizationCache.Count > 1) - { - foreach (var mode in m_DataModeSanitizationCache) - { - menu.AddItem(Styles.DataModes.dataModeNameLabels[mode], - m_CachedDataMode == mode, - SelectDataMode, - mode); - } - - menu.AddSeparator(""); - } - } - if(window is ISupportsOverlays) { var binding = ShortcutManager.instance.GetShortcutBinding("Overlays/Show Overlay Menu"); diff --git a/Editor/Mono/ImportSettings/DesktopPluginImporterExtension.cs b/Editor/Mono/ImportSettings/DesktopPluginImporterExtension.cs index 4f012f99ce..22bae6dbbb 100644 --- a/Editor/Mono/ImportSettings/DesktopPluginImporterExtension.cs +++ b/Editor/Mono/ImportSettings/DesktopPluginImporterExtension.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Text.RegularExpressions; using System.IO; using System.Linq; using UnityEditor.Modules; @@ -25,7 +26,12 @@ internal enum DesktopPluginCPUArchitecture internal class DesktopSingleCPUProperty : Property { public DesktopSingleCPUProperty(BuildTarget buildTarget, DesktopPluginCPUArchitecture architecture) - : base(EditorGUIUtility.TrTextContent(GetArchitectureNameInGUI(buildTarget, architecture)), "CPU", architecture, BuildPipeline.GetBuildTargetName(buildTarget)) + : base(EditorGUIUtility.TrTextContent(GetArchitectureNameInGUI(buildTarget, architecture)), cpuKey, architecture, BuildPipeline.GetBuildTargetName(buildTarget)) + { + } + + public DesktopSingleCPUProperty(GUIContent name, BuildTarget buildTarget) + : base(name, cpuKey, DesktopPluginCPUArchitecture.AnyCPU, BuildPipeline.GetBuildTargetName(buildTarget)) { } @@ -66,7 +72,7 @@ class DesktopMultiCPUProperty : Property private readonly GUIContent[] m_SupportedArchitectureNames; public DesktopMultiCPUProperty(BuildTarget buildTarget, params DesktopPluginCPUArchitecture[] supportedArchitectures) : - base("CPU", "CPU", DesktopPluginCPUArchitecture.None, BuildPipeline.GetBuildTargetName(buildTarget)) + base(cpuKey, cpuKey, DesktopPluginCPUArchitecture.None, BuildPipeline.GetBuildTargetName(buildTarget)) { // Add "None" and "AnyCPU" architectures to the supported architecture list m_SupportedArchitectures = new DesktopPluginCPUArchitecture[supportedArchitectures.Length + 2]; @@ -141,6 +147,14 @@ internal override void OnGUI(PluginImporterInspector inspector) // macOS has multiple architectures, but one target. private readonly DesktopMultiCPUProperty m_MacOS; + // For Managed plugins the only options for CPU architecture should be "None" or "AnyCPU", so it can be DesktopSingleCPUProperty + private readonly DesktopSingleCPUProperty m_Windows32Managed; + private readonly DesktopSingleCPUProperty m_Windows64Managed; + private readonly DesktopSingleCPUProperty m_LinuxManaged; + private readonly DesktopSingleCPUProperty m_MacOSManaged; + + private Property[] nativeProperties = null; + private Property[] managedProperties = null; public DesktopPluginImporterExtension() : base(null) @@ -150,12 +164,27 @@ public DesktopPluginImporterExtension() m_Linux = new DesktopSingleCPUProperty(BuildTarget.StandaloneLinux64, DesktopPluginCPUArchitecture.x86_64); m_MacOS = new DesktopMultiCPUProperty(BuildTarget.StandaloneOSX, DesktopPluginCPUArchitecture.x86_64, DesktopPluginCPUArchitecture.ARM64); - properties = new Property[] + // Windows 32-bit (x86) and Windows 64-bit (ARM64/x86_64) are separate targets, so they have separate checkboxes + // Linux only has x64 architecture and Mac has a single target for both 64-bit architectures (ARM64/Intel-64bit) + m_Windows32Managed = new DesktopSingleCPUProperty(EditorGUIUtility.TrTextContent("Windows x86"), BuildTarget.StandaloneWindows); + m_Windows64Managed = new DesktopSingleCPUProperty(EditorGUIUtility.TrTextContent("Windows 64-bit"), BuildTarget.StandaloneWindows64); + m_LinuxManaged = new DesktopSingleCPUProperty(EditorGUIUtility.TrTextContent("Linux x64"),BuildTarget.StandaloneLinux64); + m_MacOSManaged = new DesktopSingleCPUProperty(EditorGUIUtility.TrTextContent("macOS 64-bit"),BuildTarget.StandaloneOSX); + + nativeProperties = new Property[] { m_WindowsX86, m_WindowsX86_X64, m_Linux, - m_MacOS + m_MacOS, + }; + + managedProperties = new Property[] + { + m_Windows32Managed, + m_Windows64Managed, + m_LinuxManaged, + m_MacOSManaged }; } @@ -174,7 +203,7 @@ private bool IsUsableOnOSX(PluginImporter imp) return true; string ext = FileUtil.GetPathExtension(imp.assetPath).ToLower(); - return ext == "so" || ext == "bundle" || ext == "dylib" || IsCppPluginFile(imp.assetPath); + return ext == "bundle" || ext == "dylib" || ext == "xcprivacy" || IsLinuxLibrary(imp.assetPath) || IsCppPluginFile(imp.assetPath); } private bool IsUsableOnLinux(PluginImporter imp) @@ -182,33 +211,54 @@ private bool IsUsableOnLinux(PluginImporter imp) if (!imp.isNativePlugin) return true; - string ext = FileUtil.GetPathExtension(imp.assetPath).ToLower(); - return ext == "so" || IsCppPluginFile(imp.assetPath); + return IsLinuxLibrary(imp.assetPath) || IsCppPluginFile(imp.assetPath); + } + + protected override Property[] GetPropertiesForInspector(PluginImporterInspector inspector) + { + return inspector.importer.isNativePlugin ? nativeProperties : managedProperties; } public override void OnPlatformSettingsGUI(PluginImporterInspector inspector) { PluginImporter imp = inspector.importer; EditorGUI.BeginChangeCheck(); - if (IsUsableOnWindows(imp)) + // skip CPU property for things that aren't native libs + if (imp.isNativePlugin) { - EditorGUILayout.LabelField(EditorGUIUtility.TrTextContent("Windows"), EditorStyles.boldLabel); - m_WindowsX86.OnGUI(inspector); - m_WindowsX86_X64.OnGUI(inspector); - EditorGUILayout.Space(); - } + if (IsUsableOnWindows(imp)) + { + EditorGUILayout.LabelField(EditorGUIUtility.TrTextContent("Windows"), EditorStyles.boldLabel); + m_WindowsX86.OnGUI(inspector); + m_WindowsX86_X64.OnGUI(inspector); + EditorGUILayout.Space(); + } - if (IsUsableOnLinux(imp)) + if (IsUsableOnLinux(imp)) + { + EditorGUILayout.LabelField(EditorGUIUtility.TrTextContent("Linux"), EditorStyles.boldLabel); + m_Linux.OnGUI(inspector); + EditorGUILayout.Space(); + } + + if (IsUsableOnOSX(imp)) + { + EditorGUILayout.LabelField(EditorGUIUtility.TrTextContent("macOS"), EditorStyles.boldLabel); + m_MacOS.OnGUI(inspector); + EditorGUILayout.Space(); + } + } + else { - EditorGUILayout.LabelField(EditorGUIUtility.TrTextContent("Linux"), EditorStyles.boldLabel); - m_Linux.OnGUI(inspector); + // Managed plugins are usable on all platforms + m_Windows32Managed.OnGUI(inspector); + m_Windows64Managed.OnGUI(inspector); EditorGUILayout.Space(); - } - if (IsUsableOnOSX(imp)) - { - EditorGUILayout.LabelField(EditorGUIUtility.TrTextContent("macOS"), EditorStyles.boldLabel); - m_MacOS.OnGUI(inspector); + m_LinuxManaged.OnGUI(inspector); + EditorGUILayout.Space(); + + m_MacOSManaged.OnGUI(inspector); EditorGUILayout.Space(); } @@ -218,7 +268,7 @@ public override void OnPlatformSettingsGUI(PluginImporterInspector inspector) public void ValidateSingleCPUTargets(PluginImporterInspector inspector) { - var singleCPUTargets = properties.OfType(); + var singleCPUTargets = GetPropertiesForInspector(inspector).OfType(); foreach (var target in singleCPUTargets) { @@ -226,7 +276,7 @@ public void ValidateSingleCPUTargets(PluginImporterInspector inspector) foreach (var importer in inspector.importers) { - importer.SetPlatformData(target.platformName, "CPU", target.value.ToString()); + importer.SetPlatformData(target.platformName, cpuKey, target.value.ToString()); } } } @@ -237,7 +287,7 @@ public override string CalculateFinalPluginPath(string platformName, PluginImpor bool pluginForWindows = target == BuildTarget.StandaloneWindows || target == BuildTarget.StandaloneWindows64; #pragma warning disable 612, 618 bool pluginForOSX = target == BuildTarget.StandaloneOSXIntel || target == BuildTarget.StandaloneOSXIntel64 || target == BuildTarget.StandaloneOSX; - bool pluginForLinux = target == BuildTarget.StandaloneLinux || target == BuildTarget.StandaloneLinux64 || target == BuildTarget.StandaloneLinuxUniversal || target == BuildTarget.CloudRendering; + bool pluginForLinux = target == BuildTarget.StandaloneLinux || target == BuildTarget.StandaloneLinux64 || target == BuildTarget.StandaloneLinuxUniversal || target == BuildTarget.LinuxHeadlessSimulation; #pragma warning restore 612, 618 if (!pluginForLinux && !pluginForOSX && !pluginForWindows) @@ -251,13 +301,19 @@ public override string CalculateFinalPluginPath(string platformName, PluginImpor if (pluginForLinux && !IsUsableOnLinux(imp)) return string.Empty; - string cpu = imp.GetPlatformData(platformName, "CPU"); + string cpu = imp.GetPlatformData(platformName, cpuKey); if (string.Compare(cpu, "None", true) == 0) return string.Empty; if (pluginForWindows) { + if (string.Compare(cpu, nameof(DesktopPluginCPUArchitecture.ARM64), true) == 0) + { + // Windows on Arm64 is not supported for Standalone Windows in this version of Unity + return string.Empty; + } + // Fix case 1185926: plugins for x86_64 are supposed to be copied to Plugins/x86_64 // Plugins for x86 are supposed to be copied to Plugins/x86 var cpuName = target == BuildTarget.StandaloneWindows ? nameof(DesktopPluginCPUArchitecture.x86) : nameof(DesktopPluginCPUArchitecture.x86_64); @@ -274,6 +330,13 @@ public override string CalculateFinalPluginPath(string platformName, PluginImpor return Path.GetFileName(imp.assetPath); } + // Regex that matchers strings ending in ".so" or ".so.12" or ".so.4.7" and so on. + private static Regex LinuxLibraryRegex = new Regex(@"\.so(\.[0-9]+)*$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + internal static bool IsLinuxLibrary(string assetPath) + { + return LinuxLibraryRegex.IsMatch(assetPath); + } + internal static bool IsCppPluginFile(string assetPath) { var extension = Path.GetExtension(assetPath).ToLower(); diff --git a/Editor/Mono/ImportSettings/IHVImageFormatImporterInspector.cs b/Editor/Mono/ImportSettings/IHVImageFormatImporterInspector.cs index 384c5b7040..6735c17c46 100644 --- a/Editor/Mono/ImportSettings/IHVImageFormatImporterInspector.cs +++ b/Editor/Mono/ImportSettings/IHVImageFormatImporterInspector.cs @@ -21,6 +21,8 @@ internal class IHVImageFormatImporterInspector : AssetImporterEditor SerializedProperty m_WrapW; SerializedProperty m_StreamingMipmaps; SerializedProperty m_StreamingMipmapsPriority; + SerializedProperty m_IgnoreMipmapLimit; + SerializedProperty m_MipmapLimitGroupName; internal class Styles { @@ -51,6 +53,8 @@ public override void OnEnable() m_WrapW = serializedObject.FindProperty("m_TextureSettings.m_WrapW"); m_StreamingMipmaps = serializedObject.FindProperty("m_StreamingMipmaps"); m_StreamingMipmapsPriority = serializedObject.FindProperty("m_StreamingMipmapsPriority"); + m_IgnoreMipmapLimit = serializedObject.FindProperty("m_IgnoreMipmapLimit"); + m_MipmapLimitGroupName = serializedObject.FindProperty("m_MipmapLimitGroupName"); } // alas it looks impossible to share code so we copy paste from TextureImporterInspector.TextureSettingsGUI() @@ -107,6 +111,9 @@ public override void OnInspectorGUI() EditorGUILayout.PropertyField(m_StreamingMipmapsPriority, Styles.streamingMipmapsPriority); EditorGUI.indentLevel--; } + + TextureImporterInspector.DoMipmapLimitsGUI(m_IgnoreMipmapLimit, m_MipmapLimitGroupName); + serializedObject.ApplyModifiedProperties(); ApplyRevertGUI(); } diff --git a/Editor/Mono/ImportSettings/SpeedTreeImporterInspector.cs b/Editor/Mono/ImportSettings/SpeedTreeImporterInspector.cs index ad1aa221cb..624383e3f0 100644 --- a/Editor/Mono/ImportSettings/SpeedTreeImporterInspector.cs +++ b/Editor/Mono/ImportSettings/SpeedTreeImporterInspector.cs @@ -97,6 +97,14 @@ protected override bool OnApplyRevertGUI() { bool applied = base.OnApplyRevertGUI(); + bool hasModified = HasModified(); + if (tabs == null) // Hitting apply, we lose the tabs object within base.OnApplyRevertGUI() + { + if(hasModified) + Apply(); + return applied; + } + bool doMatsHaveDifferentShaders = (tabs[0] as SpeedTreeImporterModelEditor).doMaterialsHaveDifferentShader; if (HasRemappedMaterials() || doMatsHaveDifferentShaders) @@ -104,7 +112,6 @@ protected override bool OnApplyRevertGUI() // Force material upgrade when a custom render pipeline is active so that render pipeline-specific material // modifications may be applied. bool upgrade = upgradeMaterials || (UnityEngine.Rendering.GraphicsSettings.currentRenderPipeline != null); - bool hasModified = HasModified(); if (GUILayout.Button(GetGenButtonText(hasModified, upgrade))) { // Apply the changes and generate the materials before importing so that asset previews are up-to-date. diff --git a/Editor/Mono/ImportSettings/SpeedTreeImporterMaterialEditor.cs b/Editor/Mono/ImportSettings/SpeedTreeImporterMaterialEditor.cs index caeccc41ea..1e6270dae5 100644 --- a/Editor/Mono/ImportSettings/SpeedTreeImporterMaterialEditor.cs +++ b/Editor/Mono/ImportSettings/SpeedTreeImporterMaterialEditor.cs @@ -33,7 +33,7 @@ private static class Styles public static GUIContent InternalMaterialHelp = EditorGUIUtility.TrTextContent("Materials are embedded inside the imported asset."); public static GUIContent MaterialAssignmentsHelp = EditorGUIUtility.TrTextContent("Material assignments can be remapped below."); - public static GUIContent ExternalMaterialSearchHelp = EditorGUIUtility.TrTextContent("Remap the imported materials to materials from the Unity project."); + public static GUIContent ExternalMaterialSearchHelp = EditorGUIUtility.TrTextContent("Searches the user provided directory and matches the materials that share the same name and LOD with the originally imported material."); public static GUIContent SelectMaterialFolder = EditorGUIUtility.TrTextContent("Select Materials Folder"); } @@ -265,24 +265,42 @@ private bool MaterialRemapOptons() { if (GUILayout.Button(Styles.RemapMaterialsInProject)) { + bool bStartedAssetEditing = false; try { - AssetDatabase.StartAssetEditing(); - foreach (var t in targets) { - var importer = t as SpeedTreeImporter; - var folderToSearch = importer.materialFolderPath; + SpeedTreeImporter importer = t as SpeedTreeImporter; + string folderToSearch = importer.materialFolderPath; folderToSearch = EditorUtility.OpenFolderPanel(Styles.SelectMaterialFolder.text, importer.materialFolderPath, ""); - importer.SearchAndRemapMaterials(FileUtil.GetProjectRelativePath(folderToSearch)); - AssetDatabase.WriteImportSettingsIfDirty(importer.assetPath); - AssetDatabase.ImportAsset(importer.assetPath, ImportAssetOptions.ForceUpdate); + bool bUserSelectedAFolder = folderToSearch != ""; // folderToSearch is empty if the user cancels the window + if (bUserSelectedAFolder) + { + string projectRelativePath = FileUtil.GetProjectRelativePath(folderToSearch); + bool bRelativePathIsNotEmpty = projectRelativePath != ""; + if (bRelativePathIsNotEmpty) + { + AssetDatabase.StartAssetEditing(); + bStartedAssetEditing = true; + importer.SearchAndRemapMaterials(projectRelativePath); + + AssetDatabase.WriteImportSettingsIfDirty(importer.assetPath); + AssetDatabase.ImportAsset(importer.assetPath, ImportAssetOptions.ForceUpdate); + } + else + { + Debug.LogWarning("Selected folder is outside of the project's folder hierarchy, please provide a folder from the project.\n"); + } + } } } finally { - AssetDatabase.StopAssetEditing(); + if (bStartedAssetEditing) + { + AssetDatabase.StopAssetEditing(); + } } return true; diff --git a/Editor/Mono/ImportSettings/SpeedTreeImporterModelEditor.cs b/Editor/Mono/ImportSettings/SpeedTreeImporterModelEditor.cs index 30979f31e6..e1d49f97c4 100644 --- a/Editor/Mono/ImportSettings/SpeedTreeImporterModelEditor.cs +++ b/Editor/Mono/ImportSettings/SpeedTreeImporterModelEditor.cs @@ -9,6 +9,7 @@ using UnityEditor.AnimatedValues; using UnityEditor.AssetImporters; using UnityEngine; +using UnityEngine.Rendering; namespace UnityEditor { @@ -16,72 +17,142 @@ internal class SpeedTreeImporterModelEditor : BaseSpeedTreeImporterTabUI { private class Styles { - public static GUIContent LODHeader = EditorGUIUtility.TrTextContent("LODs"); - public static GUIContent ResetLOD = EditorGUIUtility.TrTextContent("Reset LOD to...", "Unify the LOD settings for all selected assets."); - public static GUIContent SmoothLOD = EditorGUIUtility.TrTextContent("Smooth LOD", "Toggles smooth LOD transitions."); - public static GUIContent AnimateCrossFading = EditorGUIUtility.TrTextContent("Animate Cross-fading", "Cross-fading is animated instead of being calculated by distance."); - public static GUIContent CrossFadeWidth = EditorGUIUtility.TrTextContent("Crossfade Width", "Proportion of the last 3D mesh LOD region width which is used for cross-fading to billboard tree."); - public static GUIContent FadeOutWidth = EditorGUIUtility.TrTextContent("Fade Out Width", "Proportion of the billboard LOD region width which is used for fading out the billboard."); - public static GUIContent MeshesHeader = EditorGUIUtility.TrTextContent("Meshes"); - public static GUIContent ScaleFactor = EditorGUIUtility.TrTextContent("Scale Factor", "How much to scale the tree model compared to what is in the .spm file."); - public static GUIContent ScaleFactorHelp = EditorGUIUtility.TrTextContent("The default value of Scale Factor is 0.3048, the conversion ratio from feet to meters, as these are the most conventional measurements used in SpeedTree and Unity, respectively."); + public static GUIContent UnitConversion = EditorGUIUtility.TrTextContent("Unit Conversion", "Select the unit conversion to apply to the imported SpeedTree asset"); + public static GUIContent ScaleFactor = EditorGUIUtility.TrTextContent("Scale Factor", "How much to scale the tree model, interpreting the exported units as meters. Must be positive."); - public static GUIContent MaterialsHeader = EditorGUIUtility.TrTextContent("Materials"); + public static GUIContent EnableColorVariation = EditorGUIUtility.TrTextContent("Color Variation", "Color is determined by linearly interpolating between the Main Color & Color Variation values based on the world position X, Y and Z values"); + public static GUIContent EnableBump = EditorGUIUtility.TrTextContent("Normal Map", "Enable normal mapping (aka Bump mapping)."); + public static GUIContent EnableSubsurface = EditorGUIUtility.TrTextContent("Subsurface Scattering", "Enable subsurface scattering effects."); public static GUIContent MainColor = EditorGUIUtility.TrTextContent("Main Color", "The color modulating the diffuse lighting component."); - public static GUIContent HueVariation = EditorGUIUtility.TrTextContent("Hue Color", "Apply to LODs that have Hue Variation effect enabled."); + public static GUIContent HueVariation = EditorGUIUtility.TrTextContent("Variation Color (RGB), Intensity (A)", "Tint the tree with the Variation Color"); public static GUIContent AlphaTestRef = EditorGUIUtility.TrTextContent("Alpha Cutoff", "The alpha-test reference value."); - public static GUIContent CastShadows = EditorGUIUtility.TrTextContent("Cast Shadows", "The tree casts shadow."); - public static GUIContent ReceiveShadows = EditorGUIUtility.TrTextContent("Receive Shadows", "The tree receives shadow."); - public static GUIContent UseLightProbes = EditorGUIUtility.TrTextContent("Use Light Probes", "The tree uses light probe for lighting."); - public static GUIContent UseReflectionProbes = EditorGUIUtility.TrTextContent("Use Reflection Probes", "The tree uses reflection probe for rendering."); - public static GUIContent EnableBump = EditorGUIUtility.TrTextContent("Normal Map", "Enable normal mapping (aka Bump mapping)."); - public static GUIContent EnableHue = EditorGUIUtility.TrTextContent("Enable Hue Variation", "Enable Hue variation color (color is adjusted between Main Color and Hue Color)."); - public static GUIContent EnableSubsurface = EditorGUIUtility.TrTextContent("Enable Subsurface", "Enable subsurface scattering effects."); - public static GUIContent WindQuality = EditorGUIUtility.TrTextContent("Wind Quality", "Controls the wind quality."); - public static GUIContent ApplyAndGenerate = EditorGUIUtility.TrTextContent("Apply & Generate Materials", "Apply current importer settings and generate materials with new settings."); - public static GUIContent Regenerate = EditorGUIUtility.TrTextContent("Regenerate Materials", "Regenerate materials from the current importer settings."); + public static GUIContent LightingHeader = EditorGUIUtility.TrTextContent("Lighting"); + public static GUIContent CastShadows = EditorGUIUtility.TrTextContent("Cast Shadows", "The tree casts shadow"); + public static GUIContent ReceiveShadows = EditorGUIUtility.TrTextContent("Receive Shadows", "The tree receives shadow"); + public static GUIContent UseLightProbes = EditorGUIUtility.TrTextContent("Light Probes", "The tree uses light probe for lighting"); // TODO: update help text + public static GUIContent UseReflectionProbes = EditorGUIUtility.TrTextContent("Reflection Probes", "The tree uses reflection probe for rendering"); // TODO: update help text + + public static GUIContent AdditionalSettingsHeader = EditorGUIUtility.TrTextContent("Additional Settings"); + public static GUIContent MotionVectorMode = EditorGUIUtility.TrTextContent("Motion Vectors", "Motion vector mode to set for the mesh renderer of each LOD object"); + + + public static GUIContent WindHeader = EditorGUIUtility.TrTextContent("Wind"); + public static GUIContent WindQuality = EditorGUIUtility.TrTextContent("Wind Quality", "Controls the wind effect's quality."); + + public static GUIContent LODHeader = EditorGUIUtility.TrTextContent("LOD"); + public static GUIContent ResetLOD = EditorGUIUtility.TrTextContent("Reset LOD to...", "Unify the LOD settings for all selected assets"); + public static GUIContent SmoothLOD = EditorGUIUtility.TrTextContent("Smooth Transitions", "Toggles smooth LOD transitions"); + public static GUIContent AnimateCrossFading = EditorGUIUtility.TrTextContent("Animate Cross-fading", "Cross-fading is animated instead of being calculated by distance"); + public static GUIContent CrossFadeWidth = EditorGUIUtility.TrTextContent("Crossfade Width", "Proportion of the last 3D mesh LOD region width which is used for cross-fading to billboard tree"); + public static GUIContent FadeOutWidth = EditorGUIUtility.TrTextContent("Fade Out Width", "Proportion of the billboard LOD region width which is used for fading out the billboard"); + + public static GUIContent EnableLodCustomizationsWarn = EditorGUIUtility.TrTextContent("Customizing LOD options may help with tuning the GPU performance but will likely negatively impact the instanced draw batching, i.e. CPU performance.\nPlease use the per-LOD customizations with careful memory and performance profiling for both CPU and GPU and remember that these options are a trade-off rather than a free win."); + public static GUIContent BillboardSettingsHelp = EditorGUIUtility.TrTextContent("Billboard options are separate from the 3D model options shown above.\nChange the options below for influencing billboard rendering."); + + public static GUIContent ApplyAndGenerate = EditorGUIUtility.TrTextContent("Apply & Generate Materials", "Apply current importer settings and generate asset materials with the new settings."); + public static GUIContent Regenerate = EditorGUIUtility.TrTextContent("Regenerate Materials", "Regenerate materials using the current import settings."); + + public static GUIContent[] ReflectionProbeUsageNames = (Enum.GetNames(typeof(ReflectionProbeUsage)).Select(x => ObjectNames.NicifyVariableName(x)).ToArray()).Select(x => new GUIContent(x)).ToArray(); + public static GUIContent[] WindQualityNames = SpeedTreeImporter.windQualityNames.Select(s => new GUIContent(s)).ToArray(); + public static GUIContent[] UnitConversionNames = + { + new GUIContent("Leave As Is") + , new GUIContent("ft to m") + , new GUIContent("cm to m") + , new GUIContent("inch to m") + , new GUIContent("Custom") + }; + public static GUIContent[] MotionVectorModeNames = // Match SharedRendererDataTypes.h / enum MotionVectorGenerationMode + { + new GUIContent("Camera Motion Only") // kMotionVectorCamera = 0, // Use camera motion for motion vectors + , new GUIContent("Per Object Motion") // kMotionVectorObject, // Use a per object motion vector pass for this object + , new GUIContent("Force No Motion") // kMotionVectorForceNoMotion, // Force no motion for this object (0 into motion buffer) + }; } + //-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + // DATA + //-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + // mesh + private SerializedProperty m_ScaleFactor; + private SerializedProperty m_UnitConversionEnumValue; + private SerializedProperty m_MotionVectorModeEnumValue; + + // material + private SerializedProperty m_MainColor; + private SerializedProperty m_EnableHueVariation; + private SerializedProperty m_HueVariation; + private SerializedProperty m_AlphaTestRef; + private SerializedProperty m_EnableBumpMapping; + private SerializedProperty m_EnableSubsurfaceScattering; + + // lighting + private SerializedProperty m_EnableShadowCasting; + private SerializedProperty m_EnableShadowReceiving; + private SerializedProperty m_EnableLightProbeUsage; + private SerializedProperty m_ReflectionProbeUsage; + + // wind + private SerializedProperty m_HighestWindQuality; + private SerializedProperty m_SelectedWindQuality; + + // lod private SerializedProperty m_LODSettings; private SerializedProperty m_EnableSmoothLOD; private SerializedProperty m_AnimateCrossFading; private SerializedProperty m_BillboardTransitionCrossFadeWidth; private SerializedProperty m_FadeOutWidth; - private SerializedProperty m_MainColor; - private SerializedProperty m_HueVariation; - private SerializedProperty m_AlphaTestRef; - private SerializedProperty m_ScaleFactor; + private bool m_AllAreV8; private bool m_AllAreNotV8; - private const float kFeetToMetersRatio = 0.3048f; - // LODGroup GUI private int m_SelectedLODSlider = -1; private int m_SelectedLODRange = 0; + private SavedBool[] m_LODGroupFoldoutHeaderValues = null; + private Texture2D[] m_LODColorTextures; private readonly AnimBool m_ShowSmoothLODOptions = new AnimBool(); private readonly AnimBool m_ShowCrossFadeWidthOptions = new AnimBool(); public bool doMaterialsHaveDifferentShader = false; + //-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + // INTERFACE + //-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- public SpeedTreeImporterModelEditor(AssetImporterEditor panelContainer) : base(panelContainer) {} internal override void OnEnable() { + m_ScaleFactor = serializedObject.FindProperty("m_ScaleFactor"); + m_UnitConversionEnumValue = serializedObject.FindProperty("m_UnitConversionEnumValue"); + m_MotionVectorModeEnumValue = serializedObject.FindProperty("m_MotionVectorModeEnumValue"); + + m_MainColor = serializedObject.FindProperty("m_MainColor"); + m_EnableHueVariation = serializedObject.FindProperty("m_EnableHueVariation"); + m_HueVariation = serializedObject.FindProperty("m_HueVariation"); + m_AlphaTestRef = serializedObject.FindProperty("m_AlphaTestRef"); + m_EnableBumpMapping = serializedObject.FindProperty("m_EnableBumpMapping"); + m_EnableSubsurfaceScattering = serializedObject.FindProperty("m_EnableSubsurfaceScattering"); + + m_EnableShadowCasting = serializedObject.FindProperty("m_EnableShadowCasting"); + m_EnableShadowReceiving = serializedObject.FindProperty("m_EnableShadowReceiving"); + m_EnableLightProbeUsage = serializedObject.FindProperty("m_EnableLightProbes"); + m_ReflectionProbeUsage = serializedObject.FindProperty("m_ReflectionProbeEnumValue"); + + m_HighestWindQuality = serializedObject.FindProperty("m_BestWindQuality"); + m_SelectedWindQuality = serializedObject.FindProperty("m_SelectedWindQuality"); + m_LODSettings = serializedObject.FindProperty("m_LODSettings"); m_EnableSmoothLOD = serializedObject.FindProperty("m_EnableSmoothLODTransition"); m_AnimateCrossFading = serializedObject.FindProperty("m_AnimateCrossFading"); m_BillboardTransitionCrossFadeWidth = serializedObject.FindProperty("m_BillboardTransitionCrossFadeWidth"); m_FadeOutWidth = serializedObject.FindProperty("m_FadeOutWidth"); - m_MainColor = serializedObject.FindProperty("m_MainColor"); - m_HueVariation = serializedObject.FindProperty("m_HueVariation"); - m_AlphaTestRef = serializedObject.FindProperty("m_AlphaTestRef"); - m_ScaleFactor = serializedObject.FindProperty("m_ScaleFactor"); m_ShowSmoothLODOptions.value = m_EnableSmoothLOD.hasMultipleDifferentValues || m_EnableSmoothLOD.boolValue; m_ShowSmoothLODOptions.valueChanged.AddListener(Repaint); @@ -90,6 +161,8 @@ internal override void OnEnable() m_AllAreV8 = importers.All(im => im.isV8); m_AllAreNotV8 = importers.All(im => !im.isV8); + + ResetFoldoutLists(); } internal override void OnDisable() @@ -108,6 +181,36 @@ internal override void OnDisable() i => i == lodCount - 1 && (target as SpeedTreeImporter).hasBillboard ? "Billboard" : String.Format("LOD {0}", i), i => m_LODSettings.GetArrayElementAtIndex(i).FindPropertyRelative("height").floatValue); } + private void ExpandSelectedHeaderAndCloseRemaining(int index) + { + // need this to safeguard against drag & drop on Culled section + // as that sets the LOD index to 8 which is outside of the total + // allowed LOD range + if (index >= m_LODSettings.arraySize) + return; + + Array.ForEach(m_LODGroupFoldoutHeaderValues, el => el.value = false); + m_LODGroupFoldoutHeaderValues[index].value = true; + } + void InitAndSetFoldoutLabelTextures() + { + m_LODColorTextures = new Texture2D[m_LODSettings.arraySize]; + for (int i = 0; i < m_LODColorTextures.Length; i++) + { + m_LODColorTextures[i] = new Texture2D(1, 1); + m_LODColorTextures[i].SetPixel(0, 0, LODGroupGUI.kLODColors[i]); + } + } + void ResetFoldoutLists() + { + int lodArraySize = m_LODSettings.arraySize; + m_LODGroupFoldoutHeaderValues = new SavedBool[lodArraySize]; + for (int i = 0; i < lodArraySize; i++) + { + m_LODGroupFoldoutHeaderValues[i] = new SavedBool($"{target.GetType()}.lodFoldout{i}", false); + } + InitAndSetFoldoutLabelTextures(); + } public bool HasSameLODConfig() { @@ -132,8 +235,24 @@ public bool CanUnifyLODConfig() private bool DoMaterialsHaveDifferentShader() { + if(assetTargets is null || assetTargets.Length == 0) + { + return false; + } + + // Check whether the imported asset is a valid one. + // GameObject cast will fail for non-geometry SpeedTree files + // such as collections on some v7 assets. If the object is not valid, + // the IEnumerable::ToArray() function call below will throw + // an InvalidType exception, hence we do an explicit source check here. + GameObject obj = assetTargets[0] as GameObject; + if (obj == null) + { + return false; + } + + var prefabs = assetTargets?.Cast()?.ToArray(); var importerArray = importers.ToArray(); - var prefabs = assetTargets?.Cast().ToArray(); // In tests assetTargets can become null for (int i = 0; i < Math.Min(importerArray.Length, prefabs?.Length ?? 0); ++i) @@ -163,11 +282,23 @@ private bool DoMaterialsHaveDifferentShader() return false; } + private void ShowAdditionalSettingsGUI() + { + // motion vectors + GUILayout.Label(Styles.AdditionalSettingsHeader, EditorStyles.boldLabel); + EditorGUILayout.Popup(m_MotionVectorModeEnumValue, Styles.MotionVectorModeNames, Styles.MotionVectorMode); + + EditorGUILayout.Space(); + } public override void OnInspectorGUI() { + // settings GUIs ShowMeshGUI(); + ShowAdditionalSettingsGUI(); ShowMaterialGUI(); + ShowLightingGUI(); + ShowWindGUI(); ShowLODGUI(); EditorGUILayout.Space(); @@ -176,34 +307,95 @@ public override void OnInspectorGUI() doMaterialsHaveDifferentShader = !materialsNeedToBeUpgraded && DoMaterialsHaveDifferentShader(); if (materialsNeedToBeUpgraded) - EditorGUILayout.HelpBox(String.Format("SpeedTree materials need to be upgraded. Please back them up (if modified manually) then hit the \"{0}\" button below.", Styles.ApplyAndGenerate.text), MessageType.Warning); + { + EditorGUILayout.HelpBox( + String.Format("SpeedTree materials need to be upgraded. Please back them up (if modified manually) then hit the \"{0}\" button below.", Styles.ApplyAndGenerate.text) + , MessageType.Warning + ); + } if (doMaterialsHaveDifferentShader) - EditorGUILayout.HelpBox(String.Format("There is a different SpeedTree shader provided by the current render pipeline which probably is more suitable for rendering. Hit the \"{0}\" button to regenerate the materials.", - (panelContainer as SpeedTreeImporterInspector).GetGenButtonText(HasModified(), materialsNeedToBeUpgraded).text), MessageType.Warning); + { + EditorGUILayout.HelpBox( + String.Format("There is a different SpeedTree shader provided by the current render pipeline which probably is more suitable for rendering. Hit the \"{0}\" button to regenerate the materials." + , (panelContainer as SpeedTreeImporterInspector).GetGenButtonText(HasModified() + , materialsNeedToBeUpgraded).text + ) + , MessageType.Warning + ); + } } private void ShowMeshGUI() { GUILayout.Label(Styles.MeshesHeader, EditorStyles.boldLabel); - EditorGUILayout.PropertyField(m_ScaleFactor, Styles.ScaleFactor); + EditorGUILayout.Popup(m_UnitConversionEnumValue, Styles.UnitConversionNames, Styles.UnitConversion); - // Display a help box to explain the rationale for the default value (feet to meters conversion ratio). - if (!m_ScaleFactor.hasMultipleDifferentValues && Mathf.Approximately(m_ScaleFactor.floatValue, kFeetToMetersRatio)) + bool bShowCustomScaleFactor = m_UnitConversionEnumValue.intValue == Styles.UnitConversionNames.Length-1; + if (bShowCustomScaleFactor) { - EditorGUILayout.HelpBox(Styles.ScaleFactorHelp.text, MessageType.Info); + EditorGUILayout.PropertyField(m_ScaleFactor, Styles.ScaleFactor); + if (m_ScaleFactor.floatValue < 0f) + { + m_ScaleFactor.floatValue = 0f; + } + if (m_ScaleFactor.floatValue == 0f) + { + EditorGUILayout.HelpBox("Scale factor must be positive.", MessageType.Warning); + } } - } - private void ShowMaterialGUI() + EditorGUILayout.Space(); + } + public void ShowMaterialGUI() { - GUILayout.Label(Styles.MaterialsHeader, EditorStyles.boldLabel); + EditorGUILayout.LabelField("Material", EditorStyles.boldLabel); EditorGUILayout.PropertyField(m_MainColor, Styles.MainColor); - EditorGUILayout.PropertyField(m_HueVariation, Styles.HueVariation); + EditorGUILayout.PropertyField(m_EnableHueVariation, Styles.EnableColorVariation); + if (importers.First().enableHueByDefault) + { + EditorGUILayout.PropertyField(m_HueVariation, Styles.HueVariation); + } + if (m_AllAreNotV8) EditorGUILayout.Slider(m_AlphaTestRef, 0f, 1f, Styles.AlphaTestRef); + + EditorGUILayout.PropertyField(m_EnableBumpMapping, Styles.EnableBump); + EditorGUILayout.PropertyField(m_EnableSubsurfaceScattering, Styles.EnableSubsurface); + + EditorGUILayout.Space(); + } + + private void ShowLightingGUI() + { + GUILayout.Label(Styles.LightingHeader, EditorStyles.boldLabel); + + EditorGUILayout.PropertyField(m_EnableShadowCasting, Styles.CastShadows); + + // from the docs page: https://docs.unity3d.com/Manual/SpeedTree.html + // Known issues: As with any other renderer, the Receive Shadows option has no effect while using deferred rendering. + // TODO: test and conditionally expose this field + using (new EditorGUI.DisabledScope(!UnityEngine.Rendering.SupportedRenderingFeatures.active.receiveShadows)) + { + EditorGUILayout.PropertyField(m_EnableShadowReceiving, Styles.ReceiveShadows); + } + + EditorGUILayout.PropertyField(m_EnableLightProbeUsage, Styles.UseLightProbes); + + + EditorGUILayout.Space(); + } + + + private void ShowWindGUI() + { + GUILayout.Label(Styles.WindHeader, EditorStyles.boldLabel); + int NumAvailableWindQualityOptions = 1 + m_HighestWindQuality.intValue; // 0 is None, we want at least 1 value + ArraySegment availableWindQualityOptions = new ArraySegment(Styles.WindQualityNames, 0, NumAvailableWindQualityOptions); + EditorGUILayout.Popup(m_SelectedWindQuality, availableWindQualityOptions.ToArray(), Styles.WindQuality); + EditorGUILayout.Space(); } private void ShowLODGUI() @@ -211,77 +403,48 @@ private void ShowLODGUI() m_ShowSmoothLODOptions.target = m_EnableSmoothLOD.hasMultipleDifferentValues || m_EnableSmoothLOD.boolValue; m_ShowCrossFadeWidthOptions.target = m_AnimateCrossFading.hasMultipleDifferentValues || !m_AnimateCrossFading.boolValue; + // label GUILayout.Label(Styles.LODHeader, EditorStyles.boldLabel); - EditorGUILayout.PropertyField(m_EnableSmoothLOD, Styles.SmoothLOD); - - EditorGUI.indentLevel++; - if (EditorGUILayout.BeginFadeGroup(m_ShowSmoothLODOptions.faded)) + // LOD Transitions { - EditorGUILayout.PropertyField(m_AnimateCrossFading, Styles.AnimateCrossFading); - if (EditorGUILayout.BeginFadeGroup(m_ShowCrossFadeWidthOptions.faded)) + EditorGUILayout.PropertyField(m_EnableSmoothLOD, Styles.SmoothLOD); + EditorGUI.indentLevel++; + if (EditorGUILayout.BeginFadeGroup(m_ShowSmoothLODOptions.faded)) { - EditorGUILayout.Slider(m_BillboardTransitionCrossFadeWidth, 0.0f, 1.0f, Styles.CrossFadeWidth); - EditorGUILayout.Slider(m_FadeOutWidth, 0.0f, 1.0f, Styles.FadeOutWidth); + EditorGUILayout.PropertyField(m_AnimateCrossFading, Styles.AnimateCrossFading); + if (EditorGUILayout.BeginFadeGroup(m_ShowCrossFadeWidthOptions.faded)) + { + EditorGUILayout.Slider(m_BillboardTransitionCrossFadeWidth, 0.0f, 1.0f, Styles.CrossFadeWidth); + EditorGUILayout.Slider(m_FadeOutWidth, 0.0f, 1.0f, Styles.FadeOutWidth); + } + EditorGUILayout.EndFadeGroup(); } EditorGUILayout.EndFadeGroup(); + EditorGUI.indentLevel--; } - EditorGUILayout.EndFadeGroup(); - EditorGUI.indentLevel--; EditorGUILayout.Space(); + + // LOD slider + Customizations if (HasSameLODConfig()) { - EditorGUILayout.Space(); - var area = GUILayoutUtility.GetRect(0, LODGroupGUI.kSliderBarHeight, GUILayout.ExpandWidth(true)); var lods = GetLODInfoArray(area); - DrawLODLevelSlider(area, lods); + bool bDrawLODCustomizationGUI = m_SelectedLODRange != -1 && lods.Count > 0; EditorGUILayout.Space(); - EditorGUILayout.Space(); - - if (m_SelectedLODRange != -1 && lods.Count > 0) - { - EditorGUILayout.LabelField(lods[m_SelectedLODRange].LODName + " Options", EditorStyles.boldLabel); - bool isBillboard = (m_SelectedLODRange == lods.Count - 1) && importers.First().hasBillboard; - - EditorGUILayout.PropertyField(m_LODSettings.GetArrayElementAtIndex(m_SelectedLODRange).FindPropertyRelative("castShadows"), Styles.CastShadows); - - using (new EditorGUI.DisabledScope(!UnityEngine.Rendering.SupportedRenderingFeatures.active.receiveShadows)) - { - EditorGUILayout.PropertyField(m_LODSettings.GetArrayElementAtIndex(m_SelectedLODRange).FindPropertyRelative("receiveShadows"), Styles.ReceiveShadows); - } - - var useLightProbes = m_LODSettings.GetArrayElementAtIndex(m_SelectedLODRange).FindPropertyRelative("useLightProbes"); - EditorGUILayout.PropertyField(useLightProbes, Styles.UseLightProbes); - if (!useLightProbes.hasMultipleDifferentValues && useLightProbes.boolValue && isBillboard) - EditorGUILayout.HelpBox("Enabling Light Probe for billboards breaks batched rendering and may cause performance problem.", MessageType.Warning); - - // TODO: reflection probe support when PBS is implemented - //EditorGUILayout.PropertyField(m_LODSettings.GetArrayElementAtIndex(m_SelectedLODRange).FindPropertyRelative("useReflectionProbes"), Styles.UseReflectionProbes); - EditorGUILayout.PropertyField(m_LODSettings.GetArrayElementAtIndex(m_SelectedLODRange).FindPropertyRelative("enableBump"), Styles.EnableBump); - EditorGUILayout.PropertyField(m_LODSettings.GetArrayElementAtIndex(m_SelectedLODRange).FindPropertyRelative("enableHue"), Styles.EnableHue); - - if (m_AllAreV8) - EditorGUILayout.PropertyField(m_LODSettings.GetArrayElementAtIndex(m_SelectedLODRange).FindPropertyRelative("enableSubsurface"), Styles.EnableSubsurface); - - int bestWindQuality = importers.Min(im => im.bestWindQuality); - if (bestWindQuality > 0) - { - if (isBillboard) - { - bestWindQuality = 1; // billboard has only one level of wind quality - } + DrawLODLevelSlider(area, lods); - EditorGUILayout.Popup( - m_LODSettings.GetArrayElementAtIndex(m_SelectedLODRange).FindPropertyRelative("windQuality"), - SpeedTreeImporter.windQualityNames.Take(bestWindQuality + 1).Select(s => new GUIContent(s)).ToArray(), - Styles.WindQuality); - } + if (bDrawLODCustomizationGUI) + { + GUILayout.Space(5); + DrawLODGroupFoldouts(lods); } } + + // Mixed Value LOD Slider else { if (CanUnifyLODConfig()) @@ -367,6 +530,7 @@ private void DrawLODLevelSlider(Rect sliderPosition, List l { m_SelectedLODSlider = -1; m_SelectedLODRange = lod.LODLevel; + ExpandSelectedHeaderAndCloseRemaining(m_SelectedLODRange); break; } } @@ -399,6 +563,192 @@ private void DrawLODLevelSlider(Rect sliderPosition, List l } } + private void DrawLODGroupFoldouts(List lods) + { + // check camera and bail if null + Camera camera = null; + if (SceneView.lastActiveSceneView && SceneView.lastActiveSceneView.camera) + camera = SceneView.lastActiveSceneView.camera; + if (camera == null) + return; + + // draw lod foldouts + for (int i = 0; i < m_LODSettings.arraySize; i++) + { + DrawLODGroupFoldout(camera, i, ref m_LODGroupFoldoutHeaderValues[i], lods); + } + } + + static private string GetLODSubmeshAndTriCountLabel(int numLODs, int lodGroupIndex, SpeedTreeImporter im, LODGroup lodGroup) + { + LOD[] lods = lodGroup.GetLODs(); + Debug.Assert(lods.Length == numLODs); + + int[][] primitiveCounts = new int[numLODs][]; + int[] submeshCounts = new int[numLODs]; + for (int i = 0; i < lods.Length; i++) + { + Renderer[] renderers = lods[i].renderers; + primitiveCounts[i] = new int[renderers.Length]; + + for (int j = 0; j < renderers.Length; j++) + { + bool hasMismatchingSubMeshTopologyTypes = LODGroupEditor.CheckIfMeshesHaveMatchingTopologyTypes(renderers); + + Mesh rendererMesh = LODGroupEditor.GetMeshFromRendererIfAvailable(renderers[j]); + if (rendererMesh == null) + continue; + + submeshCounts[i] += rendererMesh.subMeshCount; + + if (hasMismatchingSubMeshTopologyTypes) + { + primitiveCounts[i][j] = rendererMesh.vertexCount; + } + else + { + for (int subMeshIndex = 0; subMeshIndex < rendererMesh.subMeshCount; subMeshIndex++) + { + primitiveCounts[i][j] += (int)rendererMesh.GetIndexCount(subMeshIndex) / 3; + } + } + } + } + + int totalTriCount = (primitiveCounts.Length > 0 && primitiveCounts[lodGroupIndex] != null) + ? primitiveCounts[lodGroupIndex].Sum() + : 0; + int lod0TriCount = primitiveCounts[0].Sum(); + var triCountChange = lod0TriCount != 0 ? (float)totalTriCount / lod0TriCount * 100 : 0; + string triangleChangeLabel = lodGroupIndex > 0 && lod0TriCount != 0 ? $"({triCountChange.ToString("f2")}% LOD0)" : ""; + + bool wideInspector = Screen.width >= 480; + triangleChangeLabel = wideInspector ? triangleChangeLabel : ""; + string submeshCountLabel = wideInspector ? $"- {submeshCounts[lodGroupIndex]} Sub Mesh(es)" : ""; + + return $"{totalTriCount} {LODGroupGUI.GUIStyles.m_TriangleCountLabel.text} {triangleChangeLabel} {submeshCountLabel}"; + } + private void DrawLODGroupFoldout(Camera camera, int lodGroupIndex, ref SavedBool foldoutState, List lodInfoList) + { + GameObject[] prefabs = assetTargets?.Cast().ToArray(); // In tests assetTargets can become null + SpeedTreeImporter[] importerArray = importers.ToArray(); + int numSelectedAssets = Math.Min(importerArray.Length, prefabs?.Length ?? 0); + bool isDrawingSelectedLODGroup = m_SelectedLODRange == lodGroupIndex; + + // even though multiple assets may be selected, this code path + // ensures the numLODs match for all the selected assets (see HasSameLODConfig() calls) + int numLODs = m_LODSettings.arraySize; + + string LODFoldoutHeaderLabel = (importerArray[0].hasBillboard && lodGroupIndex == m_LODSettings.arraySize - 1) + ? "Billboard" + : $"LOD {lodGroupIndex}"; + + // primitive and submesh counts are displayed only when a single asset is selected + string LODFoldoutHeaderGroupAdditionalText = numSelectedAssets == 1 + ? GetLODSubmeshAndTriCountLabel(numLODs, lodGroupIndex, importerArray[0], prefabs[0].GetComponentInChildren()) + : ""; + + // ------------------------------------------------------------------------------------------------------------------------------ + + if (isDrawingSelectedLODGroup) + LODGroupGUI.DrawRoundedBoxAroundLODDFoldout(lodGroupIndex, m_SelectedLODRange); + else + EditorGUILayout.BeginVertical(EditorStyles.helpBox); + + foldoutState.value = LODGroupGUI.FoldoutHeaderGroupInternal( + GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.inspectorTitlebarFlat) + , foldoutState.value + , LODFoldoutHeaderLabel + , m_LODColorTextures[lodGroupIndex] + , LODGroupGUI.kLODColors[lodGroupIndex] * 0.6f // 0.5f magic number is copied from LODGroupsGUI.cs + , LODFoldoutHeaderGroupAdditionalText + ); + + if (foldoutState.value) // expanded LOD-specific options panel + { + DrawLODSettingCustomizationGUI(lodInfoList, lodGroupIndex); + } + + if (isDrawingSelectedLODGroup) + GUILayoutUtility.EndLayoutGroup(); + else + EditorGUILayout.EndVertical(); + } + + private void DrawLODSettingCustomizationGUI(List lods, int lodIndex) + { + bool isBillboard = (lodIndex == lods.Count - 1) && importers.First().hasBillboard; + int windQuality = m_HighestWindQuality.intValue; + if (isBillboard) + { + windQuality = 1; // billboard has only one level of wind quality + } + + + SerializedProperty selectedLODProp = m_LODSettings.GetArrayElementAtIndex(lodIndex); + SerializedProperty lodSettingOverride = selectedLODProp.FindPropertyRelative("enableSettingOverride"); + + // We don't want to clutter the GUI with same options but for billboards, instead + // we treat the Billboard LOD level as always 'overrideSettings' and display the + // billboard options below without the 'enableSettingOverride' warning text. + if (isBillboard) + { + EditorGUILayout.LabelField("Billboard Options", EditorStyles.boldLabel); + EditorGUILayout.HelpBox(Styles.BillboardSettingsHelp.text, MessageType.Info); + } + else + { + // Toggle + GUIContent customizationLabel = EditorGUIUtility.TrTextContent(String.Format("Customize {0} options", lods[lodIndex].LODName), "To override options for a certain LOD, check this box and select the LOD from the LOD slider above"); + EditorGUILayout.PropertyField(lodSettingOverride, customizationLabel); + + // Warning + if (lodSettingOverride.boolValue) + { + EditorGUILayout.HelpBox(Styles.EnableLodCustomizationsWarn.text, MessageType.Warning); + } + } + EditorGUILayout.Space(); + + // settings + using (new EditorGUI.DisabledScope(!lodSettingOverride.boolValue)) + { + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Lighting", EditorStyles.boldLabel); + EditorGUILayout.PropertyField(selectedLODProp.FindPropertyRelative("castShadows"), Styles.CastShadows); + + using (new EditorGUI.DisabledScope(!UnityEngine.Rendering.SupportedRenderingFeatures.active.receiveShadows)) + { + EditorGUILayout.PropertyField(selectedLODProp.FindPropertyRelative("receiveShadows"), Styles.ReceiveShadows); + } + + var useLightProbes = selectedLODProp.FindPropertyRelative("useLightProbes"); + EditorGUILayout.PropertyField(useLightProbes, Styles.UseLightProbes); + if (!useLightProbes.hasMultipleDifferentValues && useLightProbes.boolValue && isBillboard) + EditorGUILayout.HelpBox("Enabling Light Probe for billboards breaks batched rendering and may cause performance problem.", MessageType.Warning); + + // TODO: reflection probe support when PBS is implemented + //EditorGUILayout.PropertyField(SelectedLODProp.FindPropertyRelative("useReflectionProbes"), Styles.UseReflectionProbes); + + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Wind", EditorStyles.boldLabel); + EditorGUILayout.Popup( + selectedLODProp.FindPropertyRelative("windQuality"), + SpeedTreeImporter.windQualityNames.Take(windQuality + 1).Select(s => new GUIContent(s)).ToArray(), + Styles.WindQuality); + + + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Material", EditorStyles.boldLabel); + EditorGUILayout.PropertyField(selectedLODProp.FindPropertyRelative("enableHue"), Styles.EnableColorVariation); + EditorGUILayout.PropertyField(selectedLODProp.FindPropertyRelative("enableBump"), Styles.EnableBump); + + if (m_AllAreV8) + EditorGUILayout.PropertyField(selectedLODProp.FindPropertyRelative("enableSubsurface"), Styles.EnableSubsurface); + } + } + + private void OnResetLODMenuClick(object userData) { var toHeights = (userData as SpeedTreeImporter).LODHeights; diff --git a/Editor/Mono/ImportSettings/TextureImportPlatformSettings.cs b/Editor/Mono/ImportSettings/TextureImportPlatformSettings.cs index a4e666ece8..647382bae2 100644 --- a/Editor/Mono/ImportSettings/TextureImportPlatformSettings.cs +++ b/Editor/Mono/ImportSettings/TextureImportPlatformSettings.cs @@ -142,6 +142,15 @@ public void Apply() public static BuildPlatform[] GetBuildPlayerValidPlatforms() { List validPlatforms = BuildPlatforms.instance.GetValidPlatforms(); + + // The Dedicated Server is a subtarget of Standalone, textures are generally stripped from server builds, + // so overriding the texture settings for the Dedicated Server is a rare case that doesn't add much value. + // Supporting texture overrides for Dedicated Server is possible, but as importer dependencies cannot distinguish + // today between Standalone-Server and Standalone-Non-Server, such support means that textures would reimport on + // switching, for instance, between Standalone-Win and Standalon-OSX. That would represent a performance regression + // and so, we have opted to not support texture overrides for Dedicated Server. + validPlatforms.Remove(BuildPlatforms.instance.BuildPlatformFromNamedBuildTarget(NamedBuildTarget.Server)); + return validPlatforms.ToArray(); } @@ -160,18 +169,27 @@ internal static void ShowPlatformSpecificSettings(List m_TextureTypes; internal SpriteImportMode spriteImportMode { @@ -499,6 +506,8 @@ void CacheSerializedProperties() m_IsReadable = serializedObject.FindProperty("m_IsReadable"); m_StreamingMipmaps = serializedObject.FindProperty("m_StreamingMipmaps"); m_StreamingMipmapsPriority = serializedObject.FindProperty("m_StreamingMipmapsPriority"); + m_IgnoreMipmapLimit = serializedObject.FindProperty("m_IgnoreMipmapLimit"); + m_MipmapLimitGroupName = serializedObject.FindProperty("m_MipmapLimitGroupName"); m_VTOnly = serializedObject.FindProperty("m_VTOnly"); m_sRGBTexture = serializedObject.FindProperty("m_sRGBTexture"); m_EnableMipMap = serializedObject.FindProperty("m_EnableMipMap"); @@ -535,6 +544,10 @@ void CacheSerializedProperties() m_FlipbookRows = serializedObject.FindProperty("m_FlipbookRows"); m_FlipbookColumns = serializedObject.FindProperty("m_FlipbookColumns"); + m_CookieLightType = serializedObject.FindProperty("m_CookieLightType"); + + m_PlatformSettingsArrProp = serializedObject.FindProperty("m_PlatformSettings"); + m_TextureTypes = new List(); foreach (var o in targets) { @@ -638,7 +651,11 @@ void InitializeGUI() public override void OnEnable() { base.OnEnable(); + Initialize(); + } + void Initialize() + { s_DefaultPlatformName = TextureImporter.defaultPlatformName; // Can't be called everywhere so we save it here for later use. m_ShowAdvanced = EditorPrefs.GetBool("TextureImporterShowAdvanced", m_ShowAdvanced); @@ -692,6 +709,7 @@ void SetSerializedPropertySettings(TextureImporterSettings settings) m_IsReadable.intValue = settings.readable ? 1 : 0; m_StreamingMipmaps.intValue = settings.streamingMipmaps ? 1 : 0; m_StreamingMipmapsPriority.intValue = settings.streamingMipmapsPriority; + m_IgnoreMipmapLimit.intValue = settings.ignoreMipmapLimit ? 1 : 0; m_VTOnly.intValue = settings.vtOnly ? 1 : 0; m_EnableMipMap.intValue = settings.mipmapEnabled ? 1 : 0; m_sRGBTexture.intValue = settings.sRGBTexture ? 1 : 0; @@ -775,6 +793,8 @@ internal TextureImporterSettings GetSerializedPropertySettings(TextureImporterSe settings.streamingMipmaps = m_StreamingMipmaps.intValue > 0; if (!m_StreamingMipmapsPriority.hasMultipleDifferentValues) settings.streamingMipmapsPriority = m_StreamingMipmapsPriority.intValue; + if (!m_IgnoreMipmapLimit.hasMultipleDifferentValues) + settings.ignoreMipmapLimit = m_IgnoreMipmapLimit.intValue > 0; if (!m_VTOnly.hasMultipleDifferentValues) settings.vtOnly = m_VTOnly.intValue > 0; @@ -858,26 +878,11 @@ internal TextureImporterSettings GetSerializedPropertySettings(TextureImporterSe void CookieGUI(TextureInspectorGUIElement guiElements) { EditorGUI.BeginChangeCheck(); - CookieMode cm; - if (m_BorderMipMap.intValue > 0) - cm = CookieMode.Spot; - else if (m_TextureShape.intValue == (int)TextureImporterShape.TextureCube) - cm = CookieMode.Point; - else - cm = CookieMode.Directional; - cm = (CookieMode)EditorGUILayout.Popup(s_Styles.cookieType, (int)cm, s_Styles.cookieOptions); - if (EditorGUI.EndChangeCheck()) - SetCookieMode(cm); + EditorGUILayout.Popup(m_CookieLightType, s_Styles.cookieOptions, s_Styles.cookieType); - if (cm == CookieMode.Point) - { - m_TextureShape.intValue = (int)TextureImporterShape.TextureCube; - } - else - { - m_TextureShape.intValue = (int)TextureImporterShape.Texture2D; - } + if (EditorGUI.EndChangeCheck()) + SetCookieLightTypeDefaults((TextureImporterCookieLightType)m_CookieLightType.intValue); } void CubemapMappingGUI(TextureInspectorGUIElement guiElements) @@ -887,33 +892,33 @@ void CubemapMappingGUI(TextureInspectorGUIElement guiElements) { if ((TextureImporterShape)m_TextureShape.intValue == TextureImporterShape.TextureCube) { - using (new EditorGUI.DisabledScope(!m_IsPOT && m_NPOTScale.intValue == (int)TextureImporterNPOTScale.None)) - { - EditorGUI.showMixedValue = m_GenerateCubemap.hasMultipleDifferentValues || m_SeamlessCubemap.hasMultipleDifferentValues; + Rect controlRect = EditorGUILayout.GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.popup); + GUIContent label = EditorGUI.BeginProperty(controlRect, s_Styles.cubemap, m_GenerateCubemap); - EditorGUI.BeginChangeCheck(); - - int value = EditorGUILayout.IntPopup(s_Styles.cubemap, m_GenerateCubemap.intValue, s_Styles.cubemapOptions, s_Styles.cubemapValues2); - if (EditorGUI.EndChangeCheck()) - m_GenerateCubemap.intValue = value; + EditorGUI.showMixedValue = m_GenerateCubemap.hasMultipleDifferentValues || m_SeamlessCubemap.hasMultipleDifferentValues; - EditorGUI.indentLevel++; + EditorGUI.BeginChangeCheck(); - // Convolution - if (ShouldDisplayGUIElement(guiElements, TextureInspectorGUIElement.CubeMapConvolution)) - { - EditorGUILayout.IntPopup(m_CubemapConvolution, - s_Styles.cubemapConvolutionOptions, - s_Styles.cubemapConvolutionValues, - s_Styles.cubemapConvolution); - } + int value = EditorGUI.IntPopup(controlRect, label, m_GenerateCubemap.intValue, s_Styles.cubemapOptions, s_Styles.cubemapValues2); + if (EditorGUI.EndChangeCheck()) + m_GenerateCubemap.intValue = value; - ToggleFromInt(m_SeamlessCubemap, s_Styles.seamlessCubemap); + EditorGUI.EndProperty(); + EditorGUI.indentLevel++; - EditorGUI.indentLevel--; - EditorGUI.showMixedValue = false; - EditorGUILayout.Space(); + // Convolution + if (ShouldDisplayGUIElement(guiElements, TextureInspectorGUIElement.CubeMapConvolution)) + { + EditorGUILayout.IntPopup(m_CubemapConvolution, + s_Styles.cubemapConvolutionOptions, + s_Styles.cubemapConvolutionValues, + s_Styles.cubemapConvolution); } + + ToggleFromInt(m_SeamlessCubemap, s_Styles.seamlessCubemap); + + EditorGUI.indentLevel--; + EditorGUILayout.Space(); } } EditorGUILayout.EndFadeGroup(); @@ -926,16 +931,21 @@ void ElementsAtlasGui(TextureInspectorGUIElement guiElements) m_ShowElementsAtlasSettings.target = isLayerShape; if (EditorGUILayout.BeginFadeGroup(m_ShowElementsAtlasSettings.faded) && isLayerShape) { + var isEditorTargetPreset = Presets.Preset.IsEditorTargetAPreset(target); + EditorGUI.indentLevel++; EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(m_FlipbookColumns, s_Styles.flipbookColumns); if (EditorGUI.EndChangeCheck()) { var val = m_FlipbookColumns.intValue; - val = Mathf.Clamp(val, 1, m_TextureWidth); + + // It happens when we are directly changing a TextureImporter preset and not an ImportSettings (targetting a 2D texture). + // We don't wanna clamp the value to 'm_TextureWidth', or it will always be set to -1 since there's no texture targeted. + val = Mathf.Clamp(val, 1, !isEditorTargetPreset ? m_TextureWidth : val); m_FlipbookColumns.intValue = val; } - if (m_TextureWidth % m_FlipbookColumns.intValue != 0) + if (!isEditorTargetPreset && m_TextureWidth % m_FlipbookColumns.intValue != 0) EditorGUILayout.HelpBox($"Image width {m_TextureWidth} does not divide into {m_FlipbookColumns.intValue} columns exactly", MessageType.Warning, true); EditorGUI.BeginChangeCheck(); @@ -943,10 +953,13 @@ void ElementsAtlasGui(TextureInspectorGUIElement guiElements) if (EditorGUI.EndChangeCheck()) { var val = m_FlipbookRows.intValue; - val = Mathf.Clamp(val, 1, m_TextureHeight); + + // It happens when we are directly changing a TextureImporter preset and not an ImportSettings (targetting a 2D texture). + // We don't wanna clamp the value to 'm_TextureHeight', or it will always be set to -1 since there's no texture targeted. + val = Mathf.Clamp(val, 1, !isEditorTargetPreset ? m_TextureHeight : val); m_FlipbookRows.intValue = val; } - if (m_TextureHeight % m_FlipbookRows.intValue != 0) + if (!isEditorTargetPreset && m_TextureHeight % m_FlipbookRows.intValue != 0) EditorGUILayout.HelpBox($"Image height {m_TextureHeight} does not divide into {m_FlipbookRows.intValue} rows exactly", MessageType.Warning, true); EditorGUI.indentLevel--; } @@ -963,6 +976,13 @@ void ColorSpaceGUI(TextureInspectorGUIElement guiElements) void POTScaleGUI(TextureInspectorGUIElement guiElements) { + // Cubemap face sizes are calculated using the original file's width/height + the cubemap generation mode. + // Currently, the calculation always outputs POT face sizes: displaying this control is thus unnecessary. + if ((TextureImporterShape)m_TextureShape.intValue == TextureImporterShape.TextureCube) + { + return; + } + using (new EditorGUI.DisabledScope(m_IsPOT)) { EnumPopup(m_NPOTScale, typeof(TextureImporterNPOTScale), s_Styles.npot); @@ -971,14 +991,11 @@ void POTScaleGUI(TextureInspectorGUIElement guiElements) void ReadableGUI(TextureInspectorGUIElement guiElements) { - bool enabled = CanReadWrite(); - using (new EditorGUI.DisabledScope(!enabled)) + ToggleFromInt(m_IsReadable, s_Styles.readWrite); + + if (m_IsReadable.intValue > 0 && !m_IsReadable.hasMultipleDifferentValues && ShouldShowWarningForReadWrite()) { - ToggleFromInt(m_IsReadable, s_Styles.readWrite); - if (!enabled && m_IsReadable.intValue > 0) - { - EditorGUILayout.HelpBox(s_Styles.readWriteWarning.text, MessageType.Warning, true); - } + EditorGUILayout.HelpBox(s_Styles.readWriteWarning.text, MessageType.Info, true); } } @@ -1020,15 +1037,7 @@ void AlphaHandlingGUI(TextureInspectorGUIElement guiElements) bool showAlphaSource = true; if (ShouldDisplayGUIElement(guiElements, TextureInspectorGUIElement.SingleChannelComponent)) { - EditorGUI.showMixedValue = m_SingleChannelComponent.hasMultipleDifferentValues; - EditorGUI.BeginChangeCheck(); - int newSingleChannelComponent = EditorGUILayout.IntPopup(s_Styles.singleChannelComponent, m_SingleChannelComponent.intValue, s_Styles.singleChannelComponentOptions, s_Styles.singleChannelComponentValues); - - EditorGUI.showMixedValue = false; - if (EditorGUI.EndChangeCheck()) - { - m_SingleChannelComponent.intValue = newSingleChannelComponent; - } + EditorGUILayout.IntPopup(m_SingleChannelComponent, s_Styles.singleChannelComponentOptions, s_Styles.singleChannelComponentValues, s_Styles.singleChannelComponent); showAlphaSource = (m_SingleChannelComponent.intValue == (int)TextureImporterSingleChannelComponent.Alpha); } @@ -1041,15 +1050,7 @@ void AlphaHandlingGUI(TextureInspectorGUIElement guiElements) bool success = CountImportersWithAlpha(targets, out countWithAlpha); success = success && CountImportersWithHDR(targets, out countHDR); - EditorGUI.showMixedValue = m_AlphaSource.hasMultipleDifferentValues; - EditorGUI.BeginChangeCheck(); - int newAlphaUsage = EditorGUILayout.IntPopup(s_Styles.alphaSource, m_AlphaSource.intValue, s_Styles.alphaSourceOptions, s_Styles.alphaSourceValues); - - EditorGUI.showMixedValue = false; - if (EditorGUI.EndChangeCheck()) - { - m_AlphaSource.intValue = newAlphaUsage; - } + EditorGUILayout.IntPopup(m_AlphaSource, s_Styles.alphaSourceOptions, s_Styles.alphaSourceValues, s_Styles.alphaSource); bool showAlphaIsTransparency = success && (TextureImporterAlphaSource)m_AlphaSource.intValue != TextureImporterAlphaSource.None && countHDR == 0; // AlphaIsTransparency is not properly implemented for HDR texture yet. using (new EditorGUI.DisabledScope(assetTarget != null && !showAlphaIsTransparency)) @@ -1150,6 +1151,79 @@ private void SpriteGUI(TextureInspectorGUIElement guiElements) EditorGUI.indentLevel--; } + internal static void DoMipmapLimitsGUI(SerializedProperty ignoreMipmapLimitProp, SerializedProperty groupNameProp) + { + if (s_Styles == null) + s_Styles = new Styles(); + + // The property itself is "ignoreMipmapLimit". However, in the UI, we display it + // as "Use Mipmap Limits" as it makes more sense to hide the group names dropdown + // when "Use Mipmap Limits" is toggled off rather than when "Ignore Mipmap Limit" + // is toggled on. + Rect rect = EditorGUILayout.GetControlRect(true, EditorGUI.kSingleLineHeight); + GUIContent label = EditorGUI.BeginProperty(rect, s_Styles.useMipmapLimits, ignoreMipmapLimitProp); + bool useMipmapLimits = ignoreMipmapLimitProp.propertyType == SerializedPropertyType.Integer ? ignoreMipmapLimitProp.intValue == 0 : !ignoreMipmapLimitProp.boolValue; + using (var changed = new EditorGUI.ChangeCheckScope()) + { + useMipmapLimits = EditorGUI.Toggle(rect, label, useMipmapLimits); + if (changed.changed) + { + if (ignoreMipmapLimitProp.propertyType == SerializedPropertyType.Integer) + { + ignoreMipmapLimitProp.intValue = useMipmapLimits ? 0 : 1; + } + else + { + ignoreMipmapLimitProp.boolValue = !useMipmapLimits; + } + } + } + EditorGUI.EndProperty(); + + if (useMipmapLimits) + { + using (new EditorGUI.IndentLevelScope()) + { + List options = new List(); + options.Add(L10n.Tr("None (Use Global Mipmap Limit)")); + + // Add all known groups + var groupNames = TextureMipmapLimitGroups.GetGroups(); + if (groupNames.Length > 0) + { + options.Add(string.Empty); // Separator + options.AddRange(groupNames); + } + + // If a group is not known, make sure to add it to the options anyway + if (groupNameProp.stringValue != string.Empty && !options.Contains(groupNameProp.stringValue)) + { + options.Add(string.Empty); // Seperator + options.Add(groupNameProp.stringValue); + } + + rect = EditorGUILayout.GetControlRect(true, EditorGUI.kSingleLineHeight); + label = EditorGUI.BeginProperty(rect, s_Styles.mipmapLimitGroupName, groupNameProp); + using (var changed = new EditorGUI.ChangeCheckScope()) + { + int selectedIndex = groupNameProp.hasMultipleDifferentValues ? -1 : ((groupNameProp.stringValue == string.Empty) ? 0 : options.IndexOf(groupNameProp.stringValue)); + selectedIndex = EditorGUI.Popup(rect, label, selectedIndex, options.ToArray()); + if (changed.changed) + { + groupNameProp.stringValue = selectedIndex == 0 ? string.Empty : options[selectedIndex]; + } + } + EditorGUI.EndProperty(); + + bool displayGroupNameWarning = groupNameProp.stringValue.Length > 0 && !TextureMipmapLimitGroups.HasGroup(groupNameProp.stringValue); + if (displayGroupNameWarning) + { + EditorGUILayout.HelpBox(s_Styles.mipmapLimitGroupWarning.text, MessageType.Warning, true); + } + } + } + } + void MipMapGUI(TextureInspectorGUIElement guiElements) { ToggleFromInt(m_EnableMipMap, s_Styles.generateMipMaps); @@ -1164,6 +1238,12 @@ void MipMapGUI(TextureInspectorGUIElement guiElements) { EditorGUI.indentLevel++; + // If VTOnly, then we don't show the MipmapLimits GUI since its values are ignored anyway + if ((TextureImporterShape)m_TextureShape.intValue == TextureImporterShape.Texture2D && !m_VTOnly.boolValue) + { + DoMipmapLimitsGUI(m_IgnoreMipmapLimit, m_MipmapLimitGroupName); + } + StreamingMipmapsGUI(); EditorGUILayout.Popup(m_MipMapMode, s_Styles.mipMapFilterOptions, s_Styles.mipMapFilter); @@ -1182,15 +1262,41 @@ void MipMapGUI(TextureInspectorGUIElement guiElements) ToggleFromInt(m_FadeOut, s_Styles.mipmapFadeOutToggle); if (m_FadeOut.intValue > 0) { + const int minLimit = 0; + const int maxLimit = 10; + + // For presets, we need two separate controls as we have 2 separate SerializedProperties. EditorGUI.indentLevel++; - EditorGUI.BeginChangeCheck(); - float min = m_MipMapFadeDistanceStart.intValue; - float max = m_MipMapFadeDistanceEnd.intValue; - EditorGUILayout.MinMaxSlider(s_Styles.mipmapFadeOut, ref min, ref max, 0, 10); - if (EditorGUI.EndChangeCheck()) + if (Presets.Preset.IsEditorTargetAPreset(target)) + { + // Fade Start + EditorGUI.BeginChangeCheck(); + EditorGUILayout.PropertyField(m_MipMapFadeDistanceStart, s_Styles.mipmapFadeStartMip); + if (EditorGUI.EndChangeCheck()) + { + m_MipMapFadeDistanceStart.intValue = Math.Clamp(m_MipMapFadeDistanceStart.intValue, minLimit, m_MipMapFadeDistanceEnd.intValue); + } + + // Fade End + EditorGUI.BeginChangeCheck(); + EditorGUILayout.PropertyField(m_MipMapFadeDistanceEnd, s_Styles.mipmapFadeEndMip); + if (EditorGUI.EndChangeCheck()) + { + m_MipMapFadeDistanceEnd.intValue = Math.Clamp(m_MipMapFadeDistanceEnd.intValue, m_MipMapFadeDistanceStart.intValue, maxLimit); + } + } + else { - m_MipMapFadeDistanceStart.intValue = Mathf.RoundToInt(min); - m_MipMapFadeDistanceEnd.intValue = Mathf.RoundToInt(max); + // Fade Range + EditorGUI.BeginChangeCheck(); + float min = m_MipMapFadeDistanceStart.intValue; + float max = m_MipMapFadeDistanceEnd.intValue; + EditorGUILayout.MinMaxSlider(s_Styles.mipmapFadeOut, ref min, ref max, minLimit, maxLimit); + if (EditorGUI.EndChangeCheck()) + { + m_MipMapFadeDistanceStart.intValue = Mathf.RoundToInt(min); + m_MipMapFadeDistanceEnd.intValue = Mathf.RoundToInt(max); + } } EditorGUI.indentLevel--; } @@ -1310,13 +1416,7 @@ void TextureSettingsGUI() } // Filter mode - EditorGUI.BeginChangeCheck(); - EditorGUI.showMixedValue = m_FilterMode.hasMultipleDifferentValues; - FilterMode filter = (FilterMode)m_FilterMode.intValue; - filter = (FilterMode)EditorGUILayout.IntPopup(s_Styles.filterMode, (int)filter, s_Styles.filterModeOptions, m_FilterModeOptions); - EditorGUI.showMixedValue = false; - if (EditorGUI.EndChangeCheck()) - m_FilterMode.intValue = (int)filter; + EditorGUILayout.IntPopup(m_FilterMode, s_Styles.filterModeOptions, m_FilterModeOptions, s_Styles.filterMode); // Aniso bool showAniso = (FilterMode)m_FilterMode.intValue != FilterMode.Point @@ -1325,17 +1425,13 @@ void TextureSettingsGUI() && (TextureImporterShape)m_TextureShape.intValue != TextureImporterShape.Texture3D; using (new EditorGUI.DisabledScope(!showAniso)) { - EditorGUI.BeginChangeCheck(); - EditorGUI.showMixedValue = m_Aniso.hasMultipleDifferentValues; - int aniso = EditorGUILayout.IntSlider("Aniso Level", m_Aniso.intValue, 0, 16); - EditorGUI.showMixedValue = false; - if (EditorGUI.EndChangeCheck()) - m_Aniso.intValue = aniso; + EditorGUILayout.IntSlider(m_Aniso, 0, 16, "Aniso Level"); - TextureInspector.DoAnisoGlobalSettingNote(aniso); + TextureInspector.DoAnisoGlobalSettingNote(m_Aniso.intValue); } } + public override void OnInspectorGUI() { serializedObject.Update(); @@ -1348,13 +1444,13 @@ public override void OnInspectorGUI() EditorGUILayout.Space(); // Texture Usage + int oldTextureType = m_TextureType.intValue; EditorGUI.BeginChangeCheck(); - EditorGUI.showMixedValue = m_TextureType.hasMultipleDifferentValues; - int newTextureType = EditorGUILayout.IntPopup(s_Styles.textureTypeTitle, m_TextureType.intValue, s_Styles.textureTypeOptions, s_Styles.textureTypeValues); - EditorGUI.showMixedValue = false; + EditorGUILayout.IntPopup(m_TextureType, s_Styles.textureTypeOptions, s_Styles.textureTypeValues, s_Styles.textureTypeTitle); // (case 857001) EndChangeCheck will return true even if the same value is selected. // Consequently the sprite will be reset to Single mode and looks very confusing to the user. - if (EditorGUI.EndChangeCheck() && (m_TextureType.intValue != newTextureType)) + int newTextureType = m_TextureType.intValue; + if (EditorGUI.EndChangeCheck() && (oldTextureType != newTextureType)) { // please note that in GetSerializedPropertySettings() we will init TextureImporterSettings from current state // and at this point m_TextureType still has *old* value @@ -1362,9 +1458,9 @@ public override void OnInspectorGUI() // NB we do these weird things partly because ApplyTextureType has early out // NB hence we want settings to have *old* textureType when calling it TextureImporterSettings settings = GetSerializedPropertySettings(); + settings.textureType = (TextureImporterType)oldTextureType; settings.ApplyTextureType((TextureImporterType)newTextureType); settings.textureType = (TextureImporterType)newTextureType; - m_TextureType.intValue = newTextureType; SetSerializedPropertySettings(settings); @@ -1375,12 +1471,7 @@ public override void OnInspectorGUI() int[] shapeArray = s_Styles.textureShapeValuesDictionnary[m_TextureTypeGUIElements[(int)newTextureType].shapeCaps]; using (new EditorGUI.DisabledScope(shapeArray.Length == 1 || m_TextureType.intValue == (int)TextureImporterType.Cookie)) // Cookie is a special case because the cookie type drives the shape of the texture { - EditorGUI.BeginChangeCheck(); - EditorGUI.showMixedValue = m_TextureShape.hasMultipleDifferentValues; - int newTextureShape = EditorGUILayout.IntPopup(s_Styles.textureShape, m_TextureShape.intValue, s_Styles.textureShapeOptionsDictionnary[m_TextureTypeGUIElements[(int)newTextureType].shapeCaps], s_Styles.textureShapeValuesDictionnary[m_TextureTypeGUIElements[(int)newTextureType].shapeCaps]); - EditorGUI.showMixedValue = false; - if (EditorGUI.EndChangeCheck()) - m_TextureShape.intValue = newTextureShape; + EditorGUILayout.IntPopup(m_TextureShape, s_Styles.textureShapeOptionsDictionnary[m_TextureTypeGUIElements[(int)newTextureType].shapeCaps], s_Styles.textureShapeValuesDictionnary[m_TextureTypeGUIElements[(int)newTextureType].shapeCaps], s_Styles.textureShape); } // Switching usage can lead to a subset of the current available shapes. @@ -1436,6 +1527,7 @@ public override void OnInspectorGUI() TextureSettingsGUI(); BaseTextureImportPlatformSettings.InitPlatformSettings(m_PlatformSettings.ConvertAll(x => x as BaseTextureImportPlatformSettings)); + m_PlatformSettings.ForEach(settings => settings.CacheSerializedProperties(m_PlatformSettingsArrProp)); GUILayout.Space(10); //Show platform grouping @@ -1553,23 +1645,26 @@ static bool CountImportersWithHDR(Object[] importers, out int count) } } - void SetCookieMode(CookieMode cm) + void SetCookieLightTypeDefaults(TextureImporterCookieLightType cookieLightType) { - switch (cm) + // Note that, out of all of these, only the TextureShape is truly strongly enforced. + // The other settings are nothing more than recommended defaults and can be modified + // by the user at any time. + switch (cookieLightType) { - case CookieMode.Spot: + case TextureImporterCookieLightType.Spot: m_BorderMipMap.intValue = 1; m_WrapU.intValue = m_WrapV.intValue = m_WrapW.intValue = (int)TextureWrapMode.Clamp; m_GenerateCubemap.intValue = (int)TextureImporterGenerateCubemap.AutoCubemap; m_TextureShape.intValue = (int)TextureImporterShape.Texture2D; break; - case CookieMode.Point: + case TextureImporterCookieLightType.Point: m_BorderMipMap.intValue = 0; m_WrapU.intValue = m_WrapV.intValue = m_WrapW.intValue = (int)TextureWrapMode.Clamp; m_GenerateCubemap.intValue = (int)TextureImporterGenerateCubemap.Spheremap; m_TextureShape.intValue = (int)TextureImporterShape.TextureCube; break; - case CookieMode.Directional: + case TextureImporterCookieLightType.Directional: m_BorderMipMap.intValue = 0; m_WrapU.intValue = m_WrapV.intValue = m_WrapW.intValue = (int)TextureWrapMode.Repeat; m_GenerateCubemap.intValue = (int)TextureImporterGenerateCubemap.AutoCubemap; @@ -1599,14 +1694,24 @@ private static bool IsPowerOfTwo(int f) return ((f & (f - 1)) == 0); } - bool CanReadWrite() + bool ShouldShowWarningForReadWrite() { - foreach (TextureImportPlatformSettings ps in m_PlatformSettings) + const int maxRecommendedSize = 536870912; // 512 MB + + if (textureInspector && textureInspector.targets != null) { - if (ps.model.platformTextureSettings.maxTextureSize > TextureImporter.MaxTextureSizeAllowedForReadable) - return false; + foreach (Texture texture in textureInspector.targets) + { + if (texture) + { + if (TextureUtil.GetStorageMemorySizeLong(texture) > maxRecommendedSize) + { + return true; + } + } + } } - return true; + return false; } public virtual void BuildTargetList() @@ -1669,6 +1774,12 @@ public override void DrawPreview(Rect previewArea) VirtualTexturing.System.Update(); } + internal override void PostSerializedObjectCreation() + { + base.PostSerializedObjectCreation(); + Initialize(); + } + private void RefreshPreviewChannelSelection() { //If the Preview is null or NOT the TextureInspector (e.g. ObjectPreview) then return, we do not need to refresh the preview channel selection diff --git a/Editor/Mono/Inspector/AddComponent/AddComponentGUI.cs b/Editor/Mono/Inspector/AddComponent/AddComponentGUI.cs index b143411312..b82002d7c6 100644 --- a/Editor/Mono/Inspector/AddComponent/AddComponentGUI.cs +++ b/Editor/Mono/Inspector/AddComponent/AddComponentGUI.cs @@ -13,17 +13,11 @@ internal class AddComponentGUI : AdvancedDropdownGUI private static class Styles { public static GUIStyle itemStyle = "DD LargeItemStyle"; - internal static GUIStyle lineStyleFaint = new GUIStyle("DD LargeItemStyle"); - static Styles() - { - float val = EditorGUIUtility.isProSkin ? 0.5f : 0.25f; - var color = new Color(val, val, val, 1f); - lineStyleFaint.active.textColor = color; - lineStyleFaint.focused.textColor = color; - lineStyleFaint.hover.textColor = color; - lineStyleFaint.normal.textColor = color; - } + const string k_includeNamespaceProSkin = "{0} ({1})"; + const string k_includeNamespace = "{0} ({1})"; + + public static string includeNamespaceString => EditorGUIUtility.isProSkin ? k_includeNamespaceProSkin : k_includeNamespace; } private Vector2 m_IconSize = new Vector2(16, 16); @@ -39,22 +33,17 @@ public AddComponentGUI(AdvancedDropdownDataSource dataSource, Action', ':', '"', '|', '?', '*', (char)0}; private readonly char[] kPathSepChars = new char[] {'/', '\\'}; private readonly string kResourcesTemplatePath = "Resources/ScriptTemplates"; + private static System.CodeDom.Compiler.CodeDomProvider s_CSharpDOMProvider; private string m_Directory = string.Empty; @@ -34,6 +36,7 @@ public NewScriptDropdownItem() internal bool CanCreate() { return m_ClassName.Length > 0 && + !ClassNameIsInvalidIdentifier() && !ClassNameIsInvalid() && !File.Exists(TargetPath()) && !ClassAlreadyExists() && @@ -46,8 +49,11 @@ public string GetError() var blockReason = string.Empty; if (m_ClassName != string.Empty) { + if (ClassNameIsInvalid()) blockReason = "The script name may only consist of a-z, A-Z, 0-9, _."; + else if (ClassNameIsInvalidIdentifier()) + blockReason = $"The script name is invalid in C#: {m_ClassName}."; else if (File.Exists(TargetPath())) blockReason = "A script called \"" + m_ClassName + "\" already exists at that path."; else if (ClassAlreadyExists()) @@ -94,7 +100,21 @@ private string TargetDir() private bool ClassNameIsInvalid() { - return !System.CodeDom.Compiler.CodeGenerator.IsValidLanguageIndependentIdentifier(m_ClassName); + return !System.CodeDom.Compiler.CodeGenerator.IsValidLanguageIndependentIdentifier(className); + } + + private bool ClassNameIsInvalidIdentifier() + { + return !IsValidIdentifier(m_ClassName); + } + + internal static bool IsValidIdentifier(string className) + { + if (s_CSharpDOMProvider == null) + { + s_CSharpDOMProvider = new CSharpCodeProvider(); + } + return s_CSharpDOMProvider.IsValidIdentifier(className); } private bool ClassExists(string className) diff --git a/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownDataSource.cs b/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownDataSource.cs index a0e5cf80d6..df35ec80ee 100644 --- a/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownDataSource.cs +++ b/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownDataSource.cs @@ -13,11 +13,13 @@ internal abstract class AdvancedDropdownDataSource private AdvancedDropdownItem m_MainTree; private AdvancedDropdownItem m_SearchTree; + private AdvancedDropdownItem m_CurrentContextTree; private List m_SelectedIDs = new List(); public AdvancedDropdownItem mainTree { get { return m_MainTree; }} public AdvancedDropdownItem searchTree { get { return m_SearchTree; }} public List selectedIDs { get { return m_SelectedIDs; }} + public bool CurrentFolderContextualSearch { get; set; } protected AdvancedDropdownItem root { get { return m_MainTree; }} protected List m_SearchableElements; @@ -34,8 +36,13 @@ public void ReloadData() protected abstract AdvancedDropdownItem FetchData(); - public void RebuildSearch(string search) + public void RebuildSearch(string search, AdvancedDropdownItem currentTree) { + if (CurrentFolderContextualSearch && m_CurrentContextTree != currentTree && currentTree != searchTree) + { + m_SearchableElements = null; + } + m_CurrentContextTree = currentTree; m_SearchTree = Search(search); } @@ -126,7 +133,7 @@ protected virtual AdvancedDropdownItem Search(string searchString) void BuildSearchableElements() { m_SearchableElements = new List(); - BuildSearchableElements(root); + BuildSearchableElements(CurrentFolderContextualSearch && m_CurrentContextTree != null ? m_CurrentContextTree : root); } void BuildSearchableElements(AdvancedDropdownItem item) diff --git a/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownGUI.cs b/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownGUI.cs index 860a7b0db3..9598613756 100644 --- a/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownGUI.cs +++ b/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownGUI.cs @@ -34,6 +34,8 @@ internal static void LoadStyles() Debug.Assert(Event.current.type == EventType.Repaint && Styles.itemStyle != null); } + public static string k_SearchFieldName = "ComponentSearch"; + //This should ideally match line height private Vector2 s_IconSize = new Vector2(13, 13); private AdvancedDropdownDataSource m_DataSource; @@ -41,6 +43,7 @@ internal static void LoadStyles() internal Rect m_SearchRect; internal Rect m_HeaderRect; + internal Rect areaRect { get; set; } internal virtual float searchHeight => m_SearchRect.height; internal virtual float headerHeight => m_HeaderRect.height; internal virtual GUIStyle lineStyle => Styles.itemStyle; @@ -55,12 +58,21 @@ public AdvancedDropdownGUI(AdvancedDropdownDataSource dataSource) internal virtual void DrawItem(AdvancedDropdownItem item, string name, Texture2D icon, bool enabled, bool drawArrow, bool selected, bool hasSearch) { var content = item.content; - var imgTemp = content.image; - //we need to pretend we have an icon to calculate proper width in case + + // We need to pretend we have an icon to calculate proper width in case + var lastContentImage = content.image; if (content.image == null) content.image = Texture2D.whiteTexture; + + // Clamp the rect width var rect = GetItemRect(content); - content.image = imgTemp; + var maxWidth = areaRect.width - GUI.skin.verticalScrollbar.fixedWidth; // todo: find a way to detect if we have a scrollbar + if (drawArrow) + maxWidth -= Styles.rightArrow.fixedWidth + Styles.rightArrow.margin.right; + if (maxWidth > 0) + rect.width = Math.Min(rect.width, maxWidth); + + content.image = lastContentImage; if (!string.IsNullOrEmpty(content.tooltip) && rect.Contains(Event.current.mousePosition) && !string.Equals(content.tooltip, content.text, StringComparison.Ordinal)) @@ -69,7 +81,7 @@ internal virtual void DrawItem(AdvancedDropdownItem item, string name, Texture2D if (Event.current.type != EventType.Repaint) return; - var imageTemp = content.image; + lastContentImage = content.image; if (m_DataSource.selectedIDs.Any() && m_DataSource.selectedIDs.Contains(item.id)) { var checkMarkRect = new Rect(rect); @@ -78,7 +90,7 @@ internal virtual void DrawItem(AdvancedDropdownItem item, string name, Texture2D rect.x += iconSize.x + 1; rect.width -= iconSize.x + 1; - //don't draw the icon if the check mark is present + // Don't draw the icon if the check mark is present content.image = null; } else if (content.image == null) @@ -87,20 +99,28 @@ internal virtual void DrawItem(AdvancedDropdownItem item, string name, Texture2D rect.x += iconSize.x + 1; rect.width -= iconSize.x + 1; } + EditorGUI.BeginDisabled(!enabled); + var lastStyleClipping = lineStyle.clipping; + lineStyle.clipping = TextClipping.Clip; + DrawItemContent(item, rect, content, false, false, selected, selected); - content.image = imageTemp; + + lineStyle.clipping = lastStyleClipping; + content.image = lastContentImage; + if (drawArrow) { var yOffset = (lineStyle.fixedHeight - Styles.rightArrow.fixedHeight) / 2; Rect arrowRect = new Rect( - rect.xMax - Styles.rightArrow.fixedWidth - Styles.rightArrow.margin.right, + rect.xMax, rect.y + yOffset, Styles.rightArrow.fixedWidth, Styles.rightArrow.fixedHeight); Styles.rightArrow.Draw(arrowRect, false, false, false, false); } + EditorGUI.EndDisabled(); } @@ -153,7 +173,7 @@ internal void DrawSearchField(bool isSearchFieldDisabled, string searchString, A using (new EditorGUI.DisabledScope(isSearchFieldDisabled)) { - GUI.SetNextControlName("ComponentSearch"); + GUI.SetNextControlName(k_SearchFieldName); var newSearch = DrawSearchFieldControl(searchString); diff --git a/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownWindow.cs b/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownWindow.cs index 6fdf2c9ca8..cb1c5cfaf7 100644 --- a/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownWindow.cs +++ b/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownWindow.cs @@ -56,7 +56,7 @@ protected internal string searchString set { m_Search = value; - m_DataSource.RebuildSearch(m_Search); + m_DataSource.RebuildSearch(m_Search, m_CurrentlyRenderedTree); m_CurrentlyRenderedTree = m_DataSource.mainTree; if (hasSearch) { @@ -193,7 +193,7 @@ protected virtual Vector2 CalculateWindowSize(Rect buttonRect) size.y = Mathf.Clamp(size.y, minSize.y, maxSize.y); - var fitRect = ContainerWindow.FitRectToScreen(new Rect(buttonRect.x, buttonRect.y, size.x, size.y), true, true); + var fitRect = ContainerWindow.FitRectToMouseScreen(new Rect(buttonRect.x, buttonRect.y, size.x, size.y), true, null); // If the scrollbar is visible, we want to add extra space to compensate it if (fitRect.height < size.y) size.x += GUI.skin.verticalScrollbar.fixedWidth; @@ -273,7 +273,7 @@ private void OnDirtyList() m_DataSource.ReloadData(); if (hasSearch) { - m_DataSource.RebuildSearch(searchString); + m_DataSource.RebuildSearch(searchString, m_CurrentlyRenderedTree); if (state.GetSelectedIndex(m_CurrentlyRenderedTree) < 0) { state.SetSelectedIndex(m_CurrentlyRenderedTree, 0); @@ -338,14 +338,15 @@ private void HandleKeyboard() } // Do these if we're not in search mode - if (!hasSearch) + if (!hasSearch && + (string.IsNullOrEmpty(GUI.GetNameOfFocusedControl()) || GUI.GetNameOfFocusedControl() == AdvancedDropdownGUI.k_SearchFieldName)) { if (evt.keyCode == KeyCode.LeftArrow || evt.keyCode == KeyCode.Backspace) { GoToParent(); evt.Use(); } - if (evt.keyCode == KeyCode.RightArrow) + else if (evt.keyCode == KeyCode.RightArrow) { var idx = m_State.GetSelectedIndex(m_CurrentlyRenderedTree); if (idx > -1 && m_CurrentlyRenderedTree.children.ElementAt(idx).children.Any()) @@ -354,7 +355,7 @@ private void HandleKeyboard() } evt.Use(); } - if (evt.keyCode == KeyCode.Escape) + else if (evt.keyCode == KeyCode.Escape) { Close(); evt.Use(); @@ -391,6 +392,7 @@ private void DrawDropdown(float anim, AdvancedDropdownItem group) areaPosition.y += kBorderThickness; areaPosition.height -= kBorderThickness * 2; areaPosition.width -= kBorderThickness * 2; + m_Gui.areaRect = areaPosition; GUILayout.BeginArea(m_Gui.GetAnimRect(areaPosition, anim)); // Header @@ -494,6 +496,7 @@ private void DrawList(AdvancedDropdownItem item) protected void GoToParent() { + GUI.FocusControl(""); if (m_ViewsStack.Count == 0) return; m_LastTime = DateTime.Now.Ticks; @@ -507,6 +510,7 @@ protected void GoToParent() private void GoToChild() { + GUI.FocusControl(""); m_ViewsStack.Push(m_CurrentlyRenderedTree); m_LastTime = DateTime.Now.Ticks; if (m_NewAnimTarget < 0) diff --git a/Editor/Mono/Inspector/AdvancedDropdown/EditorGUI/StatelessAdvancedDropdown.cs b/Editor/Mono/Inspector/AdvancedDropdown/EditorGUI/StatelessAdvancedDropdown.cs index 25031500d4..e62c5d9312 100644 --- a/Editor/Mono/Inspector/AdvancedDropdown/EditorGUI/StatelessAdvancedDropdown.cs +++ b/Editor/Mono/Inspector/AdvancedDropdown/EditorGUI/StatelessAdvancedDropdown.cs @@ -181,7 +181,7 @@ public static Enum DoEnumMaskPopup(Rect rect, Enum options, GUIStyle style) { var enumData = EnumDataUtility.GetCachedEnumData(options.GetType()); var optionValue = EnumDataUtility.EnumFlagsToInt(enumData, options); - MaskFieldGUI.GetMenuOptions(optionValue, enumData.displayNames, enumData.flagValues, out var buttonText, out var buttonTextMixed, out var optionNames, out var optionMaskValues, out var selectedOptions); + MaskFieldGUI.GetMenuOptions(optionValue, enumData.displayNames, enumData.flagValues, out var buttonText, out var buttonTextMixed, out _, out _, out _); var id = EditorGUIUtility.GetControlID("AdvancedDropdown".GetHashCode(), FocusType.Keyboard, rect); @@ -244,7 +244,7 @@ static int DoMaskField(Rect rect, int mask, string[] displayedOptions, GUIStyle for (int i = 0; i < flagValues.Length; ++i) flagValues[i] = (1 << i); - MaskFieldGUI.GetMenuOptions(mask, displayedOptions, flagValues, out var buttonText, out var buttonTextMixed, out var optionNames, out var optionMaskValues, out var selectedOptions); + MaskFieldGUI.GetMenuOptions(mask, displayedOptions, flagValues, out var buttonText, out var buttonTextMixed, out _, out _, out _); var id = EditorGUIUtility.GetControlID("AdvancedDropdown".GetHashCode(), FocusType.Keyboard, rect); diff --git a/Editor/Mono/Inspector/AnimationClipEditor.cs b/Editor/Mono/Inspector/AnimationClipEditor.cs index 09dd5b3a6b..4379379277 100644 --- a/Editor/Mono/Inspector/AnimationClipEditor.cs +++ b/Editor/Mono/Inspector/AnimationClipEditor.cs @@ -1587,6 +1587,14 @@ private void MuscleClipGUI() CurveGUI(); } + if(m_ClipInfo != null && m_ClipInfo.hasAdditiveReferencePose && m_ClipInfo.GetCurveCount() > 0 && + (m_ClipInfo.additiveReferencePoseFrame < m_ClipInfo.firstFrame || m_ClipInfo.additiveReferencePoseFrame > m_ClipInfo.lastFrame)) + { + EditorGUILayout.HelpBox("Additional curves will be compared to zero values instead of the source clip's curves. "+ + "This is because the source clip doesn't include these specific curves. " + + "To ensure accurate comparisons, consider using a reference pose frame within the clip's start and end frames.", MessageType.Warning); + } + if (m_ClipInfo != null) { wasChanged = GUI.changed; @@ -1780,6 +1788,7 @@ internal class EventManipulationHandler private AnimationWindowEvent[] m_Events; private TimeArea m_Timeline; + private AnimationEventEditorState m_EventEditorState = new(); public EventManipulationHandler(TimeArea timeArea) { @@ -1828,11 +1837,17 @@ public bool HandleEventManipulation(Rect rect, ref AnimationEvent[] events, Anim sharedOffset = Mathf.FloorToInt(Mathf.Max(0, spread - (eventMarker.width - 1) * (sharedLeft))); } + // UUM-49717 + // Depending on the resolution and the scale of the display, the icon size could be greater than the visible height of the timeline. + // We divide it so that it fits. + float absRectHeight = Mathf.Abs(rect.height); + int divider = absRectHeight > 0 && absRectHeight < eventMarker.height ? Mathf.CeilToInt(eventMarker.height / absRectHeight) : 1; + Rect r = new Rect( - keypos + sharedOffset - eventMarker.width / 2, + keypos + sharedOffset - eventMarker.width / (2 * divider), (rect.height - 10) * (float)(sharedLeft - shared + 1) / Mathf.Max(1, shared - 1), - eventMarker.width, - eventMarker.height); + eventMarker.width / divider, + eventMarker.height / divider); hitRects[i] = r; drawRects[i] = r; @@ -2062,7 +2077,7 @@ public void Draw(Rect window) { EditorGUI.indentLevel++; if (m_Events != null && m_Events.Length > 0) - AnimationWindowEventInspector.OnEditAnimationEvents(m_Events); + AnimationWindowEventInspector.OnEditAnimationEvents(m_Events, m_EventEditorState); else AnimationWindowEventInspector.OnDisabledAnimationEvent(); diff --git a/Editor/Mono/Inspector/AssemblyDefinitionImporterInspector.cs b/Editor/Mono/Inspector/AssemblyDefinitionImporterInspector.cs index 27719993f8..5ca9eef4c9 100644 --- a/Editor/Mono/Inspector/AssemblyDefinitionImporterInspector.cs +++ b/Editor/Mono/Inspector/AssemblyDefinitionImporterInspector.cs @@ -692,7 +692,7 @@ private void DrawPrecompiledReferenceListElement(Rect rect, int index, bool isac EditorGUI.showMixedValue = false; } - [MenuItem("CONTEXT/AssemblyDefinitionImporter/Reset")] + [MenuItem("CONTEXT/AssemblyDefinitionImporter/Reset", secondaryPriority = 8)] internal static void ContextReset(MenuCommand command) { var templatePath = AssetsMenuUtility.GetScriptTemplatePath(ScriptTemplate.AsmDef_NewAssembly); diff --git a/Editor/Mono/Inspector/AssemblyDefinitionReferenceImporterInspector.cs b/Editor/Mono/Inspector/AssemblyDefinitionReferenceImporterInspector.cs index fff4954108..f07e18192d 100644 --- a/Editor/Mono/Inspector/AssemblyDefinitionReferenceImporterInspector.cs +++ b/Editor/Mono/Inspector/AssemblyDefinitionReferenceImporterInspector.cs @@ -126,7 +126,7 @@ protected override void InitializeExtraDataInstance(UnityEngine.Object extraData } } - [MenuItem("CONTEXT/AssemblyDefinitionReferenceImporter/Reset")] + [MenuItem("CONTEXT/AssemblyDefinitionReferenceImporter/Reset", secondaryPriority = 9)] internal static void ContextReset(MenuCommand command) { var templatePath = AssetsMenuUtility.GetScriptTemplatePath(ScriptTemplate.AsmRef_NewAssemblyReference); diff --git a/Editor/Mono/Inspector/AudioClipInspector.cs b/Editor/Mono/Inspector/AudioClipInspector.cs index 9d469b68c6..6ed9c724cc 100644 --- a/Editor/Mono/Inspector/AudioClipInspector.cs +++ b/Editor/Mono/Inspector/AudioClipInspector.cs @@ -28,6 +28,9 @@ internal class AudioClipInspector : Editor static GUIContent s_AutoPlayIcon; static GUIContent s_LoopIcon; + static private string s_PreviewDisabledMessage = "AudioClip preview not available when Unity Audio is disabled in Project Settings"; + static private string s_TrPreviewDisabledMessage = L10n.Tr(s_PreviewDisabledMessage); + static Texture2D s_DefaultIcon; private Material m_HandleLinesMaterial; @@ -48,9 +51,11 @@ static void Init() s_AutoPlay = EditorPrefs.GetBool("AutoPlayAudio", false); s_Loop = false; - s_AutoPlayIcon = EditorGUIUtility.TrIconContent("preAudioAutoPlayOff", "Turn Auto Play on/off"); - s_PlayIcon = EditorGUIUtility.TrIconContent("PlayButton", "Play"); - s_LoopIcon = EditorGUIUtility.TrIconContent("preAudioLoopOff", "Loop on/off"); + var unityAudioDisabled = AudioSettings.unityAudioDisabled; + + s_AutoPlayIcon = EditorGUIUtility.TrIconContent("preAudioAutoPlayOff", unityAudioDisabled ? s_PreviewDisabledMessage : "Turn Auto Play on/off"); + s_PlayIcon = EditorGUIUtility.TrIconContent("PlayButton", unityAudioDisabled ? s_PreviewDisabledMessage : "Play"); + s_LoopIcon = EditorGUIUtility.TrIconContent("preAudioLoopOff", unityAudioDisabled ? s_PreviewDisabledMessage : "Loop on/off"); s_DefaultIcon = EditorGUIUtility.LoadIcon("Profiler.Audio"); } @@ -118,6 +123,7 @@ public override void OnPreviewSettings() AudioClip clip = target as AudioClip; m_MultiEditing = targets.Length > 1; + using (new EditorGUI.DisabledScope(AudioSettings.unityAudioDisabled)) { using (new EditorGUI.DisabledScope(m_MultiEditing && !playing)) { @@ -297,8 +303,13 @@ public override void OnPreviewGUI(Rect r, GUIStyle background) s_PlayFirst = false; } + if (AudioSettings.unityAudioDisabled) + { + EditorGUILayout.HelpBox(s_TrPreviewDisabledMessage, MessageType.Info); + } + // force update GUI - if (playing) + if (playing && GUIView.current != null) GUIView.current.Repaint(); } diff --git a/Editor/Mono/Inspector/AudioMixerGroupEditor.cs b/Editor/Mono/Inspector/AudioMixerGroupEditor.cs index 2d2f5b98c1..8250d556b1 100644 --- a/Editor/Mono/Inspector/AudioMixerGroupEditor.cs +++ b/Editor/Mono/Inspector/AudioMixerGroupEditor.cs @@ -66,7 +66,7 @@ internal override Rect DrawHeaderHelpAndSettingsGUI(Rect r) } // Add item to the context menu of the AudioMixerGroupController inspector header - [MenuItem("CONTEXT/AudioMixerGroupController/Copy all effect settings to all snapshots")] + [MenuItem("CONTEXT/AudioMixerGroupController/Copy all effect settings to all snapshots", secondaryPriority = 10)] static void CopyAllEffectToSnapshots(MenuCommand command) { AudioMixerGroupController group = command.context as AudioMixerGroupController; @@ -78,7 +78,7 @@ static void CopyAllEffectToSnapshots(MenuCommand command) controller.CopyAllSettingsToAllSnapshots(group, controller.TargetSnapshot); } - [MenuItem("CONTEXT/AudioMixerGroupController/Toggle CPU usage display (only available on first editor instance)")] + [MenuItem("CONTEXT/AudioMixerGroupController/Toggle CPU usage display (only available on first editor instance)", secondaryPriority = 11)] static void ShowCPUUsage(MenuCommand command) { bool value = EditorPrefs.GetBool(kPrefKeyForShowCpuUsage, false); diff --git a/Editor/Mono/Inspector/AvatarPreview.cs b/Editor/Mono/Inspector/AvatarPreview.cs index b3d86349bb..e4585d9e18 100644 --- a/Editor/Mono/Inspector/AvatarPreview.cs +++ b/Editor/Mono/Inspector/AvatarPreview.cs @@ -179,6 +179,7 @@ public Vector3 rootPosition private const float kFloorTextureScale = 4; private const float kFloorAlpha = 0.5f; private const float kFloorShadowAlpha = 0.3f; + private const float kDefaultIntensity = 1.4f; private const int kDefaultLayer = 0; // Must match kDefaultLayer in TagTypes.h @@ -434,9 +435,9 @@ private PreviewRenderUtility previewUtility m_PreviewUtility.camera.allowHDR = false; m_PreviewUtility.camera.allowMSAA = false; m_PreviewUtility.ambientColor = new Color(.1f, .1f, .1f, 0); - m_PreviewUtility.lights[0].intensity = 1.4f; + m_PreviewUtility.lights[0].intensity = kDefaultIntensity; m_PreviewUtility.lights[0].transform.rotation = Quaternion.Euler(40f, 40f, 0); - m_PreviewUtility.lights[1].intensity = 1.4f; + m_PreviewUtility.lights[1].intensity = kDefaultIntensity; } return m_PreviewUtility; } @@ -714,6 +715,14 @@ public void DoRenderPreview(Rect previewRect, GUIStyle background) Matrix4x4 shadowMatrix; RenderTexture shadowMap = RenderPreviewShadowmap(previewUtility.lights[0], m_BoundingVolumeScale / 2, bodyPosition, floorPos, out shadowMatrix); + // SRP might initialize the light settings during the first frame of rendering + // (e.g HDRP is overriding the intensity value during 'InitDefaultHDAdditionalLightData'). + // So this call is necessary to avoid a flickering when selecting an animation clip. + if (previewUtility.lights[0].intensity != kDefaultIntensity || previewUtility.lights[0].intensity != kDefaultIntensity) + { + SetupPreviewLightingAndFx(probe); + } + float tempZoomFactor = (is2D ? 1.0f : m_ZoomFactor); // Position camera previewUtility.camera.orthographic = is2D; @@ -779,9 +788,9 @@ public void DoRenderPreview(Rect previewRect, GUIStyle background) private void SetupPreviewLightingAndFx(SphericalHarmonicsL2 probe) { - previewUtility.lights[0].intensity = 1.4f; + previewUtility.lights[0].intensity = kDefaultIntensity; previewUtility.lights[0].transform.rotation = Quaternion.Euler(40f, 40f, 0); - previewUtility.lights[1].intensity = 1.4f; + previewUtility.lights[1].intensity = kDefaultIntensity; RenderSettings.ambientMode = AmbientMode.Custom; RenderSettings.ambientLight = new Color(0.1f, 0.1f, 0.1f, 1.0f); RenderSettings.ambientProbe = probe; diff --git a/Editor/Mono/Inspector/AvatarPreviewSelection.cs b/Editor/Mono/Inspector/AvatarPreviewSelection.cs index cafd672df1..25b12d5bc5 100644 --- a/Editor/Mono/Inspector/AvatarPreviewSelection.cs +++ b/Editor/Mono/Inspector/AvatarPreviewSelection.cs @@ -30,7 +30,6 @@ static public void SetPreview(ModelImporterAnimationType type, GameObject go) if (instance.m_PreviewModels[(int)type] != go) { instance.m_PreviewModels[(int)type] = go; - instance.Save(false); } } diff --git a/Editor/Mono/Inspector/BlendTreeInspector.cs b/Editor/Mono/Inspector/BlendTreeInspector.cs index ad8fb0dea1..35e92099ae 100644 --- a/Editor/Mono/Inspector/BlendTreeInspector.cs +++ b/Editor/Mono/Inspector/BlendTreeInspector.cs @@ -1856,11 +1856,7 @@ void SetNewThresholdAndPosition(int index) public override bool HasPreviewGUI() { - if (m_PreviewBlendTree != null) - { - return m_PreviewBlendTree.HasPreviewGUI(); - } - return false; + return true; } public override void OnPreviewSettings() @@ -2196,11 +2192,6 @@ public void TestForReset() } } - public bool HasPreviewGUI() - { - return true; - } - public void OnPreviewSettings() { m_AvatarPreview.DoPreviewSettings(); diff --git a/Editor/Mono/Inspector/CameraEditor.cs b/Editor/Mono/Inspector/CameraEditor.cs index a4fe8fd553..b70d8cbde1 100644 --- a/Editor/Mono/Inspector/CameraEditor.cs +++ b/Editor/Mono/Inspector/CameraEditor.cs @@ -12,6 +12,7 @@ using AnimatedBool = UnityEditor.AnimatedValues.AnimBool; using UnityEngine.Scripting; using UnityEditor.Modules; +using UnityEditor.Overlays; using UnityEditorInternal.VR; using Object = UnityEngine.Object; @@ -510,7 +511,6 @@ protected Camera previewCamera } } - private static bool IsDeferredRenderingPath(RenderingPath rp) { return rp == RenderingPath.DeferredShading; } private bool wantDeferredRendering @@ -574,6 +574,9 @@ public void OnEnable() SubsystemManager.GetSubsystemDescriptors(displayDescriptors); SubsystemManager.afterReloadSubsystems += OnReloadSubsystemsComplete; + + if(!SceneViewCameraOverlay.forceDisable) + CreatePreviewOverlay(c); } public void OnDestroy() @@ -584,6 +587,7 @@ public void OnDestroy() public void OnDisable() { + SceneViewCameraOverlay.DisableCameraOverlay((Camera)target); m_ShowBGColorOptions.valueChanged.RemoveListener(Repaint); m_ShowOrthoOptions.valueChanged.RemoveListener(Repaint); m_ShowTargetEyeOption.valueChanged.RemoveListener(Repaint); @@ -762,12 +766,14 @@ public override void OnInspectorGUI() } // marked obsolete @karlh 2021/02/13 - [Obsolete("OnOverlayGUI is obsolete, use Overlay to create a preview.")] + [Obsolete("OnOverlayGUI is obsolete. Override CreatePreviewOverlay to create a preview.")] [EditorBrowsable(EditorBrowsableState.Never)] public virtual void OnOverlayGUI(Object target, SceneView sceneView) { } + public virtual Overlay CreatePreviewOverlay(Camera cam) => SceneViewCameraOverlay.GetOrCreateCameraOverlay(cam); + [RequiredByNativeCode] internal static float GetGameViewAspectRatio() { diff --git a/Editor/Mono/Inspector/CameraEditorUtils.cs b/Editor/Mono/Inspector/CameraEditorUtils.cs index d1430a24b5..bf8b8b7768 100644 --- a/Editor/Mono/Inspector/CameraEditorUtils.cs +++ b/Editor/Mono/Inspector/CameraEditorUtils.cs @@ -183,7 +183,7 @@ public static bool TryGetSensorGateFrustum(Camera camera, Vector3[] near, Vector if (near != null) { Vector2 planeSize; - planeSize.y = 2.0f * camera.nearClipPlane * Mathf.Tan(Mathf.Deg2Rad * camera.fieldOfView * 0.5f); + planeSize.y = camera.nearClipPlane * Mathf.Tan(Mathf.Deg2Rad * camera.fieldOfView * 0.5f); planeSize.x = planeSize.y * camera.sensorSize.x / camera.sensorSize.y; Vector3 rightOffset = camera.gameObject.transform.right * planeSize.x; diff --git a/Editor/Mono/Inspector/CameraOverlay.cs b/Editor/Mono/Inspector/CameraOverlay.cs index 944fd580b0..67d6646938 100644 --- a/Editor/Mono/Inspector/CameraOverlay.cs +++ b/Editor/Mono/Inspector/CameraOverlay.cs @@ -2,8 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License -using System; -using System.Linq; +using System.Collections.Generic; using UnityEditor.Overlays; using UnityEngine; using UnityEngine.Experimental.Rendering; @@ -12,9 +11,9 @@ namespace UnityEditor { - [Overlay(typeof(SceneView), k_OverlayID, k_DisplayName)] + [Overlay(id = k_OverlayID, displayName = k_DisplayName, defaultDisplay = true)] [Icon("Icons/Overlays/CameraPreview.png")] - class SceneViewCameraOverlay : TransientSceneViewOverlay + class SceneViewCameraOverlay : IMGUIOverlay { internal static bool forceDisable = false; @@ -30,61 +29,65 @@ class SceneViewCameraOverlay : TransientSceneViewOverlay Camera previewCamera => m_PreviewCamera; RenderTexture m_PreviewTexture; - int m_QualitySettingsAntiAliasing = -1; - public SceneViewCameraOverlay() + static Dictionary s_CameraOverlays = new Dictionary(); + + SceneViewCameraOverlay(Camera camera) { minSize = new Vector2(40, 40); maxSize = new Vector2(4000, 4000); - sizeOverridenChanged += UpdateSize; - - OnSelectionChanged(); - } - - public override bool visible => !forceDisable && selectedCamera != null; - - void OnSelectionChanged() - { - m_SelectedCamera = null; - - if (Selection.activeGameObject == null) - return; - - if (!Selection.activeGameObject.TryGetComponent(out m_SelectedCamera)) - m_SelectedCamera = Selection.GetFiltered(SelectionMode.TopLevel).FirstOrDefault(); - + m_SelectedCamera = camera; displayName = selectedCamera == null || string.IsNullOrEmpty(selectedCamera.name) ? "Camera Preview" : selectedCamera.name; - } - void OnPlayModeStateChanged(PlayModeStateChange playModeStateChange) - { - EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; - OnSelectionChanged(); + s_CameraOverlays.Add(camera, (this ,1)); } public override void OnCreated() { - Selection.selectionChanged += OnSelectionChanged; - - if (!EditorApplication.isPlaying && EditorApplication.isPlayingOrWillChangePlaymode) - EditorApplication.playModeStateChanged += OnPlayModeStateChanged; - m_PreviewCamera = EditorUtility.CreateGameObjectWithHideFlags("Preview Camera", HideFlags.HideAndDontSave, typeof(Camera), typeof(Skybox)).GetComponent(); m_PreviewCamera.enabled = false; - OnSelectionChanged(); } public override void OnWillBeDestroyed() { - Selection.selectionChanged -= OnSelectionChanged; - if (m_PreviewCamera != null) - UnityObject.DestroyImmediate(m_PreviewCamera.gameObject, true); + UnityObject.DestroyImmediate(m_PreviewCamera.gameObject, true); + } + + public static SceneViewCameraOverlay GetOrCreateCameraOverlay(Camera camera) + { + if (s_CameraOverlays.ContainsKey(camera)) + { + var value = s_CameraOverlays[camera]; + value.count += 1; + s_CameraOverlays[camera] = value; + return value.overlay; + } + + var overlay = new SceneViewCameraOverlay(camera); + SceneView.AddOverlayToActiveView(overlay); + return overlay; + } + + public static void DisableCameraOverlay(Camera cam) + { + if (s_CameraOverlays.ContainsKey(cam)) + { + var value = s_CameraOverlays[cam]; + value.count -= 1; + if (value.count == 0) + { + s_CameraOverlays.Remove(cam); + SceneView.RemoveOverlayFromActiveView(value.overlay); + } + else + s_CameraOverlays[cam] = value; + } } RenderTexture GetPreviewTextureWithSizeAndAA(int width, int height) @@ -96,7 +99,6 @@ RenderTexture GetPreviewTextureWithSizeAndAA(int width, int height) m_PreviewTexture.Release(); m_PreviewTexture = new RenderTexture(width, height, 24, SystemInfo.GetGraphicsFormat(DefaultFormat.LDR)); - m_QualitySettingsAntiAliasing = QualitySettings.antiAliasing; m_PreviewTexture.antiAliasing = antiAliasing; } return m_PreviewTexture; @@ -168,6 +170,10 @@ public override void OnGUI() } } + // Honor async shader compilation editor settings for this preview + bool oldShaderAsyncState = ShaderUtil.allowAsyncCompilation; + ShaderUtil.allowAsyncCompilation = EditorSettings.asyncShaderCompilation; + Vector2 previewSize = selectedCamera.targetTexture ? new Vector2(selectedCamera.targetTexture.width, selectedCamera.targetTexture.height) : PlayModeView.GetMainPlayModeViewTargetSize(); @@ -208,9 +214,12 @@ public override void OnGUI() RenderTexture.active = rt; } + previewCamera.cameraType = CameraType.Preview; previewCamera.Render(); Graphics.DrawTexture(previewRect, previewTexture, new Rect(0, 0, 1, 1), 0, 0, 0, 0, GUI.color, EditorGUIUtility.GUITextureBlit2SRGBMaterial); + + ShaderUtil.allowAsyncCompilation = oldShaderAsyncState; } } } diff --git a/Editor/Mono/Inspector/CanvasEditor.cs b/Editor/Mono/Inspector/CanvasEditor.cs index 796d8602cc..a05d419d15 100644 --- a/Editor/Mono/Inspector/CanvasEditor.cs +++ b/Editor/Mono/Inspector/CanvasEditor.cs @@ -28,6 +28,7 @@ internal class CanvasEditor : Editor SerializedProperty m_OverrideSorting; SerializedProperty m_ShaderChannels; SerializedProperty m_UpdateRectTransformForStandalone; + SerializedProperty m_VertexColorAlwaysGammaSpace; AnimBool m_OverlayMode; AnimBool m_CameraMode; @@ -47,6 +48,7 @@ private static class Styles public static GUIContent m_ShaderChannel = EditorGUIUtility.TrTextContent("Additional Shader Channels"); public static GUIContent pixelPerfectContent = EditorGUIUtility.TrTextContent("Pixel Perfect"); public static GUIContent standaloneRenderResize = EditorGUIUtility.TrTextContent("Resize Canvas", "For manual Camera.Render calls should the canvas resize to match the destination target."); + public static GUIContent vertexColorAlwaysGammaSpace = EditorGUIUtility.TrTextContent("Vertex Color Always In Gamma Color Space", "UI vertex colors are always in gamma color space disregard of the player settings"); } private bool m_AllNested = false; @@ -81,6 +83,7 @@ void OnEnable() m_PixelPerfectOverride = serializedObject.FindProperty("m_OverridePixelPerfect"); m_ShaderChannels = serializedObject.FindProperty("m_AdditionalShaderChannelsFlag"); m_UpdateRectTransformForStandalone = serializedObject.FindProperty("m_UpdateRectTransformForStandalone"); + m_VertexColorAlwaysGammaSpace = serializedObject.FindProperty("m_VertexColorAlwaysGammaSpace"); m_OverlayMode = new AnimBool(m_RenderMode.intValue == 0); m_OverlayMode.valueChanged.AddListener(Repaint); @@ -176,9 +179,13 @@ private void AllRootCanvases() EditorGUILayout.PropertyField(m_Camera, Styles.renderCamera); - if (m_Camera.objectReferenceValue == null) - EditorGUILayout.HelpBox("A Screen Space Canvas with no specified camera acts like an Overlay Canvas.", + if (Event.current.type != EventType.ExecuteCommand && // UUM-64603: make sure the HelpBox addition doesn't collide with other UI changes and causes UI to fail to update properly + m_Camera.objectReferenceValue == null) + { + EditorGUILayout.HelpBox( + "A Screen Space Canvas with no specified camera acts like an Overlay Canvas.", MessageType.Warning); + } if (m_Camera.objectReferenceValue != null) { @@ -198,9 +205,13 @@ private void AllRootCanvases() { EditorGUILayout.PropertyField(m_Camera, Styles.eventCamera); - if (m_Camera.objectReferenceValue == null) - EditorGUILayout.HelpBox("A World Space Canvas with no specified Event Camera may not register UI events correctly.", + if (Event.current.type != EventType.ExecuteCommand && // UUM-64603: make sure the HelpBox addition doesn't collide with other UI changes and causes UI to fail to update properly + m_Camera.objectReferenceValue == null) + { + EditorGUILayout.HelpBox( + "A World Space Canvas with no specified Event Camera may not register UI events correctly.", MessageType.Warning); + } EditorGUILayout.Space(); EditorGUILayout.SortingLayerField(Styles.m_SortingLayerStyle, m_SortingLayerID, EditorStyles.popup); @@ -310,8 +321,26 @@ public override void OnInspectorGUI() if (m_RenderMode.intValue == 0) // Overlay canvas { - if (((newShaderChannelValue & (int)AdditionalCanvasShaderChannels.Normal) | (newShaderChannelValue & (int)AdditionalCanvasShaderChannels.Tangent)) != 0) - EditorGUILayout.HelpBox("Shader channels Normal and Tangent are most often used with lighting, which an Overlay canvas does not support. Its likely these channels are not needed.", MessageType.Warning); + if (Event.current.type != EventType.ExecuteCommand && // UUM-64603: make sure the HelpBox addition doesn't collide with other UI changes and causes UI to fail to update properly + ((newShaderChannelValue & (int)AdditionalCanvasShaderChannels.Normal) | (newShaderChannelValue & (int)AdditionalCanvasShaderChannels.Tangent)) != 0) + { + EditorGUILayout.HelpBox( + "Shader channels Normal and Tangent are most often used with lighting, which an Overlay canvas does not support. Its likely these channels are not needed.", + MessageType.Warning); + } + } + + EditorGUILayout.PropertyField(m_VertexColorAlwaysGammaSpace, Styles.vertexColorAlwaysGammaSpace); + + if (PlayerSettings.colorSpace == ColorSpace.Linear) + { + if (Event.current.type != EventType.ExecuteCommand // UUM-64603: make sure the HelpBox addition doesn't collide with other UI changes and causes UI to fail to update properly + && !m_VertexColorAlwaysGammaSpace.boolValue) + { + EditorGUILayout.HelpBox( + "Keep vertex color in Gamma space to allow gamma to linear color space conversion to happen in UI shaders. This will enhance UI color precision in linear color space.", + MessageType.Warning); + } } } else diff --git a/Editor/Mono/Inspector/ClothInspector.cs b/Editor/Mono/Inspector/ClothInspector.cs index 45a93f4bfa..1e0aef3081 100644 --- a/Editor/Mono/Inspector/ClothInspector.cs +++ b/Editor/Mono/Inspector/ClothInspector.cs @@ -1971,7 +1971,8 @@ public override bool visible public override void OnGUI() { - s_Inspector.SelfAndInterCollisionEditing(); + if (s_Inspector != null) + s_Inspector.SelfAndInterCollisionEditing(); } } } diff --git a/Editor/Mono/Inspector/ConstrainProportionsTransformScale.cs b/Editor/Mono/Inspector/ConstrainProportionsTransformScale.cs index 5f42d32a89..6c9ed687f8 100644 --- a/Editor/Mono/Inspector/ConstrainProportionsTransformScale.cs +++ b/Editor/Mono/Inspector/ConstrainProportionsTransformScale.cs @@ -310,5 +310,15 @@ internal static bool ShouldForceEnablePropertyFields(float[] values) return true; } + + internal static bool CanUseMathExpressions(SerializedProperty property) + { + // Constrain proportions multi-selection change relies on activeObject and precise numbers, + // so we cannot use math expressions that provides different results on different objects UUM-21958 + var targetObjects = property.serializedObject.targetObjects; + + return !(targetObjects.Length > 1 && property.propertyPath.StartsWith("m_LocalScale") && + Selection.DoAllGOsHaveConstrainProportionsEnabled(targetObjects)); + } } } diff --git a/Editor/Mono/Inspector/CubemapInspector.cs b/Editor/Mono/Inspector/CubemapInspector.cs index bedd050c54..ec70c78f7e 100644 --- a/Editor/Mono/Inspector/CubemapInspector.cs +++ b/Editor/Mono/Inspector/CubemapInspector.cs @@ -4,7 +4,8 @@ using System.Globalization; using UnityEngine; - +using UnityEngine.Rendering; +using UnityEngine.Experimental.Rendering; namespace UnityEditor { @@ -15,13 +16,9 @@ internal class CubemapInspector : TextureInspector static private readonly int[] kSizesValues = { 16, 32, 64, 128, 256, 512, 1024, 2048 }; const int kTextureSize = 64; - private Texture2D[] m_Images; + private static readonly string kNativeTextureNotice = L10n.Tr("External texture: Unity cannot make changes to this Cubemap."); - protected override void OnEnable() - { - base.OnEnable(); - InitTexturesFromCubemap(); - } + private Texture2D[] m_Images; protected override void OnDisable() { @@ -41,18 +38,34 @@ protected override void OnDisable() private void InitTexturesFromCubemap() { var c = target as Cubemap; - if (c != null) + if (c is null || c.isNativeTexture) { - if (m_Images == null) - m_Images = new Texture2D[6]; - for (int i = 0; i < m_Images.Length; ++i) - { - if (m_Images[i] && !EditorUtility.IsPersistent(m_Images[i])) - DestroyImmediate(m_Images[i]); + return; + } - if (TextureUtil.GetSourceTexture(c, (CubemapFace)i)) + if (m_Images == null) + m_Images = new Texture2D[6]; + for (int i = 0; i < m_Images.Length; ++i) + { + if (m_Images[i] && !EditorUtility.IsPersistent(m_Images[i])) + DestroyImmediate(m_Images[i]); + + if (TextureUtil.GetSourceTexture(c, (CubemapFace)i)) + { + m_Images[i] = TextureUtil.GetSourceTexture(c, (CubemapFace)i); + } + else + { + // When the Cubemap is compressed, avoid "CopyCubemapFaceIntoTexture" due to potentially very high decompression cost. (example: Cubemap with no mipmaps) + // Note: the CopyTexture approach may produce results that look slightly different if "CopyCubemapFaceIntoTexture" would have downscaled to kTextureSize. + if (GraphicsFormatUtility.IsCompressedFormat(c.format) && SystemInfo.copyTextureSupport.HasFlag(CopyTextureSupport.DifferentTypes)) { - m_Images[i] = TextureUtil.GetSourceTexture(c, (CubemapFace)i); + int previewSize = System.Math.Clamp(kTextureSize, c.width >> (c.mipmapCount - 1), c.width); + m_Images[i] = new Texture2D(previewSize, previewSize, c.format, false); + m_Images[i].hideFlags = HideFlags.HideAndDontSave; + + int mipToCopy = (int)(System.Math.Log(c.width, 2) - System.Math.Log(previewSize, 2)); + Graphics.CopyTexture(c, i, mipToCopy, m_Images[i], 0, 0); } else { @@ -66,13 +79,20 @@ private void InitTexturesFromCubemap() public override void OnInspectorGUI() { + var c = target as Cubemap; + if (c == null) + return; + + if (c.isNativeTexture) + { + EditorGUILayout.HelpBox(kNativeTextureNotice, MessageType.Info); + return; + } + if (m_Images == null) InitTexturesFromCubemap(); EditorGUIUtility.labelWidth = 50; - var c = target as Cubemap; - if (c == null) - return; GUILayout.BeginVertical(); diff --git a/Editor/Mono/Inspector/CustomRenderTextureEditor.cs b/Editor/Mono/Inspector/CustomRenderTextureEditor.cs index 37eb53f336..786f6a3f25 100644 --- a/Editor/Mono/Inspector/CustomRenderTextureEditor.cs +++ b/Editor/Mono/Inspector/CustomRenderTextureEditor.cs @@ -421,7 +421,7 @@ public override void OnInspectorGUI() serializedObject.ApplyModifiedProperties(); } - [MenuItem("CONTEXT/CustomRenderTexture/Export", false)] + [MenuItem("CONTEXT/CustomRenderTexture/Export", secondaryPriority = 12)] static void SaveToDisk(MenuCommand command) { CustomRenderTexture texture = command.context as CustomRenderTexture; diff --git a/Editor/Mono/Inspector/DirectorEditor.cs b/Editor/Mono/Inspector/DirectorEditor.cs index f8f08d258c..4d64662d62 100644 --- a/Editor/Mono/Inspector/DirectorEditor.cs +++ b/Editor/Mono/Inspector/DirectorEditor.cs @@ -42,6 +42,8 @@ private static class Styles private SerializedProperty m_UpdateMethod; private SerializedProperty m_SceneBindings; + private bool m_DirtySceneBindings; + private Texture m_DefaultScriptContentTexture; private GUIContent m_BindingContent = new GUIContent(); @@ -93,14 +95,16 @@ public void OnEnable() public override void OnInspectorGUI() { if (PlayableAssetOutputsChanged() || m_BindingItems.Count != m_SceneBindings.arraySize) - SynchronizeSceneBindings(); + { + m_DirtySceneBindings = true; + } serializedObject.Update(); if (PropertyFieldAsObject(m_PlayableAsset, Styles.PlayableText, typeof(PlayableAsset))) { serializedObject.ApplyModifiedProperties(); - SynchronizeSceneBindings(); + m_DirtySceneBindings = true; // some editors (like Timeline) needs to repaint when the playable asset changes InternalEditorUtility.RepaintAllViews(); @@ -131,11 +135,13 @@ public override void OnInspectorGUI() CurrentTimeField(); } + if (serializedObject.ApplyModifiedProperties()) + { + m_DirtySceneBindings = true; + } + if (targets.Length == 1) DoDirectorBindingInspector(); - - if (serializedObject.ApplyModifiedProperties()) - SynchronizeSceneBindings(); } private bool PlayableAssetOutputsChanged() @@ -164,6 +170,13 @@ GUIContent GetContentForOutput(PlayableBinding binding, UnityEngine.Object sourc private void DoDirectorBindingInspector() { + if (m_SceneBindings.isExpanded) + { + SynchronizeSceneBindings(); + } + + serializedObject.Update(); + EditorGUILayout.BeginHorizontal(); var rect = GUILayoutUtility.GetRect(EditorGUIUtility.fieldWidth, EditorGUIUtility.fieldWidth, EditorGUI.kSingleLineHeight, EditorGUI.kSingleLineHeight, EditorStyles.foldout); @@ -190,6 +203,8 @@ private void DoDirectorBindingInspector() m_BindingList.DoLayoutList(); EditorGUI.indentLevel--; } + + serializedObject.ApplyModifiedProperties(); } PlayableBinding FindBinding(PlayableAsset source, UnityEngine.Object key) @@ -202,9 +217,11 @@ PlayableBinding FindBinding(PlayableAsset source, UnityEngine.Object key) void SynchronizeSceneBindings() { - if (targets.Length > 1) + if (targets.Length > 1 || !m_DirtySceneBindings) return; + m_DirtySceneBindings = false; + var director = (PlayableDirector)target; var playableAsset = m_PlayableAsset.objectReferenceValue as PlayableAsset; @@ -295,8 +312,6 @@ private void UpdatePlayableBindingsIfRequired(PlayableAsset playableAsset, Playa else if (!director.HasGenericBinding(binding.sourceObject)) director.SetGenericBinding(binding.sourceObject, null); } - - serializedObject.Update(); } // To show the current time field in play mode diff --git a/Editor/Mono/Inspector/Editor.cs b/Editor/Mono/Inspector/Editor.cs index 59d1c30753..faf84ac11b 100644 --- a/Editor/Mono/Inspector/Editor.cs +++ b/Editor/Mono/Inspector/Editor.cs @@ -361,8 +361,8 @@ public partial class Editor : ScriptableObject, IPreviewable, IToolModeOwner // Note that m_Dirty is not only set through 'isInspectorDirty' but also from C++ in 'SetCustomEditorIsDirty (MonoBehaviour* inspector, bool dirty)' int m_IsDirty; int m_ReferenceTargetIndex = 0; - PropertyHandlerCache m_PropertyHandlerCache = new PropertyHandlerCache(); - IPreviewable m_DummyPreview; + readonly PropertyHandlerCache m_PropertyHandlerCache = new PropertyHandlerCache(); + internal IPreviewable m_DummyPreview; AudioFilterGUI m_AudioFilterGUI; internal SerializedObject m_SerializedObject = null; @@ -385,6 +385,11 @@ internal InspectorMode inspectorMode } } + internal DataMode dataMode => + propertyViewer is EditorWindow editorWindow + ? editorWindow.dataModeController.dataMode + : DataMode.Disabled; + internal static float kLineHeight = EditorGUI.kSingleLineHeight; internal bool hideInspector = false; @@ -440,7 +445,33 @@ public virtual void DiscardChanges() // used internally to know if this the first editor in the inspector window internal bool firstInspectedEditor { get; set; } - internal IPropertyView propertyViewer { get; set; } + IPropertyView m_PropertyViewer; + + internal IPropertyView propertyViewer + { + get + { + return m_PropertyViewer; + } + set + { + if (m_PropertyViewer != value) + { + m_PropertyViewer = value; + + // We are being assigned a new property view with different inspector mode to what our serialized object was built with. + if (null != m_PropertyViewer && m_PropertyViewer.inspectorMode != m_InspectorMode) + { + // Keep the local inspector mode in sync. + m_InspectorMode = m_PropertyViewer.inspectorMode; + + // Trash the local serialized object and property cache to force a rebuild. + m_SerializedObject = null; + m_EnabledProperty = null; + } + } + } + } internal virtual bool HasLargeHeader() { @@ -547,6 +578,8 @@ internal SerializedProperty enabledProperty } } + internal virtual void PostSerializedObjectCreation() {} + internal bool isInspectorDirty { get { return m_IsDirty != 0; } @@ -629,8 +662,9 @@ internal void CleanupPropertyEditor() private void OnDisableINTERNAL() { CleanupPropertyEditor(); - if (!(preview is Editor)) - preview.Cleanup(); + propertyHandlerCache.Dispose(); + if (m_DummyPreview != null && m_DummyPreview is not Editor) + m_DummyPreview.Cleanup(); } internal virtual SerializedObject GetSerializedObjectInternal() @@ -654,8 +688,12 @@ private void CreateSerializedObject() { m_SerializedObject = new SerializedObject(targets, m_Context); m_SerializedObject.inspectorMode = inspectorMode; + if (m_SerializedObject.inspectorDataMode != dataMode) + m_SerializedObject.inspectorDataMode = dataMode; + AssignCachedProperties(this, m_SerializedObject.GetIterator()); m_EnabledProperty = m_SerializedObject.FindProperty("m_Enabled"); + PostSerializedObjectCreation(); } catch (ArgumentException e) { @@ -912,6 +950,9 @@ public void DrawHeader() EditorGUILayout.BeginVertical(UseDefaultMargins() ? EditorStyles.inspectorDefaultMargins : GUIStyle.none); } } + + // Restore previous hierarchy mode + EditorGUIUtility.hierarchyMode = hierarchyMode; } // This is the method to override to create custom header GUI. @@ -1320,10 +1361,15 @@ internal bool CanBeExpandedViaAFoldout() return true; if (m_SerializedObject == null) - CreateSerializedObject(); + { + m_SerializedObject = new SerializedObject(targets, m_Context); + m_SerializedObject.inspectorMode = m_InspectorMode; + } else m_SerializedObject.Update(); m_SerializedObject.inspectorMode = inspectorMode; + if (m_SerializedObject.inspectorDataMode != dataMode) + m_SerializedObject.inspectorDataMode = dataMode; return CanBeExpandedViaAFoldoutWithoutUpdate(); } @@ -1334,7 +1380,10 @@ internal bool CanBeExpandedViaAFoldoutWithoutUpdate() return true; if (m_SerializedObject == null) - CreateSerializedObject(); + { + m_SerializedObject = new SerializedObject(targets, m_Context); + m_SerializedObject.inspectorMode = m_InspectorMode; + } SerializedProperty property = m_SerializedObject.GetIterator(); bool analyzePropertyChildren = true; diff --git a/Editor/Mono/Inspector/EditorDragging.cs b/Editor/Mono/Inspector/EditorDragging.cs index a8f8d4f572..7cc50be9ce 100644 --- a/Editor/Mono/Inspector/EditorDragging.cs +++ b/Editor/Mono/Inspector/EditorDragging.cs @@ -101,9 +101,16 @@ void HandleNativeDragDropInBottomArea(Editor[] editors, Rect rect) if (Event.current.type == EventType.DragPerform) { - DragAndDrop.AcceptDrag(); - m_TargetIndex = -1; - GUIUtility.ExitGUI(); + // Determine if any object references are component, which would mean we are reordering components in the inspector + var anyComponent = DragAndDrop.objectReferences != null && DragAndDrop.objectReferences.Any(o => o is Component); + + // None of the object references are component, cancel further processing to avoid adding component twice + if (!anyComponent) + { + DragAndDrop.AcceptDrag(); + m_TargetIndex = -1; + GUIUtility.ExitGUI(); + } } } @@ -208,6 +215,8 @@ void HandleEditorDragging(Editor[] editors, int editorIndex, Rect targetRect, fl break; case EventType.DragPerform: + // Linux does not make a DragExited call after a drag, and needs to have its visual mode move to None. All platforms reset their visualmode back to None after a drag. + DragAndDrop.visualMode = DragAndDropVisualMode.None; if (m_TargetIndex != -1) { HandleDragPerformEvent(editors, evt, ref m_TargetIndex); diff --git a/Editor/Mono/Inspector/EditorElementUpdater.cs b/Editor/Mono/Inspector/EditorElementUpdater.cs index 488371110c..59ce1d9dc6 100644 --- a/Editor/Mono/Inspector/EditorElementUpdater.cs +++ b/Editor/Mono/Inspector/EditorElementUpdater.cs @@ -34,12 +34,27 @@ public EditorElementUpdater(PropertyEditor propertyEditor) /// /// Adds the specified to the updater. /// - /// + /// The editor element to add. public void Add(IEditorElement element) { m_EditorElements.Add(element); } + /// + /// Removes the specified from the updater. + /// + /// The editor element to remove. + public void Remove(IEditorElement element) + { + var index = m_EditorElements.IndexOf(element); + + if (index == -1) + return; + + if (m_Index > index) + m_Index--; + } + /// /// Clears the internal state and resets the enumerator. /// @@ -88,11 +103,11 @@ public void CreateInspectorElementsForViewport(ScrollView viewport, VisualElemen element.CreateInspectorElement(); // If this element contributes to the layout, re-compute it immediately to determine how much of the viewport is occupied. - if (null != element.editor && InternalEditorUtility.GetIsInspectorExpanded(element.editor.target)) + if (null != element.editor && InternalEditorUtility.GetIsInspectorExpanded(element.editor.target)&& contentContainer.childCount>0) { Panel?.UpdateWithoutRepaint(); - if (contentContainer.ElementAt(m_Index - 1).layout.yMax - scroll > viewport.layout.height) + if (contentContainer.ElementAt(contentContainer.childCount - 1).layout.yMax - scroll > viewport.layout.height) break; } } diff --git a/Editor/Mono/Inspector/EditorSettingsInspector.cs b/Editor/Mono/Inspector/EditorSettingsInspector.cs index 3f2e42d144..7e3a98bb90 100644 --- a/Editor/Mono/Inspector/EditorSettingsInspector.cs +++ b/Editor/Mono/Inspector/EditorSettingsInspector.cs @@ -7,7 +7,6 @@ using UnityEditorInternal; using UnityEngine; using UnityEditor.Hardware; -using UnityEditor.Collaboration; using UnityEngine.Assertions; namespace UnityEditor @@ -90,7 +89,6 @@ class Content public static readonly GUIContent enterPlayModeOptionsEnabled = EditorGUIUtility.TrTextContent("Enter Play Mode Options", "Enables options when Entering Play Mode"); public static readonly GUIContent enterPlayModeOptionsEnableDomainReload = EditorGUIUtility.TrTextContent("Reload Domain", "Enables Domain Reload when Entering Play Mode. Domain reload reinitializes game completely making loading behavior very close to the Player"); public static readonly GUIContent enterPlayModeOptionsEnableSceneReload = EditorGUIUtility.TrTextContent("Reload Scene", "Enables Scene Reload when Entering Play Mode. Scene reload makes loading behavior and performance characteristics very close to the Player"); - public static readonly GUIContent enterPlayModeOptionsDisableSceneBackup = EditorGUIUtility.TrTextContent("Disable Scene Backup", "Conditionally skips writing a backup of the open scenes to disk. Only scenes that are modified in-memory need to be backed up, but making modifications from script may change the scene without setting the scene's dirty flag."); public static readonly GUIContent numberingScheme = EditorGUIUtility.TrTextContent("Numbering Scheme"); @@ -245,8 +243,6 @@ public PopupElement(string id, string content) SerializedProperty m_EnableTextureStreamingInEditMode; SerializedProperty m_EnableEditorAsyncCPUTextureLoading; - SerializedProperty m_EnableRoslynAnalyzers; - SerializedProperty m_GameObjectNamingDigits; SerializedProperty m_GameObjectNamingScheme; SerializedProperty m_AssetNamingUsesSpace; @@ -297,8 +293,6 @@ public void OnEnable() m_EnableTextureStreamingInEditMode = serializedObject.FindProperty("m_EnableTextureStreamingInEditMode"); m_EnableEditorAsyncCPUTextureLoading = serializedObject.FindProperty("m_EnableEditorAsyncCPUTextureLoading"); - m_EnableRoslynAnalyzers = serializedObject.FindProperty("m_EnableRoslynAnalyzers"); - m_GameObjectNamingDigits = serializedObject.FindProperty("m_GameObjectNamingDigits"); m_GameObjectNamingScheme = serializedObject.FindProperty("m_GameObjectNamingScheme"); m_AssetNamingUsesSpace = serializedObject.FindProperty("m_AssetNamingUsesSpace"); @@ -432,21 +426,14 @@ public override void OnInspectorGUI() if (m_IsGlobalSettings) ShowUnityRemoteGUI(editorEnabled); - bool collabEnabled = Collab.instance.IsCollabEnabledForCurrentProject(); GUILayout.Space(10); + GUI.enabled = true; + GUILayout.Label(Content.assetSerialization, EditorStyles.boldLabel); + GUI.enabled = editorEnabled; + int index = m_SerializationMode.intValue; - using (new EditorGUI.DisabledScope(!collabEnabled)) - { - GUI.enabled = !collabEnabled; - GUILayout.Label(Content.assetSerialization, EditorStyles.boldLabel); - GUI.enabled = editorEnabled && !collabEnabled; - CreatePopupMenu("Mode", serializationPopupList, index, SetAssetSerializationMode); - } - if (collabEnabled) - { - EditorGUILayout.HelpBox("Asset Serialization is forced to Text when using Collaboration feature.", MessageType.Warning); - } + CreatePopupMenu("Mode", serializationPopupList, index, SetAssetSerializationMode); if (m_SerializationMode.intValue != (int)SerializationMode.ForceBinary) { @@ -704,7 +691,7 @@ private void DoAssetPipelineSettings() { // Known issue with Docs redirect - versioned pages might not open offline docs var help = Help.FindHelpNamed("ParallelImport"); - Application.OpenURL(help); + Help.BrowseURL(help); } GUILayout.EndHorizontal(); } @@ -780,16 +767,10 @@ private void DoCacheServerSettings() { // Known issue with Docs redirect - versioned pages might not open offline docs var help = Help.FindHelpNamed("UnityAccelerator"); - Application.OpenURL(help); + Help.BrowseURL(help); } GUILayout.EndHorizontal(); - var overrideAddress = AssetPipelinePreferences.GetCommandLineRemoteAddressOverride(); - if (overrideAddress != null) - { - EditorGUILayout.HelpBox("Cache Server remote address forced via command line argument. To use the cache server address specified here please restart Unity without the -CacheServerIPAddress command line argument.", MessageType.Info, true); - } - int index = Mathf.Clamp((int)EditorSettings.cacheServerMode, 0, cacheServerModePopupList.Length - 1); CreatePopupMenu(Content.mode.text, cacheServerModePopupList, index, SetCacheServerMode); @@ -1001,7 +982,6 @@ private void DoEnterPlayModeSettings() EnterPlayModeOptions options = (EnterPlayModeOptions)m_EnterPlayModeOptions.intValue; options = ToggleEnterPlayModeOptions(options, EnterPlayModeOptions.DisableDomainReload, Content.enterPlayModeOptionsEnableDomainReload); options = ToggleEnterPlayModeOptions(options, EnterPlayModeOptions.DisableSceneReload, Content.enterPlayModeOptionsEnableSceneReload); - options = ToggleEnterPlayModeOptions(options, EnterPlayModeOptions.DisableSceneBackupUnlessDirty, Content.enterPlayModeOptionsDisableSceneBackup); if (m_EnterPlayModeOptions.intValue != (int)options) { @@ -1023,23 +1003,10 @@ private void DoEnterInspectorSettings() if (EditorGUI.EndChangeCheck() && m_IsGlobalSettings) { EditorSettings.inspectorUseIMGUIDefaultInspector = m_InspectorUseIMGUIDefaultInspector.boolValue; - - // Needs to be delayCall because it forces redrawing of UI which messes with the current IMGUI context of the Settings window. - EditorApplication.delayCall += ClearEditorsAndRebuildInspectors; + PropertyEditor.ClearAndRebuildAll(); } } - static void ClearEditorsAndRebuildInspectors() - { - // Cannot use something like EditorUtility.ForceRebuildInspectors() because this only refreshes - // the inspector's values and IMGUI state, but otherwise, if the target did not change we - // re-use the Editors. We need a special clear function to properly recreate the UI using - // the new setting. - var propertyEditors = Resources.FindObjectsOfTypeAll(); - foreach (var propertyEditor in propertyEditors) - propertyEditor.ClearEditorsAndRebuild(); - } - static int GetIndexById(DevDevice[] elements, string id, int defaultIndex) { for (int i = 0; i < elements.Length; i++) diff --git a/Editor/Mono/Inspector/Enlighten/LightmapParameters.cs b/Editor/Mono/Inspector/Enlighten/LightmapParameters.cs index 53b0c5ac12..331aa9b940 100644 --- a/Editor/Mono/Inspector/Enlighten/LightmapParameters.cs +++ b/Editor/Mono/Inspector/Enlighten/LightmapParameters.cs @@ -33,6 +33,10 @@ class LightmapParametersEditor : Editor SerializedProperty m_AntiAliasingSamples; SerializedProperty m_DirectLightQuality; + SavedBool m_RealtimeGISettings; + SavedBool m_BakedGISettings; + SavedBool m_GeneralParametersSettings; + public void OnEnable() { m_Resolution = serializedObject.FindProperty("resolution"); @@ -53,6 +57,15 @@ public void OnEnable() m_Pushoff = serializedObject.FindProperty("pushoff"); m_LimitLightmapCount = serializedObject.FindProperty("limitLightmapCount"); m_LightmapMaxCount = serializedObject.FindProperty("maxLightmapCount"); + + m_RealtimeGISettings = new SavedBool("LightmapParameters.ShowRealtimeGISettings", true); + m_BakedGISettings = new SavedBool("LightmapParameters.ShowBakedGISettings", true); + m_GeneralParametersSettings = new SavedBool("LightmapParameters.ShowGeneralParametersSettings", true); + } + + public override bool UseDefaultMargins() + { + return false; } public override void OnInspectorGUI() @@ -62,64 +75,110 @@ public override void OnInspectorGUI() // realtime settings if (SupportedRenderingFeatures.IsLightmapBakeTypeSupported(LightmapBakeType.Realtime)) { - GUILayout.Label(Styles.precomputedRealtimeGIContent, EditorStyles.boldLabel); - EditorGUILayout.PropertyField(m_Resolution, Styles.resolutionContent); - EditorGUILayout.Slider(m_ClusterResolution, 0.1F, 1.0F, Styles.clusterResolutionContent); - EditorGUILayout.IntSlider(m_IrradianceBudget, 32, 2048, Styles.irradianceBudgetContent); - EditorGUILayout.IntSlider(m_IrradianceQuality, 512, 131072, Styles.irradianceQualityContent); - EditorGUILayout.Slider(m_ModellingTolerance, 0.0f, 1.0f, Styles.modellingToleranceContent); - EditorGUILayout.PropertyField(m_EdgeStitching, Styles.edgeStitchingContent); - EditorGUILayout.PropertyField(m_IsTransparent, Styles.isTransparent); - EditorGUILayout.PropertyField(m_SystemTag, Styles.systemTagContent); - EditorGUILayout.Space(); - } + m_RealtimeGISettings.value = EditorGUILayout.FoldoutTitlebar(m_RealtimeGISettings.value, Styles.precomputedRealtimeGIContent, true); + if (m_RealtimeGISettings.value) + { + ++EditorGUI.indentLevel; - // baked settings - #pragma warning disable 618 - bool usesPathTracerBakeBackend = Lightmapping.GetLightingSettingsOrDefaultsFallback().lightmapper != LightingSettings.Lightmapper.Enlighten; - bool usesEnlightenBackend = Lightmapping.GetLightingSettingsOrDefaultsFallback().lightmapper == LightingSettings.Lightmapper.Enlighten; - bool bakedEnlightenSupported = SupportedRenderingFeatures.IsLightmapperSupported((int)LightingSettings.Lightmapper.Enlighten); - #pragma warning restore 618 + EditorGUILayout.LabelField(Styles.enlightenLabel, EditorStyles.boldLabel); - GUILayout.Label(Styles.bakedGIContent, EditorStyles.boldLabel); + ++EditorGUI.indentLevel; - if (bakedEnlightenSupported) - { - using (new EditorGUI.DisabledScope(usesPathTracerBakeBackend)) - { - EditorGUILayout.PropertyField(m_BlurRadius, Styles.blurRadiusContent); - EditorGUILayout.PropertyField(m_DirectLightQuality, Styles.directLightQualityContent); + EditorGUILayout.PropertyField(m_Resolution, Styles.resolutionContent); + EditorGUILayout.Slider(m_ClusterResolution, 0.1F, 1.0F, Styles.clusterResolutionContent); + EditorGUILayout.IntSlider(m_IrradianceBudget, 32, 2048, Styles.irradianceBudgetContent); + EditorGUILayout.IntSlider(m_IrradianceQuality, 512, 131072, Styles.irradianceQualityContent); + EditorGUILayout.Slider(m_ModellingTolerance, 0.0f, 1.0f, Styles.modellingToleranceContent); + EditorGUILayout.PropertyField(m_EdgeStitching, Styles.edgeStitchingContent); + EditorGUILayout.PropertyField(m_IsTransparent, Styles.isTransparent); + EditorGUILayout.PropertyField(m_SystemTag, Styles.systemTagContent); + EditorGUILayout.Space(); + + --EditorGUI.indentLevel; + --EditorGUI.indentLevel; } } - EditorGUILayout.PropertyField(m_AntiAliasingSamples, Styles.antiAliasingSamplesContent); - const float minPushOff = 0.0001f; // Keep in sync with PLM_MIN_PUSHOFF - EditorGUILayout.Slider(m_Pushoff, minPushOff, 1.0f, Styles.pushoffContent); - EditorGUILayout.PropertyField(m_BakedLightmapTag, Styles.bakedLightmapTagContent); - using (new EditorGUI.DisabledScope(usesEnlightenBackend)) + // baked settings + m_BakedGISettings.value = EditorGUILayout.FoldoutTitlebar(m_BakedGISettings.value, Styles.bakedGIContent, true); + if (m_BakedGISettings.value) { - m_LimitLightmapCount.boolValue = EditorGUILayout.Toggle(Styles.limitLightmapCount, m_LimitLightmapCount.boolValue); - if (m_LimitLightmapCount.boolValue) +#pragma warning disable 618 + bool usesPathTracerBakeBackend = Lightmapping.GetLightingSettingsOrDefaultsFallback().lightmapper != LightingSettings.Lightmapper.Enlighten; + bool usesEnlightenBackend = Lightmapping.GetLightingSettingsOrDefaultsFallback().lightmapper == LightingSettings.Lightmapper.Enlighten; + bool bakedEnlightenSupported = SupportedRenderingFeatures.IsLightmapperSupported((int)LightingSettings.Lightmapper.Enlighten) && EditorSettings.enableEnlightenBakedGI; +#pragma warning restore 618 + + ++EditorGUI.indentLevel; + + // General + EditorGUILayout.LabelField(Styles.generalLabel, EditorStyles.boldLabel); + + ++EditorGUI.indentLevel; + + EditorGUILayout.PropertyField(m_AntiAliasingSamples, Styles.antiAliasingSamplesContent); + const float minPushOff = 0.0001f; // Keep in sync with PLM_MIN_PUSHOFF + EditorGUILayout.Slider(m_Pushoff, minPushOff, 1.0f, Styles.pushoffContent); + EditorGUILayout.PropertyField(m_BakedLightmapTag, Styles.bakedLightmapTagContent); + EditorGUILayout.Space(); + + --EditorGUI.indentLevel; + + // Progressive Lightmapper + using (new EditorGUI.DisabledScope(usesEnlightenBackend)) { - EditorGUI.indentLevel++; - EditorGUILayout.PropertyField(m_LightmapMaxCount, Styles.lightmapMaxCount); - EditorGUI.indentLevel--; + EditorGUILayout.LabelField(Styles.progressiveLabel, EditorStyles.boldLabel); + + ++EditorGUI.indentLevel; + + m_LimitLightmapCount.boolValue = EditorGUILayout.Toggle(Styles.limitLightmapCount, m_LimitLightmapCount.boolValue); + if (m_LimitLightmapCount.boolValue) + { + ++EditorGUI.indentLevel; + EditorGUILayout.PropertyField(m_LightmapMaxCount, Styles.lightmapMaxCount); + --EditorGUI.indentLevel; + } + EditorGUILayout.Space(); + + --EditorGUI.indentLevel; } - } - EditorGUILayout.Space(); - if (bakedEnlightenSupported) - { - using (new EditorGUI.DisabledScope(usesPathTracerBakeBackend)) + // Enlighten + if (bakedEnlightenSupported) { - GUILayout.Label(Styles.bakedAOContent, EditorStyles.boldLabel); - EditorGUILayout.PropertyField(m_AOQuality, Styles.aoQualityContent); - EditorGUILayout.PropertyField(m_AOAntiAliasingSamples, Styles.aoAntiAliasingSamplesContent); + using (new EditorGUI.DisabledScope(usesPathTracerBakeBackend)) + { + EditorGUILayout.LabelField(Styles.enlightenLabel, EditorStyles.boldLabel); + + ++EditorGUI.indentLevel; + + EditorGUILayout.PropertyField(m_BlurRadius, Styles.blurRadiusContent); + EditorGUILayout.PropertyField(m_DirectLightQuality, Styles.directLightQualityContent); + + EditorGUILayout.LabelField(Styles.bakedAOContent); + ++EditorGUI.indentLevel; + EditorGUILayout.PropertyField(m_AOQuality, Styles.aoQualityContent); + EditorGUILayout.PropertyField(m_AOAntiAliasingSamples, Styles.aoAntiAliasingSamplesContent); + --EditorGUI.indentLevel; + + EditorGUILayout.Space(); + + --EditorGUI.indentLevel; + } } + + --EditorGUI.indentLevel; } - GUILayout.Label(Styles.generalGIContent, EditorStyles.boldLabel); - EditorGUILayout.Slider(m_BackFaceTolerance, 0.0f, 1.0f, Styles.backFaceToleranceContent); + m_GeneralParametersSettings.value = EditorGUILayout.FoldoutTitlebar(m_GeneralParametersSettings.value, Styles.generalGIContent, true); + if (m_GeneralParametersSettings.value) + { + ++EditorGUI.indentLevel; + + EditorGUILayout.Slider(m_BackFaceTolerance, 0.0f, 1.0f, Styles.backFaceToleranceContent); + + --EditorGUI.indentLevel; + } serializedObject.ApplyModifiedProperties(); } @@ -132,8 +191,8 @@ internal override void OnHeaderControlsGUI() private class Styles { - public static readonly GUIContent generalGIContent = EditorGUIUtility.TrTextContent("General GI", "Settings used in both Precomputed Realtime Global Illumination and Baked Global Illumination."); - public static readonly GUIContent precomputedRealtimeGIContent = EditorGUIUtility.TrTextContent("Realtime GI", "Settings used in Precomputed Realtime Global Illumination where it is precomputed how indirect light can bounce between static objects, but the final lighting is done at runtime. Lights, ambient lighting in addition to the materials and emission of static objects can still be changed at runtime. Only static objects can affect GI by blocking and bouncing light, but non-static objects can receive bounced light via light probes."); // Reuse the label from the Lighting window + public static readonly GUIContent generalGIContent = EditorGUIUtility.TrTextContent("General Parameters", "Settings used in both Precomputed Realtime Global Illumination and Baked Global Illumination."); + public static readonly GUIContent precomputedRealtimeGIContent = EditorGUIUtility.TrTextContent("Realtime Global Illumination", "Settings used in Precomputed Realtime Global Illumination where it is precomputed how indirect light can bounce between static objects, but the final lighting is done at runtime. Lights, ambient lighting in addition to the materials and emission of static objects can still be changed at runtime. Only static objects can affect GI by blocking and bouncing light, but non-static objects can receive bounced light via light probes."); // Reuse the label from the Lighting window public static readonly GUIContent resolutionContent = EditorGUIUtility.TrTextContent("Resolution", "Realtime lightmap resolution in texels per world unit. This value is multiplied by the realtime resolution in the Lighting window to give the output lightmap resolution. This should generally be an order of magnitude less than what is common for baked lightmaps to keep the precompute time manageable and the performance at runtime acceptable. Note that if this is made more fine-grained, then the Irradiance Budget will often need to be increased too, to fully take advantage of this increased detail."); public static readonly GUIContent clusterResolutionContent = EditorGUIUtility.TrTextContent("Cluster Resolution", "The ratio between the resolution of the clusters with which light bounce is calculated and the resolution of the output lightmaps that sample from these."); public static readonly GUIContent irradianceBudgetContent = EditorGUIUtility.TrTextContent("Irradiance Budget", "The amount of data used by each texel in the output lightmap. Specifies how fine-grained a view of the scene an output texel has. Small values mean more averaged out lighting, since the light contributions from more clusters are treated as one. Affects runtime memory usage and to a lesser degree runtime CPU usage."); @@ -142,11 +201,11 @@ private class Styles public static readonly GUIContent modellingToleranceContent = EditorGUIUtility.TrTextContent("Modelling Tolerance", "Maximum size of gaps that can be ignored for GI."); public static readonly GUIContent edgeStitchingContent = EditorGUIUtility.TrTextContent("Edge Stitching", "If enabled, ensures that UV charts (aka UV islands) in the generated lightmaps blend together where they meet so there is no visible seam between them."); public static readonly GUIContent systemTagContent = EditorGUIUtility.TrTextContent("System Tag", "Systems are groups of objects whose lightmaps are in the same atlas. It is also the granularity at which dependencies are calculated. Multiple systems are created automatically if the scene is big enough, but it can be helpful to be able to split them up manually for e.g. streaming in sections of a level. The system tag lets you force an object into a different realtime system even though all the other parameters are the same."); - public static readonly GUIContent bakedGIContent = EditorGUIUtility.TrTextContent("Baked GI", "Settings used in Baked Global Illumination where direct and indirect lighting for static objects is precalculated and saved (baked) into lightmaps for use at runtime. This is useful when lights are known to be static, for mobile, for low end devices and other situations where there is not enough processing power to use Precomputed Realtime GI. You can toggle on each light whether it should be included in the bake."); // Reuse the label from the Lighting window + public static readonly GUIContent bakedGIContent = EditorGUIUtility.TrTextContent("Baked Global Illumination", "Settings used in Baked Global Illumination where direct and indirect lighting for static objects is precalculated and saved (baked) into lightmaps for use at runtime. This is useful when lights are known to be static, for mobile, for low end devices and other situations where there is not enough processing power to use Precomputed Realtime GI. You can toggle on each light whether it should be included in the bake."); // Reuse the label from the Lighting window public static readonly GUIContent blurRadiusContent = EditorGUIUtility.TrTextContent("Blur Radius", "The radius (in texels) of the post-processing filter that blurs baked direct lighting. This reduces aliasing artefacts and produces softer shadows."); public static readonly GUIContent antiAliasingSamplesContent = EditorGUIUtility.TrTextContent("Anti-aliasing Samples", "The maximum number of times to supersample a texel to reduce aliasing. Progressive lightmapper supersamples the positions and normals buffers (part of the G-buffer) and hence the sample count is a multiplier on the amount of memory used for those buffers. Progressive lightmapper clamps the value to the [1;16] range."); public static readonly GUIContent directLightQualityContent = EditorGUIUtility.TrTextContent("Direct Light Quality", "The number of rays used for lights with an area. Allows for accurate soft shadowing."); - public static readonly GUIContent bakedAOContent = EditorGUIUtility.TrTextContent("Baked AO", "Settings used in Baked Ambient Occlusion, where the information on dark corners and crevices in static geometry is baked. It is multiplied by indirect lighting when compositing the baked lightmap."); + public static readonly GUIContent bakedAOContent = EditorGUIUtility.TrTextContent("Ambient Occlusion", "Settings used in Baked Ambient Occlusion, where the information on dark corners and crevices in static geometry is baked. It is multiplied by indirect lighting when compositing the baked lightmap."); public static readonly GUIContent aoQualityContent = EditorGUIUtility.TrTextContent("Quality", "The number of rays to cast for computing ambient occlusion."); public static readonly GUIContent aoAntiAliasingSamplesContent = EditorGUIUtility.TrTextContent("Anti-aliasing Samples", "The maximum number of times to supersample a texel to reduce aliasing in ambient occlusion."); public static readonly GUIContent isTransparent = EditorGUIUtility.TrTextContent("Is Transparent", "If enabled, the object appears transparent during GlobalIllumination lighting calculations. Backfaces are not contributing to and light travels through the surface. This is useful for emissive invisible surfaces."); @@ -154,6 +213,10 @@ private class Styles public static readonly GUIContent bakedLightmapTagContent = EditorGUIUtility.TrTextContent("Baked Tag", "An integer that lets you force an object into a different baked lightmap even though all the other parameters are the same. This can be useful e.g. when streaming in sections of a level."); public static readonly GUIContent limitLightmapCount = EditorGUIUtility.TrTextContent("Limit Lightmap Count", "If enabled, objects with the same baked GI settings will be packed into a specified number of lightmaps. This may reduce the objects' lightmap resolution."); public static readonly GUIContent lightmapMaxCount = EditorGUIUtility.TrTextContent("Max Lightmaps", "The maximum number of lightmaps into which objects will be packed."); + + public static readonly GUIContent generalLabel = EditorGUIUtility.TrTextContent("General"); + public static readonly GUIContent progressiveLabel = EditorGUIUtility.TrTextContent("Progressive Lightmapper"); + public static readonly GUIContent enlightenLabel = EditorGUIUtility.TrTextContent("Enlighten"); } } } diff --git a/Editor/Mono/Inspector/GameObjectInspector.cs b/Editor/Mono/Inspector/GameObjectInspector.cs index 5f349c33d5..c5b6922ead 100644 --- a/Editor/Mono/Inspector/GameObjectInspector.cs +++ b/Editor/Mono/Inspector/GameObjectInspector.cs @@ -26,6 +26,20 @@ internal class GameObjectInspector : Editor SerializedProperty m_StaticEditorFlags; SerializedProperty m_Icon; string m_GOPreviousName; + bool m_SerializedObjectInitialized; + + internal override void PostSerializedObjectCreation() + { + if (m_SerializedObjectInitialized) + { + m_Name = serializedObject.FindProperty("m_Name"); + m_IsActive = serializedObject.FindProperty("m_IsActive"); + m_Layer = serializedObject.FindProperty("m_Layer"); + m_Tag = serializedObject.FindProperty("m_TagString"); + m_StaticEditorFlags = serializedObject.FindProperty("m_StaticEditorFlags"); + m_Icon = serializedObject.FindProperty("m_Icon"); + } + } static class Styles { @@ -35,7 +49,7 @@ static class Styles public static GUIContent layerContent = EditorGUIUtility.TrTextContent("Layer", "The layer that this GameObject is in.\n\nChoose Add Layer... to edit the list of available layers."); public static GUIContent tagContent = EditorGUIUtility.TrTextContent("Tag", "The tag that this GameObject has.\n\nChoose Untagged to remove the current tag.\n\nChoose Add Tag... to edit the list of available tags."); public static GUIContent staticPreviewContent = EditorGUIUtility.TrTextContent("Static Preview", "This asset is greater than 8MB so, by default, the Asset Preview displays a static preview.\nTo view the asset interactively, click the Asset Preview."); - + public static float tagFieldWidth = EditorGUI.CalcPrefixLabelWidth(Styles.tagContent, EditorStyles.boldLabel); public static float layerFieldWidth = EditorGUI.CalcPrefixLabelWidth(Styles.layerContent, EditorStyles.boldLabel); @@ -233,6 +247,7 @@ public void OnEnable() m_Tag = serializedObject.FindProperty("m_TagString"); m_StaticEditorFlags = serializedObject.FindProperty("m_StaticEditorFlags"); m_Icon = serializedObject.FindProperty("m_Icon"); + m_SerializedObjectInitialized = true; SetSelectedObjectCountLabelContent(); CalculatePrefabStatus(); @@ -566,6 +581,22 @@ void IndentToColumn1() EditorGUILayout.EndHorizontal(); } + bool HandleDragPrefabAssetOverObjectFieldPopupMenu(Rect rect) + { + var evt = Event.current; + bool perform = evt.type == EventType.DragPerform; + if (rect.Contains(evt.mousePosition) && (perform ||evt.type == EventType.DragUpdated)) + { + DragAndDropVisualMode visualMode; + if (PrefabReplaceUtility.GetDragVisualModeAndShowMenuWithReplaceMenuItemsWhenNeeded((GameObject)target, true, perform, false, false, out visualMode)) + { + DragAndDrop.visualMode = visualMode; + return perform; + } + } + return false; + } + void PrefabObjectField() { // Change Prefab asset for instance @@ -579,10 +610,15 @@ void PrefabObjectField() bool disabled = !m_IsPrefabInstanceOutermostRoot || m_IsInstanceRootInPrefabContents; using (new EditorGUI.DisabledScope(disabled)) { + Rect r = EditorGUILayout.GetControlRect(false, EditorGUI.kSingleLineHeight); + if (HandleDragPrefabAssetOverObjectFieldPopupMenu(r)) + { + GUIUtility.ExitGUI(); // handled by popup menu + } if (m_IsMissingArtifact) - newAsset = EditorGUILayout.ObjectField(m_MissingGameObject, typeof(GameObject), false) as GameObject; + newAsset = EditorGUI.ObjectField(r, m_MissingGameObject, typeof(GameObject), false) as GameObject; else - newAsset = EditorGUILayout.ObjectField(m_AllPrefabInstanceRootsAreFromThisAsset, typeof(GameObject), false) as GameObject; + newAsset = EditorGUI.ObjectField(r, m_AllPrefabInstanceRootsAreFromThisAsset, typeof(GameObject), false) as GameObject; } if (disabled) @@ -617,8 +653,11 @@ void PrefabObjectField() if (string.IsNullOrEmpty(errorMsg)) { + // targets are in reverse order from the Hierarchy selection so we reverse it here so we get the same result as repalcing from the Hierarchy + var replaceTargets = new List(targets); + replaceTargets.Reverse(); if (targets.Length > 1) - PrefabUtility.ReplacePrefabAssetOfPrefabInstances(targets.Select(e => (GameObject)e).ToArray(), newAsset, InteractionMode.UserAction); + PrefabUtility.ReplacePrefabAssetOfPrefabInstances(replaceTargets.Select(e => (GameObject)e).ToArray(), newAsset, InteractionMode.UserAction); else PrefabUtility.ReplacePrefabAssetOfPrefabInstance((GameObject)target, newAsset, InteractionMode.UserAction); CalculatePrefabStatus(); // Updates the cached m_FirstPrefabInstanceOutermostRootAsset to the newly selected Prefab @@ -1365,15 +1404,24 @@ internal void OnSceneDragInternal(SceneView sceneView, int index, EventType type internal static void DragPerform(SceneView sceneView, GameObject draggedObject, GameObject go) { - var defaultParentObject = SceneView.GetDefaultParentObjectIfSet(); + Transform defaultParentObject = SceneView.GetDefaultParentObjectIfSet(); var parent = defaultParentObject != null ? defaultParentObject : sceneView.customParentForDraggedObjects; - string uniqueName = GameObjectUtility.GetUniqueNameForSibling(parent, draggedObject.name); + if (parent != null) - draggedObject.transform.parent = parent; + { + draggedObject.transform.SetParent(parent, true); + } + + if (defaultParentObject == null && sceneView.customParentForDraggedObjects == null) + { + draggedObject.transform.SetAsLastSibling(); + } + draggedObject.hideFlags = 0; Undo.RegisterCreatedObjectUndo(draggedObject, "Place " + draggedObject.name); DragAndDrop.AcceptDrag(); + if (s_ShouldClearSelection) { Selection.objects = new[] { draggedObject }; @@ -1385,11 +1433,15 @@ internal static void DragPerform(SceneView sceneView, GameObject draggedObject, // selection to all of them by joining them to the previous selection list Selection.objects = Selection.gameObjects.Union(new[] { draggedObject }).ToArray(); } + HandleUtility.ignoreRaySnapObjects = null; + if (SceneView.mouseOverWindow != null) SceneView.mouseOverWindow.Focus(); + if (!Application.IsPlaying(draggedObject)) draggedObject.name = uniqueName; + s_CyclicNestingDetected = false; } diff --git a/Editor/Mono/Inspector/GenericInspector.cs b/Editor/Mono/Inspector/GenericInspector.cs index debe3050f8..7c2cece9fe 100644 --- a/Editor/Mono/Inspector/GenericInspector.cs +++ b/Editor/Mono/Inspector/GenericInspector.cs @@ -2,8 +2,12 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; using System.Linq; +using UnityEditor.Audio.UIElements; using UnityEngine; +using UnityEngine.UIElements; +using Object = UnityEngine.Object; namespace UnityEditor { @@ -27,6 +31,11 @@ static class Styles public static string missingScriptMessageForPrefabInstance = L10n.Tr("The associated script can not be loaded.\nPlease fix any compile errors\nand open Prefab Mode and assign a valid script to the Prefab Asset."); } + internal static string GetMissingSerializeRefererenceMessageContainer() + { + return Styles.missingSerializeReferenceInstanceMessage; + } + internal override bool GetOptimizedGUIBlock(bool isDirty, bool isVisible, out float height) { height = -1; @@ -63,11 +72,19 @@ internal override bool GetOptimizedGUIBlock(bool isDirty, bool isVisible, out fl // Update serialized object representation if (m_SerializedObject == null) - m_SerializedObject = new SerializedObject(targets, m_Context) { inspectorMode = inspectorMode }; + { + m_SerializedObject = new SerializedObject(targets, m_Context) + { + inspectorMode = inspectorMode, + inspectorDataMode = dataMode + }; + } else { m_SerializedObject.Update(); m_SerializedObject.inspectorMode = inspectorMode; + if (m_SerializedObject.inspectorDataMode != dataMode) + m_SerializedObject.inspectorDataMode = dataMode; } height = 0; @@ -231,18 +248,35 @@ public bool MissingMonoBehaviourGUI() return true; } - public bool ShowMissingSerializeReferenceWarningBoxIfRequired() + internal static bool MissingSerializeReference(Object unityTarget) { - var monoBehaviour = target as MonoBehaviour; - var scriptableObject = target as ScriptableObject; - if ((monoBehaviour != null || scriptableObject != null) && SerializationUtility.HasManagedReferencesWithMissingTypes(target)) + var monoBehaviour = unityTarget as MonoBehaviour; + var scriptableObject = unityTarget as ScriptableObject; + if ((monoBehaviour != null || scriptableObject != null) && SerializationUtility.HasManagedReferencesWithMissingTypes(unityTarget)) { - EditorGUILayout.HelpBox(Styles.missingSerializeReferenceInstanceMessage, MessageType.Warning, true); return true; } return false; } + internal static bool ShowMissingSerializeReferenceWarningBoxIfRequired(Object unityTarget) + { + if (MissingSerializeReference(unityTarget)) + { + EditorGUILayout.HelpBox(Styles.missingSerializeReferenceInstanceMessage, MessageType.Warning, true); + return true; + } + else + { + return false; + } + } + + public bool ShowMissingSerializeReferenceWarningBoxIfRequired() + { + return ShowMissingSerializeReferenceWarningBoxIfRequired(target); + } + static GUIContent s_fixupTypeContent = new GUIContent("Fix underlying type"); enum ShowTypeFixupResult { @@ -323,8 +357,10 @@ internal void OnDisableINTERNAL() { ResetOptimizedBlock(); CleanupPropertyEditor(); - if (!(preview is Editor)) - preview.Cleanup(); + propertyHandlerCache.Dispose(); + + if (m_DummyPreview != null && m_DummyPreview is not Editor) + m_DummyPreview.Cleanup(); } internal static bool ObjectIsMonoBehaviourOrScriptableObjectWithoutScript(Object obj) @@ -351,5 +387,27 @@ public override void OnInspectorGUI() base.OnInspectorGUI(); } + + public override VisualElement CreateInspectorGUI() + { + if (serializedObject == null) + return null; + + var root = new VisualElement(); + + if (MissingSerializeReference(target)) + { + root.Add(new HelpBox(GetMissingSerializeRefererenceMessageContainer(), HelpBoxMessageType.Warning)); + } + + UIElements.InspectorElement.FillDefaultInspector(root, serializedObject, this); + + if (target is MonoBehaviour behaviour && AudioUtil.HasAudioCallback(behaviour)) + { + root.Add(new OnAudioFilterReadLevelMeter(behaviour)); + } + + return root; + } } } diff --git a/Editor/Mono/Inspector/GraphicsSettingsInspector.cs b/Editor/Mono/Inspector/GraphicsSettingsInspector.cs index 8ba091cbc9..dfcfa6baec 100644 --- a/Editor/Mono/Inspector/GraphicsSettingsInspector.cs +++ b/Editor/Mono/Inspector/GraphicsSettingsInspector.cs @@ -43,6 +43,10 @@ internal class Styles public static readonly GUIContent cameraSettings = EditorGUIUtility.TrTextContent("Camera Settings"); public static readonly GUIContent renderPipeSettings = EditorGUIUtility.TrTextContent("Scriptable Render Pipeline Settings", "This defines the default render pipeline, which Unity uses when there is no override for a given quality level."); public static readonly GUIContent renderPipeLabel = EditorGUIUtility.TrTextContent("Scriptable Render Pipeline"); + public static readonly GUIContent cullingSettings = EditorGUIUtility.TrTextContent("Culling Settings"); + public static readonly GUIContent cameraRelativeSettings = EditorGUIUtility.TrTextContent("Camera-Relative Culling"); + public static readonly GUIContent cameraRelativeLightCulling = EditorGUIUtility.TrTextContent("Lights", "When enabled, Unity uses the camera position as the reference point for culling lights instead of the world space origin."); + public static readonly GUIContent cameraRelativeShadowCulling = EditorGUIUtility.TrTextContent("Shadows", "When enabled, Unity uses the camera position as the reference point for culling shadows instead of the world space origin."); } Editor m_TierSettingsEditor; @@ -56,6 +60,8 @@ internal class Styles SerializedProperty m_ScriptableRenderLoop; SerializedProperty m_LogWhenShaderIsCompiled; SerializedProperty m_LightProbeOutsideHullStrategy; + SerializedProperty m_CameraRelativeLightCulling; + SerializedProperty m_CameraRelativeShadowCulling; Object graphicsSettings { @@ -100,6 +106,8 @@ public void OnEnable() m_LogWhenShaderIsCompiled = serializedObject.FindProperty("m_LogWhenShaderIsCompiled"); m_LightProbeOutsideHullStrategy = serializedObject.FindProperty("m_LightProbeOutsideHullStrategy"); tierSettingsAnimator = new AnimatedValues.AnimBool(showTierSettingsUI, Repaint); + m_CameraRelativeLightCulling = serializedObject.FindProperty("m_CameraRelativeLightCulling"); + m_CameraRelativeShadowCulling = serializedObject.FindProperty("m_CameraRelativeShadowCulling"); } private void HandleEditorWindowButton() @@ -199,6 +207,16 @@ public override void OnInspectorGUI() shaderPreloadEditor.OnInspectorGUI(); + EditorGUILayout.Space(); + GUILayout.Label(Styles.cullingSettings, EditorStyles.boldLabel); + EditorGUI.indentLevel++; + EditorGUILayout.LabelField(Styles.cameraRelativeSettings, EditorStyles.label); + EditorGUI.indentLevel++; + EditorGUILayout.PropertyField(m_CameraRelativeLightCulling, Styles.cameraRelativeLightCulling); + EditorGUILayout.PropertyField(m_CameraRelativeShadowCulling, Styles.cameraRelativeShadowCulling); + EditorGUI.indentLevel--; + EditorGUI.indentLevel--; + serializedObject.ApplyModifiedProperties(); } diff --git a/Editor/Mono/Inspector/InspectorWindow.cs b/Editor/Mono/Inspector/InspectorWindow.cs index 5f15cc30a1..db6d972d1a 100644 --- a/Editor/Mono/Inspector/InspectorWindow.cs +++ b/Editor/Mono/Inspector/InspectorWindow.cs @@ -54,7 +54,7 @@ internal void Awake() protected override void OnDestroy() { - if (m_PreviewWindow != null) + if (m_PreviewWindow is { IsFloatingWindow: true } ) m_PreviewWindow.Close(); if (m_Tracker != null && !m_Tracker.Equals(ActiveEditorTracker.sharedTracker)) m_Tracker.Destroy(); @@ -85,12 +85,12 @@ protected override void OnEnable() m_LockTracker.tracker = tracker; m_LockTracker.lockStateChanged.AddListener(LockStateChanged); + m_Tracker.dataMode = GetDataModeController_Internal().dataMode; EditorApplication.projectWasLoaded += OnProjectWasLoaded; + EditorApplication.playModeStateChanged += OnPlayModeStateChanged; Selection.selectionChanged += OnSelectionChanged; AssemblyReloadEvents.afterAssemblyReload += OnAfterAssemblyReload; - - UpdateDataMode(); } private void OnAfterAssemblyReload() @@ -109,6 +109,15 @@ private void OnAfterAssemblyReload() tracker.ForceRebuild(); } + void OnPlayModeStateChanged(PlayModeStateChange state) + { + // Case UUM-64580: unable to interact with the inspector after exiting play mode. + // Somehow the inspector is still showing the last selected object, but the tracker does not respond anymore. + // Forcing a rebuild of the tracker after exiting play mode ensures that it is put back in a valid state. + if (state == PlayModeStateChange.EnteredEditMode) + tracker.ForceRebuild(); + } + void OnBecameVisible() { SceneView.SetActiveEditorsDirty(true); @@ -145,38 +154,28 @@ private void OnProjectWasLoaded() private void OnSelectionChanged() { + if (isLocked) + return; + RebuildContentsContainers(); if (Selection.objects.Length == 0 && m_MultiEditLabel != null) { m_MultiEditLabel.RemoveFromHierarchy(); } - UpdateDataMode(); - } - - private void UpdateDataMode() - { - UpdateSupportedDataModes(); - - // Try to respect the DataMode hint provided by the selection. - // If impossible, try to retain the current DataMode if supported. - if (IsDataModeSupported(Selection.dataModeHint)) - SwitchToDataMode(Selection.dataModeHint); - else if (!IsDataModeSupported(dataMode)) - SwitchToDefaultDataMode(); + UpdateSupportedDataModesList(); } // Note: supportedModes is cleared before and sorted after this method is called protected override void OnUpdateSupportedDataModes(List supportedModes) { - m_UserSupportedDataModes.Clear(); - // Not showing data modes in debug - if (m_InspectorMode == InspectorMode.Normal) - { - DataModeSupportUtils.GetDataModeSupport(Selection.activeObject, Selection.activeContext, m_UserSupportedDataModes); - supportedModes.AddRange(m_UserSupportedDataModes); - } + if (m_InspectorMode != InspectorMode.Normal) + return; + + m_UserSupportedDataModes.Clear(); + DataModeSupportUtils.GetDataModeSupport(Selection.activeObject, Selection.activeContext, m_UserSupportedDataModes); + supportedModes.AddRange(m_UserSupportedDataModes); } protected override void OnDisable() @@ -187,6 +186,7 @@ protected override void OnDisable() m_LockTracker?.lockStateChanged.RemoveListener(LockStateChanged); EditorApplication.projectWasLoaded -= OnProjectWasLoaded; + EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; Selection.selectionChanged -= OnSelectionChanged; AssemblyReloadEvents.afterAssemblyReload -= OnAfterAssemblyReload; } @@ -483,6 +483,9 @@ internal static void RefreshInspectors() internal override Object GetInspectedObject() { + if (tracker.hasComponentsWhichCannotBeMultiEdited && !tracker.isLocked) + return Selection.activeObject; + Editor editor = InspectorWindowUtils.GetFirstNonImportInspectorEditor(tracker.activeEditors); if (editor == null) return null; @@ -491,6 +494,9 @@ internal override Object GetInspectedObject() internal Object[] GetInspectedObjects() { + if (tracker.hasComponentsWhichCannotBeMultiEdited && !tracker.isLocked) + return Selection.objects; + Editor editor = InspectorWindowUtils.GetFirstNonImportInspectorEditor(tracker.activeEditors); if (editor == null) return null; diff --git a/Editor/Mono/Inspector/InspectorWindowUtils.cs b/Editor/Mono/Inspector/InspectorWindowUtils.cs index 375159071c..5c7e1d405c 100644 --- a/Editor/Mono/Inspector/InspectorWindowUtils.cs +++ b/Editor/Mono/Inspector/InspectorWindowUtils.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Linq; using UnityEngine; using Object = UnityEngine.Object; @@ -76,15 +77,15 @@ public static void GetPreviewableTypes(out Dictionary> previewa continue; } - List types; - - if (!previewableTypes.TryGetValue(previewAttr.m_Type, out types)) + foreach (var customPreviewType in new[] { previewAttr.m_Type }.Concat(TypeCache.GetTypesDerivedFrom(previewAttr.m_Type))) { - types = new List(); - previewableTypes.Add(previewAttr.m_Type, types); + if (!previewableTypes.TryGetValue(customPreviewType, out var types)) + { + types = new List(); + previewableTypes.Add(customPreviewType, types); + } + types.Add(type); } - - types.Add(type); } } } diff --git a/Editor/Mono/Inspector/LODGroupEditor.cs b/Editor/Mono/Inspector/LODGroupEditor.cs index 88700e4f67..6a16409f33 100644 --- a/Editor/Mono/Inspector/LODGroupEditor.cs +++ b/Editor/Mono/Inspector/LODGroupEditor.cs @@ -123,7 +123,7 @@ protected virtual void DrawLODRendererMeshListItems(Rect rect, int index, bool i Rect objectFieldRect = new Rect(rect.x, rect.y, rect.width * 0.6f, EditorGUI.kSingleLineHeight); rect.height = EditorGUI.kSingleLineHeight; - if (m_RendererMeshLists[m_ReorderableListIndex].count != m_ReoderableMeshListCounts[m_ReorderableListIndex]) + if (m_RendererMeshLists.Length != m_ReoderableMeshListCounts.Length || m_RendererMeshLists[m_ReorderableListIndex].count != m_ReoderableMeshListCounts[m_ReorderableListIndex]) { CalculatePrimitiveCountForRenderers(); UpdateRendererMeshListCounts(); @@ -493,7 +493,7 @@ bool IsObjectVisibleToCamera(Camera camera) return false; } - Mesh GetMeshFromRendererIfAvailable(Renderer renderer) + public static Mesh GetMeshFromRendererIfAvailable(Renderer renderer) { if (renderer == null) return null; @@ -555,6 +555,7 @@ void ResetMembersForEachOpenInspector() { serializedObject.Update(); m_LODs = serializedObject.FindProperty("m_LODs"); + m_MaxLODCountForMultiselection = GetMaxLODCountForMultiSelection(); CalculatePrimitiveCountForRenderers(); ResetFoldoutLists(); @@ -600,7 +601,7 @@ void ResetFoldoutLists() InitAndSetFoldoutLabelTextures(); } - bool CheckIfSubmeshesHaveMatchingTopologyTypes(Mesh mesh) + public static bool CheckIfSubmeshesHaveMatchingTopologyTypes(Mesh mesh) { var meshTopology = mesh.GetTopology(0); @@ -614,7 +615,7 @@ bool CheckIfSubmeshesHaveMatchingTopologyTypes(Mesh mesh) return false; } - bool CheckIfMeshesHaveMatchingTopologyTypes(Renderer[] renderers) + public static bool CheckIfMeshesHaveMatchingTopologyTypes(Renderer[] renderers) { for (int i = 0; i < renderers.Length; i++) { @@ -637,6 +638,14 @@ public override void OnInspectorGUI() // Grab the latest data from the object serializedObject.Update(); + // Flush this editor's state when it's out of sync with serialized data. + if (m_LODs.arraySize != m_LODGroupFoldoutHeaderValues.Length || m_LODs.arraySize != m_RendererMeshLists.Length) + { + m_MaxLODCountForMultiselection = GetMaxLODCountForMultiSelection(); + CalculatePrimitiveCountForRenderers(); + ResetFoldoutLists(); + } + EditorGUILayout.PropertyField(m_FadeMode); m_ShowAnimateCrossFading.target = m_FadeMode.intValue != (int)LODFadeMode.None; @@ -1622,23 +1631,33 @@ void InitPreview() protected void DoRenderPreview() { + bool LODSelected = activeLOD >= 0; if (m_PreviewUtility.renderTexture.width <= 0 || m_PreviewUtility.renderTexture.height <= 0 - || m_NumberOfLODs <= 0 - || activeLOD < 0) + || m_NumberOfLODs <= 0) return; var bounds = new Bounds(Vector3.zero, Vector3.zero); bool boundsSet = false; + Renderer[] renderers = null; + if (target is LODGroup targetLODGroup) + { + LOD[] lodArray = targetLODGroup.GetLODs(); + int lodIndex = LODSelected ? activeLOD : 0; // display LOD0 if no LOD is selected + if (lodIndex >= 0 && lodIndex < lodArray.Length) + { + renderers = lodArray[lodIndex].renderers; + } + } + if (renderers == null) + return; + var meshsToRender = new List(); var billboards = new List(); - var renderers = serializedObject.FindProperty(string.Format(kRenderRootPath, activeLOD)); - for (int i = 0; i < renderers.arraySize; i++) + for (int i = 0; i < renderers.Length; i++) { - var lodRenderRef = renderers.GetArrayElementAtIndex(i).FindPropertyRelative("renderer"); - var renderer = lodRenderRef.objectReferenceValue as Renderer; - + var renderer = renderers[i]; if (renderer == null) continue; diff --git a/Editor/Mono/Inspector/LightProbeGroupInspector.cs b/Editor/Mono/Inspector/LightProbeGroupInspector.cs index a32bbffc56..6cf61c3cd9 100644 --- a/Editor/Mono/Inspector/LightProbeGroupInspector.cs +++ b/Editor/Mono/Inspector/LightProbeGroupInspector.cs @@ -524,19 +524,20 @@ private static class Styles public static readonly GUIContent duplicateSelected = EditorGUIUtility.TrTextContent("Duplicate Selected", "Duplicate the selected Light Probes."); public static readonly GUIContent performDeringing = EditorGUIUtility.TrTextContent("Remove Ringing", "When enabled, removes visible overshooting often observed as ringing on objects affected by intense lighting at the expense of reduced contrast."); - // Toolbar - public static readonly GUIContent editModeButton = EditorGUIUtility.TrIconContent("EditCollider", "Edit Light Probes"); - public static readonly EditMode.SceneViewEditMode[] toolbarModes = {EditMode.SceneViewEditMode.LightProbeGroup}; - public static readonly GUIContent[] toolbarButtons = { editModeButton }; + public static readonly GUIContent[] enterToolbarButtons = { EditorGUIUtility.TrTextContent("Edit Light Probe Positions", "Change positions for Light Probes.") }; + public static readonly GUIContent[] exitToolbarButtons = { EditorGUIUtility.TrTextContent("Exit Light Probe Editing", "Exit Light Probe Positions Editing.") }; + public static readonly EditMode.SceneViewEditMode[] toolbarModes = { EditMode.SceneViewEditMode.LightProbeGroup }; } private LightProbeGroupEditor m_Editor; + private LightProbeGroup m_LightProbeGroup; private bool m_EditingProbes; private bool m_ShouldFocus; public void OnEnable() { - m_Editor = new LightProbeGroupEditor(target as LightProbeGroup); + m_LightProbeGroup = target as LightProbeGroup; + m_Editor = new LightProbeGroupEditor(m_LightProbeGroup); m_Editor.PullProbePositions(); m_Editor.DeselectProbes(); m_Editor.PushProbePositions(); @@ -604,6 +605,7 @@ public void OnDisable() m_Editor.PushProbePositions(); m_Editor = null; } + m_LightProbeGroup = null; } private void UndoRedoPerformed(in UndoRedoInfo info) @@ -628,74 +630,73 @@ public override void OnInspectorGUI() m_Editor.PullProbePositions(); - EditorGUILayout.BeginHorizontal(); - { - GUILayout.FlexibleSpace(); - EditMode.DoInspectorToolbar(Styles.toolbarModes, Styles.toolbarButtons, this); - GUILayout.FlexibleSpace(); - } - EditorGUILayout.EndHorizontal(); - GUILayout.Space(3); m_Editor.drawTetrahedra.value = EditorGUILayout.Toggle(Styles.showWireframe, m_Editor.drawTetrahedra.value); m_Editor.deringProbes = EditorGUILayout.Toggle(Styles.performDeringing, m_Editor.deringProbes); - EditorGUI.BeginDisabledGroup(EditMode.editMode != EditMode.SceneViewEditMode.LightProbeGroup); - EditorGUI.BeginDisabledGroup(m_Editor.SelectedCount == 0); - - EditorGUI.BeginChangeCheck(); - Vector3 pos = m_Editor.SelectedCount > 0 ? m_Editor.GetSelectedPositions()[0] : Vector3.zero; - Vector3 newPosition = EditorGUILayout.Vector3Field(Styles.selectedProbePosition, pos); - - if (EditorGUI.EndChangeCheck()) + var isCurrentLightProbeInEditMode = EditMode.editMode == EditMode.SceneViewEditMode.LightProbeGroup && EditMode.IsOwner(this); + EditorGUILayout.BeginHorizontal(); { - Vector3[] selectedPositions = m_Editor.GetSelectedPositions(); - Vector3 delta = CalculateDeltaAndClamp(newPosition, pos); - for (int i = 0; i < selectedPositions.Length; i++) - m_Editor.UpdateSelectedPosition(i, selectedPositions[i] + delta); + EditMode.DoInspectorToolbar(Styles.toolbarModes, isCurrentLightProbeInEditMode ? Styles.exitToolbarButtons : Styles.enterToolbarButtons, this); } - EditorGUI.EndDisabledGroup(); - - GUILayout.Space(3); - - GUILayout.BeginHorizontal(); - GUILayout.BeginVertical(); + EditorGUILayout.EndHorizontal(); - if (GUILayout.Button(Styles.addProbe)) + EditorGUI.BeginDisabledGroup(!isCurrentLightProbeInEditMode); { - var position = Vector3.zero; - if (SceneView.lastActiveSceneView) + GUILayout.BeginHorizontal(); { - var probeGroup = target as LightProbeGroup; - if (probeGroup) position = probeGroup.transform.InverseTransformPoint(position); + GUILayout.BeginVertical(); + if (GUILayout.Button(Styles.addProbe)) + { + var position = Vector3.zero; + if (SceneView.lastActiveSceneView) + position = m_LightProbeGroup.transform.InverseTransformPoint(position); + StartEditProbes(); + m_Editor.DeselectProbes(); + m_Editor.AddProbe(position); + } + + if (GUILayout.Button(Styles.deleteSelected)) + { + StartEditProbes(); + m_Editor.RemoveSelectedProbes(); + } + GUILayout.EndVertical(); } - StartEditProbes(); - m_Editor.DeselectProbes(); - m_Editor.AddProbe(position); - } + GUILayout.BeginVertical(); + { + if (GUILayout.Button(Styles.selectAll)) + { + StartEditProbes(); + m_Editor.SelectAllProbes(); + } - if (GUILayout.Button(Styles.deleteSelected)) - { - StartEditProbes(); - m_Editor.RemoveSelectedProbes(); - } - GUILayout.EndVertical(); - GUILayout.BeginVertical(); + if (GUILayout.Button(Styles.duplicateSelected)) + { + StartEditProbes(); + m_Editor.DuplicateSelectedProbes(); + } - if (GUILayout.Button(Styles.selectAll)) - { - StartEditProbes(); - m_Editor.SelectAllProbes(); - } + GUILayout.EndVertical(); + } + GUILayout.EndHorizontal(); - if (GUILayout.Button(Styles.duplicateSelected)) - { - StartEditProbes(); - m_Editor.DuplicateSelectedProbes(); - } + EditorGUI.BeginDisabledGroup(m_Editor.SelectedCount == 0); + { + EditorGUI.BeginChangeCheck(); + Vector3 pos = m_Editor.SelectedCount > 0 ? m_Editor.GetSelectedPositions()[0] : Vector3.zero; + Vector3 newPosition = EditorGUILayout.Vector3Field(Styles.selectedProbePosition, pos); - GUILayout.EndVertical(); - GUILayout.EndHorizontal(); + if (EditorGUI.EndChangeCheck()) + { + Vector3[] selectedPositions = m_Editor.GetSelectedPositions(); + Vector3 delta = CalculateDeltaAndClamp(newPosition, pos); + for (int i = 0; i < selectedPositions.Length; i++) + m_Editor.UpdateSelectedPosition(i, selectedPositions[i] + delta); + } + } + EditorGUI.EndDisabledGroup(); + } EditorGUI.EndDisabledGroup(); m_Editor.HandleEditMenuHotKeyCommands(); diff --git a/Editor/Mono/Inspector/LightingSettingsEditor.cs b/Editor/Mono/Inspector/LightingSettingsEditor.cs index 1e50def75d..dc48baaebe 100644 --- a/Editor/Mono/Inspector/LightingSettingsEditor.cs +++ b/Editor/Mono/Inspector/LightingSettingsEditor.cs @@ -104,6 +104,7 @@ internal class SharedLightingSettingsEditor SerializedProperty m_FilterMode; SerializedProperty m_TiledBaking; SerializedProperty m_NumRaysToShootPerTexel; + SerializedProperty m_RespectSceneVisibilityWhenBakingGI; enum DenoiserTarget { @@ -290,69 +291,70 @@ public void OnGUI(bool compact, bool drawAutoGenerate) InternalSettingsGUI(compact); } - public void UpdateSettings(SerializedObject lso) + public void UpdateSettings(SerializedObject lightingSettingsObject) { - if (lso != null) - { - m_GIWorkflowMode = lso.FindProperty("m_GIWorkflowMode"); - - //realtime GI - m_RealtimeResolution = lso.FindProperty("m_RealtimeResolution"); - m_EnableRealtimeGI = lso.FindProperty("m_EnableRealtimeLightmaps"); - m_RealtimeEnvironmentLighting = lso.FindProperty("m_RealtimeEnvironmentLighting"); - - //baked - m_EnabledBakedGI = lso.FindProperty("m_EnableBakedLightmaps"); - m_BakeBackend = lso.FindProperty("m_BakeBackend"); - m_MixedBakeMode = lso.FindProperty("m_MixedBakeMode"); - m_AlbedoBoost = lso.FindProperty("m_AlbedoBoost"); - m_IndirectOutputScale = lso.FindProperty("m_IndirectOutputScale"); - m_LightmapMaxSize = lso.FindProperty("m_LightmapMaxSize"); - m_LightmapParameters = lso.FindProperty("m_LightmapParameters"); - m_LightmapDirectionalMode = lso.FindProperty("m_LightmapsBakeMode"); - m_BakeResolution = lso.FindProperty("m_BakeResolution"); - m_Padding = lso.FindProperty("m_Padding"); - m_AmbientOcclusion = lso.FindProperty("m_AO"); - m_AOMaxDistance = lso.FindProperty("m_AOMaxDistance"); - m_CompAOExponent = lso.FindProperty("m_CompAOExponent"); - m_CompAOExponentDirect = lso.FindProperty("m_CompAOExponentDirect"); - m_LightmapCompression = lso.FindProperty("m_LightmapCompression"); - m_FinalGather = lso.FindProperty("m_FinalGather"); - m_FinalGatherRayCount = lso.FindProperty("m_FinalGatherRayCount"); - m_FinalGatherFiltering = lso.FindProperty("m_FinalGatherFiltering"); - - // pvr - m_PVRSampleCount = lso.FindProperty("m_PVRSampleCount"); - m_PVRDirectSampleCount = lso.FindProperty("m_PVRDirectSampleCount"); - m_PVRBounces = lso.FindProperty("m_PVRBounces"); - m_PVRCulling = lso.FindProperty("m_PVRCulling"); - m_PVRFilteringMode = lso.FindProperty("m_PVRFilteringMode"); - m_PVRFilterTypeDirect = lso.FindProperty("m_PVRFilterTypeDirect"); - m_PVRFilterTypeIndirect = lso.FindProperty("m_PVRFilterTypeIndirect"); - m_PVRFilterTypeAO = lso.FindProperty("m_PVRFilterTypeAO"); - m_PVRDenoiserTypeDirect = lso.FindProperty("m_PVRDenoiserTypeDirect"); - m_PVRDenoiserTypeIndirect = lso.FindProperty("m_PVRDenoiserTypeIndirect"); - m_PVRDenoiserTypeAO = lso.FindProperty("m_PVRDenoiserTypeAO"); - m_PVRFilteringGaussRadiusDirect = lso.FindProperty("m_PVRFilteringGaussRadiusDirect"); - m_PVRFilteringGaussRadiusIndirect = lso.FindProperty("m_PVRFilteringGaussRadiusIndirect"); - m_PVRFilteringGaussRadiusAO = lso.FindProperty("m_PVRFilteringGaussRadiusAO"); - m_PVRFilteringAtrousPositionSigmaDirect = lso.FindProperty("m_PVRFilteringAtrousPositionSigmaDirect"); - m_PVRFilteringAtrousPositionSigmaIndirect = lso.FindProperty("m_PVRFilteringAtrousPositionSigmaIndirect"); - m_PVRFilteringAtrousPositionSigmaAO = lso.FindProperty("m_PVRFilteringAtrousPositionSigmaAO"); - m_PVREnvironmentIS = lso.FindProperty("m_PVREnvironmentImportanceSampling"); - m_PVREnvironmentSampleCount = lso.FindProperty("m_PVREnvironmentSampleCount"); - m_LightProbeSampleCountMultiplier = lso.FindProperty("m_LightProbeSampleCountMultiplier"); - - //dev debug properties - m_ExportTrainingData = lso.FindProperty("m_ExportTrainingData"); - m_TrainingDataDestination = lso.FindProperty("m_TrainingDataDestination"); - m_ForceWhiteAlbedo = lso.FindProperty("m_ForceWhiteAlbedo"); - m_ForceUpdates = lso.FindProperty("m_ForceUpdates"); - m_FilterMode = lso.FindProperty("m_FilterMode"); - m_BounceScale = lso.FindProperty("m_BounceScale"); - m_TiledBaking = lso.FindProperty("m_PVRTiledBaking"); - m_NumRaysToShootPerTexel = lso.FindProperty("m_NumRaysToShootPerTexel"); - } + if (lightingSettingsObject == null) + return; + + m_GIWorkflowMode = lightingSettingsObject.FindProperty("m_GIWorkflowMode"); + + //realtime GI + m_RealtimeResolution = lightingSettingsObject.FindProperty("m_RealtimeResolution"); + m_EnableRealtimeGI = lightingSettingsObject.FindProperty("m_EnableRealtimeLightmaps"); + m_RealtimeEnvironmentLighting = lightingSettingsObject.FindProperty("m_RealtimeEnvironmentLighting"); + + //baked + m_EnabledBakedGI = lightingSettingsObject.FindProperty("m_EnableBakedLightmaps"); + m_BakeBackend = lightingSettingsObject.FindProperty("m_BakeBackend"); + m_MixedBakeMode = lightingSettingsObject.FindProperty("m_MixedBakeMode"); + m_AlbedoBoost = lightingSettingsObject.FindProperty("m_AlbedoBoost"); + m_IndirectOutputScale = lightingSettingsObject.FindProperty("m_IndirectOutputScale"); + m_LightmapMaxSize = lightingSettingsObject.FindProperty("m_LightmapMaxSize"); + m_LightmapParameters = lightingSettingsObject.FindProperty("m_LightmapParameters"); + m_LightmapDirectionalMode = lightingSettingsObject.FindProperty("m_LightmapsBakeMode"); + m_BakeResolution = lightingSettingsObject.FindProperty("m_BakeResolution"); + m_Padding = lightingSettingsObject.FindProperty("m_Padding"); + m_AmbientOcclusion = lightingSettingsObject.FindProperty("m_AO"); + m_AOMaxDistance = lightingSettingsObject.FindProperty("m_AOMaxDistance"); + m_CompAOExponent = lightingSettingsObject.FindProperty("m_CompAOExponent"); + m_CompAOExponentDirect = lightingSettingsObject.FindProperty("m_CompAOExponentDirect"); + m_LightmapCompression = lightingSettingsObject.FindProperty("m_LightmapCompression"); + m_FinalGather = lightingSettingsObject.FindProperty("m_FinalGather"); + m_FinalGatherRayCount = lightingSettingsObject.FindProperty("m_FinalGatherRayCount"); + m_FinalGatherFiltering = lightingSettingsObject.FindProperty("m_FinalGatherFiltering"); + + // pvr + m_PVRSampleCount = lightingSettingsObject.FindProperty("m_PVRSampleCount"); + m_PVRDirectSampleCount = lightingSettingsObject.FindProperty("m_PVRDirectSampleCount"); + m_PVRBounces = lightingSettingsObject.FindProperty("m_PVRBounces"); + m_PVRCulling = lightingSettingsObject.FindProperty("m_PVRCulling"); + m_PVRFilteringMode = lightingSettingsObject.FindProperty("m_PVRFilteringMode"); + m_PVRFilterTypeDirect = lightingSettingsObject.FindProperty("m_PVRFilterTypeDirect"); + m_PVRFilterTypeIndirect = lightingSettingsObject.FindProperty("m_PVRFilterTypeIndirect"); + m_PVRFilterTypeAO = lightingSettingsObject.FindProperty("m_PVRFilterTypeAO"); + m_PVRDenoiserTypeDirect = lightingSettingsObject.FindProperty("m_PVRDenoiserTypeDirect"); + m_PVRDenoiserTypeIndirect = lightingSettingsObject.FindProperty("m_PVRDenoiserTypeIndirect"); + m_PVRDenoiserTypeAO = lightingSettingsObject.FindProperty("m_PVRDenoiserTypeAO"); + m_PVRFilteringGaussRadiusDirect = lightingSettingsObject.FindProperty("m_PVRFilteringGaussRadiusDirect"); + m_PVRFilteringGaussRadiusIndirect = lightingSettingsObject.FindProperty("m_PVRFilteringGaussRadiusIndirect"); + m_PVRFilteringGaussRadiusAO = lightingSettingsObject.FindProperty("m_PVRFilteringGaussRadiusAO"); + m_PVRFilteringAtrousPositionSigmaDirect = lightingSettingsObject.FindProperty("m_PVRFilteringAtrousPositionSigmaDirect"); + m_PVRFilteringAtrousPositionSigmaIndirect = lightingSettingsObject.FindProperty("m_PVRFilteringAtrousPositionSigmaIndirect"); + m_PVRFilteringAtrousPositionSigmaAO = lightingSettingsObject.FindProperty("m_PVRFilteringAtrousPositionSigmaAO"); + m_PVREnvironmentIS = lightingSettingsObject.FindProperty("m_PVREnvironmentImportanceSampling"); + m_PVREnvironmentSampleCount = lightingSettingsObject.FindProperty("m_PVREnvironmentSampleCount"); + m_LightProbeSampleCountMultiplier = lightingSettingsObject.FindProperty("m_LightProbeSampleCountMultiplier"); + + //dev debug properties + m_ExportTrainingData = lightingSettingsObject.FindProperty("m_ExportTrainingData"); + m_TrainingDataDestination = lightingSettingsObject.FindProperty("m_TrainingDataDestination"); + m_ForceWhiteAlbedo = lightingSettingsObject.FindProperty("m_ForceWhiteAlbedo"); + m_ForceUpdates = lightingSettingsObject.FindProperty("m_ForceUpdates"); + m_FilterMode = lightingSettingsObject.FindProperty("m_FilterMode"); + m_BounceScale = lightingSettingsObject.FindProperty("m_BounceScale"); + m_TiledBaking = lightingSettingsObject.FindProperty("m_PVRTiledBaking"); + m_NumRaysToShootPerTexel = lightingSettingsObject.FindProperty("m_NumRaysToShootPerTexel"); + m_RespectSceneVisibilityWhenBakingGI = lightingSettingsObject.FindProperty("m_RespectSceneVisibilityWhenBakingGI"); } // Private methods @@ -521,7 +523,7 @@ void GeneralLightmapSettingsGUI(bool compact) { BakeBackendGUI(); - if (lightmapperSupported && !m_BakeBackend.hasMultipleDifferentValues) + if (lightmapperSupported) { #pragma warning disable 618 if (m_BakeBackend.intValue == (int)LightingSettings.Lightmapper.Enlighten) @@ -550,12 +552,9 @@ void GeneralLightmapSettingsGUI(bool compact) EditorGUILayout.PropertyField(m_PVRCulling, Styles.culling); EditorGUILayout.PropertyField(m_PVREnvironmentIS, Styles.environmentImportanceSampling); - int sampleCount = EditorGUILayout.DelayedIntField(Styles.directSampleCount, m_PVRDirectSampleCount.intValue); - m_PVRDirectSampleCount.intValue = sampleCount; - sampleCount = EditorGUILayout.DelayedIntField(Styles.indirectSampleCount, m_PVRSampleCount.intValue); - m_PVRSampleCount.intValue = sampleCount; - sampleCount = EditorGUILayout.DelayedIntField(Styles.environmentSampleCount, m_PVREnvironmentSampleCount.intValue); - m_PVREnvironmentSampleCount.intValue = sampleCount; + MultiEditableDelayedIntField(m_PVRDirectSampleCount, Styles.directSampleCount); + MultiEditableDelayedIntField(m_PVRSampleCount, Styles.indirectSampleCount); + MultiEditableDelayedIntField(m_PVREnvironmentSampleCount, Styles.environmentSampleCount); using (new EditorGUI.DisabledScope(EditorSettings.useLegacyProbeSampleCount)) { @@ -780,26 +779,12 @@ static void DrawPropertyFieldWithPostfixLabel(SerializedProperty property, GUICo const float minimumWidth = 170.0f; const float postfixLabelWidth = 80.0f; - switch (property.propertyType) - { - case SerializedPropertyType.Float: - DrawFieldWithPostfixLabel( - (Rect propertyRect) => { property.floatValue = EditorGUI.FloatField(propertyRect, label, property.floatValue); }, - postfixLabel, - EditorStyles.numberField, - minimumWidth, - postfixLabelWidth); - break; - - case SerializedPropertyType.Integer: - DrawFieldWithPostfixLabel( - (Rect propertyRect) => { property.intValue = EditorGUI.IntField(propertyRect, label, property.intValue); }, - postfixLabel, - EditorStyles.numberField, - minimumWidth, - postfixLabelWidth); - break; - } + DrawFieldWithPostfixLabel( + (Rect propertyRect) => { EditorGUI.PropertyField(propertyRect, property, label); }, + postfixLabel, + EditorStyles.numberField, + minimumWidth, + postfixLabelWidth); } static void DrawFilterSettingField(SerializedProperty gaussSetting, @@ -815,7 +800,7 @@ static void DrawFilterSettingField(SerializedProperty gaussSetting, { case LightingSettings.FilterType.Gaussian: DrawFieldWithPostfixLabel( - (Rect propertyRect) => { EditorGUI.IntSlider(propertyRect, gaussSetting, 0, 5, gaussLabel); }, + (Rect propertyRect) => { EditorGUI.Slider(propertyRect, gaussSetting, 0.0f, 5.0f, gaussLabel); }, Styles.texels, EditorStyles.toolbarSlider, minimumWidth, @@ -1030,5 +1015,27 @@ void DrawDenoiserTypeDropdown(SerializedProperty prop, GUIContent label, Denoise } EditorGUI.EndProperty(); } + + void MultiEditableDelayedIntField(SerializedProperty property, GUIContent style) + { + if (property.hasMultipleDifferentValues) + { + EditorGUI.BeginChangeCheck(); + EditorGUI.showMixedValue = true; + + int fieldValue = EditorGUILayout.DelayedIntField(style, property.intValue); + + if (EditorGUI.EndChangeCheck()) + property.intValue = fieldValue; + + EditorGUI.showMixedValue = false; + } + + else + { + int fieldValue = EditorGUILayout.DelayedIntField(style, property.intValue); + property.intValue = fieldValue; + } + } } } diff --git a/Editor/Mono/Inspector/MaskFieldDropdown.cs b/Editor/Mono/Inspector/MaskFieldDropdown.cs index 92abeb52f9..b2b710be6e 100644 --- a/Editor/Mono/Inspector/MaskFieldDropdown.cs +++ b/Editor/Mono/Inspector/MaskFieldDropdown.cs @@ -5,10 +5,10 @@ using System; using System.Collections.Generic; using System.Linq; -using UnityEditorInternal; +using System.Reflection; using UnityEngine; using static UnityEditor.MaskDropDownUtils; -using System.Reflection; +using Object = UnityEngine.Object; namespace UnityEditor { @@ -18,11 +18,15 @@ internal class MaskFieldDropDown : PopupWindowContent SerializedProperty m_SerializedProperty; + // Keep a reference to the targets so we can recreate the serialized property if it becomes invalid. (UUM-72761) + Object[] m_Targets; + string m_PropertyName; + SelectionModes[] m_SelectionMatch; string[] m_OptionNames; int[] m_flagValues; - uint[] m_OptionMaskValues; - uint[] m_SelectionMaskValues; + int[] m_OptionMaskValues; + int[] m_SelectionMaskValues; int m_AllLayersMask = 0; @@ -33,6 +37,8 @@ internal class MaskFieldDropDown : PopupWindowContent public MaskFieldDropDown(SerializedProperty property) { m_SerializedProperty = property; + m_Targets = property.serializedObject.targetObjects; + m_PropertyName = property.propertyPath; m_SingleSelection = false; } @@ -46,15 +52,12 @@ public MaskFieldDropDown(string[] optionNames, int[] flagValues, int[] optionMas // these are not flag values, i.e. 1, 2, 4... // but the mask & flagValue[0..n] for each possible flag value // this is to ensure backwards compatibility with everything that uses MaskFieldGUI.GetSelectedValueForControl - m_OptionMaskValues = Array.ConvertAll(optionMaskValues, x=>(uint)x); + m_OptionMaskValues = (int[])optionMaskValues.Clone(); m_OptionNames = (string[])optionNames.Clone(); m_flagValues = new int[optionNames.Length]; - if (flagValues == null || flagValues.Length == optionNames.Length - 2) - { - m_flagValues[0] = 0; - m_flagValues[1] = -1; - } + m_flagValues[0] = 0; + m_flagValues[1] = -1; if (flagValues == null) { @@ -63,11 +66,23 @@ public MaskFieldDropDown(string[] optionNames, int[] flagValues, int[] optionMas } else { - Array.Copy(flagValues, 0, m_flagValues, optionNames.Length - flagValues.Length, flagValues.Length); + int index = 0; + int length = flagValues.Length; + + if (flagValues[0] == 0) + { + index = 1; + length--; + } + + if (flagValues[flagValues.Length - 1] == ~0) + length--; + + Array.Copy(flagValues, index, m_flagValues, 2, length); } m_SelectionMatch = new SelectionModes[] { SelectionModes.All }; - m_SelectionMaskValues = new uint[] { (uint)mask }; + m_SelectionMaskValues = new int[] { mask }; m_SingleSelection = true; m_MaskChangeCallback = maskChangeCallback; @@ -78,6 +93,12 @@ public MaskFieldDropDown(string[] optionNames, int[] flagValues, int[] optionMas m_AllLayersMask |= val; } + public void UpdateMaskValues(int mask, int[] optionMaskValues) + { + m_OptionMaskValues = (int[])optionMaskValues.Clone(); + m_SelectionMaskValues = new int[] { mask }; + } + public override Vector2 GetWindowSize() { var rowCount = m_OptionNames[0] == "Nothing" ? m_OptionNames.Length : m_OptionNames.Length + 2; @@ -116,9 +137,13 @@ void DrawGUIForArrays() for (int i = 0; i < m_OptionNames.Length; i++) { bool toggleVal = (m_SelectionMaskValues[0] & m_OptionMaskValues[i]) == m_OptionMaskValues[i]; - if (m_SelectionMaskValues[0] != 0 && i == 0) + if ((m_SelectionMaskValues[0] != 0 && i == 0) || m_SelectionMaskValues[0] != -1 && i == 1) toggleVal = false; - if ((m_SelectionMaskValues[0] == int.MaxValue || m_SelectionMaskValues[0] == m_AllLayersMask) && i == 1) + + // Check for m_AllLayerMask != 0 was added to cover a case when we have only the first defined Layer, Everything and Nothing. + // In this case optionMaskValues when Everything select it will contain [0, -1, 0] and m_AllLayerMask will be 0 when we populate it in the constructor. + // So when we click on Nothing we will get 0 but we will continue to show Everything as checked. + if ((m_SelectionMaskValues[0] == m_AllLayersMask) && i == 1 && m_AllLayersMask != 0) toggleVal = true; var guiRect = EditorGUILayout.GetControlRect(false, EditorGUI.kSingleLineHeight); @@ -132,10 +157,14 @@ void DrawGUIForArrays() { m_SelectionMaskValues[0] = m_OptionMaskValues[i]; var oldMaskValues = (uint[])m_OptionMaskValues.Clone(); - RecalculateMasks(); + MaskFieldGUI.CalculateMaskValues(m_SelectionMaskValues[0], m_flagValues, ref m_OptionMaskValues); // If all flag options are selected the mask becomes everythingValue to be consistent with the "Everything" option - if (oldMaskValues[i] == (uint)m_AllLayersMask) + // oldMaskValues[i] == (uint)m_AllLayersMask && i == 0 && m_OptionNames[0] != "Nothing" is for case when we clicked nothing and only have the first layer defined. + // It will invert Nothing to Everything if we don't double-check it for Nothing separately. + // Check comment above to see the math why we need it separately. + if (oldMaskValues[i] == (uint)m_AllLayersMask && i != 0 + || oldMaskValues[i] == (uint)m_AllLayersMask && i == 0 && m_OptionNames[0] != "Nothing") oldMaskValues[i] = ~0u; m_MaskChangeCallback.Invoke(oldMaskValues, null, i); @@ -143,14 +172,6 @@ void DrawGUIForArrays() } } - void RecalculateMasks() - { - uint selectedValue = m_SelectionMaskValues[0] == ~0u ? (uint)m_AllLayersMask : m_SelectionMaskValues[0]; - - for (var flagIndex = 2; flagIndex < m_flagValues.Length; flagIndex++) - m_OptionMaskValues[flagIndex] = selectedValue ^ (uint)m_flagValues[flagIndex]; - } - public override void OnGUI(Rect rect) { if (Event.current.type == EventType.MouseMove) @@ -167,23 +188,29 @@ public override void OnGUI(Rect rect) DrawGUIForArrays(); return; } + + if (!m_SerializedProperty.isValid) + { + var serializedObject = new SerializedObject(m_Targets); + m_SerializedProperty = serializedObject.FindProperty(m_PropertyName); + } if (m_SerializedProperty.propertyType != SerializedPropertyType.LayerMask) return; var isNothing = m_SerializedProperty.intValue == 0; - var isEverything = m_SerializedProperty.intValue == int.MaxValue; + var isEverything = m_SerializedProperty.intValue == -1; GUILayout.Space(2); var toggleStyle = m_SerializedProperty.hasMultipleDifferentValues && isNothing ? Styles.menuItemMixed : Styles.menuItem; DrawEverythingOrNothingSelectedToggle(isNothing, "Nothing", toggleStyle, 0); toggleStyle = m_SerializedProperty.hasMultipleDifferentValues && isEverything ? Styles.menuItemMixed : Styles.menuItem; - DrawEverythingOrNothingSelectedToggle(isEverything, "Everything", toggleStyle, int.MaxValue); + DrawEverythingOrNothingSelectedToggle(isEverything, "Everything", toggleStyle, -1); for (int i = 0; i < m_OptionNames.Length; i++) { - var index = (int)Math.Log(m_OptionMaskValues[i], 2); + var index = (int)Math.Log((uint) m_OptionMaskValues[i], 2); bool toggleVal = m_SelectionMatch[index] == SelectionModes.All || m_SelectionMatch[index] == SelectionModes.Mixed ? true : false; toggleStyle = m_SelectionMatch[index] == SelectionModes.Mixed ? Styles.menuItemMixed : Styles.menuItem; @@ -206,7 +233,7 @@ void ChangeMaskValues(int maskIndex, bool add) { var selectionCount = m_SerializedProperty.serializedObject.targetObjects.Length; - m_SelectionMaskValues = new uint[selectionCount]; + m_SelectionMaskValues = new int[selectionCount]; for (int i = 0; i < selectionCount; i++) { var serializedObject = new SerializedObject(m_SerializedProperty.serializedObject.targetObjects[i]); @@ -219,7 +246,7 @@ void ChangeMaskValues(int maskIndex, bool add) property.intValue = 0; for (int j = 0; j < m_OptionMaskValues.Length; j++) { - var slotsToShift = (int)Math.Log(m_OptionMaskValues[j], 2); + var slotsToShift = (int)Math.Log((uint)m_OptionMaskValues[j], 2); property.intValue |= 1 << slotsToShift; } } @@ -232,9 +259,9 @@ void ChangeMaskValues(int maskIndex, bool add) } if (property.intValue == m_AllLayersMask) - property.intValue = int.MaxValue; + property.intValue = -1; - m_SelectionMaskValues[i] = (uint)property.intValue; + m_SelectionMaskValues[i] = property.intValue; serializedObject.ApplyModifiedProperties(); } @@ -248,11 +275,10 @@ public override void OnOpen() { m_SelectionMatch = new SelectionModes[m_LayerCount]; GetMultiSelectionValues(m_SerializedProperty, out m_SelectionMaskValues, out m_SelectionMatch, m_LayerCount); - int[] definedLayers = new int[m_SelectionMaskValues.Length]; - TagManager.GetDefinedLayers(ref m_OptionNames, ref definedLayers); - m_OptionMaskValues = definedLayers.Select(v => (uint)v).ToArray(); + m_OptionMaskValues = new int[m_SelectionMaskValues.Length]; + TagManager.GetDefinedLayers(ref m_OptionNames, ref m_OptionMaskValues); for (int i = 0; i < m_OptionMaskValues.Length; i++) - m_AllLayersMask |= (int)m_OptionMaskValues[i]; + m_AllLayersMask |= m_OptionMaskValues[i]; } for (int i = 0; i < m_OptionNames.Length; i++) @@ -286,7 +312,7 @@ internal class StaticFieldDropdown : PopupWindowContent SelectionModes[] m_SelectionMatch; string[] m_OptionNames; - uint[] m_SelectionMaskValues; + int[] m_SelectionMaskValues; int m_OptionCount; int m_AllLayersMask; List m_FunctioningOptions = new List(); @@ -365,12 +391,12 @@ public override void OnGUI(Rect rect) void ChangeMaskValues(int maskIndex, bool add) { var selectionCount = m_SerializedProperty.serializedObject.targetObjects.Length; - m_SelectionMaskValues = new uint[selectionCount]; + m_SelectionMaskValues = new int[selectionCount]; SceneModeUtility.SetStaticFlags(m_SerializedProperty.serializedObject.targetObjects, maskIndex, add); for (int i = 0; i < selectionCount; i++) - m_SelectionMaskValues[i] = (uint)m_SerializedProperty.intValue; + m_SelectionMaskValues[i] = m_SerializedProperty.intValue; m_SerializedProperty.serializedObject.ApplyModifiedProperties(); m_SerializedProperty.serializedObject.SetIsDifferentCacheDirty(); @@ -490,32 +516,32 @@ internal static void GetSingleSelectionValues(int maskValue, out SelectionModes[ } } - internal static void GetMultiSelectionValues(SerializedProperty serializedProperty, out uint[] selectionMaskValues, out SelectionModes[] selectionMatch, int layerCount) + internal static void GetMultiSelectionValues(SerializedProperty serializedProperty, out int[] selectionMaskValues, out SelectionModes[] selectionMatch, int layerCount) { var selectionCount = serializedProperty.serializedObject.targetObjects.Length; - selectionMaskValues = new uint[selectionCount]; + selectionMaskValues = new int[selectionCount]; selectionMatch = new SelectionModes[layerCount]; for (int i = 0; i < selectionCount; i++) { var serializedObject = new SerializedObject(serializedProperty.serializedObject.targetObjects[i]); var property = serializedObject.FindProperty(serializedProperty.propertyPath); - selectionMaskValues[i] = (uint)property.intValue; + selectionMaskValues[i] = property.intValue; } if (selectionCount == 1) { - GetSingleSelectionValues((int)selectionMaskValues[0], out selectionMatch, layerCount); + GetSingleSelectionValues(selectionMaskValues[0], out selectionMatch, layerCount); return; } uint[] firstSelected; - GetSelected((int)selectionMaskValues[0], out firstSelected, layerCount); + GetSelected(selectionMaskValues[0], out firstSelected, layerCount); for (int i = 1; i < selectionCount; i++) { uint[] secondSelected; - GetSelected((int)selectionMaskValues[i], out secondSelected, layerCount); + GetSelected(selectionMaskValues[i], out secondSelected, layerCount); for (int j = 0; j < layerCount; j++) { diff --git a/Editor/Mono/Inspector/MaterialEditor.cs b/Editor/Mono/Inspector/MaterialEditor.cs index b52e48ab40..1984eef4f9 100644 --- a/Editor/Mono/Inspector/MaterialEditor.cs +++ b/Editor/Mono/Inspector/MaterialEditor.cs @@ -89,7 +89,6 @@ private static class Styles private static int s_ControlHash = "EditorTextField".GetHashCode(); const float kSpacingUnderTexture = 6f; const float kMiniWarningMessageHeight = 27f; - private static Color kAmbientLightColor = new Color(0.2f, 0.2f, 0.2f, 0); private MaterialPropertyBlock m_PropertyBlock; @@ -1386,17 +1385,27 @@ internal static Color ColorPropertyInternal(Rect position, MaterialProperty prop } public Vector4 VectorProperty(MaterialProperty prop, string label) + { + return VectorProperty(prop, new GUIContent(label)); + } + + public Vector4 VectorProperty(MaterialProperty prop, GUIContent label) { Rect r = GetPropertyRect(prop, label, true); return VectorProperty(r, prop, label); } - + public Vector4 VectorProperty(Rect position, MaterialProperty prop, string label) + { + return VectorPropertyInternal(position, prop, new GUIContent(label)); + } + + public Vector4 VectorProperty(Rect position, MaterialProperty prop, GUIContent label) { return VectorPropertyInternal(position, prop, label); } - internal static Vector4 VectorPropertyInternal(in Rect position, in MaterialProperty prop, in string label) + internal static Vector4 VectorPropertyInternal(in Rect position, in MaterialProperty prop, in GUIContent label) { BeginProperty(position, prop); @@ -1564,6 +1573,11 @@ public Rect GetTexturePropertyCustomArea(Rect position) } public Texture TextureProperty(Rect position, MaterialProperty prop, string label) + { + return TextureProperty(position, prop, new GUIContent(label, string.Empty)); + } + + public Texture TextureProperty(Rect position, MaterialProperty prop, GUIContent label) { bool scaleOffset = ((prop.flags & MaterialProperty.PropFlags.NoScaleOffset) == 0); return TextureProperty(position, prop, label, scaleOffset); @@ -1573,14 +1587,19 @@ public Texture TextureProperty(Rect position, MaterialProperty prop, string labe { return TextureProperty(position, prop, label, string.Empty, scaleOffset); } - + public Texture TextureProperty(Rect position, MaterialProperty prop, string label, string tooltip, bool scaleOffset) + { + return TextureProperty(position, prop, new GUIContent(label, tooltip), scaleOffset); + } + + public Texture TextureProperty(Rect position, MaterialProperty prop, GUIContent label, bool scaleOffset) { Rect scopeRect = new Rect(position.x, position.y, position.width, EditorGUI.lineHeight); BeginProperty(scopeRect, prop); // Label - EditorGUI.PrefixLabel(position, new GUIContent(label, tooltip)); + EditorGUI.PrefixLabel(position, label); // Texture slot position.height = GetTextureFieldHeight(); @@ -2031,10 +2050,10 @@ internal void DefaultShaderPropertyInternal(Rect position, MaterialProperty prop ColorPropertyInternal(position, prop, label); break; case MaterialProperty.PropType.Texture: // textures - TextureProperty(position, prop, label.text); + TextureProperty(position, prop, label); break; case MaterialProperty.PropType.Vector: // vectors - VectorProperty(position, prop, label.text); + VectorProperty(position, prop, label); break; default: GUI.Label(position, "Unknown property type: " + prop.name + ": " + (int)prop.type); @@ -2536,12 +2555,12 @@ private bool DoReflectionProbePicker(out Rect buttonRect) public void DefaultPreviewSettingsGUI() { - if (!ShaderUtil.hardwareSupportsRectRenderTexture) + var mat = target as Material; + if (!SupportRenderingPreview(mat)) return; Init(); - var mat = target as Material; var viewType = GetPreviewType(mat); if (targets.Length > 1 || viewType == PreviewType.Mesh) { @@ -2562,15 +2581,14 @@ public void DefaultPreviewSettingsGUI() public sealed override Texture2D RenderStaticPreview(string assetPath, Object[] subAssets, int width, int height) { - if (!ShaderUtil.hardwareSupportsRectRenderTexture) + var mat = target as Material; + if (!SupportRenderingPreview(mat)) return null; Init(); - var previewRenderUtility = GetPreviewRendererUtility(); EditorUtility.SetCameraAnimateMaterials(previewRenderUtility.camera, true); - previewRenderUtility.ambientColor = kAmbientLightColor; previewRenderUtility.BeginStaticPreview(new Rect(0, 0, width, height)); StreamRenderResources(); @@ -2681,7 +2699,7 @@ private void DoRenderPreview(PreviewRenderUtility previewRenderUtility, bool ove previewRenderUtility.lights[1].intensity = 1.0f; } - previewRenderUtility.ambientColor = kAmbientLightColor; + previewRenderUtility.ambientColor = new Color(0.2f, 0.2f, 0.2f, 0); Quaternion rot = Quaternion.identity; if (DoesPreviewAllowRotation(viewType)) @@ -2745,9 +2763,25 @@ public override void OnPreviewGUI(Rect r, GUIStyle background) DefaultPreviewGUI(r, background); } - public void DefaultPreviewGUI(Rect r, GUIStyle background) + private static bool SupportRenderingPreview(Material material) { if (!ShaderUtil.hardwareSupportsRectRenderTexture) + return false; + + if (material == null) + return false; + + var assetPath = AssetDatabase.GetAssetPath(material); + if (assetPath.EndsWith(".vfx", StringComparison.InvariantCultureIgnoreCase)) + return false; + + return true; + } + + public void DefaultPreviewGUI(Rect r, GUIStyle background) + { + var mat = target as Material; + if (!SupportRenderingPreview(mat)) { if (Event.current.type == EventType.Repaint) EditorGUI.DropShadowLabel(new Rect(r.x, r.y, r.width, 40), "Material preview \nnot available"); @@ -2756,7 +2790,6 @@ public void DefaultPreviewGUI(Rect r, GUIStyle background) Init(); - var mat = target as Material; var viewType = GetPreviewType(mat); if (DoesPreviewAllowRotation(viewType)) @@ -2905,6 +2938,7 @@ internal void HandleSkybox(GameObject go, Event evt) evt.Use(); } else + { switch (evt.type) { case EventType.DragUpdated: @@ -2917,14 +2951,13 @@ internal void HandleSkybox(GameObject go, Event evt) applyAndConsumeEvent = true; break; } + } if (applyAndConsumeEvent) { - if (s_OriginalMaterial == null) - { - Undo.RecordObject(FindObjectOfType(), Styles.undoAssignSkyboxMaterial); - s_OriginalMaterial = RenderSettings.skybox; - } + Undo.RecordObject(RenderSettings.GetRenderSettings(), Styles.undoAssignSkyboxMaterial); + if (s_OriginalMaterial == null) s_OriginalMaterial = RenderSettings.skybox; + RenderSettings.skybox = target as Material; if (evt.type == EventType.DragPerform) s_OriginalMaterial = null; @@ -3047,6 +3080,7 @@ internal static void AddAdditionalMaterialMenuItems(GenericMenu menu) menu.AddItem(new GUIContent("Create Variant for Renderer"), false, () => { var directory = "Assets/Materials"; + Directory.CreateDirectory(directory); var assetPath = AssetDatabase.GetAssetPath(material); var assetName = Path.GetFileNameWithoutExtension(assetPath) + " Variant"; var package = PackageManager.PackageInfo.FindForAssetPath(assetPath); @@ -3055,7 +3089,6 @@ internal static void AddAdditionalMaterialMenuItems(GenericMenu menu) var path = AssetDatabase.GenerateUniqueAssetPath(Path.Combine(directory, assetName + ".mat")); var variant = new Material(material) { name = assetName, parent = material }; - Directory.CreateDirectory(directory); AssetDatabase.CreateAsset(variant, path); foreach (var renderer in renderers) diff --git a/Editor/Mono/Inspector/MaterialPropertyDrawer.cs b/Editor/Mono/Inspector/MaterialPropertyDrawer.cs index e0d336a28e..b2e8c8f3dc 100644 --- a/Editor/Mono/Inspector/MaterialPropertyDrawer.cs +++ b/Editor/Mono/Inspector/MaterialPropertyDrawer.cs @@ -84,7 +84,7 @@ internal static void InvalidatePropertyCache(Shader shader) var toDelete = new List(); foreach (string key in s_PropertyHandlers.Keys) { - if (key.StartsWith(keyStart)) + if (key.StartsWith(keyStart, StringComparison.Ordinal)) toDelete.Add(key); } foreach (string key in toDelete) diff --git a/Editor/Mono/Inspector/MeshRendererEditor.cs b/Editor/Mono/Inspector/MeshRendererEditor.cs index bbbdd77f84..28c24fc326 100644 --- a/Editor/Mono/Inspector/MeshRendererEditor.cs +++ b/Editor/Mono/Inspector/MeshRendererEditor.cs @@ -61,7 +61,11 @@ public override void OnInspectorGUI() displayMaterialWarning = mf != null && mf.sharedMesh != null && m_Materials.arraySize > mf.sharedMesh.subMeshCount; } - using (new EditorGUI.DisabledScope(((MeshRenderer)serializedObject.targetObject).GetComponent() != null)) + // Disable Materials menu for the legacy Tree objects, see Fogbugz case: 1283092 + Tree treeComponent = ((MeshRenderer)serializedObject.targetObject).GetComponent(); + bool hasTreeComponent = treeComponent != null; + bool isSpeedTree = hasTreeComponent && treeComponent.data == null; // SpeedTrees always have the Tree component, but have null 'data' property + using (new EditorGUI.DisabledScope(hasTreeComponent && !isSpeedTree)) { DrawMaterials(); } diff --git a/Editor/Mono/Inspector/MinMaxCurvePropertyDrawer.cs b/Editor/Mono/Inspector/MinMaxCurvePropertyDrawer.cs index be443ca486..d02eaddaa0 100644 --- a/Editor/Mono/Inspector/MinMaxCurvePropertyDrawer.cs +++ b/Editor/Mono/Inspector/MinMaxCurvePropertyDrawer.cs @@ -236,7 +236,6 @@ static void DoMinMaxCurvesField(Rect position, int id, SerializedProperty proper public override VisualElement CreatePropertyGUI(SerializedProperty property) { const string StylesheetPath = "StyleSheets/ParticleSystem/ParticleSystem.uss"; - const string HideClass = "unity-hidden"; const string AlignClass = "unity-base-field__aligned"; const string InputClass = "unity-base-popup-field__input"; const string RowClass = "unity-particle-system-min-max-curve__row"; @@ -245,7 +244,7 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) PropertyField PrepareProperty(SerializedProperty prop, string label, VisualElement parent) { var propField = new PropertyField(prop, label); - propField.AddToClassList(HideClass); + propField.AddToClassList(UIElementsUtility.hiddenClassName); propField.AddToClassList(GrowClass); parent.Add(propField); return propField; @@ -273,7 +272,7 @@ PropertyField PrepareProperty(SerializedProperty prop, string label, VisualEleme else DoMinMaxCurvesField(rect, m_Property.curveMin.GetHashCode(), m_Property.curveMax, m_Property.curveMin, m_Property.curveMultiplier, s_Styles.curveColor, s_Styles.curveBackgroundColor, xAxisLabel); if (EditorGUI.EndChangeCheck()) m_Property.curveMax.serializedObject.ApplyModifiedProperties(); }); - region.AddToClassList(HideClass); + region.AddToClassList(UIElementsUtility.hiddenClassName); region.AddToClassList(GrowClass); container.Add(region); @@ -286,7 +285,7 @@ PropertyField PrepareProperty(SerializedProperty prop, string label, VisualEleme m_Property.mode.intValue = (int)state; m_Property.mode.serializedObject.ApplyModifiedProperties(); - constantMax.EnableInClassList(HideClass, state != MinMaxCurveState.k_Scalar && state != MinMaxCurveState.k_TwoScalars); + constantMax.EnableInClassList(UIElementsUtility.hiddenClassName, state != MinMaxCurveState.k_Scalar && state != MinMaxCurveState.k_TwoScalars); if(constantMax.Children().Count() > 0) { @@ -305,8 +304,8 @@ PropertyField PrepareProperty(SerializedProperty prop, string label, VisualEleme } } - constantMin.EnableInClassList(HideClass, state != MinMaxCurveState.k_TwoScalars); - region.EnableInClassList(HideClass, state != MinMaxCurveState.k_Curve && state != MinMaxCurveState.k_TwoCurves); + constantMin.EnableInClassList(UIElementsUtility.hiddenClassName, state != MinMaxCurveState.k_TwoScalars); + region.EnableInClassList(UIElementsUtility.hiddenClassName, state != MinMaxCurveState.k_Curve && state != MinMaxCurveState.k_TwoCurves); AnimationCurvePreviewCache.ClearCache(); }; mode.RegisterValueChangedCallback(valueChangeAction); diff --git a/Editor/Mono/Inspector/MinMaxGradientPropertyDrawer.cs b/Editor/Mono/Inspector/MinMaxGradientPropertyDrawer.cs index 91f46dade6..a1512e55e8 100644 --- a/Editor/Mono/Inspector/MinMaxGradientPropertyDrawer.cs +++ b/Editor/Mono/Inspector/MinMaxGradientPropertyDrawer.cs @@ -135,7 +135,9 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) { Init(property); - return new MinMaxGradientField(m_Property, property.localizedDisplayName); + var field = new MinMaxGradientField(m_Property, preferredLabel); + PropertyField.ConfigureFieldStyles(field); + return field; } } } diff --git a/Editor/Mono/Inspector/MonoScriptInspector.cs b/Editor/Mono/Inspector/MonoScriptInspector.cs index 557cde58be..582551df23 100644 --- a/Editor/Mono/Inspector/MonoScriptInspector.cs +++ b/Editor/Mono/Inspector/MonoScriptInspector.cs @@ -83,14 +83,14 @@ internal override void OnHeaderIconGUI(Rect iconRect) // Clear default references // ReSharper disable once UnusedMember.Local - registers as menu handler - [MenuItem("CONTEXT/MonoImporter/Reset", isValidateFunction: true)] + [MenuItem("CONTEXT/MonoImporter/Reset", validate = true)] static bool ResetDefaultReferencesValidate(MenuCommand command) { return AssetDatabase.IsOpenForEdit(command.context); } // ReSharper disable once UnusedMember.Local - registers as menu handler - [MenuItem("CONTEXT/MonoImporter/Reset")] + [MenuItem("CONTEXT/MonoImporter/Reset", secondaryPriority = 13)] static void ResetDefaultReferences(MenuCommand command) { MonoImporter importer = command.context as MonoImporter; @@ -223,7 +223,8 @@ public override void OnInspectorGUI() rect.x = 0; rect.y -= 3; rect.width = GUIClip.visibleRect.width + 1; - GUI.Box(rect, m_CachedPreview, m_TextStyle); + GUI.Box(rect, ""); + EditorGUI.SelectableLabel(rect, m_CachedPreview.text, m_TextStyle); } GUI.enabled = enabledTemp; } diff --git a/Editor/Mono/Inspector/NavMeshAgentInspector.cs b/Editor/Mono/Inspector/NavMeshAgentInspector.cs index 6f000862fb..dc683a5f67 100644 --- a/Editor/Mono/Inspector/NavMeshAgentInspector.cs +++ b/Editor/Mono/Inspector/NavMeshAgentInspector.cs @@ -68,6 +68,8 @@ void OnEnable() public override void OnInspectorGUI() { + AI.NavMeshEditorHelpers.DisplayInstallPackageButtonIfNeeded(); + serializedObject.Update(); AgentTypePopupInternal(m_AgentTypeID); diff --git a/Editor/Mono/Inspector/NavMeshEditorHelpers.cs b/Editor/Mono/Inspector/NavMeshEditorHelpers.cs index c6072a7a1c..dcec20a534 100644 --- a/Editor/Mono/Inspector/NavMeshEditorHelpers.cs +++ b/Editor/Mono/Inspector/NavMeshEditorHelpers.cs @@ -8,69 +8,50 @@ namespace UnityEditor.AI { - [InitializeOnLoad] - internal static class CheckNavigationPackage + public static partial class NavMeshEditorHelpers { - const string k_NavigationPackageId = "com.unity.ai.navigation"; - const string k_NavigationComponentMenuRoot = "Component/Navigation"; + const string k_OpenAgentSettings = "NavMeshAgentInspector-OpenAgentSettings"; - static string s_NavigationPackagePath = $"Packages/{k_NavigationPackageId}/package.json"; + internal static readonly bool isPackageInstalled; - static CheckNavigationPackage() - { - if (!IsInstalled()) - { - EditorApplication.CallDelayed(HideNavComponents); - EditorApplication.CallDelayed(CloseNavigationWindow); - } - } + internal static event Action agentTypeSettingsClicked; + internal static event Action areaSettingsClicked; - static void CloseNavigationWindow() + static NavMeshEditorHelpers() { - if (EditorWindow.HasOpenInstances()) - EditorWindow.GetWindow().Close(); - } + var packagePath = Path.GetFullPath("Packages/com.unity.ai.navigation/package.json"); + isPackageInstalled = File.Exists(packagePath); - static void HideNavComponents() - { - // Look for existing navigation menus, if none then nothing to do - var navMenuItems = Menu.GetMenuItems(k_NavigationComponentMenuRoot, false, false); - if (navMenuItems != null && navMenuItems.Length > 0) + // open the agent settings if the package was just installed via the dialog box in OpenAgentSettings() + // there will have just been a domain reload, so a session state var is the only way to know the installation occurred from that user action + // use a delay call to ensure SessionState API is called on the main thread, regardless of which thread NavMeshEditorHelpers is called on the first time + EditorApplication.delayCall += () => { - // Remove trunk registered components entries - Menu.RemoveMenuItem($"{k_NavigationComponentMenuRoot}/Nav Mesh Agent"); - Menu.RemoveMenuItem($"{k_NavigationComponentMenuRoot}/Nav Mesh Obstacle"); - Menu.RemoveMenuItem($"{k_NavigationComponentMenuRoot}/Off Mesh Link"); - } - - // Register for the next menu modifications as we need to ensure components entries are not added again... - Menu.menuChanged += OnMenuChanged; - } - - static void OnMenuChanged() - { - // Unregister from the menu modifications callback as we don't want to be notified until we actually try to remove the components - Menu.menuChanged -= OnMenuChanged; - - EditorApplication.CallDelayed(HideNavComponents); + if (SessionState.GetBool(k_OpenAgentSettings, false)) + { + SessionState.SetBool(k_OpenAgentSettings, false); + OpenAgentSettings(-1); + } + }; } - internal static bool IsInstalled() - { - var packagePath = Path.GetFullPath(s_NavigationPackagePath); - return !String.IsNullOrEmpty(packagePath) && File.Exists(packagePath); - } - } - - public static partial class NavMeshEditorHelpers - { - internal static event Action agentTypeSettingsClicked; - internal static event Action areaSettingsClicked; - public static void OpenAgentSettings(int agentTypeID) { - if (!CheckNavigationPackage.IsInstalled()) - Debug.LogWarning("Unable to open Agent settings because the Navigation window is not available. Please install the AI Navigation package to add that window."); + if (!isPackageInstalled) + { + if (EditorUtility.DisplayDialog( + L10n.Tr("AI Navigation Package Not Installed"), + L10n.Tr("Agent types cannot be configured, because the AI Navigation package is not installed. Would you like to install it?"), + L10n.Tr("Install"), + L10n.Tr("Cancel") + )) + { + PackageManager.Client.Add("com.unity.ai.navigation"); + SessionState.SetBool(k_OpenAgentSettings, true); + } + else + Debug.LogWarning(L10n.Tr("Unable to open Agent settings because the Navigation window is not available. Please install the AI Navigation package to add that window.")); + } if (agentTypeSettingsClicked != null) agentTypeSettingsClicked(agentTypeID); @@ -78,13 +59,21 @@ public static void OpenAgentSettings(int agentTypeID) public static void OpenAreaSettings() { - if (!CheckNavigationPackage.IsInstalled()) - Debug.LogWarning("Unable to open Area settings because the Navigation window is not available. Please install the AI Navigation package to add that window."); + if (!isPackageInstalled) + Debug.LogWarning(L10n.Tr("Unable to open Area settings because the Navigation window is not available. Please install the AI Navigation package to add that window.")); if (areaSettingsClicked != null) areaSettingsClicked(); } + internal static void DisplayInstallPackageButtonIfNeeded() + { + if (isPackageInstalled) + return; + if (GUILayout.Button(Content.InstallPackage)) + PackageManager.Client.Add("com.unity.ai.navigation"); + } + public static void DrawAgentDiagram(Rect rect, float agentRadius, float agentHeight, float agentClimb, float agentSlope) { if (Event.current.type != EventType.Repaint) @@ -194,5 +183,13 @@ public static void DrawAgentDiagram(Rect rect, float agentRadius, float agentHei Handles.color = oldColor; } + + static class Content + { + public static readonly GUIContent InstallPackage = EditorGUIUtility.TrTextContent( + "Install AI Navigation Package", + "Install the AI Navigation package in order to access all navigation components and workflows." + ); + } } } diff --git a/Editor/Mono/Inspector/NavMeshObstacleInspector.cs b/Editor/Mono/Inspector/NavMeshObstacleInspector.cs index cd410b7a55..7db6cb8d4b 100644 --- a/Editor/Mono/Inspector/NavMeshObstacleInspector.cs +++ b/Editor/Mono/Inspector/NavMeshObstacleInspector.cs @@ -43,6 +43,8 @@ void OnEnable() public override void OnInspectorGUI() { + AI.NavMeshEditorHelpers.DisplayInstallPackageButtonIfNeeded(); + serializedObject.Update(); EditorGUI.BeginChangeCheck(); diff --git a/Editor/Mono/Inspector/OffMeshLinkInspector.cs b/Editor/Mono/Inspector/OffMeshLinkInspector.cs index 1356eaf5d9..f98c5f210b 100644 --- a/Editor/Mono/Inspector/OffMeshLinkInspector.cs +++ b/Editor/Mono/Inspector/OffMeshLinkInspector.cs @@ -45,6 +45,8 @@ void OnEnable() public override void OnInspectorGUI() { + AI.NavMeshEditorHelpers.DisplayInstallPackageButtonIfNeeded(); + serializedObject.Update(); EditorGUILayout.PropertyField(m_Start, Styles.Start); diff --git a/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsEditor.cs b/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsEditor.cs index c7fbcbaca4..2e072c0aa2 100644 --- a/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsEditor.cs +++ b/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsEditor.cs @@ -87,7 +87,16 @@ class SettingsContent public static readonly GUIContent legacyTitle = EditorGUIUtility.TrTextContent("Legacy"); public static readonly GUIContent publishingSettingsTitle = EditorGUIUtility.TrTextContent("Publishing Settings"); + public static readonly GUIContent shaderSectionTitle = EditorGUIUtility.TrTextContent("Shader Settings"); + public static readonly GUIContent shaderVariantLoadingTitle = EditorGUIUtility.TrTextContent("Shader Variant Loading Settings"); + public static readonly GUIContent defaultShaderChunkSize = EditorGUIUtility.TrTextContent("Default chunk size (MB)*", "Use this setting to control how much memory is used when loading shader variants."); + public static readonly GUIContent defaultShaderChunkCount = EditorGUIUtility.TrTextContent("Default chunk count*", "Use this setting to control how much memory is used when loading shader variants."); + public static readonly GUIContent overrideDefaultChunkSettings = EditorGUIUtility.TrTextContent("Override", "Override the default settings for this build target."); + public static readonly GUIContent platformShaderChunkSize = EditorGUIUtility.TrTextContent("Chunk size (MB)", "Use this setting to control how much memory is used when loading shader variants."); + public static readonly GUIContent platformShaderChunkCount = EditorGUIUtility.TrTextContent("Chunk count", "Use this setting to control how much memory is used when loading shader variants."); + public static readonly GUIContent bakeCollisionMeshes = EditorGUIUtility.TrTextContent("Prebake Collision Meshes*", "Bake collision data into the meshes on build time"); + public static readonly GUIContent dedicatedServerOptimizations = EditorGUIUtility.TrTextContent("Enable Dedicated Server optimizations", "Performs additional optimizations on Dedicated Server builds."); public static readonly GUIContent keepLoadedShadersAlive = EditorGUIUtility.TrTextContent("Keep Loaded Shaders Alive*", "Prevents shaders from being unloaded"); public static readonly GUIContent preloadedAssets = EditorGUIUtility.TrTextContent("Preloaded Assets*", "Assets to load at start up in the player and kept alive until the player terminates"); public static readonly GUIContent stripEngineCode = EditorGUIUtility.TrTextContent("Strip Engine Code*", "Strip Unused Engine Code - Note that byte code stripping of managed assemblies is always enabled for the IL2CPP scripting backend."); @@ -138,12 +147,13 @@ class SettingsContent public static readonly GUIContent logObjCUncaughtExceptions = EditorGUIUtility.TrTextContent("Log Obj-C Uncaught Exceptions*"); public static readonly GUIContent enableCrashReportAPI = EditorGUIUtility.TrTextContent("Enable CrashReport API*"); public static readonly GUIContent activeColorSpace = EditorGUIUtility.TrTextContent("Color Space*"); + public static readonly GUIContent unsupportedMSAAFallback = EditorGUIUtility.TrTextContent("MSAA Fallback"); public static readonly GUIContent colorGamut = EditorGUIUtility.TrTextContent("Color Gamut*"); public static readonly GUIContent colorGamutForMac = EditorGUIUtility.TrTextContent("Color Gamut For Mac*"); public static readonly GUIContent metalForceHardShadows = EditorGUIUtility.TrTextContent("Force hard shadows on Metal*"); public static readonly GUIContent metalAPIValidation = EditorGUIUtility.TrTextContent("Metal API Validation*", "When enabled, additional binding state validation is applied."); - public static readonly GUIContent metalFramebufferOnly = EditorGUIUtility.TrTextContent("Metal Write-Only Backbuffer", "Set framebufferOnly flag on backbuffer. This prevents readback from backbuffer but enables some driver optimizations."); - public static readonly GUIContent framebufferDepthMemorylessMode = EditorGUIUtility.TrTextContent("Memoryless Depth", "Memoryless mode of framebuffer depth"); + public static readonly GUIContent metalFramebufferOnly = EditorGUIUtility.TrTextContent("Metal Write-Only Backbuffer*", "Set framebufferOnly flag on backbuffer. This prevents readback from backbuffer but enables some driver optimizations."); + public static readonly GUIContent framebufferDepthMemorylessMode = EditorGUIUtility.TrTextContent("Memoryless Depth*", "Memoryless mode of framebuffer depth"); public static readonly GUIContent[] memorylessModeNames = { EditorGUIUtility.TrTextContent("Unused"), EditorGUIUtility.TrTextContent("Forced"), EditorGUIUtility.TrTextContent("Automatic") }; public static readonly GUIContent vulkanEnableSetSRGBWrite = EditorGUIUtility.TrTextContent("SRGB Write Mode*", "If set, enables Graphics.SetSRGBWrite() for toggling sRGB write mode during the frame but may decrease performance especially on tiled GPUs."); public static readonly GUIContent vulkanNumSwapchainBuffers = EditorGUIUtility.TrTextContent("Number of swapchain buffers*"); @@ -162,7 +172,7 @@ class SettingsContent public static readonly GUIContent applicationBuildNumber = EditorGUIUtility.TrTextContent("Build"); public static readonly GUIContent appleDeveloperTeamID = EditorGUIUtility.TrTextContent("iOS Developer Team ID", "Developers can retrieve their Team ID by visiting the Apple Developer site under Account > Membership."); public static readonly GUIContent useOnDemandResources = EditorGUIUtility.TrTextContent("Use on-demand resources*"); - public static readonly GUIContent gcIncremental = EditorGUIUtility.TrTextContent("Use incremental GC", "With incremental Garbage Collection, the Garbage Collector will try to time-slice the collection task into multiple steps, to avoid long GC times preventing content from running smoothly."); + public static readonly GUIContent gcIncremental = EditorGUIUtility.TrTextContent("Use incremental GC*", "With incremental Garbage Collection, the Garbage Collector will try to time-slice the collection task into multiple steps, to avoid long GC times preventing content from running smoothly."); public static readonly GUIContent accelerometerFrequency = EditorGUIUtility.TrTextContent("Accelerometer Frequency*"); public static readonly GUIContent cameraUsageDescription = EditorGUIUtility.TrTextContent("Camera Usage Description*", "String shown to the user when requesting permission to use the device camera. Written to the NSCameraUsageDescription field in Xcode project's info.plist file"); public static readonly GUIContent locationUsageDescription = EditorGUIUtility.TrTextContent("Location Usage Description*", "String shown to the user when requesting permission to access the device location. Written to the NSLocationWhenInUseUsageDescription field in Xcode project's info.plist file."); @@ -192,7 +202,7 @@ class SettingsContent public static readonly GUIContent scriptingDefineSymbolsApply = EditorGUIUtility.TrTextContent("Apply"); public static readonly GUIContent scriptingDefineSymbolsApplyRevert = EditorGUIUtility.TrTextContent("Revert"); public static readonly GUIContent scriptingDefineSymbolsCopyDefines = EditorGUIUtility.TrTextContent("Copy Defines", "Copy applied defines"); - public static readonly GUIContent suppressCommonWarnings = EditorGUIUtility.TrTextContent("Suppress Common Warnings", "Suppresses C# warnings CS0169 and CS0649."); + public static readonly GUIContent suppressCommonWarnings = EditorGUIUtility.TrTextContent("Suppress Common Warnings", "Suppresses C# warnings CS0169, CS0649, and CS0282."); public static readonly GUIContent scriptingBackend = EditorGUIUtility.TrTextContent("Scripting Backend"); public static readonly GUIContent managedStrippingLevel = EditorGUIUtility.TrTextContent("Managed Stripping Level", "If scripting backend is IL2CPP, managed stripping can't be disabled."); public static readonly GUIContent il2cppCompilerConfiguration = EditorGUIUtility.TrTextContent("C++ Compiler Configuration"); @@ -217,7 +227,6 @@ class SettingsContent public static readonly GUIContent scriptCompilationTitle = EditorGUIUtility.TrTextContent("Script Compilation"); public static readonly GUIContent allowUnsafeCode = EditorGUIUtility.TrTextContent("Allow 'unsafe' Code", "Allow compilation of unsafe code for predefined assemblies (Assembly-CSharp.dll, etc.)"); public static readonly GUIContent useDeterministicCompilation = EditorGUIUtility.TrTextContent("Use Deterministic Compilation", "Compile with -deterministic compilation flag"); - public static readonly GUIContent enableRoslynAnalyzers = EditorGUIUtility.TrTextContent("Enable Roslyn Analyzers", "User-written scripts will be compiled with Roslyn analyzer DLLs that are present in the project."); public static readonly GUIContent activeInputHandling = EditorGUIUtility.TrTextContent("Active Input Handling*"); public static readonly GUIContent[] activeInputHandlingOptions = new GUIContent[] { EditorGUIUtility.TrTextContent("Input Manager (Old)"), EditorGUIUtility.TrTextContent("Input System Package (New)"), EditorGUIUtility.TrTextContent("Both") }; public static readonly GUIContent normalMapEncodingLabel = EditorGUIUtility.TrTextContent("Normal Map Encoding"); @@ -231,7 +240,7 @@ class SettingsContent public static readonly GUIContent lightmapQualityAndroidWarning = EditorGUIUtility.TrTextContent("The Lightmap Encoding scheme you have selected requires OpenGL ES 3.0 or Vulkan. Navigate to 'Android Player Settings' > 'Rendering' and disable the OpenGL ES 2 API."); public static readonly GUIContent hdrCubemapQualityAndroidWarning = EditorGUIUtility.TrTextContent("The HDR Cubemap Encoding scheme you have selected requires OpenGL ES 3.0 or Vulkan. Navigate to 'Android Player Settings' > 'Rendering' and disable the OpenGL ES 2 API."); public static readonly GUIContent legacyClampBlendShapeWeights = EditorGUIUtility.TrTextContent("Clamp BlendShapes (Deprecated)*", "If set, the range of BlendShape weights in SkinnedMeshRenderers will be clamped."); - public static readonly GUIContent virtualTexturingSupportEnabled = EditorGUIUtility.TrTextContent("Virtual Texturing*", "Enable support for Virtual Texturing. Changing this value requires an Editor restart."); + public static readonly GUIContent virtualTexturingSupportEnabled = EditorGUIUtility.TrTextContent("Virtual Texturing (Experimental)*", "Enable Virtual Texturing. This feature is experimental and not ready for production use. Changing this value requires an Editor restart."); public static readonly GUIContent virtualTexturingUnsupportedPlatformWarning = EditorGUIUtility.TrTextContent("The current target platform does not support Virtual Texturing. To build for this platform, uncheck Enable Virtual Texturing."); public static readonly GUIContent virtualTexturingUnsupportedAPI = EditorGUIUtility.TrTextContent("The target graphics API does not support Virtual Texturing. To target compatible graphics APIs, uncheck 'Auto Graphics API', and remove OpenGL ES 2/3 and OpenGLCore."); public static readonly GUIContent virtualTexturingUnsupportedAPIWin = EditorGUIUtility.TrTextContent("The target Windows graphics API does not support Virtual Texturing. To target compatible graphics APIs, uncheck 'Auto Graphics API for Windows', and remove OpenGL ES 2/3 and OpenGLCore."); @@ -244,24 +253,33 @@ class SettingsContent public static readonly GUIContent notApplicableInfo = EditorGUIUtility.TrTextContent("Not applicable for this platform."); public static readonly GUIContent loadStoreDebugModeCheckbox = EditorGUIUtility.TrTextContent("Load/Store Action Debug Mode", "Initializes Framebuffer such that errors in the load/store actions will be visually apparent. (Removed in Release Builds)"); public static readonly GUIContent loadStoreDebugModeEditorOnlyCheckbox = EditorGUIUtility.TrTextContent("Editor Only", "Load/Store Action Debug Mode will only affect the Editor"); - - public static string undoChangedBatchingString { get { return LocalizationDatabase.GetLocalizedString("Changed Batching Settings"); } } - public static string undoChangedGraphicsAPIString { get { return LocalizationDatabase.GetLocalizedString("Changed Graphics API Settings"); } } - public static string undoChangedScriptingDefineString { get { return LocalizationDatabase.GetLocalizedString("Changed Scripting Define Settings"); } } - public static string undoChangedGraphicsJobsString { get { return LocalizationDatabase.GetLocalizedString("Changed Graphics Jobs Setting"); } } - public static string undoChangedGraphicsJobModeString { get { return LocalizationDatabase.GetLocalizedString("Changed Graphics Job Mode Setting"); } } - public static string changeColorSpaceString { get { return LocalizationDatabase.GetLocalizedString("Changing the color space may take a significant amount of time."); } } + public static readonly GUIContent allowHDRDisplay = EditorGUIUtility.TrTextContent("Allow HDR Display Output*", "Enable the use of HDR displays and include all the resources required for them to function correctly."); + public static readonly GUIContent useHDRDisplay = EditorGUIUtility.TrTextContent("Use HDR Display Output*", "Checks if the main display supports HDR and if it does, switches to HDR output at the start of the application."); + public static readonly GUIContent hdrOutputRequireHDRRenderingWarning = EditorGUIUtility.TrTextContent("The active Render Pipeline does not have HDR enabled. Enable HDR in the Render Pipeline Asset to see the changes."); + + public static readonly string undoChangedBatchingString = L10n.Tr("Changed Batching Settings"); + public static readonly string undoChangedGraphicsAPIString = L10n.Tr("Changed Graphics API Settings"); + public static readonly string undoChangedScriptingDefineString = L10n.Tr("Changed Scripting Define Settings"); + public static readonly string undoChangedGraphicsJobsString = L10n.Tr("Changed Graphics Jobs Setting"); + public static readonly string undoChangedGraphicsJobModeString = L10n.Tr("Changed Graphics Job Mode Setting"); + public static readonly string changeColorSpaceString = L10n.Tr("Changing the color space may take a significant amount of time."); + public static readonly string undoChangedPlatformShaderChunkSizeString = L10n.Tr("Changed Shader Chunk Size Platform Setting"); + public static readonly string undoChangedPlatformShaderChunkCountString = L10n.Tr("Changed Shader Chunk Count Platform Setting"); + public static readonly string undoChangedDefaultShaderChunkSizeString = L10n.Tr("Changed Shader Chunk Size Default Setting"); + public static readonly string undoChangedDefaultShaderChunkCountString = L10n.Tr("Changed Shader Chunk Count Default Setting"); } class RecompileReason { - public static readonly string scriptingDefineSymbolsModified = "Scripting define symbols setting modified"; - public static readonly string suppressCommonWarningsModified = "Suppress common warnings setting modified"; - public static readonly string allowUnsafeCodeModified = "Allow 'unsafe' code setting modified"; - public static readonly string apiCompatibilityLevelModified = "API Compatibility level modified"; - public static readonly string useDeterministicCompilationModified = "Use deterministic compilation modified"; - public static readonly string additionalCompilerArgumentsModified = "Additional compiler arguments modified"; - public static readonly string activeBuildTargetGroupModified = "Active build target group modified"; + public static readonly string scriptingDefineSymbolsModified = L10n.Tr("Scripting define symbols setting modified"); + public static readonly string suppressCommonWarningsModified = L10n.Tr("Suppress common warnings setting modified"); + public static readonly string allowUnsafeCodeModified = L10n.Tr("Allow 'unsafe' code setting modified"); + public static readonly string apiCompatibilityLevelModified = L10n.Tr("API Compatibility level modified"); + public static readonly string useDeterministicCompilationModified = L10n.Tr("Use deterministic compilation modified"); + public static readonly string additionalCompilerArgumentsModified = L10n.Tr("Additional compiler arguments modified"); + public static readonly string activeBuildTargetGroupModified = L10n.Tr("Active build target group modified"); + + public static readonly string presetChanged = L10n.Tr("Preset changed"); } PlayerSettingsSplashScreenEditor m_SplashScreenEditor; @@ -378,6 +396,7 @@ PlayerSettingsIconsEditor iconsEditor SerializedProperty m_DefaultScreenHeight; SerializedProperty m_ActiveColorSpace; + SerializedProperty m_UnsupportedMSAAFallback; SerializedProperty m_StripUnusedMeshComponents; SerializedProperty m_StrictShaderVariantMatching; SerializedProperty m_MipStripping; @@ -394,6 +413,7 @@ PlayerSettingsIconsEditor iconsEditor SerializedProperty m_KeepLoadedShadersAlive; SerializedProperty m_PreloadedAssets; SerializedProperty m_BakeCollisionMeshes; + SerializedProperty m_DedicatedServerOptimizations; SerializedProperty m_ResizableWindow; SerializedProperty m_FullscreenMode; SerializedProperty m_VisibleInBackground; @@ -431,7 +451,6 @@ PlayerSettingsIconsEditor iconsEditor // Scripting SerializedProperty m_UseDeterministicCompilation; SerializedProperty m_ScriptingBackend; - SerializedProperty m_EnableRoslynAnalyzers; SerializedProperty m_APICompatibilityLevel; SerializedProperty m_DefaultAPICompatibilityLevel; SerializedProperty m_Il2CppCompilerConfiguration; @@ -466,7 +485,6 @@ public static void SyncColorGamuts() s_ColorGamutList.list = PlayerSettings.GetColorGamuts().ToList(); } - int selectedPlatform = 0; int scriptingDefinesControlID = 0; int serializedActiveInputHandler = 0; @@ -499,6 +517,9 @@ public static void SyncColorGamuts() bool isPresetWindowOpen = false; bool hasPresetWindowClosed = false; + private const string kDeprecatedAppendixString = " (Deprecated)"; + const string kSelectedPlatform = "PlayerSettings.SelectedPlatform"; + public SerializedProperty FindPropertyAssert(string name) { SerializedProperty property = serializedObject.FindProperty(name); @@ -529,6 +550,7 @@ void OnEnable() m_UIStatusBarHidden = FindPropertyAssert("uIStatusBarHidden"); m_UIStatusBarStyle = FindPropertyAssert("uIStatusBarStyle"); m_ActiveColorSpace = FindPropertyAssert("m_ActiveColorSpace"); + m_UnsupportedMSAAFallback = FindPropertyAssert("unsupportedMSAAFallback"); m_StripUnusedMeshComponents = FindPropertyAssert("StripUnusedMeshComponents"); m_StrictShaderVariantMatching = FindPropertyAssert("strictShaderVariantMatching"); m_MipStripping = FindPropertyAssert("mipStripping"); @@ -575,7 +597,6 @@ void OnEnable() m_GCIncremental = FindPropertyAssert("gcIncremental"); m_UseDeterministicCompilation = FindPropertyAssert("useDeterministicCompilation"); m_ScriptingBackend = FindPropertyAssert("scriptingBackend"); - m_EnableRoslynAnalyzers = FindPropertyAssert("enableRoslynAnalyzers"); m_APICompatibilityLevel = FindPropertyAssert("apiCompatibilityLevelPerPlatform"); m_DefaultAPICompatibilityLevel = FindPropertyAssert("apiCompatibilityLevel"); m_Il2CppCompilerConfiguration = FindPropertyAssert("il2cppCompilerConfiguration"); @@ -610,6 +631,7 @@ void OnEnable() m_KeepLoadedShadersAlive = FindPropertyAssert("keepLoadedShadersAlive"); m_PreloadedAssets = FindPropertyAssert("preloadedAssets"); m_BakeCollisionMeshes = FindPropertyAssert("bakeCollisionMeshes"); + m_DedicatedServerOptimizations = FindPropertyAssert("dedicatedServerOptimizations"); m_ResizableWindow = FindPropertyAssert("resizableWindow"); m_UseMacAppStoreValidation = FindPropertyAssert("useMacAppStoreValidation"); m_MacAppStoreCategory = FindPropertyAssert("macAppStoreCategory"); @@ -638,15 +660,19 @@ void OnEnable() m_VirtualTexturingSupportEnabled = FindPropertyAssert("virtualTexturingSupportEnabled"); m_ShaderPrecisionModel = FindPropertyAssert("shaderPrecisionModel"); - m_ForceSRGBBlit = FindPropertyAssert("forceSRGBBlit"); + m_ForceSRGBBlit = FindPropertyAssert("hmiForceSRGBBlit"); - m_SettingsExtensions = new ISettingEditorExtension[validPlatforms.Length]; - for (int i = 0; i < validPlatforms.Length; i++) + var validPlatformsLength = validPlatforms.Length; + m_SettingsExtensions = new ISettingEditorExtension[validPlatformsLength]; + var currentPlatform = 0; + for (int i = 0; i < validPlatformsLength; i++) { string module = ModuleManager.GetTargetStringFromBuildTargetGroup(validPlatforms[i].namedBuildTarget.ToBuildTargetGroup()); m_SettingsExtensions[i] = ModuleManager.GetEditorSettingsExtension(module); if (m_SettingsExtensions[i] != null) m_SettingsExtensions[i].OnEnable(this); + if (validPlatforms[i].IsActive()) + currentPlatform = i; } for (int i = 0; i < m_SectionAnimators.Length; i++) @@ -660,6 +686,13 @@ void OnEnable() // we access this cache both from player settings editor and script side when changing api s_GraphicsDeviceLists.Clear(); + var selectedPlatform = SessionState.GetInt(kSelectedPlatform, currentPlatform); + if (selectedPlatform < 0) + selectedPlatform = 0; + + if (selectedPlatform >= validPlatformsLength) + selectedPlatform = validPlatformsLength - 1; + // Setup initial values to prevent immediate script recompile (or editor restart) NamedBuildTarget namedBuildTarget = validPlatforms[selectedPlatform].namedBuildTarget; serializedActiveInputHandler = m_ActiveInputHandler.intValue; @@ -766,8 +799,7 @@ private void CheckUpdatePresetSelectorStatus() if (isPreset) return; - var selectors = Resources.FindObjectsOfTypeAll(); - var isOpen = selectors != null && selectors.Length > 0; + bool isOpen = PresetEditorHelper.presetEditorOpen; hasPresetWindowClosed = (isPresetWindowOpen && !isOpen); isPresetWindowOpen = isOpen; @@ -840,8 +872,13 @@ public override void OnInspectorGUI() EditorGUILayout.Space(); EditorGUI.BeginChangeCheck(); - int oldPlatform = selectedPlatform; - selectedPlatform = EditorGUILayout.BeginPlatformGrouping(validPlatforms, null); + int oldPlatform = SessionState.GetInt(kSelectedPlatform, 0); + int selectedPlatformValue = EditorGUILayout.BeginPlatformGrouping(validPlatforms, null); + if (selectedPlatformValue != oldPlatform) + { + SessionState.SetInt(kSelectedPlatform, selectedPlatformValue); + } + if (EditorGUI.EndChangeCheck()) { // Awesome hackery to get string from delayed textfield when switching platforms @@ -849,22 +886,24 @@ public override void OnInspectorGUI() { EditorGUI.EndEditingActiveTextField(); GUIUtility.keyboardControl = 0; - string[] defines = PlayerSettings.ConvertScriptingDefineStringToArray(EditorGUI.s_DelayedTextEditor.text); + string[] defines = ScriptingDefinesHelper.ConvertScriptingDefineStringToArray(EditorGUI.s_DelayedTextEditor.text); SetScriptingDefineSymbolsForGroup(validPlatforms[oldPlatform].namedBuildTarget, defines); } // Reset focus when changing between platforms. // If we don't do this, the resolution width/height value will not update correctly when they have the focus GUI.FocusControl(""); + + m_SerializedObject.ApplyModifiedPropertiesWithoutUndo(); } - BuildPlatform platform = validPlatforms[selectedPlatform]; + BuildPlatform platform = validPlatforms[selectedPlatformValue]; if (!isPreset) { CheckUpdatePresetSelectorStatus(); } - GUILayout.Label(string.Format(L10n.Tr("Settings for {0}"), validPlatforms[selectedPlatform].title.text)); + GUILayout.Label(string.Format(L10n.Tr("Settings for {0}"), validPlatforms[selectedPlatformValue].title.text)); // Increase the offset to accomodate large labels, though keep a minimum of 150. EditorGUIUtility.labelWidth = Mathf.Max(150, EditorGUIUtility.labelWidth + 4); @@ -880,13 +919,13 @@ public override void OnInspectorGUI() } } - m_IconsEditor.IconSectionGUI(platform.namedBuildTarget, m_SettingsExtensions[selectedPlatform], selectedPlatform, sectionIndex++); + m_IconsEditor.IconSectionGUI(platform.namedBuildTarget, m_SettingsExtensions[selectedPlatformValue], selectedPlatformValue, sectionIndex++); - ResolutionSectionGUI(platform.namedBuildTarget, m_SettingsExtensions[selectedPlatform], sectionIndex++); - m_SplashScreenEditor.SplashSectionGUI(platform, m_SettingsExtensions[selectedPlatform], sectionIndex++); - DebugAndCrashReportingGUI(platform, m_SettingsExtensions[selectedPlatform], sectionIndex++); - OtherSectionGUI(platform, m_SettingsExtensions[selectedPlatform], sectionIndex++); - PublishSectionGUI(platform, m_SettingsExtensions[selectedPlatform], sectionIndex++); + ResolutionSectionGUI(platform.namedBuildTarget, m_SettingsExtensions[selectedPlatformValue], sectionIndex++); + m_SplashScreenEditor.SplashSectionGUI(platform, m_SettingsExtensions[selectedPlatformValue], sectionIndex++); + DebugAndCrashReportingGUI(platform, m_SettingsExtensions[selectedPlatformValue], sectionIndex++); + OtherSectionGUI(platform, m_SettingsExtensions[selectedPlatformValue], sectionIndex++); + PublishSectionGUI(platform, m_SettingsExtensions[selectedPlatformValue], sectionIndex++); if (sectionIndex != kNumberGUISections) Debug.LogError("Mismatched number of GUI sections."); @@ -897,6 +936,11 @@ public override void OnInspectorGUI() if (hasPresetWindowClosed) { + // We recompile after the window is closed just to make sure all the values are set/shown correctly. + // There might be a smarter idea where you detect the values that have changed and only do it if it's required, + // but the way the Preset window applies those changes as well as the way IMGUI works makes it difficult to track. + SetReason(RecompileReason.presetChanged); + OnPresetSelectorClosed(); } else if (HasReasonToCompile()) @@ -992,7 +1036,7 @@ public void ResolutionSectionGUI(NamedBuildTarget namedBuildTarget, ISettingEdit settingsExtension.ResolutionSectionGUI(h, kLabelFloatMinW, kLabelFloatMaxW); } - if (namedBuildTarget == NamedBuildTarget.Standalone || namedBuildTarget == NamedBuildTarget.EmbeddedLinux) + if (namedBuildTarget == NamedBuildTarget.Standalone) { GUILayout.Label(SettingsContent.resolutionTitle, EditorStyles.boldLabel); @@ -1048,6 +1092,9 @@ public void ResolutionSectionGUI(NamedBuildTarget namedBuildTarget, ISettingEdit if (namedBuildTarget == NamedBuildTarget.iOS) EditorGUILayout.PropertyField(m_UseOSAutoRotation, SettingsContent.useOSAutoRotation); + if (settingsExtension != null) + settingsExtension.AutoRotationSectionGUI(); + EditorGUI.indentLevel++; GUILayout.Label(SettingsContent.allowedOrientationTitle, EditorStyles.boldLabel); @@ -1148,18 +1195,22 @@ static private string GraphicsDeviceTypeToString(BuildTarget target, GraphicsDev { if (target == BuildTarget.WebGL) { - if (graphicsDeviceType == GraphicsDeviceType.OpenGLES2) return "WebGL 1 (Deprecated)"; + if (graphicsDeviceType == GraphicsDeviceType.OpenGLES2) return "WebGL 1" + kDeprecatedAppendixString; if (graphicsDeviceType == GraphicsDeviceType.OpenGLES3) return "WebGL 2"; } string name = graphicsDeviceType.ToString(); if (target == BuildTarget.iOS || target == BuildTarget.tvOS) { if (name.Contains("OpenGLES")) - name += " (Deprecated)"; + name += kDeprecatedAppendixString; } else if (graphicsDeviceType == GraphicsDeviceType.OpenGLES2) { - name += " (Deprecated)"; + name += kDeprecatedAppendixString; + } + else if (target == BuildTarget.StandaloneOSX && graphicsDeviceType == GraphicsDeviceType.OpenGLCore) + { + name += kDeprecatedAppendixString; } return name; } @@ -1167,7 +1218,7 @@ static private string GraphicsDeviceTypeToString(BuildTarget target, GraphicsDev // Parses a GraphicsDeviceType from a string. static private GraphicsDeviceType GraphicsDeviceTypeFromString(string graphicsDeviceType) { - graphicsDeviceType = graphicsDeviceType.Replace(" (Deprecated)", ""); + graphicsDeviceType = graphicsDeviceType.Replace(kDeprecatedAppendixString, ""); graphicsDeviceType = graphicsDeviceType.Replace(" (Experimental)", ""); if (graphicsDeviceType == "WebGL 1") return GraphicsDeviceType.OpenGLES2; if (graphicsDeviceType == "WebGL 2") return GraphicsDeviceType.OpenGLES3; @@ -1479,6 +1530,7 @@ void GraphicsAPIsGUI(BuildTargetGroup targetGroup, BuildTarget target) { BuildTargetGroup.Standalone, new List { ColorGamut.sRGB, ColorGamut.DisplayP3 } }, { BuildTargetGroup.iOS, new List { ColorGamut.sRGB, ColorGamut.DisplayP3 } }, { BuildTargetGroup.tvOS, new List { ColorGamut.sRGB, ColorGamut.DisplayP3 } }, + { BuildTargetGroup.VisionOS, new List { ColorGamut.sRGB, ColorGamut.DisplayP3 } }, { BuildTargetGroup.Android, new List {ColorGamut.sRGB, ColorGamut.DisplayP3 } } }; @@ -1486,8 +1538,7 @@ private static bool IsColorGamutSupportedOnTargetGroup(BuildTargetGroup targetGr { if (gamut == ColorGamut.sRGB) return true; - if (s_SupportedColorGamuts.ContainsKey(targetGroup) && - s_SupportedColorGamuts[targetGroup].Contains(gamut)) + if (s_SupportedColorGamuts.ContainsKey(targetGroup) && s_SupportedColorGamuts[targetGroup].Contains(gamut)) return true; return false; } @@ -1698,6 +1749,7 @@ public void OtherSectionGUI(BuildPlatform platform, ISettingEditorExtension sett OtherSectionIdentificationGUI(platform, settingsExtension); } OtherSectionConfigurationGUI(platform, settingsExtension); + OtherSectionShaderSettingsGUI(platform); OtherSectionScriptCompilationGUI(platform); OtherSectionOptimizationGUI(platform); OtherSectionLoggingGUI(); @@ -1707,6 +1759,75 @@ public void OtherSectionGUI(BuildPlatform platform, ISettingEditorExtension sett EndSettingsBox(); } + private void OtherSectionShaderSettingsGUI(BuildPlatform platform) + { + GUILayout.Label(SettingsContent.shaderSectionTitle, EditorStyles.boldLabel); + + using (new EditorGUI.DisabledScope(EditorApplication.isPlaying || EditorApplication.isCompiling)) + { + EditorGUI.BeginChangeCheck(); + + ShaderPrecisionModel currShaderPrecisionModel = PlayerSettings.GetShaderPrecisionModel(); + ShaderPrecisionModel[] shaderPrecisionModelValues = { ShaderPrecisionModel.PlatformDefault, ShaderPrecisionModel.Unified }; + ShaderPrecisionModel newShaderPrecisionModel = BuildEnumPopup(SettingsContent.shaderPrecisionModel, currShaderPrecisionModel, shaderPrecisionModelValues, SettingsContent.shaderPrecisionModelOptions); + if (EditorGUI.EndChangeCheck() && currShaderPrecisionModel != newShaderPrecisionModel) + { + PlayerSettings.SetShaderPrecisionModel(newShaderPrecisionModel); + } + } + + EditorGUILayout.PropertyField(m_StrictShaderVariantMatching, SettingsContent.strictShaderVariantMatching); + + EditorGUILayout.PropertyField(m_KeepLoadedShadersAlive, SettingsContent.keepLoadedShadersAlive); + + GUILayout.Label(SettingsContent.shaderVariantLoadingTitle, EditorStyles.boldLabel); + + EditorGUI.BeginChangeCheck(); + int defaultChunkSize = PlayerSettings.GetDefaultShaderChunkSizeInMB(); + int newDefaultChunkSize = EditorGUILayout.IntField(SettingsContent.defaultShaderChunkSize, defaultChunkSize); + if (EditorGUI.EndChangeCheck() && newDefaultChunkSize > 0 && newDefaultChunkSize != defaultChunkSize) + { + Undo.RecordObject(target, SettingsContent.undoChangedDefaultShaderChunkSizeString); + PlayerSettings.SetDefaultShaderChunkSizeInMB(newDefaultChunkSize); + } + + EditorGUI.BeginChangeCheck(); + int defaultChunkCount = PlayerSettings.GetDefaultShaderChunkCount(); + int newDefaultChunkCount = EditorGUILayout.IntField(SettingsContent.defaultShaderChunkCount, defaultChunkCount); + if (EditorGUI.EndChangeCheck() && newDefaultChunkCount >= 0 && newDefaultChunkCount != defaultChunkCount) + { + Undo.RecordObject(target, SettingsContent.undoChangedDefaultShaderChunkCountString); + PlayerSettings.SetDefaultShaderChunkCount(newDefaultChunkCount); + } + + bool oldOverride = PlayerSettings.GetOverrideShaderChunkSettingsForPlatform(platform.defaultTarget); + bool newOverride = EditorGUILayout.Toggle(SettingsContent.overrideDefaultChunkSettings, oldOverride); + if (oldOverride != newOverride) + PlayerSettings.SetOverrideShaderChunkSettingsForPlatform(platform.defaultTarget, newOverride); + + if (newOverride) + { + int currentChunkSize = PlayerSettings.GetShaderChunkSizeInMBForPlatform(platform.defaultTarget); + int newChunkSize = EditorGUILayout.IntField(SettingsContent.platformShaderChunkSize, currentChunkSize); + if (EditorGUI.EndChangeCheck() && newChunkSize > 0 && newChunkSize != currentChunkSize) + { + Undo.RecordObject(target, SettingsContent.undoChangedPlatformShaderChunkSizeString); + PlayerSettings.SetShaderChunkSizeInMBForPlatform(platform.defaultTarget, newChunkSize); + } + + EditorGUI.BeginChangeCheck(); + int currentChunkCount = PlayerSettings.GetShaderChunkCountForPlatform(platform.defaultTarget); + int newChunkCount = EditorGUILayout.IntField(SettingsContent.platformShaderChunkCount, currentChunkCount); + if (EditorGUI.EndChangeCheck() && newChunkCount >= 0 && newChunkCount != currentChunkCount) + { + Undo.RecordObject(target, SettingsContent.undoChangedPlatformShaderChunkCountString); + PlayerSettings.SetShaderChunkCountForPlatform(platform.defaultTarget, newChunkCount); + } + } + } + + + private void OtherSectionRenderingGUI(BuildPlatform platform, ISettingEditorExtension settingsExtension) { // Rendering related settings @@ -1730,6 +1851,26 @@ private void OtherSectionRenderingGUI(BuildPlatform platform, ISettingEditorExte } } + using (new EditorGUI.DisabledScope(EditorApplication.isPlaying)) + { + switch (platform.defaultTarget) + { + case BuildTarget.StandaloneOSX: + case BuildTarget.StandaloneWindows: + case BuildTarget.iOS: + case BuildTarget.Android: + case BuildTarget.StandaloneWindows64: + case BuildTarget.WebGL: + case BuildTarget.WSAPlayer: + case BuildTarget.StandaloneLinux64: + case BuildTarget.tvOS: + EditorGUILayout.PropertyField(m_UnsupportedMSAAFallback, SettingsContent.unsupportedMSAAFallback); + break; + default: + break; + } + } + // Display a warning for platforms that some devices don't support linear rendering if the settings are not fine for linear colorspace if (PlayerSettings.colorSpace == ColorSpace.Linear) { @@ -1774,11 +1915,13 @@ private void OtherSectionRenderingGUI(BuildPlatform platform, ISettingEditorExte // Output color spaces ColorGamutGUI(platform.namedBuildTarget.ToBuildTargetGroup()); - // Metal + // What we call "Metal Validation" is a random bunch of extra checks we do in editor in metal code if (Application.platform == RuntimePlatform.OSXEditor && BuildTargetDiscovery.BuildTargetSupportsRenderer(platform, GraphicsDeviceType.Metal)) - { m_MetalAPIValidation.boolValue = EditorGUILayout.Toggle(SettingsContent.metalAPIValidation, m_MetalAPIValidation.boolValue); + // Metal + if (BuildTargetDiscovery.BuildTargetSupportsRenderer(platform, GraphicsDeviceType.Metal)) + { EditorGUILayout.PropertyField(m_MetalFramebufferOnly, SettingsContent.metalFramebufferOnly); if (platform.namedBuildTarget == NamedBuildTarget.iOS || platform.namedBuildTarget == NamedBuildTarget.tvOS) EditorGUILayout.PropertyField(m_MetalForceHardShadows, SettingsContent.metalForceHardShadows); @@ -1885,7 +2028,7 @@ private void OtherSectionRenderingGUI(BuildPlatform platform, ISettingEditorExte } bool graphicsJobsOptionEnabled = true; - bool graphicsJobs = PlayerSettings.GetGraphicsJobsForPlatform(platform.defaultTarget); + bool graphicsJobs = PlayerSettings.GetGraphicsJobsForPlatform(platform.namedBuildTarget.ToBuildTargetGroup() == BuildTargetGroup.Standalone ? EditorUserBuildSettings.selectedStandaloneTarget : platform.defaultTarget); bool newGraphicsJobs = graphicsJobs; bool graphicsJobsModeOptionEnabled = graphicsJobs; @@ -1959,7 +2102,8 @@ private void OtherSectionRenderingGUI(BuildPlatform platform, ISettingEditorExte if (EditorGUI.EndChangeCheck() && (newGraphicsJobs != graphicsJobs)) { Undo.RecordObject(target, SettingsContent.undoChangedGraphicsJobsString); - PlayerSettings.SetGraphicsJobsForPlatform(platform.defaultTarget, newGraphicsJobs); + PlayerSettings.SetGraphicsJobsForPlatform(platform.namedBuildTarget.ToBuildTargetGroup() == BuildTargetGroup.Standalone ? EditorUserBuildSettings.selectedStandaloneTarget : platform.defaultTarget, newGraphicsJobs); + } } if (gfxJobModesSupported) @@ -2087,6 +2231,7 @@ private void OtherSectionRenderingGUI(BuildPlatform platform, ISettingEditorExte { EditorGUI.indentLevel++; streamingPriority = EditorGUILayout.DelayedIntField(SettingsContent.lightmapStreamingPriority, streamingPriority); + streamingPriority = Math.Clamp(streamingPriority, Texture2D.streamingMipmapsPriorityMin, Texture2D.streamingMipmapsPriorityMax); EditorGUI.indentLevel--; } if (EditorGUI.EndChangeCheck()) @@ -2121,7 +2266,7 @@ private void OtherSectionRenderingGUI(BuildPlatform platform, ISettingEditorExte } // Tickbox for OpenGL-only option to toggle Profiler GPU Recorders. - if (platform.namedBuildTarget == NamedBuildTarget.Standalone || platform.namedBuildTarget == NamedBuildTarget.Android) + if (platform.namedBuildTarget == NamedBuildTarget.Standalone || platform.namedBuildTarget == NamedBuildTarget.Android || platform.namedBuildTarget == NamedBuildTarget.EmbeddedLinux || platform.namedBuildTarget == NamedBuildTarget.QNX) { PlayerSettings.enableOpenGLProfilerGPURecorders = EditorGUILayout.Toggle(SettingsContent.enableOpenGLProfilerGPURecorders, PlayerSettings.enableOpenGLProfilerGPURecorders); @@ -2136,38 +2281,52 @@ private void OtherSectionRenderingGUI(BuildPlatform platform, ISettingEditorExte if (hdrDisplaySupported) { - string label = "Use display in HDR mode"; - string tooltip = "Switch the display to HDR output (on supported displays)" + ((platform.namedBuildTarget == NamedBuildTarget.XboxOne) ? " at start of application." : "."); - bool oldUseHDRDisplay = PlayerSettings.useHDRDisplay; - PlayerSettings.useHDRDisplay = EditorGUILayout.Toggle(EditorGUIUtility.TrTextContent(label, tooltip), oldUseHDRDisplay); bool requestRepaint = false; - - if (oldUseHDRDisplay != PlayerSettings.useHDRDisplay) + bool oldAllowHDRDisplaySupport = PlayerSettings.allowHDRDisplaySupport; + PlayerSettings.allowHDRDisplaySupport = EditorGUILayout.Toggle(SettingsContent.allowHDRDisplay, oldAllowHDRDisplaySupport); + if (oldAllowHDRDisplaySupport != PlayerSettings.allowHDRDisplaySupport) requestRepaint = true; - if (platform.namedBuildTarget.ToBuildTargetGroup() == BuildTargetGroup.Standalone || platform.namedBuildTarget == NamedBuildTarget.WindowsStoreApps) + using (new EditorGUI.DisabledScope(!PlayerSettings.allowHDRDisplaySupport)) { - using (new EditorGUI.DisabledScope(!PlayerSettings.useHDRDisplay)) + using (new EditorGUI.IndentLevelScope()) { - using (new EditorGUI.IndentLevelScope()) - { - EditorGUI.BeginChangeCheck(); - D3DHDRDisplayBitDepth oldBitDepth = PlayerSettings.D3DHDRBitDepth; - D3DHDRDisplayBitDepth[] bitDepthValues = { D3DHDRDisplayBitDepth.D3DHDRDisplayBitDepth10, D3DHDRDisplayBitDepth.D3DHDRDisplayBitDepth16 }; - GUIContent HDRBitDepthLabel = EditorGUIUtility.TrTextContent("Swap Chain Bit Depth", "Affects the bit depth of the final swap chain format and color space."); - GUIContent[] HDRBitDepthNames = { EditorGUIUtility.TrTextContent("Bit Depth 10"), EditorGUIUtility.TrTextContent("Bit Depth 16") }; + bool oldUseHDRDisplay = PlayerSettings.useHDRDisplay; + PlayerSettings.useHDRDisplay = EditorGUILayout.Toggle(SettingsContent.useHDRDisplay, oldUseHDRDisplay); - D3DHDRDisplayBitDepth bitDepth = BuildEnumPopup(HDRBitDepthLabel, oldBitDepth, bitDepthValues, HDRBitDepthNames); - if (EditorGUI.EndChangeCheck()) + if (oldUseHDRDisplay != PlayerSettings.useHDRDisplay) + requestRepaint = true; + + if (platform.namedBuildTarget.ToBuildTargetGroup() == BuildTargetGroup.Standalone || platform.namedBuildTarget == NamedBuildTarget.WindowsStoreApps) + { + using (new EditorGUI.DisabledScope(!PlayerSettings.useHDRDisplay)) { - PlayerSettings.D3DHDRBitDepth = bitDepth; - if (oldBitDepth != bitDepth) - requestRepaint = true; + using (new EditorGUI.IndentLevelScope()) + { + EditorGUI.BeginChangeCheck(); + HDRDisplayBitDepth oldBitDepth = PlayerSettings.hdrBitDepth; + HDRDisplayBitDepth[] bitDepthValues = { HDRDisplayBitDepth.BitDepth10, HDRDisplayBitDepth.BitDepth16 }; + GUIContent hdrBitDepthLabel = EditorGUIUtility.TrTextContent("Swap Chain Bit Depth", "Affects the bit depth of the final swap chain format and color space."); + GUIContent[] hdrBitDepthNames = { EditorGUIUtility.TrTextContent("Bit Depth 10"), EditorGUIUtility.TrTextContent("Bit Depth 16") }; + + HDRDisplayBitDepth bitDepth = BuildEnumPopup(hdrBitDepthLabel, oldBitDepth, bitDepthValues, hdrBitDepthNames); + if (EditorGUI.EndChangeCheck()) + { + PlayerSettings.hdrBitDepth = bitDepth; + if (oldBitDepth != bitDepth) + requestRepaint = true; + } + } } } } } + if (PlayerSettings.allowHDRDisplaySupport && GraphicsSettings.currentRenderPipeline != null && !SupportedRenderingFeatures.active.supportsHDR) + { + EditorGUILayout.HelpBox(SettingsContent.hdrOutputRequireHDRRenderingWarning.text, MessageType.Info); + } + if (requestRepaint) EditorApplication.RequestRepaintAllViews(); } @@ -2229,19 +2388,6 @@ private void OtherSectionRenderingGUI(BuildPlatform platform, ISettingEditorExte } } } - - using (new EditorGUI.DisabledScope(EditorApplication.isPlaying || EditorApplication.isCompiling)) - { - EditorGUI.BeginChangeCheck(); - - ShaderPrecisionModel currShaderPrecisionModel = PlayerSettings.GetShaderPrecisionModel(); - ShaderPrecisionModel[] shaderPrecisionModelValues = { ShaderPrecisionModel.PlatformDefault, ShaderPrecisionModel.Unified }; - ShaderPrecisionModel newShaderPrecisionModel = BuildEnumPopup(SettingsContent.shaderPrecisionModel, currShaderPrecisionModel, shaderPrecisionModelValues, SettingsContent.shaderPrecisionModelOptions); - if (EditorGUI.EndChangeCheck() && currShaderPrecisionModel != newShaderPrecisionModel) - { - PlayerSettings.SetShaderPrecisionModel(newShaderPrecisionModel); - } - } } if (!isPreset) EditorGUILayout.Space(); @@ -2654,7 +2800,7 @@ private void OtherSectionConfigurationGUI(BuildPlatform platform, ISettingEditor // Privacy permissions bool showPrivacyPermissions = - platform.namedBuildTarget == NamedBuildTarget.iOS || platform.namedBuildTarget == NamedBuildTarget.tvOS; + platform.namedBuildTarget == NamedBuildTarget.iOS || platform.namedBuildTarget == NamedBuildTarget.tvOS || platform.namedBuildTarget == NamedBuildTarget.VisionOS; if (showPrivacyPermissions) { @@ -2698,13 +2844,16 @@ private void OtherSectionConfigurationGUI(BuildPlatform platform, ISettingEditor if (platform.namedBuildTarget == NamedBuildTarget.iOS) { EditorGUILayout.PropertyField(m_PrepareIOSForRecording, SettingsContent.prepareIOSForRecording); + EditorGUILayout.PropertyField(m_ForceIOSSpeakersWhenRecording, SettingsContent.forceIOSSpeakersWhenRecording); } EditorGUILayout.PropertyField(m_UIRequiresPersistentWiFi, SettingsContent.UIRequiresPersistentWiFi); - EditorGUILayout.PropertyField(m_IOSURLSchemes, SettingsContent.iOSURLSchemes, true); } } + if (platform.namedBuildTarget == NamedBuildTarget.iOS || platform.namedBuildTarget == NamedBuildTarget.tvOS || platform.namedBuildTarget == NamedBuildTarget.VisionOS) + EditorGUILayout.PropertyField(m_IOSURLSchemes, SettingsContent.iOSURLSchemes, true); + if (settingsExtension != null) settingsExtension.ConfigurationSectionGUI(); @@ -2761,7 +2910,7 @@ private string GetScriptingDefineSymbolsForGroup(NamedBuildTarget buildTarget) private void SetScriptingDefineSymbolsForGroup(NamedBuildTarget buildTarget, string[] defines) { - m_ScriptingDefines.SetMapValue(buildTarget.TargetName, PlayerSettings.ConvertScriptingDefineArrayToString(defines)); + m_ScriptingDefines.SetMapValue(buildTarget.TargetName, ScriptingDefinesHelper.ConvertScriptingDefineArrayToString(defines)); } string[] GetAdditionalCompilerArgumentsForGroup(NamedBuildTarget buildTarget) @@ -2797,12 +2946,21 @@ private void OtherSectionScriptCompilationGUI(BuildPlatform platform) { using (var vertical = new EditorGUILayout.VerticalScope()) { - lastNamedBuildTarget = platform.namedBuildTarget; - if (serializedScriptingDefines == null || scriptingDefineSymbolsList == null) + { InitReorderableScriptingDefineSymbolsList(platform.namedBuildTarget); + } - scriptingDefineSymbolsList.DoLayoutList(); + if (lastNamedBuildTarget.TargetName == platform.namedBuildTarget.TargetName) + { + scriptingDefineSymbolsList.DoLayoutList(); + } + else + { + // If platform changes, update define symbols + serializedScriptingDefines = GetScriptingDefineSymbolsForGroup(platform.namedBuildTarget); + UpdateScriptingDefineSymbolsLists(); + } using (new EditorGUILayout.HorizontalScope()) { @@ -2810,8 +2968,7 @@ private void OtherSectionScriptCompilationGUI(BuildPlatform platform) var GUIState = GUI.enabled; - if (GUILayout.Button(SettingsContent.scriptingDefineSymbolsCopyDefines, - EditorStyles.miniButton)) + if (GUILayout.Button(SettingsContent.scriptingDefineSymbolsCopyDefines, EditorStyles.miniButton)) { EditorGUIUtility.systemCopyBuffer = PlayerSettings.GetScriptingDefineSymbols(platform.namedBuildTarget); } @@ -2859,7 +3016,16 @@ private void OtherSectionScriptCompilationGUI(BuildPlatform platform) using (new EditorGUI.PropertyScope(vertical.rect, GUIContent.none, m_AdditionalCompilerArguments)) { - additionalCompilerArgumentsReorderableList.DoLayoutList(); + if (lastNamedBuildTarget.TargetName == platform.namedBuildTarget.TargetName) + { + additionalCompilerArgumentsReorderableList.DoLayoutList(); + } + else + { + // If platform changes, update define symbols + serializedAdditionalCompilerArguments = GetAdditionalCompilerArgumentsForGroup(platform.namedBuildTarget); + UpdateAdditionalCompilerArgumentsLists(); + } using (new EditorGUILayout.HorizontalScope()) { @@ -2888,6 +3054,9 @@ private void OtherSectionScriptCompilationGUI(BuildPlatform platform) } } } + //We want to cache latest build target only after rendering both Scripting Defines and Additional Args + //Because both elements share the same logic + lastNamedBuildTarget = platform.namedBuildTarget; } } @@ -2914,8 +3083,6 @@ private void OtherSectionScriptCompilationGUI(BuildPlatform platform) serializedUseDeterministicCompilation = m_UseDeterministicCompilation.boolValue; SetReason(RecompileReason.useDeterministicCompilationModified); } - - EditorGUILayout.PropertyField(m_EnableRoslynAnalyzers, SettingsContent.enableRoslynAnalyzers); } void DrawTextField(Rect rect, int index) @@ -3004,8 +3171,10 @@ private void OtherSectionOptimizationGUI(BuildPlatform platform) // Optimization GUILayout.Label(SettingsContent.optimizationTitle, EditorStyles.boldLabel); + if (platform.namedBuildTarget == NamedBuildTarget.Server) + EditorGUILayout.PropertyField(m_DedicatedServerOptimizations, SettingsContent.dedicatedServerOptimizations); + EditorGUILayout.PropertyField(m_BakeCollisionMeshes, SettingsContent.bakeCollisionMeshes); - EditorGUILayout.PropertyField(m_KeepLoadedShadersAlive, SettingsContent.keepLoadedShadersAlive); if (isPreset) EditorGUI.indentLevel++; @@ -3064,7 +3233,6 @@ private void OtherSectionOptimizationGUI(BuildPlatform platform) m_VertexChannelCompressionMask.intValue = (int)vertexFlags; EditorGUILayout.PropertyField(m_StripUnusedMeshComponents, SettingsContent.stripUnusedMeshComponents); - EditorGUILayout.PropertyField(m_StrictShaderVariantMatching, SettingsContent.strictShaderVariantMatching); EditorGUILayout.PropertyField(m_MipStripping, SettingsContent.mipStripping); EditorGUILayout.Space(); @@ -3439,7 +3607,7 @@ void InitReorderableScriptingDefineSymbolsList(NamedBuildTarget namedBuildTarget { // Get Scripting Define Symbols data string defines = GetScriptingDefineSymbolsForGroup(namedBuildTarget); - scriptingDefinesList = new List(PlayerSettings.ConvertScriptingDefineStringToArray(serializedScriptingDefines)); + scriptingDefinesList = new List(ScriptingDefinesHelper.ConvertScriptingDefineStringToArray(serializedScriptingDefines)); // Initialize Reorderable List scriptingDefineSymbolsList = new ReorderableList(scriptingDefinesList, typeof(string), true, true, true, true); @@ -3452,7 +3620,7 @@ void InitReorderableScriptingDefineSymbolsList(NamedBuildTarget namedBuildTarget void UpdateScriptingDefineSymbolsLists() { - scriptingDefinesList = new List(PlayerSettings.ConvertScriptingDefineStringToArray(serializedScriptingDefines)); + scriptingDefinesList = new List(ScriptingDefinesHelper.ConvertScriptingDefineStringToArray(serializedScriptingDefines)); scriptingDefineSymbolsList.list = scriptingDefinesList; scriptingDefineSymbolsList.DoLayoutList(); hasScriptingDefinesBeenModified = false; @@ -3460,7 +3628,8 @@ void UpdateScriptingDefineSymbolsLists() void InitReorderableAdditionalCompilerArgumentsList(NamedBuildTarget namedBuildTarget) { - additionalCompilerArgumentsList = new List(serializedAdditionalCompilerArguments); + var additionalCompilerArgumentsArray = GetAdditionalCompilerArgumentsForGroup(namedBuildTarget); + additionalCompilerArgumentsList = additionalCompilerArgumentsArray.ToList(); additionalCompilerArgumentsReorderableList = new ReorderableList(additionalCompilerArgumentsList, typeof(string), true, true, true, true); additionalCompilerArgumentsReorderableList.drawElementCallback = (rect, index, isActive, isFocused) => DrawTextFieldAdditionalCompilerArguments(rect, index); diff --git a/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsSplashScreenEditor.cs b/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsSplashScreenEditor.cs index db95a28427..6025d723be 100644 --- a/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsSplashScreenEditor.cs +++ b/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsSplashScreenEditor.cs @@ -81,7 +81,7 @@ static AssetDeleteResult OnWillDeleteAsset(string asset, RemoveAssetOptions opti class Texts { public GUIContent animate = EditorGUIUtility.TrTextContent("Animation"); - public GUIContent backgroundColor = EditorGUIUtility.TrTextContent("Background Color", "Background color when no background image is used."); + public GUIContent backgroundColor = EditorGUIUtility.TrTextContent("Background Color", "Background color when no background image is used. On Android, use this property to set static splash image background color."); public GUIContent backgroundImage = EditorGUIUtility.TrTextContent("Background Image", "Image to be used in landscape and portrait (when portrait image is not set)."); public GUIContent backgroundPortraitImage = EditorGUIUtility.TrTextContent("Alternate Portrait Image*", "Optional image to be used in portrait mode."); public GUIContent backgroundTitle = EditorGUIUtility.TrTextContent("Background*"); @@ -319,13 +319,20 @@ public void SplashSectionGUI(BuildPlatform platform, ISettingEditorExtension set } else { - ObjectReferencePropertyField(m_VirtualRealitySplashScreen, k_Texts.vrSplashScreen); + bool VREnabled = BuildPipeline.IsFeatureSupported("ENABLE_VR", platform.defaultTarget); + + if (VREnabled) + ObjectReferencePropertyField(m_VirtualRealitySplashScreen, k_Texts.vrSplashScreen); if (TargetSupportsOptionalBuiltinSplashScreen(platform.namedBuildTarget.ToBuildTargetGroup(), settingsExtension)) BuiltinCustomSplashScreenGUI(platform.namedBuildTarget.ToBuildTargetGroup(), settingsExtension); if (settingsExtension != null) + { settingsExtension.SplashSectionGUI(); + if (!m_ShowUnitySplashScreen.boolValue && settingsExtension.SupportsStaticSplashScreenBackgroundColor()) + EditorGUILayout.PropertyField(m_SplashScreenBackgroundColor, k_Texts.backgroundColor); + } if (m_ShowUnitySplashScreen.boolValue) m_Owner.ShowSharedNote(); @@ -439,7 +446,8 @@ private void BuiltinCustomSplashScreenGUI(BuildTargetGroup targetGroup, ISetting // Background EditorGUILayout.LabelField(k_Texts.backgroundTitle, EditorStyles.boldLabel); EditorGUILayout.Slider(m_SplashScreenOverlayOpacity, Application.HasProLicense() ? k_MinProEditionOverlayOpacity : k_MinPersonalEditionOverlayOpacity, 1.0f, k_Texts.overlayOpacity); - m_ShowBackgroundColorAnimator.target = m_SplashScreenBackgroundLandscape.objectReferenceValue == null; + m_ShowBackgroundColorAnimator.target = m_SplashScreenBackgroundLandscape.objectReferenceValue == null || + (settingsExtension?.SupportsStaticSplashScreenBackgroundColor() ?? false); if (EditorGUILayout.BeginFadeGroup(m_ShowBackgroundColorAnimator.faded)) EditorGUILayout.PropertyField(m_SplashScreenBackgroundColor, k_Texts.backgroundColor); EditorGUILayout.EndFadeGroup(); diff --git a/Editor/Mono/Inspector/PreviewRenderUtility.cs b/Editor/Mono/Inspector/PreviewRenderUtility.cs index 29aa496442..d3807a21ff 100644 --- a/Editor/Mono/Inspector/PreviewRenderUtility.cs +++ b/Editor/Mono/Inspector/PreviewRenderUtility.cs @@ -250,9 +250,6 @@ public void BeginPreview(Rect r, GUIStyle previewBackground) if (Unsupported.SetOverrideLightingSettings(previewScene.scene)) { - RenderSettings.ambientMode = AmbientMode.Flat; - RenderSettings.ambientLight = ambientColor; - RenderSettings.defaultReflectionMode = UnityEngine.Rendering.DefaultReflectionMode.Custom; RenderSettings.customReflectionTexture = defaultEnvTexture; } @@ -282,15 +279,25 @@ public void BeginStaticPreview(Rect r) if (!EditorApplication.isUpdating && Unsupported.SetOverrideLightingSettings(previewScene.scene)) { - // User can set an ambientColor if they want to override the default black color - // Cannot grab the main scene light probe/color instead as this is sometimes run on a worker without access to the original probe/color. - RenderSettings.ambientMode = AmbientMode.Flat; - RenderSettings.ambientLight = ambientColor; - // Reflection mode should be set to the default (skybox) to stay consistent with the worker thread settings when the preview is created while saving - RenderSettings.defaultReflectionMode = UnityEngine.Rendering.DefaultReflectionMode.Skybox; + RenderSettings.defaultReflectionMode = UnityEngine.Rendering.DefaultReflectionMode.Custom; + RenderSettings.customReflectionTexture = GetDefaultReflection(); } } + private static Cubemap s_DefaultReflection; + private static Cubemap GetDefaultReflection() + { + // Reflection texture set to default prefab mode cubemap so that there are at least some reflections + // (cannot use ReflectionProbe.defaultTexture as static previews are generated before it is set, case UUM-1820) + const string path = "PrefabMode/DefaultReflectionForPrefabMode.exr"; + if (s_DefaultReflection == null) + s_DefaultReflection = EditorGUIUtility.Load(path) as Cubemap; + if (s_DefaultReflection == null) + Debug.LogError("Could not find: " + path); + + return s_DefaultReflection; + } + private void InitPreview(Rect r) { // If the background colour has changed then we can't make any assumptions @@ -304,7 +311,7 @@ private void InitPreview(Rect r) } m_TargetRect = r; - float scaleFac = GetScaleFactor(r.width, r.height); + float scaleFac = EditorGUIUtility.pixelsPerPoint; int rtWidth = (int)(r.width * scaleFac); int rtHeight = (int)(r.height * scaleFac); @@ -510,6 +517,14 @@ protected static GameObject CreateLight() public void Render(bool allowScriptableRenderPipeline = false, bool updatefov = true) { + if (!EditorApplication.isUpdating && Unsupported.SetOverrideLightingSettings(previewScene.scene)) + { + // User can set an ambientColor if they want to override the default black color + // Cannot grab the main scene light probe/color instead as this is sometimes run on a worker without access to the original probe/color. + RenderSettings.ambientMode = AmbientMode.Flat; + RenderSettings.ambientLight = ambientColor; + } + foreach (var light in lights) light.enabled = true; var oldAllowPipes = Unsupported.useScriptableRenderPipeline; diff --git a/Editor/Mono/Inspector/PreviewWindow.cs b/Editor/Mono/Inspector/PreviewWindow.cs index 32dbf2ced1..403adc892c 100644 --- a/Editor/Mono/Inspector/PreviewWindow.cs +++ b/Editor/Mono/Inspector/PreviewWindow.cs @@ -16,6 +16,8 @@ internal class PreviewWindow : InspectorWindow VisualElement previewElement => m_previewElement ?? (m_previewElement = rootVisualElement.Q(className: "unity-inspector-preview")); + internal bool IsFloatingWindow => parent is { window.rootView: not null, window.showMode: not ShowMode.MainWindow }; + public void SetParentInspector(InspectorWindow inspector) { m_ParentInspectorWindow = inspector; @@ -52,7 +54,7 @@ protected override void OnEnable() protected override void OnDisable() { base.OnDisable(); - if (m_ParentInspectorWindow != null) + if (m_ParentInspectorWindow != null && GetInspectors().Contains(m_ParentInspectorWindow)) m_ParentInspectorWindow.RebuildContentsContainers(); } diff --git a/Editor/Mono/Inspector/PropertyDrawerCache.cs b/Editor/Mono/Inspector/PropertyDrawerCache.cs index 76f61f50f8..c20a704867 100644 --- a/Editor/Mono/Inspector/PropertyDrawerCache.cs +++ b/Editor/Mono/Inspector/PropertyDrawerCache.cs @@ -2,13 +2,14 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; using System.Collections.Generic; namespace UnityEditor { - internal class PropertyHandlerCache + internal class PropertyHandlerCache : IDisposable { - protected Dictionary m_PropertyHandlers = new Dictionary(); + internal readonly protected Dictionary m_PropertyHandlers = new Dictionary(); internal PropertyHandler GetHandler(SerializedProperty property) { @@ -47,7 +48,17 @@ private static int GetPropertyHash(SerializedProperty property) public void Clear() { - m_PropertyHandlers.Clear(); + if (m_PropertyHandlers.Count > 0) + { + foreach (var handler in m_PropertyHandlers.Values) + { + handler.Dispose(); + } + + m_PropertyHandlers.Clear(); + } } + + public void Dispose() => Clear(); } } diff --git a/Editor/Mono/Inspector/PropertyEditor.cs b/Editor/Mono/Inspector/PropertyEditor.cs index aec5c0d37a..2d836a5038 100644 --- a/Editor/Mono/Inspector/PropertyEditor.cs +++ b/Editor/Mono/Inspector/PropertyEditor.cs @@ -47,7 +47,7 @@ interface IPropertySourceOpener Object hoveredObject { get; } } - class PropertyEditor : EditorWindow, IPropertyView, IHasCustomMenu, IDataModeHandlerAndDispatcher + class PropertyEditor : EditorWindow, IPropertyView, IHasCustomMenu { internal const string k_AssetPropertiesMenuItemName = "Assets/Properties... _&P"; protected const string s_MultiEditClassName = "unity-inspector-no-multi-edit-warning"; @@ -89,6 +89,7 @@ class PropertyEditor : EditorWindow, IPropertyView, IHasCustomMenu, IDataModeHan [SerializeField] protected string m_GlobalObjectId = ""; [SerializeField] protected InspectorMode m_InspectorMode = InspectorMode.Normal; + private static readonly List m_AllPropertyEditors = new List(); private Object m_InspectedObject; private static PropertyEditor s_LastPropertyEditor; protected int m_LastInitialEditorInstanceID; @@ -126,10 +127,14 @@ class PropertyEditor : EditorWindow, IPropertyView, IHasCustomMenu, IDataModeHan protected bool m_HasPreview; protected HashSet m_DrawnSelection = new HashSet(); + readonly List m_EditorTargetTypes = new List(); + + List m_SupportedDataModes = new(4); + static readonly List k_DisabledDataModes = new() {DataMode.Disabled}; + public GUIView parent => m_Parent; public HashSet editorsWithImportedObjectLabel { get; } = new HashSet(); public EditorDragging editorDragging { get; } - internal int inspectorElementModeOverride { get; set; } = 0; public Editor lastInteractedEditor { get; set; } internal static PropertyEditor HoveredPropertyEditor { get; private set; } internal static PropertyEditor FocusedPropertyEditor { get; private set; } @@ -323,10 +328,10 @@ protected virtual void OnEnable() m_PreviewResizer.Init("InspectorPreview"); m_LabelGUI.OnEnable(); m_FirstInitialize = true; + var shouldUpdateSupportedDataModes = m_SerializedDataModeController == null; CreateTracker(); EditorApplication.focusChanged += OnFocusChanged; - EditorApplication.playModeStateChanged += OnPlayModeStateChanged; Undo.undoRedoEvent += OnUndoRedoPerformed; PrefabUtility.prefabInstanceUnpacked += OnPrefabInstanceUnpacked; ObjectChangeEvents.changesPublished += OnObjectChanged; @@ -337,6 +342,14 @@ protected virtual void OnEnable() rootVisualElement.RegisterCallback(OnMouseLeave); rootVisualElement.RegisterCallback(OnFocusIn); rootVisualElement.RegisterCallback(OnFocusOut); + + dataModeController.dataModeChanged += OnDataModeChanged; + EditorApplication.playModeStateChanged += OnPlayModeStateChanged; + + if (shouldUpdateSupportedDataModes) + EditorApplication.CallDelayed(UpdateSupportedDataModesList); + + if (!m_AllPropertyEditors.Contains(this)) m_AllPropertyEditors.Add(this); } [UsedImplicitly] @@ -349,7 +362,6 @@ protected virtual void OnDisable() m_LastVerticalScrollValue = m_ScrollView?.verticalScroller.value ?? 0; EditorApplication.focusChanged -= OnFocusChanged; - EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; Undo.undoRedoEvent -= OnUndoRedoPerformed; PrefabUtility.prefabInstanceUnpacked -= OnPrefabInstanceUnpacked; ObjectChangeEvents.changesPublished -= OnObjectChanged; @@ -360,6 +372,11 @@ protected virtual void OnDisable() rootVisualElement.UnregisterCallback(OnMouseLeave); rootVisualElement.UnregisterCallback(OnFocusIn); rootVisualElement.UnregisterCallback(OnFocusOut); + + dataModeController.dataModeChanged -= OnDataModeChanged; + EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; + + m_AllPropertyEditors.Remove(this); } private void OnMouseEnter(MouseEnterEvent e) => HoveredPropertyEditor = this; @@ -417,7 +434,7 @@ protected virtual void OnGUI() [UsedImplicitly] protected virtual void Update() { - ActiveEditorTracker.Internal_GetActiveEditorsNonAlloc(tracker, s_Editors); + ActiveEditorTracker.Internal_GetActiveEditorsNonAlloc(tracker, ref s_Editors); if (s_Editors.Length == 0) return; @@ -440,6 +457,11 @@ protected virtual void Update() UpdateWindowObjectNameTitle(); } + internal static IEnumerable GetPropertyEditors() + { + return m_AllPropertyEditors.AsEnumerable(); + } + protected void SetMode(InspectorMode mode) { if (m_InspectorMode != mode) @@ -535,6 +557,23 @@ private void OnGeometryChanged(GeometryChangedEvent e) RestoreVerticalScrollIfNeeded(); } + internal static void ClearAndRebuildAll() + { + // Needs to be delayCall because it forces redrawing of UI which messes with the current IMGUI context of the Settings window. + EditorApplication.delayCall += ClearAndRebuildAllDelayed; + } + + static void ClearAndRebuildAllDelayed() + { + // Cannot use something like EditorUtility.ForceRebuildInspectors() because this only refreshes + // the inspector's values and IMGUI state, but otherwise, if the target did not change we + // re-use the Editors. We need a special clear function to properly recreate the UI using + // the new setting. + var propertyEditors = Resources.FindObjectsOfTypeAll(); + foreach (var propertyEditor in propertyEditors) + propertyEditor.ClearEditorsAndRebuild(); + } + internal void ClearEditorsAndRebuild() { // Clear the editors Element so that a real rebuild is done @@ -687,7 +726,6 @@ protected virtual void CreateTracker() m_Tracker = new ActiveEditorTracker { inspectorMode = InspectorMode.Normal }; if (LoadPersistedObject()) { - dataMode = GetLastKnownDataMode(); m_Tracker.ForceRebuild(); } } @@ -728,8 +766,23 @@ protected virtual void UpdateWindowObjectNameTitle() Repaint(); } - private void OnUndoRedoPerformed(in UndoRedoInfo info) + void OnUndoRedoPerformed(in UndoRedoInfo info) { + // Fix for a swapping an `m_Script` field in debug mode followed by an undo/redo. This causes the serialized object + // to be reset back to it's previous type. The editor instance remains the same, the serialized object remains the same and property iterators return + // the previous type properties. This breaks most assumptions in the inspectors and property drawers and causes numerous errors. As a patch we do a nuclear + // rebuild of everything if this state is detected. + var editors = tracker.activeEditors; + for (var i = 0; i < m_EditorTargetTypes.Count && i < editors.Length; i++) + { + var targetType = editors[i].target ? editors[i].target.GetType() : null; + + if (targetType == m_EditorTargetTypes[i]) + continue; + + ActiveEditorTracker.sharedTracker.ForceRebuild(); + } + // We need to detect and rebuild removed or suppressed component titlebars if the // backend changes. Situations where this detection is needed is when: // 1) Undo could cause a removed component to become a suppressed component and vice versa @@ -1005,6 +1058,13 @@ internal virtual void RebuildContentsContainers() Editor[] editors = tracker.activeEditors; + // Fix for a swapping an `m_Script` field in debug mode followed by an undo/redo. This causes the serialized object to be reset back to it's previous type. + // The editor instance remains the same, the serialized object remains the same and property iterators return the previous type properties. + // Here we track the last built editor types which is compared during the next undo-redo operation. + m_EditorTargetTypes.Clear(); + foreach (var editor in editors) + m_EditorTargetTypes.Add(editor.target ? editor.target.GetType() : null); + if (editors.Any() && versionControlElement != null) { versionControlElement.Add(CreateIMGUIContainer( @@ -1075,10 +1135,6 @@ internal virtual void RebuildContentsContainers() ScriptAttributeUtility.ClearGlobalCache(); EndRebuildContentContainers(); - - if (dataMode == DataMode.Disabled) - SwitchToDefaultDataMode(); - Repaint(); RefreshTitle(); } @@ -1297,7 +1353,7 @@ private Object[] GetInspectedAssets() // This is used if more than one asset is selected // Ideally the tracker should be refactored to track not just editors but also the selection that caused them, so we wouldn't need this - return Selection.objects.Where(IsOpenForEdit).ToArray(); + return Selection.objects.Where(EditorUtility.IsPersistent).ToArray(); } protected virtual bool BeginDrawPreviewAndLabels() { return true; } @@ -1467,7 +1523,7 @@ private void DrawPreviewAndLabels() GUILayout.BeginVertical(Styles.footer); if (hasLabels) { - using (new EditorGUI.DisabledScope(assets.Any(a => EditorUtility.IsPersistent(a) && !Editor.IsAppropriateFileOpenForEdit(a)))) + using (new EditorGUI.DisabledScope(assets.Any(a => !IsOpenForEdit(a) || !Editor.IsAppropriateFileOpenForEdit(a)))) { m_LabelGUI.OnLabelGUI(assets); } @@ -1475,7 +1531,10 @@ private void DrawPreviewAndLabels() if (hasBundleName) { - m_AssetBundleNameGUI.OnAssetBundleNameGUI(assets); + using (new EditorGUI.DisabledScope(assets.Any(a => !IsOpenForEdit(a)))) + { + m_AssetBundleNameGUI.OnAssetBundleNameGUI(assets); + } } GUILayout.EndVertical(); } @@ -2193,6 +2252,9 @@ private void AddComponentButton(Editor[] editors) m_OpenAddComponentMenu = false; if (AddComponentWindow.Show(rect, editor.targets.Cast().Where(o => o).ToArray())) { + // Repaint the inspector window to ensure the AddComponentWindow.Show + // does not clear the inspector window gl buffer, which blacks out the inspector window. + this.RepaintImmediately(); GUIUtility.ExitGUI(); } } @@ -2261,6 +2323,8 @@ private Dictionary ProcessEditorElementsToRebuild(Editor[] if (!(ed.target is ParticleSystemRenderer)) { currentElement.ReinitCulled(newEditorsIndex); + if (!InspectorElement.disabledThrottling) + m_EditorElementUpdater.Add(currentElement); // We need to move forward as the current element is the culled one, so we're not really // interested in it. @@ -2274,8 +2338,11 @@ private Dictionary ProcessEditorElementsToRebuild(Editor[] { return null; } - + + editors[newEditorsIndex].propertyViewer = this; currentElement.Reinit(newEditorsIndex); + if (!InspectorElement.disabledThrottling) + m_EditorElementUpdater.Add(currentElement); editorToElementMap[ed.target.GetInstanceID()] = currentElement; ++newEditorsIndex; ++previousEditorsIndex; @@ -2285,6 +2352,7 @@ private Dictionary ProcessEditorElementsToRebuild(Editor[] for (int j = previousEditorsIndex; j < currentElements.Count; ++j) { currentElements[j].RemoveFromHierarchy(); + m_EditorElementUpdater.Remove(currentElements[j]); } return editorToElementMap; @@ -2369,123 +2437,52 @@ static void OpenHoveredItemPropertyEditor(ShortcutManagement.ShortcutArguments a } } - static readonly List k_DisabledDataModes = new() {DataMode.Disabled}; - - readonly List m_SupportedDataModes = new(4); - - bool areDataModesEnabled => - m_InspectorMode == InspectorMode.Normal && - m_SupportedDataModes.Count > 0; - - List GetSupportedDataModesForContext() => - areDataModesEnabled && m_SupportedDataModes.Count > 0 - ? m_SupportedDataModes - : k_DisabledDataModes; - - DataMode GetLastKnownDataMode() - { - if (areDataModesEnabled) - return EditorApplication.isPlaying - ? m_LastKnownPlayModeDataMode - : m_LastKnownEditModeDataMode; - - return DataMode.Disabled; - } - - [SerializeField] DataMode m_LastKnownEditModeDataMode = DataMode.Authoring; - [SerializeField] DataMode m_LastKnownPlayModeDataMode = DataMode.Runtime; - - public event Action dataModeChanged; - - // Caching the DataMode to avoid round-trips to native multiple times per frame. - // Note: DataMode.Disabled is the same default as in ActiveEditorTracker.cpp - DataMode m_DataMode = DataMode.Disabled; - - public DataMode dataMode - { - get => m_DataMode; - private set - { - if (m_DataMode == value) - return; - - tracker.dataMode = value; - m_DataMode = value; - } - } + internal static DataMode GetPreferredDataMode() + => EditorApplication.isPlaying + ? DataMode.Mixed + : DataMode.Authoring; - public IReadOnlyList supportedDataModes => GetSupportedDataModesForContext(); + List GetSupportedDataModes() => m_SupportedDataModes; - public bool IsDataModeSupported(DataMode mode) => GetSupportedDataModesForContext().Contains(mode); + bool m_IsEnteringPlaymode; - public void SwitchToNextDataMode() + void OnPlayModeStateChanged(PlayModeStateChange stateChange) { - var possibleDataModes = GetSupportedDataModesForContext(); - var nextIndex = possibleDataModes.IndexOf(dataMode) + 1; - if (nextIndex >= possibleDataModes.Count) - nextIndex = 0; + if (stateChange is not (PlayModeStateChange.EnteredEditMode or PlayModeStateChange.EnteredPlayMode)) + return; - SwitchToDataMode(possibleDataModes[nextIndex]); + m_IsEnteringPlaymode = true; + UpdateSupportedDataModesList(); } - public void SwitchToDefaultDataMode() + void OnDataModeChanged(DataModeChangeEventArgs evt) { - var selectedDataMode = GetLastKnownDataMode(); - - if (!IsDataModeSupported(selectedDataMode)) - { - // Fallback - selectedDataMode = supportedDataModes[0]; - } - - SwitchToDataMode(selectedDataMode); + tracker.dataMode = evt.nextDataMode; + tracker.ForceRebuild(); } - public void SwitchToDataMode(DataMode mode) + protected void UpdateSupportedDataModesList() { - if (!IsDataModeSupported(mode)) - SwitchToDefaultDataMode(); - - if (dataMode == mode) - return; + m_SupportedDataModes.Clear(); + OnUpdateSupportedDataModes(m_SupportedDataModes); + m_SupportedDataModes.Sort(); - dataMode = mode; - tracker.ForceRebuild(); + if (m_InspectorMode != InspectorMode.Normal || m_SupportedDataModes.Count == 0) + m_SupportedDataModes = k_DisabledDataModes; - dataModeChanged?.Invoke(dataMode); - } + var dataMode = Selection.dataModeHint == DataMode.Disabled ? GetPreferredDataMode() : Selection.dataModeHint; - void OnPlayModeStateChanged(PlayModeStateChange playModeStateChange) - { - switch (playModeStateChange) + // When entering playmode, Inspector relies on getting selection hint + // from Hierarchy window to switch to the proper data mode. + // However when inspector is locked, selection is locked too. + // Here we force the preferred data mode to inspector for this special case. + if (m_IsEnteringPlaymode && m_Tracker.isLocked) { - case PlayModeStateChange.ExitingEditMode: - { - // We're about to exit edit mode, save the last known DataMode - m_LastKnownEditModeDataMode = dataMode; - break; - } - case PlayModeStateChange.ExitingPlayMode: - { - // We're about to exit play mode, save the last known DataMode - m_LastKnownPlayModeDataMode = dataMode; - break; - } - case PlayModeStateChange.EnteredEditMode: - case PlayModeStateChange.EnteredPlayMode: - { - UpdateSupportedDataModes(); - SwitchToDefaultDataMode(); - break; - } + dataMode = GetPreferredDataMode(); + m_IsEnteringPlaymode = false; } - } - protected void UpdateSupportedDataModes() - { - m_SupportedDataModes.Clear(); - OnUpdateSupportedDataModes(m_SupportedDataModes); - m_SupportedDataModes.Sort(); + dataModeController.UpdateSupportedDataModes(m_SupportedDataModes, dataMode); } protected virtual void OnUpdateSupportedDataModes(List supportedModes) diff --git a/Editor/Mono/Inspector/QualitySettingsEditor.cs b/Editor/Mono/Inspector/QualitySettingsEditor.cs index 9442c7ee90..b8e86995e9 100644 --- a/Editor/Mono/Inspector/QualitySettingsEditor.cs +++ b/Editor/Mono/Inspector/QualitySettingsEditor.cs @@ -18,6 +18,42 @@ private class Content { public static readonly GUIContent kPlatformTooltip = EditorGUIUtility.TrTextContent("", "Allow quality setting on platform"); public static readonly GUIContent kAddQualityLevel = EditorGUIUtility.TrTextContent("Add Quality Level"); + + public static readonly GUIContent kGlobalTextureMipmapLimit = EditorGUIUtility.TrTextContent("Global Mipmap Limit", "The base texture quality level."); + + public static readonly GUIContent kTextureMipmapLimitGroupsHeader = EditorGUIUtility.TrTextContent("Mipmap Limit Groups", "Mipmap Limit Groups are used to control quality on a per-texture basis."); + public static readonly GUIContent[] kTextureMipmapLimitGroupsOverrideModeItems = + { + EditorGUIUtility.TrTextContent("Override Global Mipmap Limit: Full Resolution", "Global Mipmap Limit is ignored, upload at full resolution."), + EditorGUIUtility.TrTextContent("Override Global Mipmap Limit: Half Resolution", "Global Mipmap Limit is ignored, upload at half resolution."), + EditorGUIUtility.TrTextContent("Override Global Mipmap Limit: Quarter Resolution", "Global Mipmap Limit is ignored, upload at quarter resolution."), + EditorGUIUtility.TrTextContent("Override Global Mipmap Limit: Eighth Resolution", "Global Mipmap Limit is ignored, upload at eighth resolution.") + }; + public static readonly GUIContent[] kTextureMipmapLimitGroupsOffsetModeItems = + { + EditorGUIUtility.TrTextContent("Offset Global Mipmap Limit: -3", "Upload 3 mips extra compared to the Global Mipmap Limit."), + EditorGUIUtility.TrTextContent("Offset Global Mipmap Limit: -2", "Upload 2 mips extra compared to the Global Mipmap Limit."), + EditorGUIUtility.TrTextContent("Offset Global Mipmap Limit: -1", "Upload 1 mip extra compared to the Global Mipmap Limit."), + EditorGUIUtility.TrTextContent("Use Global Mipmap Limit", "No offset or override occurs, simply use the Global Mipmap Limit. (Default)"), + EditorGUIUtility.TrTextContent("Offset Global Mipmap Limit: +1", "Upload 1 mip less compared to the Global Mipmap Limit."), + EditorGUIUtility.TrTextContent("Offset Global Mipmap Limit: +2", "Upload 2 mips less compared to the Global Mipmap Limit."), + EditorGUIUtility.TrTextContent("Offset Global Mipmap Limit: +3", "Upload 3 mips less compared to the Global Mipmap Limit.") + }; + public static readonly GUIContent kTextureMipmapLimitGroupsOptions = EditorGUIUtility.TrIconContent("_Menu", "Show additional options"); + public static readonly GUIContent kTextureMipmapLimitGroupsOptionsIdentify = EditorGUIUtility.TrTextContent("Identify textures"); + public static readonly GUIContent kTextureMipmapLimitGroupsOptionsDuplicate = EditorGUIUtility.TrTextContent("Duplicate group"); + public static readonly GUIContent kTextureMipmapLimitGroupsOptionsRename = EditorGUIUtility.TrTextContent("Rename group"); + public static readonly GUIContent kTextureMipmapLimitGroupsAddButton = EditorGUIUtility.TrIconContent("Toolbar Plus", "Create a new mipmap limit group. Note that this adds a group to all quality levels, not only the active one!"); + public static readonly GUIContent kTextureMipmapLimitGroupsRemoveButton = EditorGUIUtility.TrIconContent("Toolbar Minus", "Remove mipmap limit group. Note that this removes the group from all quality levels, not only the active one!"); + + public static readonly string kTextureMipmapLimitGroupsDialogTitleOnUpdate = L10n.Tr("Mipmap Limit Groups: Update textures?"); + public static readonly string kTextureMipmapLimitGroupsDialogMessageOnRemove = L10n.Tr("Textures in your project may still be using '{0}'.\n\nSelect 'No' to remove the group without modifying its associated textures. Relevant textures stay bound to the group and fall back automatically to the global mipmap limit.\n\nSelect 'Yes' to remove the group and reset the group property of associated textures to 'None'. This triggers a re-import and may take some time. An undo cannot revert the importer changes."); + public static readonly string kTextureMipmapLimitGroupsDialogMessageOnRename = L10n.Tr("Textures in your project may still be using '{0}'.\n\nSelect 'No' to rename the group without modifying its associated textures. Relevant textures stay bound to the group and fall back automatically to the global mipmap limit.\n\nSelect 'Yes' to rename the group and update the group property of associated textures to '{1}'. This triggers a re-import and may take some time. An undo cannot revert the importer changes."); + public static readonly string kTextureMipmapLimitGroupsDialogTitleOnFailure = L10n.Tr("Mipmap Limit Groups: Operation failed"); + public static readonly string kTextureMipmapLimitGroupsDialogMessageOnRenameFail = L10n.Tr("The mipmap limit group '{0}' already exists.\n'{1}' was not renamed."); + public static readonly string kTextureMipmapLimitGroupsDialogMessageOnUpdateAssetsError = L10n.Tr("An error occured while updating texture assets: {0}"); + public static readonly string kTextureMipmapLimitGroupsDialogMessageOnIdentifyFail = L10n.Tr("No textures are linked to the mipmap limit group '{0}'."); + public static readonly GUIContent kStreamingMipmapsActive = EditorGUIUtility.TrTextContent("Texture Streaming", "When enabled, Unity only streams texture mipmaps relevant to the current Camera's position in a Scene. This reduces the total amount of memory Unity needs for textures. Individual textures must also have 'Streaming Mip Maps' enabled in their Import Settings."); public static readonly GUIContent kStreamingMipmapsMemoryBudget = EditorGUIUtility.TrTextContent("Memory Budget", "The amount of memory (in megabytes) to allocate for all loaded textures."); public static readonly GUIContent kStreamingMipmapsRenderersPerFrame = EditorGUIUtility.TrTextContent("Renderers Per Frame", "The number of renderers to process each frame. A lower number decreases the CPU load but delays mipmap loading."); @@ -30,14 +66,33 @@ private class Content public static readonly GUIContent kBillboardsFaceCameraPos = EditorGUIUtility.TrTextContent("Billboards Face Camera Position", "When enabled, terrain billboards face towards the camera position. Otherwise, they face towards the camera plane. This makes billboards look nicer when the camera rotates but it is more resource intensive to process."); public static readonly GUIContent kUseLegacyDistribution = EditorGUIUtility.TrTextContent("Use Legacy Details Distribution", "When enabled, terrain details will be scattered using the old scattering algorithm that often resulted in overlapping details. Included for backwards compatibility with terrain authored in Unity 2022.1 and earlier."); public static readonly GUIContent kVSyncCountLabel = EditorGUIUtility.TrTextContent("VSync Count", "Specifies how Unity synchronizes rendering with the refresh rate of the display device."); + public static readonly GUIContent kRealtimeLGiCpuUsageLabel = EditorGUIUtility.TrTextContent("Realtime GI CPU Usage", "How many CPU worker threads to create for Realtime Global Illumination lighting calculations in the Player. Increasing this makes the system react faster to changes in lighting at a cost of using more CPU time. The higher the CPU Usage value, the more worker threads are created for solving Realtime GI."); public static readonly GUIContent kLODBiasLabel = EditorGUIUtility.TrTextContent("LOD Bias", "The bias Unity uses to determine which model to render when a GameObject’s on-screen size is between two LOD levels. Values between 0 and 1 favor the less detailed model. Values greater than 1 favor the more detailed model."); public static readonly GUIContent kMaximumLODLevelLabel = EditorGUIUtility.TrTextContent("Maximum LOD Level", "The highest LOD to use in the application."); public static readonly GUIContent kEnableLODCrossFadeLabel = EditorGUIUtility.TrTextContent("LOD Cross Fade", "Enables or disables LOD Cross Fade."); - public static readonly GUIContent kMipStrippingHint = EditorGUIUtility.TrTextContent("Where the maximum possible texture mip resolution for a platform is less than full, enabling Texture MipMap Stripping in Player Settings can reduce the package size."); + public static readonly GUIContent kMipStrippingHint = EditorGUIUtility.TrTextContent("Detected platforms with textures that never use their highest resolution mips. Enable Texture Mipmap Stripping in the Player Settings to reduce the package size of those platforms."); public static readonly GUIContent kAsyncUploadTimeSlice = EditorGUIUtility.TrTextContent("Time Slice", "The amount of time (in milliseconds) Unity spends uploading Texture and Mesh data to the GPU per frame."); public static readonly GUIContent kAsyncUploadBufferSize = EditorGUIUtility.TrTextContent("Buffer Size", "The size (in megabytes) of the upload buffer Unity uses to stream Texture and Mesh data to GPU."); public static readonly GUIContent kAsyncUploadPersistentBuffer = EditorGUIUtility.TrTextContent("Persistent Buffer", "When enabled, the upload buffer persists even when there is nothing left to upload."); + public static readonly GUIContent kAsyncUploadBufferSizeWarning = EditorGUIUtility.TrTextContent("Unity has detected that you are using an upload buffer size of {0} MB with the '{1}' setting enabled. If you have issues with excessive memory usage, you may need to reduce the upload buffer size or disable the '{1}' setting. Memory fragmentation can occur if you choose the latter option."); + + public static readonly GUIContent kOverrideTerrainPixelError = EditorGUIUtility.TrTextContent("", "Whether to override pixel error in active Terrains."); + public static readonly GUIContent kOverrideTerrainBasemapDist = EditorGUIUtility.TrTextContent("", "Whether to override base map distance in active Terrains."); + public static readonly GUIContent kOverrideTerrainDensityScale = EditorGUIUtility.TrTextContent("", "Whether to override detail density scale in active Terrains."); + public static readonly GUIContent kOverrideTerrainDetailDistance = EditorGUIUtility.TrTextContent("", "Whether to override detail distance in active Terrains."); + public static readonly GUIContent kOverrideTerrainTreeDistance = EditorGUIUtility.TrTextContent("", "Whether to override tree distance in active Terrains."); + public static readonly GUIContent kOverrideTerrainBillboardStart = EditorGUIUtility.TrTextContent("", "Whether to override billboard start distance in active Terrains."); + public static readonly GUIContent kOverrideTerrainFadeLength = EditorGUIUtility.TrTextContent("", "Whether to override billboard fade length in active Terrains."); + public static readonly GUIContent kOverrideTerrainMaxTrees = EditorGUIUtility.TrTextContent("", "Whether to override max mesh trees in active Terrains."); + public static readonly GUIContent kTerrainPixelError = EditorGUIUtility.TrTextContent("Pixel Error", "Value set to Terrain pixel error (See Terrain settings)"); + public static readonly GUIContent kTerrainBasemapDistance = EditorGUIUtility.TrTextContent("Base Map Dist.", "Value set to Terrain base map distance (See Terrain settings)"); + public static readonly GUIContent kTerrainDetailDensityScale = EditorGUIUtility.TrTextContent("Detail Density Scale", "Value set to Terrain detail density scale (See Terrain settings)"); + public static readonly GUIContent kTerrainDetailDistance = EditorGUIUtility.TrTextContent("Detail Distance", "Value set to Terrain detail object distance (See Terrain settings)"); + public static readonly GUIContent kTerrainTreeDistance = EditorGUIUtility.TrTextContent("Tree Distance", "Value set to Terrain tree distance (See Terrain settings)"); + public static readonly GUIContent kTerrainBillboardStart = EditorGUIUtility.TrTextContent("Billboard Start", "Value set to Terrain billboard start distance (See Terrain settings)"); + public static readonly GUIContent kTerrainFadeLength = EditorGUIUtility.TrTextContent("Fade Length", "Value set to Terrain billboard fade length (See Terrain settings)"); + public static readonly GUIContent kTerrainMaxTrees = EditorGUIUtility.TrTextContent("Max Mesh Trees", "Value set to Terrain max mesh trees (See Terrain settings)"); public static readonly GUIContent kRenderPipelineObject = EditorGUIUtility.TrTextContent("Render Pipeline Asset", "Specifies the Render Pipeline Asset to use for this quality level. It overrides the value set in the Graphics Settings Window."); } @@ -51,14 +106,24 @@ private class Styles public static readonly GUIStyle kListOddBg = "ObjectPickerResultsEven"; public static readonly GUIStyle kDefaultDropdown = "QualitySettingsDefault"; + public static readonly GUIStyle kTextureMipmapLimitGroupsOptionsButton = new GUIStyle(EditorStyles.miniButton) { padding = new RectOffset() }; + public const int kMinToggleWidth = 15; public const int kMaxToggleWidth = 20; public const int kHeaderRowHeight = 20; public const int kLabelWidth = 80; + + public const int kTextureMipmapLimitGroupsLabelWidthOffset = -6; + public const int kTextureMipmapLimitGroupsOptionsWidth = 16; + public const int kTextureMipmapLimitGroupsOffsetTop = 2; + public const int kTextureMipmapLimitGroupsPaddingRight = 4; + + public static readonly Vector2 kTextureMipmapLimitGroupsOptionsMenuOffset = new Vector2(-134, 19); } public const int kMinAsyncRingBufferSize = 2; - public const int kMaxAsyncRingBufferSize = 512; + public const int kMaxAsyncRingBufferSize = 2047; + public const int kAsyncRingBufferSizeWarningThreshold = 513; public const int kMinAsyncUploadTimeSlice = 1; public const int kMaxAsyncUploadTimeSlice = 33; @@ -67,12 +132,32 @@ private class Styles private SerializedProperty m_PerPlatformDefaultQualityProperty; private List m_ValidPlatforms; + private SerializedProperty m_TextureMipmapLimitGroupNamesProperty; + private SerializedProperty m_TextureMipmapLimitGroupSettingsProperty; // Always refers to the active quality level + private ReorderableList m_TextureMipmapLimitGroupsList; + private bool m_TextureMipmapLimitGroupBeingRenamed = false; + private int m_TextureMipmapLimitGroupBeingRenamedIndex = -1; + private bool m_TextureMipmapLimitGroupsTextFieldNeedsFocus = false; + private bool m_TextureMipmapLimitGroupsRenameShowUpdatePrompt = true; + + private Editor m_PresetEditor; + private Presets.Preset m_QualitySettingsPreset; + public void OnEnable() { m_QualitySettings = new SerializedObject(target); m_QualitySettingsProperty = m_QualitySettings.FindProperty("m_QualitySettings"); m_PerPlatformDefaultQualityProperty = m_QualitySettings.FindProperty("m_PerPlatformDefaultQuality"); m_ValidPlatforms = BuildPlatforms.instance.GetValidPlatforms(); + + m_TextureMipmapLimitGroupNamesProperty = m_QualitySettings.FindProperty("m_TextureMipmapLimitGroupNames"); + m_TextureMipmapLimitGroupsList = new ReorderableList(m_QualitySettings, m_TextureMipmapLimitGroupNamesProperty, false, true, true, true); + // The ReorderableList uses the GroupNames property as an indicator for how many groups really do exist. + m_TextureMipmapLimitGroupsList.drawHeaderCallback = DrawTextureMipmapLimitGroupsHeader; + m_TextureMipmapLimitGroupsList.drawElementCallback = DrawTextureMipmapLimitGroupsElement; + m_TextureMipmapLimitGroupsList.drawFooterCallback = DrawTextureMipmapLimitGroupsFooter; + m_TextureMipmapLimitGroupsList.onAddCallback = AddTextureMipmapLimitGroup; + m_TextureMipmapLimitGroupsList.onRemoveCallback = RemoveTextureMipmapLimitGroup; } private struct QualitySetting @@ -427,7 +512,14 @@ void MipStrippingHintGUI() if (PlayerSettings.mipStripping) return; - EditorGUILayout.HelpBox(Content.kMipStrippingHint.text, MessageType.Info, false); + EditorGUILayout.Space(-20f); // Move back towards the mipmap limit groups UI. + using (new EditorGUILayout.HorizontalScope()) + { + float marginRight = Presets.Preset.IsEditorTargetAPreset(target) ? 67f : 64f; + EditorGUILayout.HelpBox(Content.kMipStrippingHint.text, MessageType.Info, false); + EditorGUILayout.GetControlRect(false, GUILayoutUtility.GetLastRect().height, GUILayout.Width(marginRight)); + // Limit the width of the HelpBox in order to avoid clipping into the mipmap limit groups UI. + } } /** @@ -473,6 +565,20 @@ private void DrawCascadeSplitGUI(ref SerializedProperty shadowCascadeSplit) } } + private bool DrawOverrideToggle(ref SerializedProperty overrideProperty, TerrainQualityOverrides overrideFlag, GUIContent overrideStyle) + { + var overrideFlagsPropertyValue = (TerrainQualityOverrides)overrideProperty.enumValueFlag; + bool overrideActive = overrideFlagsPropertyValue.HasFlag(overrideFlag); + + var overrideRect = GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.toggle, GUILayout.Height(EditorGUIUtility.singleLineHeight), GUILayout.ExpandWidth(false)); + overrideActive = GUI.Toggle(overrideRect, overrideActive, overrideStyle); + overrideProperty.enumValueFlag = overrideActive + ? (int)(overrideFlagsPropertyValue | overrideFlag) + : (int)(overrideFlagsPropertyValue & ~overrideFlag); + + return overrideActive; + } + static List s_PlatformsWithDifferentRPAssets = new(); void CheckSameRenderPipelineAssetForOverridenQualityLevels() @@ -536,14 +642,25 @@ public override void OnInspectorGUI() var shadowCascade4SplitProperty = currentSettings.FindPropertyRelative("shadowCascade4Split"); var shadowMaskUsageProperty = currentSettings.FindPropertyRelative("shadowmaskMode"); var skinWeightsProperty = currentSettings.FindPropertyRelative("skinWeights"); - var textureQualityProperty = currentSettings.FindPropertyRelative("textureQuality"); + var globalTextureMipmapLimitProperty = currentSettings.FindPropertyRelative("globalTextureMipmapLimit"); + m_TextureMipmapLimitGroupSettingsProperty = currentSettings.FindPropertyRelative("textureMipmapLimitSettings"); var anisotropicTexturesProperty = currentSettings.FindPropertyRelative("anisotropicTextures"); var antiAliasingProperty = currentSettings.FindPropertyRelative("antiAliasing"); var softParticlesProperty = currentSettings.FindPropertyRelative("softParticles"); var realtimeReflectionProbes = currentSettings.FindPropertyRelative("realtimeReflectionProbes"); var billboardsFaceCameraPosition = currentSettings.FindPropertyRelative("billboardsFaceCameraPosition"); var useLegacyDetailsDistribution = currentSettings.FindPropertyRelative("useLegacyDetailDistribution"); + var terrainQualityOverridesProperty = currentSettings.FindPropertyRelative("terrainQualityOverrides"); + var terrainPixelErrorProperty = currentSettings.FindPropertyRelative("terrainPixelError"); + var terrainDetailDensityScaleProperty = currentSettings.FindPropertyRelative("terrainDetailDensityScale"); + var terrainBasemapDistanceProperty = currentSettings.FindPropertyRelative("terrainBasemapDistance"); + var terrainDetailDistanceProperty = currentSettings.FindPropertyRelative("terrainDetailDistance"); + var terrainTreeDistanceProperty = currentSettings.FindPropertyRelative("terrainTreeDistance"); + var terrainBillboardStartProperty = currentSettings.FindPropertyRelative("terrainBillboardStart"); + var terrainFadeLengthProperty = currentSettings.FindPropertyRelative("terrainFadeLength"); + var terrainMaxTreesProperty = currentSettings.FindPropertyRelative("terrainMaxTrees"); var vSyncCountProperty = currentSettings.FindPropertyRelative("vSyncCount"); + var realtimeGICPUUsageProperty = currentSettings.FindPropertyRelative("realtimeGICPUUsage"); var lodBiasProperty = currentSettings.FindPropertyRelative("lodBias"); var maximumLODLevelProperty = currentSettings.FindPropertyRelative("maximumLODLevel"); var enableLODCrossFadeProperty = currentSettings.FindPropertyRelative("enableLODCrossFade"); @@ -599,6 +716,9 @@ public override void OnInspectorGUI() EditorGUILayout.HelpBox(EditorGUIUtility.TrTextContent($"VSync Count '{vSyncCountProperty.enumLocalizedDisplayNames[vSyncCountProperty.enumValueIndex]}' is ignored on Android, iOS and tvOS.", EditorGUIUtility.GetHelpIcon(MessageType.Warning))); } + if (usingSRP) + EditorGUILayout.PropertyField(realtimeGICPUUsageProperty, Content.kRealtimeLGiCpuUsageLabel); + bool shadowMaskSupported = SupportedRenderingFeatures.IsMixedLightingModeSupported(MixedLightingMode.Shadowmask); bool showShadowMaskUsage = shadowMaskSupported && !SupportedRenderingFeatures.active.overridesShadowmask; @@ -606,15 +726,19 @@ public override void OnInspectorGUI() GUILayout.Label(EditorGUIUtility.TempContent("Textures"), EditorStyles.boldLabel); EditorGUI.BeginChangeCheck(); - EditorGUILayout.PropertyField(textureQualityProperty); + EditorGUILayout.PropertyField(globalTextureMipmapLimitProperty, Content.kGlobalTextureMipmapLimit); if (EditorGUI.EndChangeCheck() && usingSRP) { RenderPipelineManager.CleanupRenderPipeline(); } + + EditorGUILayout.Space(3); + m_TextureMipmapLimitGroupsList.DoLayoutList(); if (QualitySettings.IsTextureResReducedOnAnyPlatform()) { MipStrippingHintGUI(); } + EditorGUILayout.PropertyField(anisotropicTexturesProperty); var streamingMipmapsActiveProperty = currentSettings.FindPropertyRelative("streamingMipmapsActive"); @@ -650,6 +774,67 @@ public override void OnInspectorGUI() EditorGUILayout.PropertyField(billboardsFaceCameraPosition, Content.kBillboardsFaceCameraPos); EditorGUILayout.PropertyField(useLegacyDetailsDistribution, Content.kUseLegacyDistribution); + GUILayout.Space(5); + GUILayout.Label(EditorGUIUtility.TempContent("Terrain Setting Overrides"), EditorStyles.boldLabel); + + var originalLabelWidth = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth -= EditorStyles.toggle.CalcSize(GUIContent.none).x + EditorStyles.toggle.margin.left; + + EditorGUILayout.BeginHorizontal(); + bool pixelErrorActive = DrawOverrideToggle(ref terrainQualityOverridesProperty, TerrainQualityOverrides.PixelError, Content.kOverrideTerrainPixelError); + using (new EditorGUI.DisabledScope(!pixelErrorActive)) + EditorGUILayout.Slider(terrainPixelErrorProperty, 1.0f, 200.0f, Content.kTerrainPixelError); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + bool basemapActive = DrawOverrideToggle(ref terrainQualityOverridesProperty, TerrainQualityOverrides.BasemapDistance, Content.kOverrideTerrainBasemapDist); + using (new EditorGUI.DisabledScope(!basemapActive)) + { + EditorGUI.BeginChangeCheck(); + var newValue = EditorGUILayout.PowerSlider(Content.kTerrainBasemapDistance, Mathf.Clamp(terrainBasemapDistanceProperty.floatValue, 0.0f, 20000.0f), 0.0f, 20000.0f, 2); + if (EditorGUI.EndChangeCheck()) + terrainBasemapDistanceProperty.floatValue = newValue; + } + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + bool detailDensityActive = DrawOverrideToggle(ref terrainQualityOverridesProperty, TerrainQualityOverrides.DetailDensity, Content.kOverrideTerrainDensityScale); + using (new EditorGUI.DisabledScope(!detailDensityActive)) + EditorGUILayout.Slider(terrainDetailDensityScaleProperty, 0.0f, 1.0f, Content.kTerrainDetailDensityScale); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + bool detailDistanceActive = DrawOverrideToggle(ref terrainQualityOverridesProperty, TerrainQualityOverrides.DetailDistance, Content.kOverrideTerrainDetailDistance); + using (new EditorGUI.DisabledScope(!detailDistanceActive)) + EditorGUILayout.Slider(terrainDetailDistanceProperty, 0, 1000, Content.kTerrainDetailDistance); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + bool treeDistanceActive = DrawOverrideToggle(ref terrainQualityOverridesProperty, TerrainQualityOverrides.TreeDistance, Content.kOverrideTerrainTreeDistance); + using (new EditorGUI.DisabledScope(!treeDistanceActive)) + EditorGUILayout.Slider(terrainTreeDistanceProperty, 0.0f, 5000.0f, Content.kTerrainTreeDistance); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + bool billboardStartActive = DrawOverrideToggle(ref terrainQualityOverridesProperty, TerrainQualityOverrides.BillboardStart, Content.kOverrideTerrainBillboardStart); + using (new EditorGUI.DisabledScope(!billboardStartActive)) + EditorGUILayout.Slider(terrainBillboardStartProperty, 5, 2000, Content.kTerrainBillboardStart); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + bool fadeLengthActive = DrawOverrideToggle(ref terrainQualityOverridesProperty, TerrainQualityOverrides.FadeLength, Content.kOverrideTerrainFadeLength); + using (new EditorGUI.DisabledScope(!fadeLengthActive)) + EditorGUILayout.Slider(terrainFadeLengthProperty, 0, 200, Content.kTerrainFadeLength); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + bool maxTreesActive = DrawOverrideToggle(ref terrainQualityOverridesProperty, TerrainQualityOverrides.MaxTrees, Content.kOverrideTerrainMaxTrees); + using (new EditorGUI.DisabledScope(!maxTreesActive)) + EditorGUILayout.IntSlider(terrainMaxTreesProperty, 0, 10000, Content.kTerrainMaxTrees); + EditorGUILayout.EndHorizontal(); + + EditorGUIUtility.labelWidth = originalLabelWidth; + if (!usingSRP || showShadowMaskUsage) { GUILayout.Space(10); @@ -685,6 +870,12 @@ public override void OnInspectorGUI() asyncUploadTimeSliceProperty.intValue = Mathf.Clamp(asyncUploadTimeSliceProperty.intValue, kMinAsyncUploadTimeSlice, kMaxAsyncUploadTimeSlice); asyncUploadBufferSizeProperty.intValue = Mathf.Clamp(asyncUploadBufferSizeProperty.intValue, kMinAsyncRingBufferSize, kMaxAsyncRingBufferSize); + if (asyncUploadBufferSizeProperty.intValue >= kAsyncRingBufferSizeWarningThreshold && asyncUploadPersistentBufferProperty.boolValue) + { + string messageToDisplay = string.Format(Content.kAsyncUploadBufferSizeWarning.text, asyncUploadBufferSizeProperty.intValue, Content.kAsyncUploadPersistentBuffer.text); + EditorGUILayout.HelpBox(messageToDisplay, MessageType.Warning, false); + } + GUILayout.Space(10); GUILayout.Label(EditorGUIUtility.TempContent("Level of Detail"), EditorStyles.boldLabel); @@ -712,6 +903,548 @@ public override void OnInspectorGUI() m_QualitySettings.ApplyModifiedProperties(); } + void DrawTextureMipmapLimitGroupsHeader(Rect rect) + { + EditorGUI.PrefixLabel(rect, Content.kTextureMipmapLimitGroupsHeader); + + Event e = Event.current; + if (rect.Contains(e.mousePosition) && e.type == EventType.ContextClick) + { + GenericMenu menu = EditorGUI.FillPropertyContextMenu(m_TextureMipmapLimitGroupSettingsProperty); + + if (Presets.Preset.IsEditorTargetAPreset(target)) + { + // If we are dealing with presets, we will be able to find the "Include Property" and/or "Exclude Property" menu items. + // Our texture mipmap limit group names and group settings arrays are always separate properties entirely, which means + // that those menu items will not always function as one would expect out-of-the-box, so we apply some custom logic here. + GenericMenu.MenuItem includePropItem = menu.menuItems.Find(menu => menu.content.text == L10n.Tr("Include Property")); + GenericMenu.MenuItem excludePropItem = menu.menuItems.Find(menu => menu.content.text == L10n.Tr("Exclude Property")); + + if (m_PresetEditor is null) // Can cache the PresetEditor/preset asset since they won't change. + { + m_PresetEditor = (includePropItem?.func2?.Target ?? excludePropItem.func2.Target) as Editor; + m_QualitySettingsPreset = m_PresetEditor.target as Presets.Preset; + } + string groupSettingsArrayPropertyPath = (includePropItem?.userData ?? excludePropItem.userData) as string; + const string groupNamesArrayPropertyPath = "m_TextureMipmapLimitGroupNames"; + + // Whenever we include the group settings array of any quality level, it could be that the + // group names array is not included. (if we used "Exclude all properties", for example) + // In that case, forcefully include the group names array. (including only the settings will + // not transfer group names over and can replace the settings of unrelated groups!) + if (includePropItem is not null) + { + var includePropertyMethod = includePropItem.func2.Method; + + includePropItem.func2 = null; + includePropItem.func = () => + { + includePropertyMethod.Invoke(m_PresetEditor, new object[] { groupSettingsArrayPropertyPath }); + + if (m_QualitySettingsPreset.excludedProperties.Contains(groupNamesArrayPropertyPath)) + { + if (!m_QualitySettingsPreset.excludedProperties.Any(p => p == groupSettingsArrayPropertyPath || groupSettingsArrayPropertyPath.StartsWith(p + ".", System.StringComparison.Ordinal))) + { + // If and only if the group names array was excluded in the first place, + // and the group settings array was successfully included, then include + // the group names array too. + includePropertyMethod.Invoke(m_PresetEditor, new object[] { groupNamesArrayPropertyPath }); + } + } + }; + } + + // Depending on how many group settings arrays are still included after the + // current one gets excluded, we take different paths. + // 1 or more group settings arrays included -> group names stay included too. + // 0 group settings arrays included -> exclude group names. + if (excludePropItem is not null) + { + var excludePropertyMethod = excludePropItem.func2.Method; + + excludePropItem.func2 = null; + excludePropItem.func = () => + { + excludePropertyMethod.Invoke(m_PresetEditor, new object[] { groupSettingsArrayPropertyPath }); + + // If the group names array was included (it can be manually excluded!), + // then check if we've got all group settings arrays excluded. If that is + // the case, exclude the group names array too as described earlier. + if (!m_QualitySettingsPreset.excludedProperties.Contains(groupNamesArrayPropertyPath)) + { + bool areAllGroupSettingsExcluded = true; + int counter = 0; + while (areAllGroupSettingsExcluded && counter < m_QualitySettingsProperty.arraySize) + { + string propertyPathToCheck = $"m_QualitySettings.Array.data[{counter}].textureMipmapLimitSettings"; + if (!m_QualitySettingsPreset.excludedProperties.Any(p => p == propertyPathToCheck || propertyPathToCheck.StartsWith(p + ".", System.StringComparison.Ordinal))) + { + areAllGroupSettingsExcluded = false; + } + ++counter; + } + if (areAllGroupSettingsExcluded) + { + excludePropertyMethod.Invoke(m_PresetEditor, new object[] { groupNamesArrayPropertyPath }); + } + } + }; + } + } + + e.Use(); + menu.ShowAsContext(); + } + } + + void DrawTextureMipmapLimitGroupsElement(Rect rect, int index, bool isActive, bool isFocused) + { + rect.y += Styles.kTextureMipmapLimitGroupsOffsetTop; // Elements need to be manually centered due to the usage of Rects + Rect labelPosition = new Rect(rect.x, rect.y, EditorGUIUtility.labelWidth + Styles.kTextureMipmapLimitGroupsLabelWidthOffset, EditorGUI.lineHeight); + Rect optionsPosition = new Rect(rect.xMax - Styles.kTextureMipmapLimitGroupsOptionsWidth, rect.y, Styles.kTextureMipmapLimitGroupsOptionsWidth, rect.height); + Rect dropdownPosition = new Rect(labelPosition.xMax + Styles.kTextureMipmapLimitGroupsPaddingRight, rect.y, + rect.width - labelPosition.width - Styles.kTextureMipmapLimitGroupsPaddingRight - (optionsPosition.width + Styles.kTextureMipmapLimitGroupsPaddingRight), rect.height); + + string groupName = m_TextureMipmapLimitGroupNamesProperty.GetArrayElementAtIndex(index).stringValue; + SerializedProperty groupSettingsProp = m_TextureMipmapLimitGroupSettingsProperty.GetArrayElementAtIndex(index); + bool isOffset = groupSettingsProp.FindPropertyRelative("limitBiasMode").intValue == 0; + int mipmapLimit = groupSettingsProp.FindPropertyRelative("limitBias").intValue; + + DoTextureMipmapLimitGroupNameLabel(labelPosition, EditorGUIUtility.TempContent(groupName, L10n.Tr("Mipmap Limit Group") + $" {index}"), index, groupName); + DoTextureMipmapLimitGroupsSettingsDropdown(dropdownPosition, isOffset, mipmapLimit, index, groupName); + DoTextureMipmapLimitGroupsOptions(optionsPosition, index, groupName); + } + + void DrawTextureMipmapLimitGroupsFooter(Rect rect) + { + GUIContent toolbarPlus = ReorderableList.defaultBehaviours.iconToolbarPlus; + GUIContent toolbarMinus = ReorderableList.defaultBehaviours.iconToolbarMinus; + + // Temporarily replace tooltips + ReorderableList.defaultBehaviours.iconToolbarPlus = Content.kTextureMipmapLimitGroupsAddButton; + ReorderableList.defaultBehaviours.iconToolbarMinus = Content.kTextureMipmapLimitGroupsRemoveButton; + ReorderableList.defaultBehaviours.DrawFooter(rect, m_TextureMipmapLimitGroupsList); + ReorderableList.defaultBehaviours.iconToolbarPlus = toolbarPlus; + ReorderableList.defaultBehaviours.iconToolbarMinus = toolbarMinus; + } + + void DoTextureMipmapLimitGroupNameLabel(Rect rect, GUIContent label, int index, string groupName) + { + if (m_TextureMipmapLimitGroupBeingRenamed && index == m_TextureMipmapLimitGroupBeingRenamedIndex) + { + DoTextureMipmapLimitGroupNameTextField(rect, label); + } + else + { + GUI.Label(rect, label, EditorStyles.label); + Event e = Event.current; + if (rect.Contains(e.mousePosition) && e.type == EventType.ContextClick) + { + ShowTextureMipmapLimitGroupsContextMenu(new Rect(e.mousePosition, Vector2.zero), Vector2.zero, index, groupName); + } + } + } + + void DoTextureMipmapLimitGroupNameTextField(Rect rect, GUIContent label) + { + const string controlName = "TextFieldTextureMipmapLimitGroup"; + + GUI.SetNextControlName(controlName); + EditorGUI.DelayedTextField(rect, label.text, EditorStyles.textField); + + Event e = Event.current; + if (m_TextureMipmapLimitGroupsTextFieldNeedsFocus) + { + if (e.type == EventType.Repaint) // Wait until all other events are out of the way + { + EditorGUI.s_DelayedTextEditor.text = label.text; // Should already be the case, but just to make sure. + EditorGUI.s_DelayedTextEditor.SelectAll(); + EditorGUI.FocusTextInControl(controlName); + m_TextureMipmapLimitGroupsTextFieldNeedsFocus = false; + } + } + else + { + // If clicking out, the rename is NOT cancelled. + if (e.isMouse && !rect.Contains(e.mousePosition)) + { + EndRenamingTextureMipmapLimitGroup(EditorGUI.s_DelayedTextEditor.text); + } + // If editing stops or focus is lost, submit the current content of the text editor. + // Pressing ESC effectively cancels the rename. Pressing Enter, Tab or clicking away + // submits the user's new group name. (renaming is cancelled if the name didn't change) + else if (!EditorGUIUtility.editingTextField || GUI.GetNameOfFocusedControl() != controlName) + { + // Cannot open a dialog box until a Repaint event if the user clicked away from the QualitySettingsEditor. + if (e.type == EventType.Repaint) + { + EndRenamingTextureMipmapLimitGroup(EditorGUI.s_DelayedTextEditor.text); + GUIUtility.ExitGUI(); // Prevents layout errors when clicking on certain other windows. (Hierarchy) + } + else + GUI.InternalRepaintEditorWindow(); // Prevents missing controls when window doesn't repaint on its own. + } + } + } + + void DoTextureMipmapLimitGroupsSettingsDropdown(Rect rect, bool isOffset, int mipmapLimit, int index, string groupName) + { + const int limitValueToItemsArrayIndexOffset = 3; + GUIContent content = (isOffset ? (mipmapLimit > -4 && mipmapLimit < 4) : (mipmapLimit >= 0 && mipmapLimit < 4)) // Is limit within array bounds? + ? (isOffset ? Content.kTextureMipmapLimitGroupsOffsetModeItems[mipmapLimit + limitValueToItemsArrayIndexOffset] : Content.kTextureMipmapLimitGroupsOverrideModeItems[mipmapLimit]) + : EditorGUIUtility.TrTextContent($"{(isOffset ? "Offset" : "Override")} Global Mipmap Limit: {((mipmapLimit >= 0) ? $"+{mipmapLimit}" : $"{mipmapLimit}")}", "Custom User Setting"); + + if (EditorGUI.Button(rect, content, EditorStyles.popup)) + { + m_TextureMipmapLimitGroupsList.index = index; + + GenericMenu menu = new GenericMenu(); + for (int j = 0; j < Content.kTextureMipmapLimitGroupsOffsetModeItems.Length; ++j) + { + int limitValueFromItemsArrayIndex = j - limitValueToItemsArrayIndexOffset; + menu.AddItem(Content.kTextureMipmapLimitGroupsOffsetModeItems[j], isOffset ? mipmapLimit == limitValueFromItemsArrayIndex : false, + () => SetTextureMipmapLimitGroupSettings(index, limitValueFromItemsArrayIndex, TextureMipmapLimitBiasMode.OffsetGlobalLimit)); + } + menu.AddSeparator(string.Empty); + for (int j = 0; j < Content.kTextureMipmapLimitGroupsOverrideModeItems.Length; ++j) + { + int limit = j; + menu.AddItem(Content.kTextureMipmapLimitGroupsOverrideModeItems[j], !isOffset ? mipmapLimit == limit : false, + () => SetTextureMipmapLimitGroupSettings(index, limit, TextureMipmapLimitBiasMode.OverrideGlobalLimit)); + } + + menu.DropDown(rect); + } + } + + void DoTextureMipmapLimitGroupsOptions(Rect rect, int index, string groupName) + { + if (EditorGUI.Button(rect, Content.kTextureMipmapLimitGroupsOptions, Styles.kTextureMipmapLimitGroupsOptionsButton)) + { + m_TextureMipmapLimitGroupsList.index = index; + EndRenamingTextureMipmapLimitGroup(); + + ShowTextureMipmapLimitGroupsContextMenu(rect, Styles.kTextureMipmapLimitGroupsOptionsMenuOffset, index, groupName); + } + } + + void ShowTextureMipmapLimitGroupsContextMenu(Rect rect, Vector2 offset, int index, string groupName) + { + GenericMenu menu = new GenericMenu(); + menu.AddItem(Content.kTextureMipmapLimitGroupsOptionsIdentify, false, () => IdentifyAssetsUsingTextureMipmapLimitGroup(groupName)); + menu.AddSeparator(string.Empty); + menu.AddItem(Content.kTextureMipmapLimitGroupsOptionsDuplicate, false, () => DuplicateTextureMipmapLimitGroup(index)); + menu.AddItem(Content.kTextureMipmapLimitGroupsOptionsRename, false, () => StartRenamingTextureMipmapLimitGroup(index)); + menu.DropDown(new Rect(rect.position + offset, Vector2.zero)); + } + + void AddTextureMipmapLimitGroup(ReorderableList list) + { + int newGroupIndex = (++m_TextureMipmapLimitGroupNamesProperty.arraySize) - 1; + string newGroupName = GetNewTextureMipmapLimitGroupName(); + m_TextureMipmapLimitGroupNamesProperty.GetArrayElementAtIndex(newGroupIndex).stringValue = newGroupName; + + // For all quality levels, we need to add default settings for the new group. + for (int i = 0; i < m_QualitySettingsProperty.arraySize; ++i) + { + SerializedProperty settingsArrProp = m_QualitySettingsProperty.GetArrayElementAtIndex(i).FindPropertyRelative("textureMipmapLimitSettings"); + settingsArrProp.arraySize++; + SerializedProperty settingsProp = settingsArrProp.GetArrayElementAtIndex(newGroupIndex); + settingsProp.FindPropertyRelative("limitBias").intValue = 0; + settingsProp.FindPropertyRelative("limitBiasMode").intValue = 0; + } + + m_QualitySettings.ApplyModifiedProperties(); + StartRenamingTextureMipmapLimitGroup(newGroupIndex, true); + } + + void RemoveTextureMipmapLimitGroup(ReorderableList list) + { + int indexOfGroupToRemove = list.index; + string nameOfGroupToRemove = m_TextureMipmapLimitGroupNamesProperty.GetArrayElementAtIndex(indexOfGroupToRemove).stringValue; + + bool isRemoveOperationCancelled = false; + + if (!Presets.Preset.IsEditorTargetAPreset(target)) + { + int selection = EditorUtility.DisplayDialogComplex(Content.kTextureMipmapLimitGroupsDialogTitleOnUpdate, + string.Format(Content.kTextureMipmapLimitGroupsDialogMessageOnRemove, GetShortTextureMipmapLimitGroupName(nameOfGroupToRemove)), + L10n.Tr("No"), L10n.Tr("Cancel"), L10n.Tr("Yes")); + + switch (selection) + { + case 2: // Yes + UpdateTextureAssetsLinkedToOldTextureMipmapLimitGroup(nameOfGroupToRemove, string.Empty); + break; + + case 0: // No + break; + + case 1: // Cancel + isRemoveOperationCancelled = true; + break; + } + } + + if (!isRemoveOperationCancelled) + { + m_TextureMipmapLimitGroupNamesProperty.DeleteArrayElementAtIndex(indexOfGroupToRemove); + + // For all quality levels, we need to remove the settings of the deleted group. + for (int i = 0; i < m_QualitySettingsProperty.arraySize; ++i) + { + SerializedProperty settingsArrProp = m_QualitySettingsProperty.GetArrayElementAtIndex(i).FindPropertyRelative("textureMipmapLimitSettings"); + settingsArrProp.DeleteArrayElementAtIndex(indexOfGroupToRemove); + } + + m_QualitySettings.ApplyModifiedProperties(); + m_TextureMipmapLimitGroupsList.m_Selection = new List(); + InspectorWindow.RepaintAllInspectors(); + } + + EndRenamingTextureMipmapLimitGroup(); + } + + void DuplicateTextureMipmapLimitGroup(int indexOfGroupToDuplicate) + { + int newGroupIndex = (++m_TextureMipmapLimitGroupNamesProperty.arraySize) - 1; + string newGroupName = GetNewTextureMipmapLimitGroupName(); + m_TextureMipmapLimitGroupNamesProperty.GetArrayElementAtIndex(newGroupIndex).stringValue = newGroupName; + + // For all quality levels, we need to duplicate the settings of the other group. + for (int i = 0; i < m_QualitySettingsProperty.arraySize; ++i) + { + SerializedProperty settingsArrProp = m_QualitySettingsProperty.GetArrayElementAtIndex(i).FindPropertyRelative("textureMipmapLimitSettings"); + settingsArrProp.arraySize++; + + SerializedProperty newSettingsProp = settingsArrProp.GetArrayElementAtIndex(newGroupIndex); + SerializedProperty settingsPropToDuplicate = settingsArrProp.GetArrayElementAtIndex(indexOfGroupToDuplicate); + newSettingsProp.FindPropertyRelative("limitBias").intValue = settingsPropToDuplicate.FindPropertyRelative("limitBias").intValue; + newSettingsProp.FindPropertyRelative("limitBiasMode").intValue = settingsPropToDuplicate.FindPropertyRelative("limitBiasMode").intValue; + } + + m_QualitySettings.ApplyModifiedProperties(); + StartRenamingTextureMipmapLimitGroup(newGroupIndex, true); + } + + void UpdateTextureAssetsLinkedToOldTextureMipmapLimitGroup(string oldName, string newName) + { + // If we operate on importers while they are selected, the user will still be prompted to confirm changes + // that they already agreed to. To avoid this: reset the selection, and restore it after we're done. + Object[] originalSelection = Selection.objects; + + try + { + Selection.objects = null; + + string[] guids = AssetDatabase.FindAssets("t:texture t:preset"); + AssetDatabase.StartAssetEditing(); + + for (int i = 0; i < guids.Length; ++i) + { + string assetPath = AssetDatabase.GUIDToAssetPath(guids[i]); + Presets.Preset preset = AssetDatabase.LoadMainAssetAtPath(assetPath) as Presets.Preset; + bool isPreset = preset is not null; + AssetImporter importer = isPreset ? preset.GetReferenceObject() as AssetImporter : AssetImporter.GetAtPath(assetPath); + + if (importer is TextureImporter) + { + TextureImporter texImporter = importer as TextureImporter; + if (texImporter.textureShape == TextureImporterShape.Texture2D && texImporter.mipmapLimitGroupName == oldName) + { + texImporter.mipmapLimitGroupName = newName; + if (!isPreset) + importer.SaveAndReimport(); + else + { + preset.UpdateProperties(importer); + AssetDatabase.ImportAsset(assetPath); + } + } + } + else if (importer is IHVImageFormatImporter) + { + IHVImageFormatImporter ihvImporter = importer as IHVImageFormatImporter; + if (ihvImporter.mipmapLimitGroupName == oldName) + { + ihvImporter.mipmapLimitGroupName = newName; + if (!isPreset) + importer.SaveAndReimport(); + else + { + preset.UpdateProperties(importer); + AssetDatabase.ImportAsset(assetPath); + } + } + } + } + } + catch (System.Exception e) + { + EditorUtility.DisplayDialog(Content.kTextureMipmapLimitGroupsDialogTitleOnFailure, + string.Format(Content.kTextureMipmapLimitGroupsDialogMessageOnUpdateAssetsError, e.Message), + L10n.Tr("OK")); + } + finally + { + AssetDatabase.StopAssetEditing(); + Selection.objects = originalSelection; + } + } + + // Acts on the current quality level + void SetTextureMipmapLimitGroupSettings(int groupIndex, int mipmapLimit, TextureMipmapLimitBiasMode mode) + { + SerializedProperty settingsToModify = m_TextureMipmapLimitGroupSettingsProperty.GetArrayElementAtIndex(groupIndex); + + settingsToModify.FindPropertyRelative("limitBias").intValue = mipmapLimit; + settingsToModify.FindPropertyRelative("limitBiasMode").intValue = (int)mode; + + m_QualitySettings.ApplyModifiedProperties(); + } + + void IdentifyAssetsUsingTextureMipmapLimitGroup(string groupNameToIdentify) + { + List newSelection = new List(); + string[] guids = AssetDatabase.FindAssets("t:texture"); + + for (int i = 0; i < guids.Length; ++i) + { + TextureImporter importer = AssetImporter.GetAtPath(AssetDatabase.GUIDToAssetPath(guids[i])) as TextureImporter; + if (importer is not null && importer.textureShape == TextureImporterShape.Texture2D && importer.mipmapLimitGroupName == groupNameToIdentify) + { + newSelection.Add(AssetDatabase.LoadAssetAtPath(importer.assetPath)); + } + } + + if (newSelection.Count > 0) + { + Selection.objects = newSelection.ToArray(); + } + else + { + EditorUtility.DisplayDialog(Content.kTextureMipmapLimitGroupsDialogTitleOnFailure, + string.Format(Content.kTextureMipmapLimitGroupsDialogMessageOnIdentifyFail, GetShortTextureMipmapLimitGroupName(groupNameToIdentify)), + L10n.Tr("OK")); + } + } + + void StartRenamingTextureMipmapLimitGroup(int index, bool isNewGroup = false) + { + m_TextureMipmapLimitGroupsList.index = index; + m_TextureMipmapLimitGroupsTextFieldNeedsFocus = true; + m_TextureMipmapLimitGroupBeingRenamed = true; + m_TextureMipmapLimitGroupBeingRenamedIndex = index; + m_TextureMipmapLimitGroupsRenameShowUpdatePrompt = !Presets.Preset.IsEditorTargetAPreset(target) && !isNewGroup; + + if (isNewGroup) + { + InspectorWindow.RepaintAllInspectors(); + } + } + + void EndRenamingTextureMipmapLimitGroup(string newName = "") + { + if (!m_TextureMipmapLimitGroupBeingRenamed) + { + return; + } + m_TextureMipmapLimitGroupBeingRenamed = false; + + if (newName != string.Empty) + { + string toRename = m_TextureMipmapLimitGroupNamesProperty.GetArrayElementAtIndex(m_TextureMipmapLimitGroupBeingRenamedIndex).stringValue; + if (toRename == newName) + { + return; + } + + string shortNewName = GetShortTextureMipmapLimitGroupName(newName); + string shortToRename = GetShortTextureMipmapLimitGroupName(toRename); + + for (int i = 0; i < m_TextureMipmapLimitGroupNamesProperty.arraySize; ++i) + { + if (m_TextureMipmapLimitGroupNamesProperty.GetArrayElementAtIndex(i).stringValue == newName) + { + EditorUtility.DisplayDialog(Content.kTextureMipmapLimitGroupsDialogTitleOnFailure, + string.Format(Content.kTextureMipmapLimitGroupsDialogMessageOnRenameFail, shortNewName, shortToRename), + L10n.Tr("OK")); + return; + } + } + + bool applyModifiedProperties = true; + if (m_TextureMipmapLimitGroupsRenameShowUpdatePrompt) + { + int selection = EditorUtility.DisplayDialogComplex(Content.kTextureMipmapLimitGroupsDialogTitleOnUpdate, + string.Format(Content.kTextureMipmapLimitGroupsDialogMessageOnRename, shortToRename, shortNewName), + L10n.Tr("No"), L10n.Tr("Cancel"), L10n.Tr("Yes")); + + switch (selection) + { + case 2: // Yes + UpdateTextureAssetsLinkedToOldTextureMipmapLimitGroup(toRename, newName); + break; + + case 0: // No + break; + + case 1: // Cancel + applyModifiedProperties = false; + break; + } + } + + if (applyModifiedProperties) + { + m_TextureMipmapLimitGroupNamesProperty.GetArrayElementAtIndex(m_TextureMipmapLimitGroupBeingRenamedIndex).stringValue = newName; + m_QualitySettings.ApplyModifiedProperties(); + InspectorWindow.RepaintAllInspectors(); + } + } + + m_TextureMipmapLimitGroupBeingRenamedIndex = -1; + GUI.FocusControl(string.Empty); + } + + string GetNewTextureMipmapLimitGroupName() + { + string newName = L10n.Tr("New Group"); + string[] existingNames = GetAllKnownTextureMipmapLimitGroupNames(); + + int counter = 0; + while (existingNames.Any(existingName => existingName == newName)) + { + newName = L10n.Tr("New Group") + string.Format(" ({0})", ++counter); + } + + return newName; + } + + string[] GetAllKnownTextureMipmapLimitGroupNames() + { + string[] existingNames = new string[m_TextureMipmapLimitGroupNamesProperty.arraySize]; + for (int i = 0; i < m_TextureMipmapLimitGroupNamesProperty.arraySize; ++i) + { + existingNames[i] = m_TextureMipmapLimitGroupNamesProperty.GetArrayElementAtIndex(i).stringValue; + } + return existingNames; + } + + // Only meant to limit the length of various messages addressed to the user that concern mipmap limit groups. + // Don't use elsewhere. + string GetShortTextureMipmapLimitGroupName(string groupName) + { + const int maxGroupNameLength = 41; + // ^ Cannot fit more than this many characters on 1 line. + if (groupName.Length > maxGroupNameLength) + { + const string suffix = " (...)"; + groupName = groupName.Substring(0, maxGroupNameLength - suffix.Length) + suffix; + } + return groupName; + } + [SettingsProvider] internal static SettingsProvider CreateProjectSettingsProvider() { diff --git a/Editor/Mono/Inspector/RectTransformEditor.cs b/Editor/Mono/Inspector/RectTransformEditor.cs index fad08c5a5c..0e81c1792c 100644 --- a/Editor/Mono/Inspector/RectTransformEditor.cs +++ b/Editor/Mono/Inspector/RectTransformEditor.cs @@ -630,7 +630,7 @@ void FloatFieldLabelAbove(Rect position, FloatGetter getter, FloatSetter setter, Rect positionLabel = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); Rect positionField = new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight, position.width, EditorGUIUtility.singleLineHeight); EditorGUI.HandlePrefixLabel(position, positionLabel, label, id); - float newValue = EditorGUI.DoFloatField(EditorGUI.s_RecycledEditor, positionField, positionLabel, id, value, EditorGUI.kFloatFieldFormatString, EditorStyles.textField, true); + float newValue = EditorGUI.DoFloatField(EditorGUI.s_DelayedTextEditor, positionField, positionLabel, id, value, EditorGUI.kFloatFieldFormatString, EditorStyles.textField, true); if (EditorGUI.EndChangeCheck()) { Undo.RecordObjects(targets, "Modified RectTransform Values"); @@ -651,7 +651,8 @@ void Vector2Field(Rect position, SerializedProperty xProperty = vec2Property.FindPropertyRelative("x"); SerializedProperty yProperty = vec2Property.FindPropertyRelative("y"); - EditorGUI.PrefixLabel(position, -1, label); + int id = GUIUtility.GetControlID(label.GetHashCode(), FocusType.Keyboard, position); + EditorGUI.PrefixLabel(position, id, label); float t = EditorGUIUtility.labelWidth; int l = EditorGUI.indentLevel; Rect r0 = GetColumnRect(position, 0); diff --git a/Editor/Mono/Inspector/ReflectionProbeEditor.cs b/Editor/Mono/Inspector/ReflectionProbeEditor.cs index a9eb5df846..077323794e 100644 --- a/Editor/Mono/Inspector/ReflectionProbeEditor.cs +++ b/Editor/Mono/Inspector/ReflectionProbeEditor.cs @@ -692,7 +692,7 @@ private void OnPreSceneGUICallback(SceneView sceneView) if (!reflectiveMaterial) return; - if (!StageUtility.IsGameObjectRenderedByCameraAndPartOfEditableScene(p.gameObject, Camera.current)) + if (StageUtility.IsGizmoCulledBySceneCullingMasksOrFocusedScene(p.gameObject, Camera.current)) return; Matrix4x4 m = new Matrix4x4(); diff --git a/Editor/Mono/Inspector/RenderPipelineAssetSelector.cs b/Editor/Mono/Inspector/RenderPipelineAssetSelector.cs index daa12c7af6..dca6915221 100644 --- a/Editor/Mono/Inspector/RenderPipelineAssetSelector.cs +++ b/Editor/Mono/Inspector/RenderPipelineAssetSelector.cs @@ -50,6 +50,9 @@ internal static void RenderPipelineAssetField(GUIContent content, SerializedObje } } + private static bool s_ObjectSelectorClosed = false; + private static Object s_LastPickedObject = null; + /// /// Draws the object field and, if the user attempts to change the value, asks the user for confirmation. /// @@ -77,11 +80,17 @@ internal static void RenderPipelineAssetField(SerializedObject serializedObject, style: EditorStyles.objectField, onObjectSelectorClosed: obj => { - if (!ObjectSelector.SelectionCanceled()) - PromptConfirmation(serializedObject, serializedProperty, obj); + if (ObjectSelector.SelectionCanceled()) return; + s_ObjectSelectorClosed = true; + s_LastPickedObject = obj; }); - if (!ObjectSelector.isVisible) + if (s_ObjectSelectorClosed) + { + s_ObjectSelectorClosed = false; + PromptConfirmation(serializedObject, serializedProperty, s_LastPickedObject); + } + else if (!ObjectSelector.isVisible) // Drag and drop { PromptConfirmation(serializedObject, serializedProperty, selectedRenderPipelineAsset); } diff --git a/Editor/Mono/Inspector/RenderTextureEditor.cs b/Editor/Mono/Inspector/RenderTextureEditor.cs index db07e6b22d..d539d32dbf 100644 --- a/Editor/Mono/Inspector/RenderTextureEditor.cs +++ b/Editor/Mono/Inspector/RenderTextureEditor.cs @@ -27,6 +27,7 @@ private class Styles public readonly GUIContent enableMipmaps = EditorGUIUtility.TrTextContent("Enable Mip Maps", "This render texture will have Mip Maps."); public readonly GUIContent autoGeneratesMipmaps = EditorGUIUtility.TrTextContent("Auto generate Mip Maps", "This render texture automatically generates its Mip Maps."); public readonly GUIContent useDynamicScale = EditorGUIUtility.TrTextContent("Dynamic Scaling", "Allow the texture to be automatically resized by ScalableBufferManager, to support dynamic resolution."); + public readonly GUIContent enableRandomWrite = EditorGUIUtility.TrTextContent("Random Write", "Enable/disable random access write into the color buffer of this render texture."); public readonly GUIContent shadowSamplingMode = EditorGUIUtility.TrTextContent("Shadow Sampling Mode", "Enable/disable shadow depth-compare sampling and percentage closer filtering."); public readonly GUIContent[] renderTextureAntiAliasing = @@ -68,6 +69,7 @@ protected enum GUIElements SerializedProperty m_Dimension; SerializedProperty m_sRGB; SerializedProperty m_UseDynamicScale; + SerializedProperty m_EnableRandomWrite; SerializedProperty m_ShadowSamplingMode; protected override void OnEnable() @@ -85,6 +87,7 @@ protected override void OnEnable() m_Dimension = serializedObject.FindProperty("m_Dimension"); m_sRGB = serializedObject.FindProperty("m_SRGB"); m_UseDynamicScale = serializedObject.FindProperty("m_UseDynamicScale"); + m_EnableRandomWrite = serializedObject.FindProperty("m_EnableRandomWrite"); m_ShadowSamplingMode = serializedObject.FindProperty("m_ShadowSamplingMode"); Undo.undoRedoEvent += OnUndoRedoPerformed; @@ -220,6 +223,7 @@ protected void OnRenderTextureGUI(GUIElements guiElements) } EditorGUILayout.PropertyField(m_UseDynamicScale, styles.useDynamicScale); + EditorGUILayout.PropertyField(m_EnableRandomWrite, styles.enableRandomWrite); var rt = target as RenderTexture; if (GUI.changed && rt != null) diff --git a/Editor/Mono/Inspector/RendererLightingSettings.cs b/Editor/Mono/Inspector/RendererLightingSettings.cs index 849e6fe48e..4a0cb1a861 100644 --- a/Editor/Mono/Inspector/RendererLightingSettings.cs +++ b/Editor/Mono/Inspector/RendererLightingSettings.cs @@ -323,9 +323,6 @@ public void RenderSettings(bool showLightmapSettings) ShowAtlasGUI(m_Renderers[0].GetInstanceID(), true); ShowRealtimeLMGUI(m_Renderers[0]); - if (Lightmapping.HasZeroAreaMesh(m_Renderers[0])) - EditorGUILayout.HelpBox(Styles.zeroAreaPackingMesh.text, MessageType.Warning); - DisplayMeshWarning(); if (showEnlightenSettings) @@ -379,6 +376,7 @@ public void RenderTerrainSettings() { EditorGUILayout.HelpBox(Styles.giNotEnabledInfo.text, MessageType.Info); EditorGUI.indentLevel -= 1; + EditorGUILayout.EndFoldoutHeaderGroup(); return; } @@ -814,14 +812,6 @@ private void DisplayMeshWarning() EditorGUILayout.HelpBox(Styles.noNormalsNoLightmapping.text, MessageType.Warning); return; } - - if (showEnlightenSettings) - { - if (Lightmapping.HasZeroAreaMesh(m_Renderers[0])) - { - EditorGUILayout.HelpBox(Styles.zeroAreaPackingMesh.text, MessageType.Warning); - } - } } static Mesh GetSharedMesh(Renderer renderer) diff --git a/Editor/Mono/Inspector/ReorderableListWrapper.cs b/Editor/Mono/Inspector/ReorderableListWrapper.cs index b2a5dd1139..bbd241016d 100644 --- a/Editor/Mono/Inspector/ReorderableListWrapper.cs +++ b/Editor/Mono/Inspector/ReorderableListWrapper.cs @@ -24,10 +24,13 @@ public static class Constants internal ReorderableList m_ReorderableList; float m_HeaderHeight; bool m_Reorderable = false; - bool m_IsNotInPrefabContextModeWithOverrides = false; + bool m_ListIsPatchedInPrefabModeInContext = false; + bool m_DisableListElements = false; SerializedProperty m_OriginalProperty; SerializedProperty m_ArraySize; + string m_PropertyPath = string.Empty; + string m_PropertyPathArraySize = string.Empty; internal static Rect s_ToolTipRect; @@ -41,16 +44,26 @@ internal SerializedProperty Property set { m_OriginalProperty = value; - if (!m_OriginalProperty.isValid) return; + if (!m_OriginalProperty.isValid) + { + m_ArraySize = null; + m_PropertyPath = string.Empty; + m_PropertyPathArraySize = string.Empty; + return; + } m_ArraySize = m_OriginalProperty.FindPropertyRelative("Array.size"); + m_PropertyPath = m_OriginalProperty.propertyPath; + m_PropertyPathArraySize = m_OriginalProperty + ".Array.size"; if (m_ReorderableList != null) { bool versionChanged = !SerializedProperty.VersionEquals(m_ReorderableList.serializedProperty, m_OriginalProperty); + bool serializedObjectChanged = m_ReorderableList.serializedProperty.serializedObject.m_NativeObjectPtr != m_OriginalProperty.serializedObject.m_NativeObjectPtr; m_ReorderableList.serializedProperty = m_OriginalProperty; + UpdatePrefabPatchState(m_OriginalProperty.serializedObject.targetObject); - if (versionChanged || m_ArraySize != null && m_LastArraySize != m_ArraySize.intValue) + if (versionChanged || serializedObjectChanged || m_ArraySize != null && m_LastArraySize != m_ArraySize.intValue) { m_ReorderableList.InvalidateCacheRecursive(); ReorderableList.InvalidateParentCaches(m_ReorderableList.serializedProperty.propertyPath); @@ -78,18 +91,16 @@ public static string GetPropertyIdentifier(SerializedProperty serializedProperty public ReorderableListWrapper(SerializedProperty property, GUIContent label, bool reorderable = true) { - Property = property; - m_HeaderHeight = Constants.kDefaultFoldoutHeaderHeight; - Init(reorderable); + Init(reorderable, property); } - void Init(bool reorderable) + void Init(bool reorderable, SerializedProperty property) { m_Reorderable = reorderable; - SerializedProperty childProperty = Property.Copy(); + SerializedProperty childProperty = property.Copy(); childProperty.Next(true); - m_ReorderableList = new ReorderableList(Property.serializedObject, Property.Copy(), m_Reorderable, false, true, true); + m_ReorderableList = new ReorderableList(property.serializedObject, property.Copy(), m_Reorderable, false, true, true); m_ReorderableList.headerHeight = ReorderableList.Defaults.minHeaderHeight; m_ReorderableList.m_IsEditable = true; m_ReorderableList.multiSelect = true; @@ -98,13 +109,16 @@ void Init(bool reorderable) m_ReorderableList.onCanAddCallback += (list) => { - return m_IsNotInPrefabContextModeWithOverrides; + return !m_ListIsPatchedInPrefabModeInContext; }; m_ReorderableList.onCanRemoveCallback += (list) => { - return m_IsNotInPrefabContextModeWithOverrides; + return !m_ListIsPatchedInPrefabModeInContext; }; + + Property = property; + m_HeaderHeight = Constants.kDefaultFoldoutHeaderHeight; } internal void InvalidateCache() => m_ReorderableList.InvalidateCache(); @@ -114,13 +128,28 @@ public float GetHeight() return m_HeaderHeight + (Property.isExpanded && m_ReorderableList != null ? Constants.kHeaderPadding + m_ReorderableList.GetHeight() : 0.0f); } + void UpdatePrefabPatchState(Object serializedObjectTarget) + { + m_DisableListElements = false; + m_ListIsPatchedInPrefabModeInContext = false; + + var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); + if (prefabStage != null) + { + m_ListIsPatchedInPrefabModeInContext = prefabStage.HasPatchedPropertyModificationsFor(serializedObjectTarget, m_PropertyPath); + if (m_ListIsPatchedInPrefabModeInContext) + { + m_DisableListElements = prefabStage.HasPatchedPropertyModificationsFor(serializedObjectTarget, m_PropertyPathArraySize); + } + } + + if (m_ReorderableList != null) + m_ReorderableList.draggable = m_Reorderable && !m_ListIsPatchedInPrefabModeInContext; + } + public void Draw(GUIContent label, Rect r, Rect visibleArea, string tooltip, bool includeChildren) { r.xMin += EditorGUI.indent; - var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); - m_IsNotInPrefabContextModeWithOverrides = prefabStage == null || prefabStage.mode != PrefabStage.Mode.InContext || !PrefabStage.s_PatchAllOverriddenProperties - || Selection.objects.All(obj => PrefabUtility.IsPartOfAnyPrefab(obj) && !AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(obj)).Equals(AssetDatabase.AssetPathToGUID(prefabStage.assetPath))); - m_ReorderableList.draggable = m_Reorderable && m_IsNotInPrefabContextModeWithOverrides; Rect headerRect = new Rect(r.x, r.y, r.width, m_HeaderHeight); Rect sizeRect = new Rect(headerRect.xMax - Constants.kArraySizeWidth - EditorGUI.indent * EditorGUI.indentLevel, headerRect.y, @@ -165,7 +194,11 @@ public void Draw(GUIContent label, Rect r, Rect visibleArea, string tooltip, boo m_ReorderableList.InvalidateCacheRecursive(); } + if (m_DisableListElements) + GUI.enabled = false; + DrawChildren(r, headerRect, sizeRect, visibleArea, prevType); + GUI.enabled = prevEnabled; } void DrawChildren(Rect listRect, Rect headerRect, Rect sizeRect, Rect visibleRect, EventType previousEvent) diff --git a/Editor/Mono/Inspector/ScriptExecutionOrderInspector.cs b/Editor/Mono/Inspector/ScriptExecutionOrderInspector.cs index 51a99df4f3..e036f85f60 100644 --- a/Editor/Mono/Inspector/ScriptExecutionOrderInspector.cs +++ b/Editor/Mono/Inspector/ScriptExecutionOrderInspector.cs @@ -123,7 +123,7 @@ public static class Styles }; } - [MenuItem("CONTEXT/MonoManager/Reset")] + [MenuItem("CONTEXT/MonoManager/Reset", secondaryPriority = 14)] private static void Reset(MenuCommand cmd) { var instances = ScriptExecutionOrderInspector.GetInstances(); diff --git a/Editor/Mono/Inspector/SpriteFrameInspector.cs b/Editor/Mono/Inspector/SpriteFrameInspector.cs index b27603b0a9..f8ab7db318 100644 --- a/Editor/Mono/Inspector/SpriteFrameInspector.cs +++ b/Editor/Mono/Inspector/SpriteFrameInspector.cs @@ -115,14 +115,19 @@ public static Texture2D BuildPreviewTexture(Sprite sprite, Material spriteRender if (!isPolygon) { // Try to have a minimum of 64 pixels for width and height, unless requested width and height is smaller - var minWidth = Mathf.Min(64, width); - var minHeight = Mathf.Min(64, height); + var minWidth = Mathf.Min(64f, width); + var minHeight = Mathf.Min(64f, height); PreviewHelpers.AdjustWidthAndHeightForStaticPreview((int) spriteWidth, (int) spriteHeight, ref width, ref height); - // Set minimum size for width and height to prevent small previews for small sprites - width = Mathf.Max(minWidth, width); - height = Mathf.Max(minHeight, height); + // Set minimum size for width/height to prevent small previews for small sprites + if (width < minWidth && height < minHeight) + { + var ratio = Mathf.Min( minWidth / width, minHeight / height); + ratio = Mathf.CeilToInt(ratio); + width = Mathf.FloorToInt(width * ratio); + height = Mathf.FloorToInt(height * ratio); + } } SavedRenderTargetState savedRTState = new SavedRenderTargetState(); diff --git a/Editor/Mono/Inspector/SpriteRendererEditor.cs b/Editor/Mono/Inspector/SpriteRendererEditor.cs index d0e6c680cb..9d63a89e57 100644 --- a/Editor/Mono/Inspector/SpriteRendererEditor.cs +++ b/Editor/Mono/Inspector/SpriteRendererEditor.cs @@ -253,48 +253,48 @@ void FlipToggle(Rect r, GUIContent label, SerializedProperty property) private void ShowMaterialError() { - if (IsMaterialTextureAtlasConflict()) - ShowError("Material has CanUseSpriteAtlas=False tag. Sprite texture has atlasHint set. Rendering artifacts possible."); + bool materialHasMainTex = DoesMaterialHaveSpriteTexture("_MainTex"); + bool materialHasBaseMap = DoesMaterialHaveSpriteTexture("_BaseMap"); - bool isTextureTiled; - if (!DoesMaterialHaveSpriteTexture(out isTextureTiled)) - ShowError("Material does not have a _MainTex texture property. It is required for SpriteRenderer."); + if (!materialHasMainTex && !materialHasBaseMap) + { + ShowWarning("Material does not have a _MainTex or _BaseMap texture property. Having one of them is required for SpriteRenderer."); + } else { - if (isTextureTiled) - ShowError("Material texture property _MainTex has offset/scale set. It is incompatible with SpriteRenderer."); + if(materialHasMainTex) + CheckPropertyForScaleAndOffset("_MainTex"); + else if(materialHasBaseMap) + CheckPropertyForScaleAndOffset("_BaseMap"); } } - private bool IsMaterialTextureAtlasConflict() + private void CheckPropertyForScaleAndOffset(string propertyName) { - return false; + Material material = (target as SpriteRenderer).sharedMaterial; + if (material != null) + { + Vector2 offset = material.GetTextureOffset(propertyName); + Vector2 scale = material.GetTextureScale(propertyName); + if (offset.x != 0 || offset.y != 0 || scale.x != 1 || scale.y != 1) + { + ShowWarning("Material texture property " + propertyName + " has offset/scale set. It is incompatible with SpriteRenderer."); + } + } } - private bool DoesMaterialHaveSpriteTexture(out bool tiled) + private bool DoesMaterialHaveSpriteTexture(string propertyName) { - tiled = false; - Material material = (target as SpriteRenderer).sharedMaterial; if (material == null) return true; - - bool has = material.HasProperty("_MainTex"); - if (has) - { - Vector2 offset = material.GetTextureOffset("_MainTex"); - Vector2 scale = material.GetTextureScale("_MainTex"); - if (offset.x != 0 || offset.y != 0 || scale.x != 1 || scale.y != 1) - tiled = true; - } - - return material.HasProperty("_MainTex"); + return material.HasProperty(propertyName); } - private static void ShowError(string error) + private static void ShowWarning(string message) { - var c = new GUIContent(error) {image = Styles.warningIcon}; + var c = new GUIContent(message) {image = Styles.warningIcon}; GUILayout.Space(5); GUILayout.BeginVertical(EditorStyles.helpBox); diff --git a/Editor/Mono/Inspector/TagManagerInspector.cs b/Editor/Mono/Inspector/TagManagerInspector.cs index 1ac0eb1f36..0dc69f7ca6 100644 --- a/Editor/Mono/Inspector/TagManagerInspector.cs +++ b/Editor/Mono/Inspector/TagManagerInspector.cs @@ -151,7 +151,8 @@ public override void OnGUI(Rect windowRect) } GUI.enabled = m_NewTagName.Length != 0; - if (GUILayout.Button("Save") || hitEnter) + var savePressed = GUILayout.Button("Save"); + if (!string.IsNullOrWhiteSpace(m_NewTagName) && (savePressed || hitEnter)) { EnterCB(m_NewTagName); editorWindow.Close(); @@ -240,20 +241,35 @@ void AddToSortLayerList(ReorderableList list) list.serializedProperty.GetArrayElementAtIndex(list.index).FindPropertyRelative("name").stringValue = "New Layer"; list.serializedProperty.serializedObject.ApplyModifiedProperties(); } + + if (SortingLayer.onLayerAdded != null) + SortingLayer.onLayerAdded(SortingLayer.layers[list.index]); + + if (SortingLayer.onLayerChanged != null) + SortingLayer.onLayerChanged(); } public void ReorderSortLayerList(ReorderableList list) { serializedObject.ApplyModifiedProperties(); tagManager.UpdateSortingLayersOrder(); + + if (SortingLayer.onLayerChanged != null) + SortingLayer.onLayerChanged(); } private void RemoveFromSortLayerList(ReorderableList list) { + if (SortingLayer.onLayerRemoved != null) + SortingLayer.onLayerRemoved(SortingLayer.layers[list.index]); + ReorderableList.defaultBehaviours.DoRemoveButton(list); serializedObject.ApplyModifiedProperties(); serializedObject.Update(); tagManager.UpdateSortingLayersOrder(); + + if (SortingLayer.onLayerChanged != null) + SortingLayer.onLayerChanged(); } private bool CanEditSortLayerEntry(int index) diff --git a/Editor/Mono/Inspector/Texture2DArrayInspector.cs b/Editor/Mono/Inspector/Texture2DArrayInspector.cs index eb45552ba9..becde6411c 100644 --- a/Editor/Mono/Inspector/Texture2DArrayInspector.cs +++ b/Editor/Mono/Inspector/Texture2DArrayInspector.cs @@ -15,7 +15,7 @@ class Texture2DArrayInspector : TextureInspector public override string GetInfoString() { var tex = (Texture2DArray)target; - var info = $"{tex.width}x{tex.height} {tex.depth} slice{(tex.depth != 1 ? "s" : "")} {GraphicsFormatUtility.GetFormatString(tex.format)} {EditorUtility.FormatBytes(TextureUtil.GetRuntimeMemorySizeLong(tex))}"; + var info = $"{tex.width}x{tex.height} {tex.depth} slice{(tex.depth != 1 ? "s" : "")} {GraphicsFormatUtility.GetFormatString(tex.format)} {EditorUtility.FormatBytes(TextureUtil.GetStorageMemorySizeLong(tex))}"; return info; } diff --git a/Editor/Mono/Inspector/Texture3DPreview.cs b/Editor/Mono/Inspector/Texture3DPreview.cs index e9a4c7d0b9..dc1d425b59 100644 --- a/Editor/Mono/Inspector/Texture3DPreview.cs +++ b/Editor/Mono/Inspector/Texture3DPreview.cs @@ -97,6 +97,7 @@ static class Styles const float s_MinViewDistance = 0f; const float s_MaxViewDistance = 5.0f; static readonly Vector2 s_InitialRotation = new Vector2(15, 30); + const float s_MaxPreviewPixelCount = 512 * 512 * 512; PreviewRenderUtility m_PreviewUtility; Preview3DMode m_Preview3DMode; @@ -187,7 +188,7 @@ public string GetInfoString() Vector3 resolution = GetTextureResolution(Texture); var format = GraphicsFormatUtility.GetFormatString(Texture.graphicsFormat); - var size = EditorUtility.FormatBytes(TextureUtil.GetRuntimeMemorySizeLong(Texture)); + var size = EditorUtility.FormatBytes(TextureUtil.GetStorageMemorySizeLong(Texture)); string info = $"{resolution.x}x{resolution.y}x{resolution.z} {format} {size}"; return info; } @@ -438,6 +439,16 @@ void DrawPreview() m_PreviewUtility.Render(); } + bool IsPreviewExpensiveToDisplay() + { + if (m_Preview3DMode == Preview3DMode.Volume || m_Preview3DMode == Preview3DMode.SDF) + { + Vector3 res = GetTextureResolution(Texture); + return res.x * res.y * res.z > s_MaxPreviewPixelCount; + } + return false; + } + public void OnPreviewGUI(Rect r, GUIStyle background) { if (!ShaderUtil.hardwareSupportsRectRenderTexture || !SystemInfo.supports3DTextures) @@ -453,6 +464,12 @@ public void OnPreviewGUI(Rect r, GUIStyle background) EditorGUI.DropShadowLabel(new Rect(r.x, r.y, r.width, 40), "Compressed 3D texture preview is not supported"); return; } + if (IsPreviewExpensiveToDisplay()) + { + if (Event.current.type == EventType.Repaint) + EditorGUI.DropShadowLabel(new Rect(r.x, r.y, r.width, 40), "Preview disabled (3D texture too large to display)"); + return; + } InitPreviewUtility(); Event e = Event.current; @@ -485,6 +502,9 @@ public Texture2D RenderStaticPreview(Texture texture, int width, int height) OnEnable(); m_QualityModifier *= 2; + if (IsPreviewExpensiveToDisplay()) + return null; + Rect r = new Rect(0, 0, width, height); m_PreviewUtility.BeginStaticPreview(r); DrawPreview(); diff --git a/Editor/Mono/Inspector/TextureInspector.cs b/Editor/Mono/Inspector/TextureInspector.cs index 27be8f1962..c66825e9d1 100644 --- a/Editor/Mono/Inspector/TextureInspector.cs +++ b/Editor/Mono/Inspector/TextureInspector.cs @@ -30,8 +30,8 @@ internal static void AdjustWidthAndHeightForStaticPreview(int textureWidth, int { // For textures larger than our wanted width and height we ensure to // keep aspect ratio of the texture and fit it to best match our wanted width and height. - float relWidth = height / (float)textureWidth; - float relHeight = width / (float)textureHeight; + float relWidth = width / (float)textureWidth; + float relHeight = height / (float)textureHeight; float scale = Mathf.Min(relHeight, relWidth); @@ -69,7 +69,7 @@ internal class TextureInspector : Editor class Styles { public GUIContent smallZoom, largeZoom; - public GUIStyle toolbarButton, previewSlider, previewSliderThumb, previewLabel; + public GUIStyle toolbarButton, previewSlider, previewSliderThumb, previewLabel, mipLevelLabel; public readonly GUIContent[] previewButtonContents = { @@ -111,6 +111,10 @@ public Styles() previewSlider = "preSlider"; previewSliderThumb = "preSliderThumb"; previewLabel = "toolbarLabel"; + + mipLevelLabel = "PreOverlayLabel"; + mipLevelLabel.alignment = TextAnchor.UpperCenter; + mipLevelLabel.padding.top = 5; } } static Styles s_Styles; @@ -161,18 +165,28 @@ public static bool IsNormalMap(Texture t) } protected virtual void OnEnable() + { + Initialize(); + } + + void CacheSerializedProperties() { m_WrapU = serializedObject.FindProperty("m_TextureSettings.m_WrapU"); m_WrapV = serializedObject.FindProperty("m_TextureSettings.m_WrapV"); m_WrapW = serializedObject.FindProperty("m_TextureSettings.m_WrapW"); m_FilterMode = serializedObject.FindProperty("m_TextureSettings.m_FilterMode"); m_Aniso = serializedObject.FindProperty("m_TextureSettings.m_Aniso"); + } + void Initialize() + { + CacheSerializedProperties(); RecordTextureMipLevels(); - SetMipLevelDefaultForVT(); - m_Texture3DPreview = CreateInstance(); + if(m_Texture3DPreview == null) + m_Texture3DPreview = CreateInstance(); + if (IsTexture3D()) { m_Texture3DPreview.Texture = target as Texture; @@ -233,7 +247,10 @@ protected virtual void OnDisable() { RestoreLastTextureMipLevels(); + m_TextureMipLevels.Clear(); + m_CubemapPreview.OnDisable(); + m_Texture3DPreview.OnDisable(); DestroyImmediate(m_Texture3DPreview); } @@ -283,6 +300,15 @@ public float GetMipLevelForRendering() return Mathf.Min(m_MipLevel, TextureUtil.GetMipmapCount(target as Texture) - 1); } + public int GetMipmapLimit(Texture t) + { + if (t is Texture2D) + { + return (t as Texture2D).activeMipmapLimit; + } + return 0; + } + public float mipLevel { get @@ -323,6 +349,13 @@ public override void OnInspectorGUI() ApplySettingsToTextures(); } + internal override void PostSerializedObjectCreation() + { + base.PostSerializedObjectCreation(); + + Initialize(); + } + // wrap/filter/aniso editors will change serialized object // but in case of textures we need an extra step to ApplySettings (so rendering uses new values) // alas we cant have good things: it will be PITA to make sure we always call that after applying changes to serialized object @@ -680,9 +713,17 @@ public override void OnPreviewSettings() if (mipCount > 1) { + int mipmapLimit = GetMipmapLimit(target as Texture); GUILayout.Box(s_Styles.smallZoom, s_Styles.previewLabel); GUI.changed = false; - m_MipLevel = Mathf.Round(GUILayout.HorizontalSlider(m_MipLevel, mipCount - 1, 0, s_Styles.previewSlider, s_Styles.previewSliderThumb, GUILayout.MaxWidth(64))); + + int leftValue = mipCount - mipmapLimit - 1; + if (m_MipLevel > leftValue) + { + // Left value can change depending on the mipmap limit. Cap slider value appropriately. + m_MipLevel = leftValue; + } + m_MipLevel = Mathf.Round(GUILayout.HorizontalSlider(m_MipLevel, leftValue, 0, s_Styles.previewSlider, s_Styles.previewSliderThumb, GUILayout.MaxWidth(64))); //For now, we don't have mipmaps smaller than the tile size when using VT. if (EditorGUI.UseVTMaterial(tex)) @@ -847,9 +888,20 @@ public override void OnPreviewGUI(Rect r, GUIStyle background) TextureUtil.SetFilterModeNoDirty(t, oldFilter); + int mipmapLimit = GetMipmapLimit(target as Texture); + int cpuMipLevel = Mathf.Min(TextureUtil.GetMipmapCount(target as Texture) - 1, (int)mipLevel + mipmapLimit); m_Pos = PreviewGUI.EndScrollView(); - if (mipLevel != 0) - EditorGUI.DropShadowLabel(new Rect(r.x, r.y, r.width, 20), "Mip " + mipLevel); + if (cpuMipLevel != 0) + { + GUIContent mipLevelTextContent = new GUIContent((cpuMipLevel != mipLevel) + ? string.Format("Mip {0}\nMip {1} on GPU (Texture Limit)", cpuMipLevel, mipLevel) + : string.Format("Mip {0}", mipLevel)); + Vector2 size = s_Styles.mipLevelLabel.CalcSize(mipLevelTextContent); + if (size.x <= r.width) + { + EditorGUI.DropShadowLabel(new Rect(r.x, r.y, r.width, size.y), mipLevelTextContent, s_Styles.mipLevelLabel); + } + } } private void DrawRect(Rect rect) diff --git a/Editor/Mono/Inspector/TrailRendererEditor.cs b/Editor/Mono/Inspector/TrailRendererEditor.cs index ff3d7b5ffc..9023f644a4 100644 --- a/Editor/Mono/Inspector/TrailRendererEditor.cs +++ b/Editor/Mono/Inspector/TrailRendererEditor.cs @@ -309,9 +309,9 @@ private void OnSceneViewGUI(SceneView sceneView) foreach (var obj in targets) { - if (obj is TrailRenderer tr) + if (obj is TrailRenderer trail) { - var worldBounds = tr.bounds; + var worldBounds = trail.bounds; Handles.DrawWireCube(worldBounds.center, worldBounds.size); } } @@ -320,9 +320,9 @@ private void OnSceneViewGUI(SceneView sceneView) } // Move trail using shape - if (m_PreviewBackupPosition.HasValue) + if (target is TrailRenderer tr) { - if (target is TrailRenderer tr) + if (m_PreviewBackupPosition.HasValue) { if (s_PreviewIsPlaying && !s_PreviewIsPaused) { @@ -388,6 +388,10 @@ private void OnSceneViewGUI(SceneView sceneView) tr.previewTimeScale = 0.0f; } } + else + { + tr.previewTimeScale = s_PreviewTimeScale; // Not playing or paused, but might be dragging the Trail around the scene view using the Transform gizmo + } } } diff --git a/Editor/Mono/Inspector/TransformRotationGUI.cs b/Editor/Mono/Inspector/TransformRotationGUI.cs index 18bd2d534b..dfa51ba56d 100644 --- a/Editor/Mono/Inspector/TransformRotationGUI.cs +++ b/Editor/Mono/Inspector/TransformRotationGUI.cs @@ -10,6 +10,7 @@ namespace UnityEditor internal class TransformRotationGUI { private GUIContent rotationContent = EditorGUIUtility.TrTextContent("Rotation", "The local rotation of this Game Object relative to the parent."); + private const float kQuaternionFloatPrecision = 1e-6f; EditorGUI.NumberFieldValue[] m_EulerFloats = { @@ -61,9 +62,20 @@ public void RotationField(bool disabled) Rect r = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight * (EditorGUIUtility.wideMode ? 1 : 2)); GUIContent label = EditorGUI.BeginProperty(r, rotationContent, m_Rotation); - m_EulerFloats[0].doubleVal = eulerAngles0.x; - m_EulerFloats[1].doubleVal = eulerAngles0.y; - m_EulerFloats[2].doubleVal = eulerAngles0.z; + + if (m_Rotation.isLiveModified) + { + Vector3 eulerValue = m_Rotation.quaternionValue.eulerAngles; + m_EulerFloats[0].doubleVal = Mathf.Floor(eulerValue.x / kQuaternionFloatPrecision) * kQuaternionFloatPrecision; + m_EulerFloats[1].doubleVal = Mathf.Floor(eulerValue.y / kQuaternionFloatPrecision) * kQuaternionFloatPrecision; + m_EulerFloats[2].doubleVal = Mathf.Floor(eulerValue.z / kQuaternionFloatPrecision) * kQuaternionFloatPrecision; + } + else + { + m_EulerFloats[0].doubleVal = eulerAngles0.x; + m_EulerFloats[1].doubleVal = eulerAngles0.y; + m_EulerFloats[2].doubleVal = eulerAngles0.z; + } int id = GUIUtility.GetControlID(s_FoldoutHash, FocusType.Keyboard, r); if (AnimationMode.InAnimationMode() && transform0.rotationOrder != RotationOrder.OrderZXY) @@ -88,8 +100,13 @@ public void RotationField(bool disabled) var prevWidth = EditorGUIUtility.labelWidth; var prevIndent = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; + SerializedProperty rotation = m_Rotation.Copy(); + for (int i = 0; i < m_EulerFloats.Length; i++) { + rotation.Next(true); + EditorGUI.BeginProperty(nr, s_XYZLabels[i], rotation); + EditorGUIUtility.labelWidth = EditorGUI.GetLabelWidth(s_XYZLabels[i]); EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = (differentRotationMask & (1 << i)) != 0; @@ -113,6 +130,8 @@ public void RotationField(bool disabled) } nr.x += w + EditorGUI.kSpacingSubLabel; + + EditorGUI.EndProperty(); } EditorGUIUtility.labelWidth = prevWidth; EditorGUI.indentLevel = prevIndent; @@ -150,9 +169,17 @@ public void RotationField(bool disabled) } } } - tr.SetLocalEulerAngles(trEuler, tr.rotationOrder); - if (tr.parent != null) - tr.SendTransformChangedScale(); // force scale update, needed if tr has non-uniformly scaled parent. + + if (m_Rotation.isLiveModified) + { + m_Rotation.quaternionValue = Quaternion.Euler(trEuler.x, trEuler.y, trEuler.z); + } + else + { + tr.SetLocalEulerAngles(trEuler, tr.rotationOrder); + if (tr.parent != null) + tr.SendTransformChangedScale(); // force scale update, needed if tr has non-uniformly scaled parent. + } } m_Rotation.serializedObject.SetIsDifferentCacheDirty(); } diff --git a/Editor/Mono/Inspector/UnityEventDrawer.cs b/Editor/Mono/Inspector/UnityEventDrawer.cs index 44ad0876e9..5571359fe3 100644 --- a/Editor/Mono/Inspector/UnityEventDrawer.cs +++ b/Editor/Mono/Inspector/UnityEventDrawer.cs @@ -160,6 +160,7 @@ private ListView CreateListView(SerializedProperty property) { showAddRemoveFooter = true, reorderMode = ListViewReorderMode.Animated, + reorderable = true, showBorder = true, showFoldoutHeader = false, showBoundCollectionSize = false, @@ -212,6 +213,20 @@ private ListView CreateListView(SerializedProperty property) eventItem.BindFields(propertyData, createMenuCallback, formatSelectedValueCallback, getArgumentCallback); }; + listView.itemsAdded += indices => + { + foreach (var i in indices) + { + var pListener = propertyRelative.GetArrayElementAtIndex(i); + var callState = pListener.FindPropertyRelative(kCallStatePath); + // SERIAL-124 + // Objects added to an array via SerializedObject do not have their default values set. + // Therefore we need to set the initial values here to fix UUM-27561 + callState.enumValueIndex = (int)UnityEventCallState.RuntimeOnly; + callState.serializedObject.ApplyModifiedPropertiesWithoutUndo(); + } + }; + return listView; } @@ -256,7 +271,7 @@ private SerializedProperty GetArgument(SerializedProperty pListener) public override VisualElement CreatePropertyGUI(SerializedProperty property) { m_Prop = property; - m_Text = property.displayName; + m_Text = preferredLabel; m_DummyEvent = GetDummyEvent(m_Prop); var listViewContainer = new VisualElement(); @@ -264,6 +279,8 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) var header = new Label(); header.text = GetHeaderText(); + header.tooltip = property.tooltip; + RegisterRightClickMenu(header, property); header.AddToClassList(kHeaderClassName); var listView = CreateListView(property); @@ -275,6 +292,46 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) return listViewContainer; } + static void RegisterRightClickMenu(Label field, SerializedProperty property) + { + field.userData = property.Copy(); + field.RegisterCallback(RightClickFieldMenuEvent, InvokePolicy.IncludeDisabled, TrickleDown.TrickleDown); + field.RegisterCallback(StopContextClickEvent, TrickleDown.TrickleDown); + } + + static void RightClickFieldMenuEvent(PointerUpEvent evt) + { + if (evt.button != (int)MouseButton.RightMouse) + return; + + if (!(evt.target is VisualElement element)) + return; + + var property = element.userData as SerializedProperty; + if (property == null) + return; + + var wasEnabled = GUI.enabled; + if (!element.enabledInHierarchy) + GUI.enabled = false; + + var menu = EditorGUI.FillPropertyContextMenu(property, null, null, element); + GUI.enabled = wasEnabled; + + if (menu == null) + return; + + var menuRect = new Rect(evt.position, Vector2.zero); + menu.DropDown(menuRect); + + evt.StopPropagation(); + } + + static void StopContextClickEvent(ContextClickEvent e) + { + e.StopImmediatePropagation(); + } + private float GetElementHeight() { return EditorGUI.kSingleLineHeight * 2 + EditorGUI.kControlVerticalSpacing + kExtraSpacing; @@ -353,6 +410,7 @@ public void OnGUI(Rect position) if (m_ReorderableList != null) { var oldIdentLevel = EditorGUI.indentLevel; + position = EditorGUI.IndentedRect(position); EditorGUI.indentLevel = 0; m_ReorderableList.DoList(position); EditorGUI.indentLevel = oldIdentLevel; diff --git a/Editor/Mono/Inspector/VersionControlSettingsInspector.cs b/Editor/Mono/Inspector/VersionControlSettingsInspector.cs index 1fae6146be..ec49745544 100644 --- a/Editor/Mono/Inspector/VersionControlSettingsInspector.cs +++ b/Editor/Mono/Inspector/VersionControlSettingsInspector.cs @@ -11,7 +11,6 @@ using UnityEditorInternal; using UnityEngine; using UnityEngine.UIElements; -using UnityEditor.Collaboration; namespace UnityEditor { @@ -24,6 +23,8 @@ class Styles public static GUIContent logLevel = new GUIContent("Log Level"); public static GUIContent automaticAdd = new GUIContent("Automatic Add", "Automatically add newly created assets to version control."); + public static GUIContent scanLocalPackagesOnConnect = new GUIContent("Scan Local Packages on Connect", + "Scan local packages during the version control initialization to automatically add newly created assets to source control."); public static GUIContent smartMerge = new GUIContent("Smart merge"); public static GUIContent vcsConnect = new GUIContent("Connect"); public static GUIContent vcsReconnect = new GUIContent("Reconnect"); @@ -156,9 +157,6 @@ private void SetVersionControlSystem(object data) private bool VersionControlSystemHasGUI() { - bool collabEnabled = Collab.instance.IsCollabEnabledForCurrentProject(); - if (!collabEnabled) - { ExternalVersionControl system = VersionControlSettings.mode; return system != ExternalVersionControl.Disabled && @@ -166,9 +164,6 @@ private bool VersionControlSystemHasGUI() system != ExternalVersionControl.Generic; } - return false; - } - string[] GetVCConfigFieldRecentValues(string fieldName) { if (m_VCConfigFieldsRecentValues.ContainsKey(fieldName)) @@ -243,20 +238,13 @@ public override void OnInspectorGUI() GUILayout.Space(10); GUILayout.BeginVertical(EditorStyles.inspectorDefaultMargins); - bool collabEnabled = Collab.instance.IsCollabEnabledForCurrentProject(); - using (new EditorGUI.DisabledScope(!collabEnabled)) + using (new EditorGUI.DisabledScope(true)) { - GUI.enabled = !collabEnabled; + GUI.enabled = true; ExternalVersionControl selvc = VersionControlSettings.mode; CreatePopupMenuVersionControl(Styles.mode.text, vcPopupList, selvc, SetVersionControlSystem); - GUI.enabled = !collabEnabled; - } - - if (collabEnabled) - { - EditorGUILayout.HelpBox("Version Control not available when using Collaboration feature.", - MessageType.Warning); + GUI.enabled = true; } GUI.enabled = true; @@ -395,6 +383,9 @@ public override void OnInspectorGUI() EditorUserSettings.AutomaticAdd = EditorGUILayout.Toggle(Styles.automaticAdd, EditorUserSettings.AutomaticAdd); + EditorUserSettings.scanLocalPackagesOnConnect = + EditorGUILayout.Toggle(Styles.scanLocalPackagesOnConnect, EditorUserSettings.scanLocalPackagesOnConnect); + if (Provider.requiresNetwork) EditorUserSettings.allowAsyncStatusUpdate = EditorGUILayout.Toggle(Styles.allowAsyncUpdate, EditorUserSettings.allowAsyncStatusUpdate); diff --git a/Editor/Mono/Inspector/VisualElements/MinMaxGradientField.cs b/Editor/Mono/Inspector/VisualElements/MinMaxGradientField.cs index fc3961be32..4726fdc017 100644 --- a/Editor/Mono/Inspector/VisualElements/MinMaxGradientField.cs +++ b/Editor/Mono/Inspector/VisualElements/MinMaxGradientField.cs @@ -12,7 +12,7 @@ namespace UnityEditor.UIElements { internal class MinMaxGradientField : BaseField { - public new class UxmlFactory : UxmlFactory { } + protected new class UxmlFactory : UxmlFactory { } public new static readonly string ussClassName = "unity-min-max-gradient-field"; public static readonly string visualInputUssClass = ussClassName + "__visual-input"; @@ -22,6 +22,15 @@ internal class MinMaxGradientField : BaseField public static readonly string colorFieldUssClass = ussClassName + "__color-field"; public static readonly string multipleValuesLabelUssClass = ussClassName + "__multiple-values-label"; + /// + /// Defines for the . + /// + /// + /// This class defines the properties of a MinMaxGradientField element that you can + /// use in a UXML asset. + /// + protected new class UxmlTraits : BaseField.UxmlTraits {} + public readonly string[] stringModes = new[] { L10n.Tr("Color"), diff --git a/Editor/Mono/InternalEditorUtility.bindings.cs b/Editor/Mono/InternalEditorUtility.bindings.cs index 71de6ae430..2c77915194 100644 --- a/Editor/Mono/InternalEditorUtility.bindings.cs +++ b/Editor/Mono/InternalEditorUtility.bindings.cs @@ -14,6 +14,7 @@ using TargetAttributes = UnityEditor.BuildTargetDiscovery.TargetAttributes; using UnityEngine.Scripting; using System.Runtime.InteropServices; +using UnityEngine.SceneManagement; namespace UnityEditorInternal { @@ -273,7 +274,7 @@ public static void SaveToSerializedFileAndForget(Object[] obj, string path, bool extern public static Object[] LoadSerializedFileAndForget(string path); [FreeFunction("LoadFileAndForgetOperation::LoadSerializedFileAndForgetAsync")] - extern public static LoadFileAndForgetOperation LoadSerializedFileAndForgetAsync(string path, long localIdentifierInFile, ulong offsetInFile=0, long fileSize=-1); + extern public static LoadFileAndForgetOperation LoadSerializedFileAndForgetAsync(string path, long localIdentifierInFile, ulong offsetInFile=0, long fileSize=-1, Scene destScene = default); [FreeFunction("InternalEditorUtilityBindings::ProjectWindowDrag")] extern public static DragAndDropVisualMode ProjectWindowDrag(HierarchyProperty property, bool perform); @@ -529,6 +530,9 @@ public static Version GetUnityVersion() [FreeFunction("InternalEditorUtilityBindings::ReadScreenPixelUnderCursor")] extern public static Color[] ReadScreenPixelUnderCursor(Vector2 cursorPosHint, int sizex, int sizey); + [FreeFunction("InternalEditorUtilityBindings::IsAllowedToReadPixelOutsideUnity")] + extern internal static bool IsAllowedToReadPixelOutsideUnity(out string errorMessage); + [StaticAccessor("GetGpuDeviceManager()", StaticAccessorType.Dot)] [NativeMethod("SetDevice")] extern public static void SetGpuDeviceAndRecreateGraphics(int index, string name); @@ -598,6 +602,14 @@ public static string TextifyEvent(Event evt) case KeyCode.F13: text = "F13"; break; case KeyCode.F14: text = "F14"; break; case KeyCode.F15: text = "F15"; break; + case KeyCode.F16: text = "F16"; break; + case KeyCode.F17: text = "F17"; break; + case KeyCode.F18: text = "F18"; break; + case KeyCode.F19: text = "F19"; break; + case KeyCode.F20: text = "F20"; break; + case KeyCode.F21: text = "F21"; break; + case KeyCode.F23: text = "F23"; break; + case KeyCode.F24: text = "F24"; break; case KeyCode.Escape: text = "[esc]"; break; @@ -672,7 +684,7 @@ extern public static float remoteScreenHeight [StaticAccessor("CustomLighting::Get()", StaticAccessorType.Dot)] [NativeMethod("SetCustomLighting")] - extern public static void SetCustomLightingInternal(Light[] lights, Color ambient); + extern public static void SetCustomLightingInternal([Unmarshalled] Light[] lights, Color ambient); public static void SetCustomLighting(Light[] lights, Color ambient) { diff --git a/Editor/Mono/InternalEditorUtility.cs b/Editor/Mono/InternalEditorUtility.cs index 9748ad749f..c81a8d2bef 100644 --- a/Editor/Mono/InternalEditorUtility.cs +++ b/Editor/Mono/InternalEditorUtility.cs @@ -304,8 +304,7 @@ internal static List GetNewSelection(ref AssetReference clickedEntry, List< var newSelection = new List(selectedInstanceIDs); if (newSelection.Contains(clickedEntry.instanceID)) { - // In case the user is performing CTRL+click on an already selected item, delay the deselection so that Drag may be initiated. - if (!(Event.current.control && Event.current.type == EventType.MouseDown)) + if (!keepMultiSelection) newSelection.Remove(clickedEntry.instanceID); } else diff --git a/Editor/Mono/LogEntries.bindings.cs b/Editor/Mono/LogEntries.bindings.cs index e738693809..18c228c4e5 100644 --- a/Editor/Mono/LogEntries.bindings.cs +++ b/Editor/Mono/LogEntries.bindings.cs @@ -133,14 +133,13 @@ internal sealed class LogEntries // returns total line count public static extern int StartGettingEntries(); + public static extern void EndGettingEntries(); public static extern int consoleFlags { get; set; } public static extern void SetConsoleFlag(int bit, bool value); public static extern void SetFilteringText(string filteringText); public static extern string GetFilteringText(); - public static extern void EndGettingEntries(); - public static extern int GetCount(); public static extern void GetCountsByType(ref int errorCount, ref int warningCount, ref int logCount); @@ -167,6 +166,6 @@ internal sealed class LogEntries [ThreadSafe] public extern static unsafe void AddMessagesImpl(void* messagesBuffer, int messagesBufferLength); - internal static extern int GetEntryRowIndex(int globalIndex); + internal static extern int GetEntryRowIndex(int globalIndex, int indexHint = -1); } } diff --git a/Editor/Mono/MaterialProperty.cs b/Editor/Mono/MaterialProperty.cs index 69995c776c..da01516790 100644 --- a/Editor/Mono/MaterialProperty.cs +++ b/Editor/Mono/MaterialProperty.cs @@ -474,7 +474,7 @@ static void HandleApplyRevert(GenericMenu menu, bool singleEditing, Object[] tar static void HandleCopyPaste(GenericMenu menu) { - GetCopyPasteAction(capturedProperties[0], out var copyAction, out var pasteAction); + GetCopyPasteAction(s_CopyPasteCache, out var copyAction, out var pasteAction); if (menu.GetItemCount() != 0) menu.AddSeparator(""); @@ -517,9 +517,11 @@ static void DoRegularMenu(GenericMenu menu, bool isOverriden, Object[] targets) if (isOverriden) HandleApplyRevert(menu, singleEditing, targets); - if (singleEditing && capturedProperties.Count == 1) + if (singleEditing && s_CopyPasteCache != null) + { HandleCopyPaste(menu); - + s_CopyPasteCache = null; + } DisplayMode displayMode = GetDisplayMode(targets); if (displayMode == DisplayMode.Material) { @@ -534,7 +536,7 @@ static void DoRegularMenu(GenericMenu menu, bool isOverriden, Object[] targets) static void GetCopyPasteAction(MaterialProperty prop, out GenericMenu.MenuFunction copyAction, out GenericMenu.MenuFunction pasteAction) { - bool canCopy = !capturedProperties[0].hasMixedValue; + bool canCopy = !s_CopyPasteCache.hasMixedValue; bool canPaste = GUI.enabled; copyAction = null; @@ -645,6 +647,7 @@ static void GotoLockOriginAction(Object[] targets) } } static List s_PropertyStack = new List(); + static MaterialProperty s_CopyPasteCache = null; internal static void ClearStack() => s_PropertyStack.Clear(); internal static void BeginProperty(MaterialProperty prop, Object[] targets) @@ -665,6 +668,9 @@ internal static void BeginProperty(MaterialSerializedProperty prop, Object[] tar internal static void BeginProperty(Rect totalRect, MaterialProperty prop, MaterialSerializedProperty serializedProp, Object[] targets, float startY = -1) { + if (Event.current.rawType == EventType.ContextClick && (totalRect.Contains(Event.current.mousePosition))) + s_CopyPasteCache = prop; + if (targets == null || IsRegistered(prop, serializedProp)) { s_PropertyStack.Add(new PropertyData() { targets = null }); diff --git a/Editor/Mono/Media/Bindings/MediaEncoder.bindings.cs b/Editor/Mono/Media/Bindings/MediaEncoder.bindings.cs index 988acc0d9c..a10f493413 100644 --- a/Editor/Mono/Media/Bindings/MediaEncoder.bindings.cs +++ b/Editor/Mono/Media/Bindings/MediaEncoder.bindings.cs @@ -161,6 +161,7 @@ internal VideoTrackEncoderAttributes(VideoTrackAttributes videoAttrs) : this() height = videoAttrs.height; includeAlpha = videoAttrs.includeAlpha; bitRateMode = videoAttrs.bitRateMode; + codecType = videoAttrs.codec; vp8.alphaLayout = videoAttrs.alphaLayout; } } diff --git a/Editor/Mono/MenuItem.cs b/Editor/Mono/MenuItem.cs index e66c456480..15c9a0f0d8 100644 --- a/Editor/Mono/MenuItem.cs +++ b/Editor/Mono/MenuItem.cs @@ -45,6 +45,7 @@ internal MenuItem(string itemName, bool isValidateFunction, int priority, bool i validate = isValidateFunction; this.priority = priority; this.editorModes = editorModes; + secondaryPriority = float.MaxValue; } private static string NormalizeMenuItemName(string rawName) @@ -55,6 +56,7 @@ private static string NormalizeMenuItemName(string rawName) public string menuItem; public bool validate; public int priority; + public float secondaryPriority; // transition period until UW-65 lands. public string[] editorModes; } @@ -64,6 +66,7 @@ class MenuItemScriptCommand : IMenuItem { public string name; public int priority; + public float secondaryPriority; public MethodInfo execute; public MethodInfo validate; public Delegate commandExecute; @@ -74,12 +77,14 @@ class MenuItemScriptCommand : IMenuItem public string Name => name; public int Priority => priority; + + public float SecondaryPriority => secondaryPriority; internal bool IsNotValid => validate != null && execute == null; public static MenuItemScriptCommand Initialize(string menuName, MenuItem menuItemAttribute, MethodInfo methodInfo) { if (!menuItemAttribute.validate) - return InitializeFromExecute(menuName, menuItemAttribute.priority, methodInfo); + return InitializeFromExecute(menuName, menuItemAttribute.priority, menuItemAttribute.secondaryPriority, methodInfo); else return InitializeFromValidate(menuName, methodInfo); } @@ -93,12 +98,13 @@ private static MenuItemScriptCommand InitializeFromValidate(string menuName, Met }; } - private static MenuItemScriptCommand InitializeFromExecute(string menuName, int priority, MethodInfo execute) + private static MenuItemScriptCommand InitializeFromExecute(string menuName, int priority, float secondaryPriority, MethodInfo execute) { return new MenuItemScriptCommand() { name = menuName, priority = priority, + secondaryPriority = secondaryPriority, execute = execute }; } @@ -128,6 +134,9 @@ internal void Update(MenuItem menuItemAttribute, MethodInfo methodInfo) return; } priority = menuItemAttribute.priority; + + secondaryPriority = menuItemAttribute.secondaryPriority; + execute = methodInfo; } else @@ -148,6 +157,7 @@ class MenuItemOrderingNative : IMenuItem { public int position = -1; public int parentPosition = -1; + public float secondaryPriority; public string currentModeFullMenuName; // name of the menu to show public string defaultModeFullMenuName; // name to find the default menu public bool addChildren; // if true then native should add all children menu @@ -159,10 +169,11 @@ public MenuItemOrderingNative() defaultModeFullMenuName = string.Empty; } - public MenuItemOrderingNative(string currentModeFullMenuName, string defaultModeFullMenuName, int position, int parentPosition, bool addChildren = false) + public MenuItemOrderingNative(string currentModeFullMenuName, string defaultModeFullMenuName, int position, int parentPosition, float secondaryPriority, bool addChildren = false) { this.position = position; this.parentPosition = parentPosition; + this.secondaryPriority = secondaryPriority; this.currentModeFullMenuName = currentModeFullMenuName; this.defaultModeFullMenuName = defaultModeFullMenuName; this.addChildren = addChildren; @@ -171,11 +182,15 @@ public MenuItemOrderingNative(string currentModeFullMenuName, string defaultMode public string Name => defaultModeFullMenuName; public int Priority => position; + + public float SecondaryPriority => secondaryPriority; } interface IMenuItem { string Name { get; } int Priority { get; } + + float SecondaryPriority { get; } } } diff --git a/Editor/Mono/Modules/BeeBuildPostprocessor.cs b/Editor/Mono/Modules/BeeBuildPostprocessor.cs index 44d1c286d7..8053a40eb5 100644 --- a/Editor/Mono/Modules/BeeBuildPostprocessor.cs +++ b/Editor/Mono/Modules/BeeBuildPostprocessor.cs @@ -10,9 +10,9 @@ using Bee.BeeDriver; using Bee.BinLog; using NiceIO; -using Bee.Core; using PlayerBuildProgramLibrary.Data; using UnityEditor.Build; +using UnityEditor.Build.Player; using UnityEditor.Build.Reporting; using UnityEditor.CrashReporting; using UnityEditor.Scripting; @@ -225,6 +225,35 @@ protected virtual string Il2CppLinkerFlagsFor(BuildPostProcessArgs args) return null; } + protected virtual string[] Il2CppAdditionalLibrariesFor(BuildPostProcessArgs args) + { + return Array.Empty(); + } + + protected virtual string[] Il2CppAdditionalDefinesFor(BuildPostProcessArgs args) + { + return Array.Empty(); + } + + protected virtual string[] Il2CppAdditionalIncludeDirectoriesFor(BuildPostProcessArgs args) + { + return Array.Empty(); + } + + protected virtual string[] Il2CppAdditionalLinkDirectoriesFor(BuildPostProcessArgs args) + { + return Array.Empty(); + } + + protected virtual string Il2CppLinkerFlagsFileFor(BuildPostProcessArgs args) + { + return null; + } + + protected virtual string Il2CppDataRelativePath(BuildPostProcessArgs args) + { + return "Data"; + } Il2CppConfig Il2CppConfigFor(BuildPostProcessArgs args) { @@ -243,7 +272,13 @@ Il2CppConfig Il2CppConfigFor(BuildPostProcessArgs args) var sysrootPath = Il2CppSysrootPathFor(args); var toolchainPath = Il2CppToolchainPathFor(args); var compilerFlags = Il2CppCompilerFlagsFor(args); + var additionalLibraries = Il2CppAdditionalLibrariesFor(args); + var additionalDefines = Il2CppAdditionalDefinesFor(args); + var additionalIncludeDirectories = Il2CppAdditionalIncludeDirectoriesFor(args); + var additionalLinkDirectories = Il2CppAdditionalLinkDirectoriesFor(args); var linkerFlags = Il2CppLinkerFlagsFor(args); + var linkerFlagsFile = Il2CppLinkerFlagsFileFor(args); + var relativeDataPath = Il2CppDataRelativePath(args); if (CrashReportingSettings.enabled) additionalArgs.Add("--emit-source-mapping"); @@ -254,6 +289,19 @@ Il2CppConfig Il2CppConfigFor(BuildPostProcessArgs args) var platformHasIncrementalGC = BuildPipeline.IsFeatureSupported("ENABLE_SCRIPTING_GC_WBARRIERS", args.target); var allowDebugging = GetAllowDebugging(args); + NPath extraTypesFile = null; + if (PlayerBuildInterface.ExtraTypesProvider != null) + { + var extraTypes = new HashSet(); + foreach (var extraType in PlayerBuildInterface.ExtraTypesProvider()) + { + extraTypes.Add(extraType); + } + + extraTypesFile = "Temp/extra-types.txt"; + extraTypesFile.WriteAllLines(extraTypes.ToArray()); + } + return new Il2CppConfig { EnableDeepProfilingSupport = GetDevelopment(args) && @@ -277,9 +325,16 @@ Il2CppConfig Il2CppConfigFor(BuildPostProcessArgs args) AdditionalArgs = additionalArgs.ToArray(), AllowDebugging = allowDebugging, CompilerFlags = compilerFlags, + AdditionalLibraries = additionalLibraries, + AdditionalDefines = additionalDefines, + AdditionalIncludeDirectories = additionalIncludeDirectories, + AdditionalLinkDirectories = additionalLinkDirectories, LinkerFlags = linkerFlags, + LinkerFlagsFile = linkerFlagsFile, SysRootPath = sysrootPath, ToolChainPath = toolchainPath, + RelativeDataPath = relativeDataPath, + ExtraTypes = extraTypesFile?.ToString(), }; } @@ -323,7 +378,8 @@ static GenerateNativePluginsForAssembliesSettings GetGenerateNativePluginsForAss ApplicationIdentifier = PlayerSettings.GetApplicationIdentifier(GetNamedBuildTarget(args)), InstallIntoBuildsFolder = GetInstallingIntoBuildsFolder(args), GenerateIdeProject = GetCreateSolution(args), - Development = (args.report.summary.options & BuildOptions.Development) == BuildOptions.Development, + Development = (args.options & BuildOptions.Development) == BuildOptions.Development, + NoGUID = (args.options & BuildOptions.NoUniqueIdentifier) == BuildOptions.NoUniqueIdentifier, UseIl2Cpp = GetUseIl2Cpp(args), UseCoreCLR = GetUseCoreCLR(args), Architecture = GetArchitecture(args), @@ -360,7 +416,7 @@ protected virtual string GetInstallPathFor(BuildPostProcessArgs args) protected string GetDataFolderFor(BuildPostProcessArgs args) { - return $"Library/PlayerDataCache/{BuildPipeline.GetBuildTargetName(args.target)}/Data"; + return $"Library/PlayerDataCache/{BuildPipeline.GetSessionIdForBuildTarget(args.target, args.subtarget)}/Data"; } protected virtual string GetPlatformNameForBuildProgram(BuildPostProcessArgs args) => args.target.ToString(); @@ -450,9 +506,9 @@ public BeeBuildPostprocessor() protected void DefaultResultProcessor(NodeFinishedMessage node, bool printErrors = true, bool printWarnings = true) { - var output = node.Node.OutputFile; + var output = node.Node.OutputDirectory; if (string.IsNullOrEmpty(output)) - output = node.Node.OutputDirectory; + output = node.Node.OutputFile; var lines = (node.Output ?? string.Empty).Split(new[] {'\r', '\n'}, StringSplitOptions.RemoveEmptyEntries); @@ -508,28 +564,28 @@ void ReportBuildOutputFiles(BuildPostProcessArgs args) var filesOutput = BeeDriverResult.DataFromBuildProgram.Get(); foreach (var outputfile in filesOutput.Files.ToNPaths().Where(f => f.FileExists() && !f.IsSymbolicLink)) args.report.RecordFileAdded(outputfile.ToString(), outputfile.Extension); + + var config = filesOutput.BootConfigArtifact.ToNPath().ReadAllLines(); + var guidKey = "build-guid="; + var guidLine = config.FirstOrDefault(l => l.StartsWith(guidKey)); + if (guidLine != null) + { + var guid = guidLine.Substring(guidKey.Length); + args.report.SetBuildGUID(new GUID(guid)); + } + else + { + args.report.SetBuildGUID(new GUID("00000000000000000000000000000000")); + } } - public override string PrepareForBuild(BuildOptions options, BuildTarget target) + public override string PrepareForBuild(BuildPlayerOptions buildOptions) { // Clean the Bee folder in PrepareForBuild, so that it is also clean for script compilation. - if ((options & BuildOptions.CleanBuildCache) == BuildOptions.CleanBuildCache) + if ((buildOptions.options & BuildOptions.CleanBuildCache) == BuildOptions.CleanBuildCache) EditorCompilation.ClearBeeBuildArtifacts(); - if (Unsupported.IsDeveloperBuild()) - { - NPath editorGitRevisionFile = $"{EditorApplication.applicationContentsPath}/Tools/BuildPipeline/gitrevision.txt"; - NPath playerGitRevisionFile = $"{BuildPipeline.GetPlaybackEngineDirectory(target, options)}/Bee/gitrevision.txt"; - if (editorGitRevisionFile.Exists() && playerGitRevisionFile.Exists()) - { - string editorGitRevision = editorGitRevisionFile.ReadAllText(); - string playerGitRevision = playerGitRevisionFile.ReadAllText(); - if (editorGitRevision != playerGitRevision) - return $"The Bee libraries used in the editor come from a different revision, than the ones used for the player. Please rebuild both editor and player when making changes to Bee player build libraries. (editor: '{editorGitRevision}', player: '{playerGitRevision}')"; - } - } - - return base.PrepareForBuild(options, target); + return base.PrepareForBuild(buildOptions); } protected virtual void CleanBuildOutput(BuildPostProcessArgs args) @@ -616,13 +672,15 @@ public override void PostProcess(BuildPostProcessArgs args) if (EditorUtility.DisplayCancelableProgressBar("Incremental Player Build", activeBuildStatus.Description, progress)) { EditorUtility.DisplayCancelableProgressBar("Incremental Player Build", "Canceling build", 1.0f); - cancellationTokenSource.Cancel(); - throw new OperationCanceledException(); + cancellationTokenSource.Cancel(); } } args.report.EndBuildStep(buildStep); BeeDriverResult = activeBuild.TaskObject.Result; + + UnityBeeDriverProfilerSession.AddTaskToWaitForBeforeFinishing(BeeDriverResult.ProfileOutputWritingTask); + if (BeeDriverResult.Success) { PostProcessCompletedBuild(args); @@ -637,7 +695,7 @@ public override void PostProcess(BuildPostProcessArgs args) ReportBuildOutputFiles(args); args.report.EndBuildStep(buildStep); } else - throw new BuildFailedException("Incremental Player build failed!"); + throw new BuildFailedException($"Player build failed: {args.report.SummarizeErrors()}", silent: true); } } catch (OperationCanceledException) @@ -648,6 +706,13 @@ public override void PostProcess(BuildPostProcessArgs args) { throw; } + catch (AggregateException e) + { + if (e.InnerException is OperationCanceledException or BuildFailedException) + throw e.InnerException; + + throw new BuildFailedException(e); + } catch (Exception e) { throw new BuildFailedException(e); diff --git a/Editor/Mono/Modules/DefaultBuildPostprocessor.cs b/Editor/Mono/Modules/DefaultBuildPostprocessor.cs index 829477f2b9..3de562de67 100644 --- a/Editor/Mono/Modules/DefaultBuildPostprocessor.cs +++ b/Editor/Mono/Modules/DefaultBuildPostprocessor.cs @@ -16,6 +16,8 @@ internal class DefaultBuildProperties : BuildProperties internal abstract class DefaultBuildPostprocessor : IBuildPostprocessor { + public static readonly string kBackupFolderPostfix = "_BackUpThisFolder_ButDontShipItWithYourGame"; + static readonly string kXrBootSettingsKey = "xr-boot-settings"; public virtual void LaunchPlayer(BuildLaunchPlayerArgs args) { @@ -63,7 +65,7 @@ public virtual bool SupportsScriptsOnlyBuild() public virtual bool UsesBeeBuild() => false; - public virtual string PrepareForBuild(BuildOptions options, BuildTarget target) + public virtual string PrepareForBuild(BuildPlayerOptions buildPlayerOptions) { return null; } @@ -112,7 +114,7 @@ public virtual void UpdateBootConfig(BuildTarget target, BootConfigData config, protected virtual string GetIl2CppDataBackupFolderName(BuildPostProcessArgs args) { - return $"{args.installPath.ToNPath().FileNameWithoutExtension}_BackUpThisFolder_ButDontShipItWithYourGame"; + return $"{args.installPath.ToNPath().FileNameWithoutExtension}{kBackupFolderPostfix}"; } public virtual string GetExtension(BuildTarget target, int subtarget, BuildOptions options) diff --git a/Editor/Mono/Modules/DefaultPlayerSettingsEditorExtension.cs b/Editor/Mono/Modules/DefaultPlayerSettingsEditorExtension.cs index e4c50858bd..bc70806e74 100644 --- a/Editor/Mono/Modules/DefaultPlayerSettingsEditorExtension.cs +++ b/Editor/Mono/Modules/DefaultPlayerSettingsEditorExtension.cs @@ -145,5 +145,12 @@ public virtual bool ShouldShowVulkanSettings() } public virtual void VulkanSectionGUI() {} + + public virtual bool SupportsStaticSplashScreenBackgroundColor() + { + return false; + } + + public virtual void AutoRotationSectionGUI() {} } } diff --git a/Editor/Mono/Modules/DefaultPluginImporterExtension.cs b/Editor/Mono/Modules/DefaultPluginImporterExtension.cs index c76d375c60..51e55c2dbc 100644 --- a/Editor/Mono/Modules/DefaultPluginImporterExtension.cs +++ b/Editor/Mono/Modules/DefaultPluginImporterExtension.cs @@ -19,6 +19,7 @@ internal class DefaultPluginImporterExtension : IPluginImporterExtension { protected bool hasModified = false; protected Property[] properties = null; + protected const string cpuKey = "CPU"; internal class Property { @@ -90,6 +91,11 @@ public DefaultPluginImporterExtension(Property[] properties) this.properties = properties; } + protected virtual Property[] GetPropertiesForInspector(PluginImporterInspector inspector) + { + return properties; + } + public virtual void ResetValues(PluginImporterInspector inspector) { hasModified = false; @@ -105,7 +111,7 @@ public virtual void Apply(PluginImporterInspector inspector) { if (!propertiesRefreshed) return; - foreach (var p in properties) + foreach (var p in GetPropertiesForInspector(inspector)) { p.Apply(inspector); } @@ -127,8 +133,11 @@ public virtual void OnPlatformSettingsGUI(PluginImporterInspector inspector) if (!propertiesRefreshed) RefreshProperties(inspector); EditorGUI.BeginChangeCheck(); - foreach (var p in properties) + foreach (var p in GetPropertiesForInspector(inspector)) { + // skip CPU property for things that aren't native libs + if (p.key == cpuKey && !inspector.importer.isNativePlugin) + continue; p.OnGUI(inspector); } if (EditorGUI.EndChangeCheck()) hasModified = true; @@ -136,7 +145,7 @@ public virtual void OnPlatformSettingsGUI(PluginImporterInspector inspector) protected virtual void RefreshProperties(PluginImporterInspector inspector) { - foreach (var p in properties) + foreach (var p in GetPropertiesForInspector(inspector)) { p.Reset(inspector); } @@ -145,7 +154,7 @@ protected virtual void RefreshProperties(PluginImporterInspector inspector) public virtual string CalculateFinalPluginPath(string platformName, PluginImporter imp) { - string cpu = imp.GetPlatformData(platformName, "CPU"); + string cpu = imp.GetPlatformData(platformName, cpuKey); if (!string.IsNullOrEmpty(cpu) && (string.Compare(cpu, "AnyCPU", true) != 0) && (string.Compare(cpu, "None", true) != 0)) return Path.Combine(cpu, Path.GetFileName(imp.assetPath)); diff --git a/Editor/Mono/Modules/DefaultTextureImportSettingsExtension.cs b/Editor/Mono/Modules/DefaultTextureImportSettingsExtension.cs index d7c3654fcd..bbf3a8cbfd 100644 --- a/Editor/Mono/Modules/DefaultTextureImportSettingsExtension.cs +++ b/Editor/Mono/Modules/DefaultTextureImportSettingsExtension.cs @@ -58,14 +58,23 @@ internal class DefaultTextureImportSettingsExtension : ITextureImportSettingsExt public virtual void ShowImportSettings(BaseTextureImportPlatformSettings editor) { // Max texture size + Rect controlRect = EditorGUILayout.GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.popup); + GUIContent label = maxSize; + if (editor.model.maxTextureSizeProperty != null) + { + label = EditorGUI.BeginProperty(controlRect, label, editor.model.maxTextureSizeProperty); + } EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = editor.model.maxTextureSizeIsDifferent; - int maxTextureSize = EditorGUILayout.IntPopup(maxSize.text, editor.model.platformTextureSettings.maxTextureSize, kMaxTextureSizeStrings, kMaxTextureSizeValues); - EditorGUI.showMixedValue = false; + int maxTextureSize = EditorGUI.IntPopup(controlRect, label, editor.model.platformTextureSettings.maxTextureSize, GUIContent.Temp(kMaxTextureSizeStrings), kMaxTextureSizeValues); if (EditorGUI.EndChangeCheck()) { editor.model.SetMaxTextureSizeForAll(maxTextureSize); } + if (editor.model.maxTextureSizeProperty != null) + { + EditorGUI.EndProperty(); + } // Show a note if max size is overriden globally by the user var userMaxSizeOverride = EditorUserBuildSettings.overrideMaxTextureSize; @@ -73,14 +82,23 @@ public virtual void ShowImportSettings(BaseTextureImportPlatformSettings editor) EditorGUILayout.HelpBox(string.Format(kMaxSizeOverrideString, userMaxSizeOverride), MessageType.Info); // Resize Algorithm + controlRect = EditorGUILayout.GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.popup); + label = kResizeAlgorithm; + if (editor.model.resizeAlgorithmProperty != null) + { + label = EditorGUI.BeginProperty(controlRect, label, editor.model.resizeAlgorithmProperty); + } EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = editor.model.resizeAlgorithmIsDifferent; - int resizeAlgorithmVal = EditorGUILayout.IntPopup(kResizeAlgorithm.text, (int)editor.model.platformTextureSettings.resizeAlgorithm, kResizeAlgorithmStrings, kResizeAlgorithmValues); - EditorGUI.showMixedValue = false; + int resizeAlgorithmVal = EditorGUI.IntPopup(controlRect, label, (int)editor.model.platformTextureSettings.resizeAlgorithm, GUIContent.Temp(kResizeAlgorithmStrings), kResizeAlgorithmValues); if (EditorGUI.EndChangeCheck()) { editor.model.SetResizeAlgorithmForAll((TextureResizeAlgorithm)resizeAlgorithmVal); } + if (editor.model.resizeAlgorithmProperty != null) + { + EditorGUI.EndProperty(); + } // Texture format int[] formatValuesForAll = {}; @@ -144,16 +162,25 @@ public virtual void ShowImportSettings(BaseTextureImportPlatformSettings editor) using (new EditorGUI.DisabledScope(formatOptionsAreDifferent || formatStringsForAll.Length == 1)) { + controlRect = EditorGUILayout.GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.popup); + label = kTextureFormat; + if (editor.model.textureFormatProperty != null) + { + label = EditorGUI.BeginProperty(controlRect, label, editor.model.textureFormatProperty); + } EditorGUI.BeginChangeCheck(); bool mixedValues = formatOptionsAreDifferent || editor.model.textureFormatIsDifferent; EditorGUI.showMixedValue = mixedValues; - var selectionResult = EditorGUILayout.IntPopup(kTextureFormat, formatForAll, EditorGUIUtility.TempContent(formatStringsForAll), formatValuesForAll); - EditorGUI.showMixedValue = false; + var selectionResult = EditorGUI.IntPopup(controlRect, label, formatForAll, GUIContent.Temp(formatStringsForAll), formatValuesForAll); if (EditorGUI.EndChangeCheck()) { editor.model.SetTextureFormatForAll((TextureImporterFormat)selectionResult); formatForAll = selectionResult; } + if (editor.model.textureFormatProperty != null) + { + EditorGUI.EndProperty(); + } if (!mixedValues && !Array.Exists(formatValuesForAll, i => i == formatForAll)) { @@ -164,18 +191,27 @@ public virtual void ShowImportSettings(BaseTextureImportPlatformSettings editor) // Texture Compression if (editor.model.isDefault && editor.model.platformTextureSettings.format == TextureImporterFormat.Automatic) { + controlRect = EditorGUILayout.GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.popup); + label = kTextureCompression; + if (editor.model.textureCompressionProperty != null) + { + label = EditorGUI.BeginProperty(controlRect, label, editor.model.textureCompressionProperty); + } EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = editor.model.overriddenIsDifferent || editor.model.textureCompressionIsDifferent; TextureImporterCompression textureCompression = - (TextureImporterCompression)EditorGUILayout.IntPopup(kTextureCompression, - (int)editor.model.platformTextureSettings.textureCompression, kTextureCompressionOptions, + (TextureImporterCompression)EditorGUI.IntPopup(controlRect, + label, (int)editor.model.platformTextureSettings.textureCompression, kTextureCompressionOptions, kTextureCompressionValues); - EditorGUI.showMixedValue = false; if (EditorGUI.EndChangeCheck()) { editor.model.SetTextureCompressionForAll(textureCompression); } + if (editor.model.textureCompressionProperty != null) + { + EditorGUI.EndProperty(); + } } // Use Crunch Compression @@ -184,16 +220,25 @@ public virtual void ShowImportSettings(BaseTextureImportPlatformSettings editor) editor.model.platformTextureSettings.textureCompression != TextureImporterCompression.Uncompressed && (textureShape == TextureImporterShape.Texture2D || textureShape == TextureImporterShape.TextureCube)) // 2DArray & 3D don't support Crunch { + controlRect = EditorGUILayout.GetToggleRect(true); + label = kCrunchedCompression; + if (editor.model.crunchedCompressionProperty != null) + { + label = EditorGUI.BeginProperty(controlRect, label, editor.model.crunchedCompressionProperty); + } EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = editor.model.overriddenIsDifferent || editor.model.crunchedCompressionIsDifferent; - bool crunchedCompression = EditorGUILayout.Toggle( - kCrunchedCompression, editor.model.platformTextureSettings.crunchedCompression); - EditorGUI.showMixedValue = false; + bool crunchedCompression = EditorGUI.Toggle( + controlRect, label, editor.model.platformTextureSettings.crunchedCompression); if (EditorGUI.EndChangeCheck()) { editor.model.SetCrunchedCompressionForAll(crunchedCompression); } + if (editor.model.crunchedCompressionProperty != null) + { + EditorGUI.EndProperty(); + } } // compression quality @@ -212,24 +257,7 @@ public virtual void ShowImportSettings(BaseTextureImportPlatformSettings editor) TextureImporterInspector.kFormatsWithCompressionSettings, (TextureImporterFormat)formatForAll))) { - EditorGUI.BeginChangeCheck(); - EditorGUI.showMixedValue = editor.model.overriddenIsDifferent || - editor.model.compressionQualityIsDifferent; - - // Prior to exposing compression quality for BC6H/BC7 formats they were always compressed at maximum quality even though the setting was - // defaulted to 'Normal'. Now BC6H/BC7 quality is exposed to the user as Fast/Normal/Best 'Normal' maps to one setting down from maximum in the - // ISPC compressor but to maintain the behaviour of existing projects we need to force their quality up to 'Best'. The 'forceMaximumCompressionQuality_BC6H_BC7' - // flag is set when loading existing texture platform settings to do this and cleared when the compression quality level is manually set (by UI or API) - bool forceBestQuality = editor.model.forceMaximumCompressionQuality_BC6H_BC7 && (((TextureImporterFormat)formatForAll == TextureImporterFormat.BC6H) || ((TextureImporterFormat)formatForAll == TextureImporterFormat.BC7)); - int compressionQuality = forceBestQuality ? (int)TextureCompressionQuality.Best : editor.model.platformTextureSettings.compressionQuality; - - compressionQuality = EditCompressionQuality(editor.model.buildTarget, compressionQuality, isCrunchedFormat, (TextureImporterFormat)formatForAll); - EditorGUI.showMixedValue = false; - if (EditorGUI.EndChangeCheck()) - { - editor.model.SetCompressionQualityForAll(compressionQuality); - //SyncPlatformSettings (); - } + EditCompressionQuality(editor, isCrunchedFormat, (TextureImporterFormat)formatForAll); } // show the ETC1 split option only for sprites on platforms supporting ETC and only when there is an alpha channel @@ -239,19 +267,47 @@ public virtual void ShowImportSettings(BaseTextureImportPlatformSettings editor) if (isETCPlatform && isDealingWithSprite && isETCFormatSelected) { + controlRect = EditorGUILayout.GetToggleRect(true); + label = kUseAlphaSplitLabel; + if (editor.model.alphaSplitProperty != null) + { + label = EditorGUI.BeginProperty(controlRect, label, editor.model.alphaSplitProperty); + } EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = editor.model.overriddenIsDifferent || editor.model.allowsAlphaSplitIsDifferent; - bool allowsAlphaSplit = EditorGUILayout.Toggle(kUseAlphaSplitLabel, editor.model.platformTextureSettings.allowsAlphaSplitting); + bool allowsAlphaSplit = EditorGUI.Toggle(controlRect, label, editor.model.platformTextureSettings.allowsAlphaSplitting); if (EditorGUI.EndChangeCheck()) { editor.model.SetAllowsAlphaSplitForAll(allowsAlphaSplit); } + if (editor.model.alphaSplitProperty != null) + { + EditorGUI.EndProperty(); + } } } - private int EditCompressionQuality(BuildTarget target, int compression, bool isCrunchedFormat, TextureImporterFormat textureFormat) + private void EditCompressionQuality(BaseTextureImportPlatformSettings editor, bool isCrunchedFormat, TextureImporterFormat textureFormat) { - bool showAsEnum = !isCrunchedFormat && (BuildTargetDiscovery.PlatformHasFlag(target, TargetAttributes.HasIntegratedGPU) || (textureFormat == TextureImporterFormat.BC6H) || (textureFormat == TextureImporterFormat.BC7)); + bool showAsEnum = !isCrunchedFormat && (BuildTargetDiscovery.PlatformHasFlag(editor.model.buildTarget, TargetAttributes.HasIntegratedGPU) || (textureFormat == TextureImporterFormat.BC6H) || (textureFormat == TextureImporterFormat.BC7)); + + Rect controlRect = showAsEnum ? EditorGUILayout.GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.popup) : EditorGUILayout.GetSliderRect(true); + GUIContent label = showAsEnum ? kCompressionQuality : kCompressionQualitySlider; + if (editor.model.compressionQualityProperty != null) + { + label = EditorGUI.BeginProperty(controlRect, label, editor.model.compressionQualityProperty); + } + + EditorGUI.BeginChangeCheck(); + EditorGUI.showMixedValue = editor.model.overriddenIsDifferent || + editor.model.compressionQualityIsDifferent; + + // Prior to exposing compression quality for BC6H/BC7 formats they were always compressed at maximum quality even though the setting was + // defaulted to 'Normal'. Now BC6H/BC7 quality is exposed to the user as Fast/Normal/Best 'Normal' maps to one setting down from maximum in the + // ISPC compressor but to maintain the behaviour of existing projects we need to force their quality up to 'Best'. The 'forceMaximumCompressionQuality_BC6H_BC7' + // flag is set when loading existing texture platform settings to do this and cleared when the compression quality level is manually set (by UI or API) + bool forceBestQuality = editor.model.forceMaximumCompressionQuality_BC6H_BC7 && ((textureFormat == TextureImporterFormat.BC6H) || (textureFormat == TextureImporterFormat.BC7)); + int compression = forceBestQuality ? (int)TextureCompressionQuality.Best : editor.model.platformTextureSettings.compressionQuality; if (showAsEnum) { @@ -261,21 +317,37 @@ private int EditCompressionQuality(BuildTarget target, int compression, bool isC else if (compression == (int)TextureCompressionQuality.Best) compressionMode = 2; - int ret = EditorGUILayout.Popup(kCompressionQuality, compressionMode, kMobileCompressionQualityOptions); + int ret = EditorGUI.Popup(controlRect, label, compressionMode, kMobileCompressionQualityOptions); switch (ret) { - case 0: return (int)TextureCompressionQuality.Fast; - case 1: return (int)TextureCompressionQuality.Normal; - case 2: return (int)TextureCompressionQuality.Best; + case 0: + compression = (int)TextureCompressionQuality.Fast; + break; + case 1: + compression = (int)TextureCompressionQuality.Normal; + break; + case 2: + compression = (int)TextureCompressionQuality.Best; + break; - default: return (int)TextureCompressionQuality.Normal; + default: + compression = (int)TextureCompressionQuality.Normal; + break; } } else - compression = EditorGUILayout.IntSlider(kCompressionQualitySlider, compression, 0, 100); + compression = EditorGUI.IntSlider(controlRect, label, compression, 0, 100); - return compression; + if (EditorGUI.EndChangeCheck()) + { + editor.model.SetCompressionQualityForAll(compression); + //SyncPlatformSettings (); + } + if (editor.model.compressionQualityProperty != null) + { + EditorGUI.EndProperty(); + } } } } diff --git a/Editor/Mono/Modules/ModuleManager.cs b/Editor/Mono/Modules/ModuleManager.cs index a6a6d59cc3..ab7951a7e9 100644 --- a/Editor/Mono/Modules/ModuleManager.cs +++ b/Editor/Mono/Modules/ModuleManager.cs @@ -212,8 +212,15 @@ private static void RegisterPlatformSupportModules() continue; } - var platformSupportModule = Activator.CreateInstance(type) as IPlatformSupportModule; - s_PlatformModules.Add(platformSupportModule.TargetName, platformSupportModule); + try + { + var platformSupportModule = Activator.CreateInstance(type) as IPlatformSupportModule; + s_PlatformModules.Add(platformSupportModule.TargetName, platformSupportModule); + } + catch (Exception ex) + { + Debug.LogError($"Could not add platformSupportModule {type.FullName}, try rebuilding the module: {ex.Message}"); + } } } diff --git a/Editor/Mono/Modules/PlatformSupportModule.cs b/Editor/Mono/Modules/PlatformSupportModule.cs index 9c5f98d742..f4da372dea 100644 --- a/Editor/Mono/Modules/PlatformSupportModule.cs +++ b/Editor/Mono/Modules/PlatformSupportModule.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using Bee.BeeDriver; using UnityEditor.DeploymentTargets; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; using UnityEngine; using Mono.Cecil; using UnityEditor.Scripting.ScriptCompilation; @@ -148,7 +150,8 @@ internal interface IBuildPostprocessor // This is the place to make sure platform has everything it needs for the build. // Use EditorUtility.Display(Cancelable)ProgressBar when running long tasks (e.g. downloading SDK from internet). // Return non-empty string indicating error message to stop the build. - string PrepareForBuild(BuildOptions options, BuildTarget target); + /// Build details. Useful to get the location of the player to build, for instance. + string PrepareForBuild(BuildPlayerOptions buildOptions); void PostProcessCompletedBuild(BuildPostProcessArgs args); @@ -258,6 +261,10 @@ internal interface ISettingEditorExtension bool SupportsFrameTimingStatistics(); void SerializedObjectUpdated(); + + bool SupportsStaticSplashScreenBackgroundColor(); + + void AutoRotationSectionGUI(); } diff --git a/Editor/Mono/Networking/PlayerConnection/ConnectionDropDown.cs b/Editor/Mono/Networking/PlayerConnection/ConnectionDropDown.cs index b07ce9d34b..6750005c69 100644 --- a/Editor/Mono/Networking/PlayerConnection/ConnectionDropDown.cs +++ b/Editor/Mono/Networking/PlayerConnection/ConnectionDropDown.cs @@ -39,7 +39,7 @@ public static string TruncateString(string s, GUIStyle style, float maxwidth) return s; maxwidth -= style.CalcSize(GUIContent.Temp("...")).x; - while (style.CalcSize(GUIContent.Temp(s)).x > maxwidth) + while (s.Length > 0 && style.CalcSize(GUIContent.Temp(s)).x > maxwidth) { s = s.Remove(s.Length - 1, 1); } @@ -120,42 +120,67 @@ public static string GetPort(int connectionId) public static GUIContent GetIcon(string name) { - var content = name switch + var content = GetBuildTargetIcon(name); + if (content == null) { - "Editor" => EditorGUIUtility.IconContent("d_SceneAsset Icon"), - "WindowsEditor" => EditorGUIUtility.IconContent("d_SceneAsset Icon"), - "WindowsPlayer" => EditorGUIUtility.IconContent("BuildSettings.Metro.Small"), - "Android" => EditorGUIUtility.IconContent("BuildSettings.Android.Small"), - "OSXPlayer" => EditorGUIUtility.IconContent("BuildSettings.Standalone.Small"), - "IPhonePlayer" => EditorGUIUtility.IconContent("BuildSettings.iPhone.Small"), - "WebGLPlayer" => EditorGUIUtility.IconContent("BuildSettings.WebGL.Small"), - "tvOS" => EditorGUIUtility.IconContent("BuildSettings.tvOS.Small"), - "Lumin" => EditorGUIUtility.IconContent("BuildSettings.Lumin.small"), - "LinuxPlayer" => EditorGUIUtility.IconContent("BuildSettings.EmbeddedLinux.Small"), - "WSAPlayerX86" => EditorGUIUtility.IconContent("BuildSettings.Metro.Small"), - "WSAPlayerX64" => EditorGUIUtility.IconContent("BuildSettings.Metro.Small"), - "WSAPlayerARM" => EditorGUIUtility.IconContent("BuildSettings.Metro.Small"), - "Switch" => EditorGUIUtility.IconContent("BuildSettings.Switch.Small"), - "Stadia" => EditorGUIUtility.IconContent("BuildSettings.Stadia.small"), - "GameCoreScarlett" => EditorGUIUtility.IconContent("BuildSettings.GameCoreScarlett.Small"), - "GameCoreXboxOne" => EditorGUIUtility.IconContent("BuildSettings.GameCoreXboxOne.Small"), - "XboxOne" => EditorGUIUtility.IconContent("BuildSettings.GameCoreXboxOne.Small"), - "EmbeddedLinuxArm64" => EditorGUIUtility.IconContent("BuildSettings.EmbeddedLinux.Small"), - "EmbeddedLinuxArm32" => EditorGUIUtility.IconContent("BuildSettings.EmbeddedLinux.Small"), - "EmbeddedLinuxX64" => EditorGUIUtility.IconContent("BuildSettings.EmbeddedLinux.Small"), - "EmbeddedLinuxX86" => EditorGUIUtility.IconContent("BuildSettings.EmbeddedLinux.Small"), - "QNXArm64" => EditorGUIUtility.IconContent("BuildSettings.QNX.Small"), - "QNXArm32" => EditorGUIUtility.IconContent("BuildSettings.QNX.Small"), - "QNXX64" => EditorGUIUtility.IconContent("BuildSettings.QNX.Small"), - "QNXX86" => EditorGUIUtility.IconContent("BuildSettings.QNX.Small"), - "PS4" => EditorGUIUtility.IconContent("BuildSettings.PS4.Small"), - "PS5" => EditorGUIUtility.IconContent("BuildSettings.PS5.Small"), - "Devices" => EditorGUIUtility.IconContent("BuildSettings.Standalone.Small"), - "" => EditorGUIUtility.IconContent("BuildSettings.Broadcom"), - _ => EditorGUIUtility.IconContent("BuildSettings.Broadcom") - }; + content = name switch + { + "Editor" => EditorGUIUtility.IconContent("d_SceneAsset Icon"), + "WindowsEditor" => EditorGUIUtility.IconContent("d_SceneAsset Icon"), + "WindowsPlayer" => EditorGUIUtility.IconContent("BuildSettings.Metro.Small"), + "Android" => EditorGUIUtility.IconContent("BuildSettings.Android.Small"), + "OSXPlayer" => EditorGUIUtility.IconContent("BuildSettings.Standalone.Small"), + "IPhonePlayer" => EditorGUIUtility.IconContent("BuildSettings.iPhone.Small"), + "WebGLPlayer" => EditorGUIUtility.IconContent("BuildSettings.WebGL.Small"), + "tvOS" => EditorGUIUtility.IconContent("BuildSettings.tvOS.Small"), + "Lumin" => EditorGUIUtility.IconContent("BuildSettings.Lumin.small"), + "LinuxPlayer" => EditorGUIUtility.IconContent("BuildSettings.EmbeddedLinux.Small"), + "WSAPlayerX86" => EditorGUIUtility.IconContent("BuildSettings.Metro.Small"), + "WSAPlayerX64" => EditorGUIUtility.IconContent("BuildSettings.Metro.Small"), + "WSAPlayerARM" => EditorGUIUtility.IconContent("BuildSettings.Metro.Small"), + "Switch" => EditorGUIUtility.IconContent("BuildSettings.Switch.Small"), + "Stadia" => EditorGUIUtility.IconContent("BuildSettings.Stadia.small"), + "EmbeddedLinuxArm64" => EditorGUIUtility.IconContent("BuildSettings.EmbeddedLinux.Small"), + "EmbeddedLinuxArm32" => EditorGUIUtility.IconContent("BuildSettings.EmbeddedLinux.Small"), + "EmbeddedLinuxX64" => EditorGUIUtility.IconContent("BuildSettings.EmbeddedLinux.Small"), + "EmbeddedLinuxX86" => EditorGUIUtility.IconContent("BuildSettings.EmbeddedLinux.Small"), + "QNXArm64" => EditorGUIUtility.IconContent("BuildSettings.QNX.Small"), + "QNXArm32" => EditorGUIUtility.IconContent("BuildSettings.QNX.Small"), + "QNXX64" => EditorGUIUtility.IconContent("BuildSettings.QNX.Small"), + "QNXX86" => EditorGUIUtility.IconContent("BuildSettings.QNX.Small"), + "PS4" => EditorGUIUtility.IconContent("BuildSettings.PS4.Small"), + "PS5" => EditorGUIUtility.IconContent("BuildSettings.PS5.Small"), + "Devices" => EditorGUIUtility.IconContent("BuildSettings.Standalone.Small"), + "" => EditorGUIUtility.IconContent("BuildSettings.Broadcom"), + _ => EditorGUIUtility.IconContent("BuildSettings.Broadcom") + }; + } + return content; } + + private static GUIContent GetBuildTargetIcon(string name) + { + var target = BuildPipeline.GetBuildTargetByName(name); + if (target == BuildTarget.NoTarget) + { + return null; + } + + var namedBuildTarget = Build.NamedBuildTarget.FromActiveSettings(target); + if (namedBuildTarget == null) + { + return null; + } + + var buildPlatform = Build.BuildPlatforms.instance.BuildPlatformFromNamedBuildTarget(namedBuildTarget); + if (buildPlatform == null || buildPlatform.smallIcon == null) + { + return null; + } + + return new GUIContent(buildPlatform.smallIcon); + } } internal class ConnectionDropDownItem : TreeViewItem @@ -709,7 +734,7 @@ public override void OnGUI(Rect rect) if (EditorGUI.Button(rect, Content.TroubleShoot, ConnectionDropDownStyles.sConnectionTrouble)) { var help = Help.FindHelpNamed("profiler-profiling-applications"); - Application.OpenURL(help); + Help.BrowseURL(help); } if(Event.current.type == EventType.MouseMove) @@ -782,18 +807,6 @@ public void AddItem(ConnectionDropDownItem connectionDropDownItem) // *begin-nonstandard-formatting* connectionItems ??= new List(); // *end-nonstandard-formatting* - // this is a hack to show switch connected over ethernet in devices - if (connectionDropDownItem.IP == "127.0.0.1" && ProfilerDriver.GetConnectionIdentifier(connectionDropDownItem.m_ConnectionId).StartsWith("Switch")) - { - connectionDropDownItem.m_TopLevelGroup = ConnectionDropDownItem.ConnectionMajorGroup.Local; - connectionDropDownItem.m_SubGroup = "Devices"; - connectionDropDownItem.IsDevice = true; - connectionDropDownItem.IconContent = ConnectionUIHelper.GetIcon("Switch"); - var fullName = ProfilerDriver.GetConnectionIdentifier(connectionDropDownItem.m_ConnectionId); - var start = fullName.IndexOf('-') + 1; - var end = fullName.IndexOf('('); - connectionDropDownItem.DisplayName = $"{fullName.Substring(start, end - start)} - {ProfilerDriver.GetProjectName(connectionDropDownItem.m_ConnectionId)}"; - } var dupes = connectionItems.FirstOrDefault(x => x.DisplayName == connectionDropDownItem.DisplayName && x.IP == connectionDropDownItem.IP && x.Port == connectionDropDownItem.Port); if (dupes != null) diff --git a/Editor/Mono/ObjectFactory.bindings.cs b/Editor/Mono/ObjectFactory.bindings.cs index dbf7cdde95..a86334c3a0 100644 --- a/Editor/Mono/ObjectFactory.bindings.cs +++ b/Editor/Mono/ObjectFactory.bindings.cs @@ -21,6 +21,9 @@ public static class ObjectFactory [FreeFunction] internal static extern void SmartResetObjectToDefault([NotNull] Object target); + [FreeFunction] + internal static extern void FinalizeObjectAndAwake([NotNull] Object target); + [FreeFunction(ThrowsException = true)] static extern Object CreateDefaultInstance([NotNull] Type type); diff --git a/Editor/Mono/ObjectListLocalGroup.cs b/Editor/Mono/ObjectListLocalGroup.cs index 3b8c3468ba..40b8ea128a 100644 --- a/Editor/Mono/ObjectListLocalGroup.cs +++ b/Editor/Mono/ObjectListLocalGroup.cs @@ -7,11 +7,8 @@ using UnityEditor.VersionControl; using UnityEditorInternal; using UnityEditorInternal.VersionControl; -using System.Collections; using System.Collections.Generic; -using System.Linq; using Math = System.Math; -using IndexOutOfRangeException = System.IndexOutOfRangeException; using AssetReference = UnityEditorInternal.InternalEditorUtility.AssetReference; namespace UnityEditor @@ -117,6 +114,7 @@ private void InitAssetPreviewIgnoreList() m_AssetPreviewIgnoreList.Add(typeof(LightingSettings)); m_AssetExtensionsPreviewIgnoreList.Add(".index"); + m_AssetExtensionsPreviewIgnoreList.Add(".vfx"); } //Use this to add the specific types that needs to ignored for AssetPreview image generation. @@ -835,6 +833,8 @@ void DrawItem(Rect position, FilteredHierarchy.FilterResult filterItem, BuiltinR } else // Icon grid { + Texture previewImage = null; + // Get icon bool drawDropShadow = false; if (string.IsNullOrEmpty(assetReference.guid) && m_Owner.GetCreateAssetUtility().instanceID == assetReference.instanceID && m_Owner.GetCreateAssetUtility().icon != null) @@ -849,17 +849,16 @@ void DrawItem(Rect position, FilteredHierarchy.FilterResult filterItem, BuiltinR else { // Check for asset preview - Texture image = null; bool shouldGetAssetPreview = ShouldGetAssetPreview(assetReference.instanceID); if (shouldGetAssetPreview) { if (assetReference.instanceID != 0) - image = AssetPreview.GetAssetPreview(assetReference.instanceID, m_Owner.GetAssetPreviewManagerID()); + previewImage = AssetPreview.GetAssetPreview(assetReference.instanceID, m_Owner.GetAssetPreviewManagerID()); else if (!string.IsNullOrEmpty(assetReference.guid)) - image = AssetPreview.GetAssetPreviewFromGUID(assetReference.guid, m_Owner.GetAssetPreviewManagerID()); + previewImage = AssetPreview.GetAssetPreviewFromGUID(assetReference.guid, m_Owner.GetAssetPreviewManagerID()); } - m_Content.image = image; + m_Content.image = previewImage; if (m_Content.image != null) drawDropShadow = true; @@ -942,11 +941,60 @@ void DrawItem(Rect position, FilteredHierarchy.FilterResult filterItem, BuiltinR if (isDropTarget) Styles.resultsLabel.Draw(new Rect(labelRect.x - 10, labelRect.y, labelRect.width + 20, labelRect.height), GUIContent.none, true, true, false, false); + + Texture2D typeIcon = null; + if (filterItem != null && previewImage != null) + { + Type type = InternalEditorUtility.GetTypeWithoutLoadingObject(filterItem.instanceID); + + if (type != typeof(Texture2D)) + { + typeIcon = filterItem.icon; + } + } + + if (builtinResource != null) + { + Type type = InternalEditorUtility.GetTypeWithoutLoadingObject(builtinResource.m_InstanceID); + + if (type != typeof(Texture2D)) + { + typeIcon = AssetPreview.GetMiniTypeThumbnail(type); + } + } + labeltext = m_Owner.GetCroppedLabelText(assetReference, labeltext, orgPosition.width); - var labelNewRect = Styles.resultsGridLabel.CalcSizeWithConstraints(GUIContent.Temp(labeltext), orgPosition.size); + var labelNewRect = Styles.resultsGridLabel.CalcSizeWithConstraints(GUIContent.Temp(labeltext, typeIcon), orgPosition.size); + labelNewRect.x += Styles.resultsGridLabel.padding.horizontal; labelRect.x = orgPosition.x + (orgPosition.width - labelNewRect.x) / 2.0f; labelRect.width = labelNewRect.x; - Styles.resultsGridLabel.Draw(labelRect, labeltext, false, false, selected, m_Owner.HasFocus()); + + Styles.resultsGridLabel.alignment = TextAnchor.MiddleCenter; + Styles.resultsGridLabel.Draw(labelRect, GUIContent.Temp(labeltext, typeIcon), false, false, selected, m_Owner.HasFocus()); + + // We only need to set the tooltip once, and not for every item. + if (labelRect.Contains(Event.current.mousePosition)) + { + string tooltip = null; + + if (filterItem != null) + { + //We use GetAssetPath to have the file extension as well + string path = AssetDatabase.GetAssetPath(filterItem.instanceID); + tooltip = path.Substring(path.LastIndexOf('/') + 1); + } + else if (builtinResource != null) + { + //We have a "None" item in the ObjectSelector that has a 0 instanceID + if (builtinResource.m_InstanceID != 0) + tooltip = builtinResource.m_Name + "\n" + "(Built-in Resource)"; + } + + if (tooltip != null) + { + GUI.Label(labelRect, GUIContent.Temp("", tooltip)); + } + } } } diff --git a/Editor/Mono/ObjectNames.cs b/Editor/Mono/ObjectNames.cs index b40fbc23fb..f90bfb6a70 100644 --- a/Editor/Mono/ObjectNames.cs +++ b/Editor/Mono/ObjectNames.cs @@ -49,7 +49,7 @@ public static bool TryGet(Type objectType, out string title) } } - private static string GetObjectTypeName([NotNull] Object o) + private static string GetObjectTypeName([NotNull] Object o, bool multiObjectEditing = false) { if (o is GameObject) return o.name; @@ -72,6 +72,9 @@ private static string GetObjectTypeName([NotNull] Object o) var meshfilter = o as MeshFilter; if (meshfilter) { + if (multiObjectEditing) + return "MeshFilter"; + var mesh = meshfilter.sharedMesh; return (mesh ? mesh.name : L10n.Tr("[none]")) + " (MeshFilter)"; } @@ -102,8 +105,7 @@ private static string GetObjectTypeName([NotNull] Object o) return o.name + " (" + o.GetType().Name + ")"; } - // Inspector title for an object. - public static string GetInspectorTitle(Object obj) + public static string GetInspectorTitle(Object obj, bool multiObjectEditing) { if (obj == null && (object)obj != null && (obj is MonoBehaviour || obj is ScriptableObject)) return L10n.Tr(" (Script)"); @@ -113,7 +115,7 @@ public static string GetInspectorTitle(Object obj) string title; if (!InspectorTitles.TryGet(obj.GetType(), out title)) - title = NicifyVariableName(GetObjectTypeName(obj)); + title = NicifyVariableName(GetObjectTypeName(obj, multiObjectEditing)); if (Attribute.IsDefined(obj.GetType(), typeof(ObsoleteAttribute))) title += L10n.Tr(" (Deprecated)"); @@ -121,6 +123,12 @@ public static string GetInspectorTitle(Object obj) return title; } + // Inspector title for an object. + public static string GetInspectorTitle(Object obj) + { + return GetInspectorTitle(obj, false); + } + // Like GetClassName but handles folders, scenes, GUISkins, and other default assets as separate types. internal static string GetTypeName(Object obj) { @@ -137,7 +145,7 @@ internal static string GetTypeName(Object obj) return "Folder"; else if (obj.GetType() == typeof(Object)) return System.IO.Path.GetExtension(pathLower) + " File"; - return ObjectNames.GetClassName(obj); + return obj.GetType().Name; } [Obsolete("Please use NicifyVariableName instead")] diff --git a/Editor/Mono/ObjectPool/PoolManager.cs b/Editor/Mono/ObjectPool/PoolManager.cs new file mode 100644 index 0000000000..ad66b8bfe1 --- /dev/null +++ b/Editor/Mono/ObjectPool/PoolManager.cs @@ -0,0 +1,29 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +namespace UnityEditor.ObjectPool +{ + [InitializeOnLoad] + static class PoolManager + { + static PoolManager() => EditorApplication.playModeStateChanged += OnEditorStateChange; + + static void OnEditorStateChange(PlayModeStateChange stateChange) + { + if(!EditorSettings.enterPlayModeOptionsEnabled + || !EditorSettings.enterPlayModeOptions.HasFlag(EnterPlayModeOptions.DisableDomainReload)) + { + return; + } + + switch (stateChange) + { + case PlayModeStateChange.EnteredEditMode: + case PlayModeStateChange.EnteredPlayMode: + UnityEngine.Pool.PoolManager.Reset(); + break; + } + } + } +} diff --git a/Editor/Mono/ObjectSelector.cs b/Editor/Mono/ObjectSelector.cs index 734a1edc1d..59fc5399bf 100644 --- a/Editor/Mono/ObjectSelector.cs +++ b/Editor/Mono/ObjectSelector.cs @@ -78,6 +78,7 @@ static class Styles ObjectTreeForSelector m_ObjectTreeWithSearch = new ObjectTreeForSelector(); UnityObject m_ObjectBeingEdited; SerializedProperty m_EditedProperty; + bool m_ShowNoneItem; bool m_SelectionCancelled; int m_LastSelectedInstanceId = 0; @@ -116,6 +117,11 @@ public List allowedInstanceIDs get { return m_AllowedIDs; } } + public UnityObject objectBeingEdited + { + get { return m_ObjectBeingEdited; } + } + // get an existing ObjectSelector or create one static ObjectSelector s_SharedObjectSelector = null; public static ObjectSelector get @@ -224,6 +230,8 @@ public void SetupPreview() void ListAreaItemSelectedCallback(bool doubleClicked) { m_LastSelectedInstanceId = GetInternalSelectedInstanceID(); + m_ListArea.m_SelectedObjectIcon = AssetDatabase.GetCachedIcon(AssetDatabase.GetAssetPath(m_LastSelectedInstanceId)); + if (doubleClicked) { ItemWasDoubleClicked(); @@ -423,13 +431,18 @@ internal void Show(Type[] requiredTypes, SerializedProperty property, bool allow Show(obj, requiredTypes, objectBeingEdited, allowSceneObjects, allowedInstanceIDs, onObjectSelectorClosed, onObjectSelectedUpdated); } - internal void Show(UnityObject obj, Type requiredType, UnityObject objectBeingEdited, bool allowSceneObjects, List allowedInstanceIDs = null, Action onObjectSelectorClosed = null, Action onObjectSelectedUpdated = null) + internal void Show(UnityObject obj, Type requiredType, UnityObject objectBeingEdited, bool allowSceneObjects, List allowedInstanceIDs = null, Action onObjectSelectorClosed = null, Action onObjectSelectedUpdated = null, bool showNoneItem = true) { - Show(obj, new Type[] { requiredType }, objectBeingEdited, allowSceneObjects, allowedInstanceIDs, onObjectSelectorClosed, onObjectSelectedUpdated); + Show(obj, new Type[] { requiredType }, objectBeingEdited, allowSceneObjects, allowedInstanceIDs, onObjectSelectorClosed, onObjectSelectedUpdated, showNoneItem); } - internal void Show(UnityObject obj, Type[] requiredTypes, UnityObject objectBeingEdited, bool allowSceneObjects, List allowedInstanceIDs = null, Action onObjectSelectorClosed = null, Action onObjectSelectedUpdated = null) + internal void Show(UnityObject obj, Type[] requiredTypes, UnityObject objectBeingEdited, bool allowSceneObjects, List allowedInstanceIDs = null, Action onObjectSelectorClosed = null, Action onObjectSelectedUpdated = null, bool showNoneItem = true) { + // We can't rely on the fact that the window will always be closed when we call Show. For example, + // if a user clicks on multiple object fields without closing the window first, there is no guarantee + // that the auxiliary window will close before the click event is processed. And since closing the window + // cleans up the undo state, we have to force close the window if it wasn't already closed. + CloseOpenedWindow(); m_ObjectSelectorReceiver = null; m_AllowSceneObjects = allowSceneObjects; m_IsShowingAssets = true; @@ -438,6 +451,7 @@ internal void Show(UnityObject obj, Type[] requiredTypes, UnityObject objectBein m_ObjectBeingEdited = objectBeingEdited; m_LastSelectedInstanceId = obj?.GetInstanceID() ?? 0; m_SelectionCancelled = false; + m_ShowNoneItem = showNoneItem; m_OnObjectSelectorClosed = onObjectSelectorClosed; m_OnObjectSelectorUpdated = onObjectSelectedUpdated; @@ -538,10 +552,8 @@ internal void Show(UnityObject obj, Type[] requiredTypes, UnityObject objectBein var shouldRepositionWindow = m_Parent != null; ShowWithMode(ShowMode.AuxWindow); - string text = "Select " + (requiredTypes[0] == null ? m_RequiredTypes[0] : requiredTypes[0].Name); - for (int i = 1; i < requiredTypes.Length; i++) - text += (i == requiredTypes.Length - 1 ? " or " : ", ") + (requiredTypes[i] == null ? m_RequiredTypes[i] : requiredTypes[i].Name); - titleContent = EditorGUIUtility.TrTextContent(text); + + titleContent = EditorGUIUtility.TrTextContent(GenerateTitleContent(requiredTypes, m_RequiredTypes)); // Deal with window size if (shouldRepositionWindow) @@ -591,6 +603,38 @@ internal void Show(UnityObject obj, Type[] requiredTypes, UnityObject objectBein } } + void CloseOpenedWindow() + { + // We check m_ModalUndoGroup as it is the only value that will be reliably set when the window is open + // and unset when the window is closed. Checking m_OnObjectSelectorClosed or m_ObjectSelectorReceiver is not enough + // as they are not always set. + if (m_ModalUndoGroup >= 0) + { + if (ObjectSelectorSearch.HasEngineOverride()) + { + m_SearchSessionHandler.CloseSelector(); + } + else + { + NotifySelectorClosed(false); + } + } + } + + internal static string GenerateTitleContent(Type[] requiredTypes, string[] requiredTypeStrings) + { + var typeName = requiredTypes[0] == null ? requiredTypeStrings[0] : requiredTypes[0].Name; + var text = "Select " + ObjectNames.NicifyVariableName(typeName); + + for (int i = 1; i < requiredTypes.Length; i++) + { + typeName = requiredTypes[i] == null ? requiredTypeStrings[i] : requiredTypes[i].Name; + text += (i == requiredTypes.Length - 1 ? " or " : ", ") + ObjectNames.NicifyVariableName(typeName); + } + + return text; + } + void ItemWasDoubleClicked() { SendEvent(ObjectSelectorSelectionDoneCommand, false); @@ -621,7 +665,7 @@ void InitIfNeeded() if (m_ListArea == null) { - m_ListArea = new ObjectListArea(m_ListAreaState, this, true); + m_ListArea = new ObjectListArea(m_ListAreaState, this, m_ShowNoneItem); m_ListArea.allowDeselection = false; m_ListArea.allowDragging = false; m_ListArea.allowFocusRendering = false; @@ -890,7 +934,7 @@ void DrawObjectIcon(Rect position, Texture icon) size = icon.width * 2; FilterMode temp = icon.filterMode; - icon.filterMode = FilterMode.Point; + icon.filterMode = FilterMode.Bilinear; GUI.DrawTexture(new Rect(position.x + ((int)position.width - size) / 2, position.y + ((int)position.height - size) / 2, size, size), icon, ScaleMode.ScaleToFit); icon.filterMode = temp; } @@ -1074,12 +1118,16 @@ void NotifySelectorClosed(bool exitGUI) if (m_ObjectSelectorReceiver != null) { m_ObjectSelectorReceiver.OnSelectionClosed(currentObject); + m_ObjectSelectorReceiver = null; } m_OnObjectSelectorClosed?.Invoke(currentObject); + m_OnObjectSelectorClosed = null; + m_OnObjectSelectorUpdated = null; SendEvent(ObjectSelectorClosedCommand, exitGUI); Undo.CollapseUndoOperations(m_ModalUndoGroup); + m_ModalUndoGroup = -1; } } } diff --git a/Editor/Mono/Overlays/Overlay.cs b/Editor/Mono/Overlays/Overlay.cs index b400f04b71..f3298b69a7 100644 --- a/Editor/Mono/Overlays/Overlay.cs +++ b/Editor/Mono/Overlays/Overlay.cs @@ -281,6 +281,52 @@ protected internal virtual Layout supportedLayouts } } + sealed class GlobalMouseBehaviourForOverlays : MouseManipulator + { + Overlay m_Overlay; + + public GlobalMouseBehaviourForOverlays(Overlay overlay) + { + m_Overlay = overlay; + } + + protected override void RegisterCallbacksOnTarget() + { + target.RegisterCallback(OnMouseDownTrickleDown, TrickleDown.TrickleDown); + target.RegisterCallback(OnMouseDownBubbleUp, TrickleDown.NoTrickleDown); + target.RegisterCallback(OnMouseUp); + target.RegisterCallback(OnMouseMove); + } + + protected override void UnregisterCallbacksFromTarget() + { + target.UnregisterCallback(OnMouseDownTrickleDown, TrickleDown.TrickleDown); + target.UnregisterCallback(OnMouseDownBubbleUp, TrickleDown.NoTrickleDown); + target.UnregisterCallback(OnMouseUp); + target.UnregisterCallback(OnMouseMove); + } + + void OnMouseDownTrickleDown(MouseDownEvent e) + { + m_Overlay.BringToFront(); + } + + void OnMouseDownBubbleUp(MouseDownEvent e) + { + e.StopPropagation(); + } + + void OnMouseUp(MouseUpEvent e) + { + e.StopPropagation(); + } + + void OnMouseMove(MouseMoveEvent e) + { + e.StopPropagation(); + } + } + internal VisualElement rootVisualElement { get @@ -294,6 +340,7 @@ internal VisualElement rootVisualElement m_RootVisualElement.name = m_RootVisualElementName; m_RootVisualElement.usageHints = UsageHints.DynamicTransform; m_RootVisualElement.AddToClassList(ussClassName); + m_RootVisualElement.AddManipulator(new GlobalMouseBehaviourForOverlays(this)); var dragger = new OverlayDragger(this); var contextClick = new ContextualMenuManipulator(BuildContextMenu); @@ -676,7 +723,8 @@ void BuildContextMenu(ContextualMenuPopulateEvent evt) menu.AppendSeparator(); var layouts = supportedLayouts; - if ((layouts & Layout.Panel) != 0) + // Panel layout is always supported by default, we only add this option in the menu if other options are available + if ((layouts & Layout.HorizontalToolbar) != 0 || (layouts & Layout.VerticalToolbar) != 0) menu.AppendAction(L10n.Tr("Panel"), action => { layout = Layout.Panel; collapsed = false; }, GetMenuItemState(layout == Layout.Panel)); if ((layouts & Layout.HorizontalToolbar) != 0) menu.AppendAction(L10n.Tr("Horizontal"), action => { layout = Layout.HorizontalToolbar; collapsed = false; }, GetMenuItemState(layout == Layout.HorizontalToolbar)); @@ -704,6 +752,7 @@ internal void Initialize(string _id, string _uss, string _display) [EditorBrowsable(EditorBrowsableState.Never)] internal void ApplySaveData(SaveData data) { + rootVisualElement.style.display = DisplayStyle.None; floatingSnapCorner = data.snapCorner; m_Floating = data.floating; m_Collapsed = data.collapsed; diff --git a/Editor/Mono/Overlays/OverlayAttribute.cs b/Editor/Mono/Overlays/OverlayAttribute.cs index 3803de4130..6bebb6bd7a 100644 --- a/Editor/Mono/Overlays/OverlayAttribute.cs +++ b/Editor/Mono/Overlays/OverlayAttribute.cs @@ -76,7 +76,7 @@ public Layout defaultLayout public OverlayAttribute() { m_EditorWindowType = null; - m_DefaultDisplay = false; + m_DefaultDisplay = true; m_Id = null; m_DisplayName = null; m_UssName = null; diff --git a/Editor/Mono/Overlays/OverlayCanvas.cs b/Editor/Mono/Overlays/OverlayCanvas.cs index f2a2945026..df912f2a5a 100644 --- a/Editor/Mono/Overlays/OverlayCanvas.cs +++ b/Editor/Mono/Overlays/OverlayCanvas.cs @@ -9,6 +9,7 @@ using System.Reflection; using UnityEngine; using UnityEngine.Profiling; + using UnityEngine.UIElements; namespace UnityEditor.Overlays @@ -31,6 +32,7 @@ class SaveData : IEquatable public Vector2 size; public bool sizeOverriden; + public SaveData() { } public SaveData(SaveData other) @@ -68,6 +70,7 @@ public SaveData(Overlay overlay, int indexInContainer = k_InvalidIndex) snapCorner = overlay.floatingSnapCorner; snapOffset = overlay.floatingSnapOffset - overlay.m_SnapOffsetDelta; snapOffsetDelta = overlay.m_SnapOffsetDelta; + } public bool Equals(SaveData other) @@ -86,6 +89,7 @@ public bool Equals(SaveData other) && id == other.id && index == other.index && layout == other.layout; + } public override bool Equals(object obj) @@ -111,9 +115,11 @@ public override int GetHashCode() hashCode = (hashCode * 397) ^ (id != null ? id.GetHashCode() : 0); hashCode = (hashCode * 397) ^ index; hashCode = (hashCode * 397) ^ (int)layout; + return hashCode; } } + } //Dock position within container @@ -206,16 +212,23 @@ internal OverlayContainer GetDockZoneContainer(DockZone zone) return null; } + bool m_MouseInCurrentCanvas = false; OverlayMenu m_Menu; internal string lastAppliedPresetName => m_LastAppliedPresetName; List m_Overlays = new List(); + List m_TransientOverlays = new(); + internal static string k_DefaultPresetName = "Default"; [SerializeField] - string m_LastAppliedPresetName = "Default"; + string m_LastAppliedPresetName = k_DefaultPresetName; [SerializeField] List m_SaveData = new List(); + + [SerializeField] + bool m_OverlaysVisible = true; + VisualElement m_RootVisualElement; internal EditorWindow containerWindow { get; set; } @@ -241,6 +254,8 @@ internal OverlayContainer GetDockZoneContainer(DockZone zone) internal IEnumerable overlays => m_Overlays.AsReadOnly(); + internal IEnumerable transientOverlays => m_TransientOverlays; + VisualElement m_WindowRoot; internal VisualElement windowRoot => m_WindowRoot; @@ -253,6 +268,8 @@ internal OverlayCanvas() { } internal void SetOverlaysEnabled(bool visible) { + m_OverlaysVisible = visible; + if (visible == overlaysEnabled) return; @@ -307,6 +324,7 @@ VisualElement CreateRoot() else defaultContainer = container; } + } m_OriginGhost = new VisualElement { name = "origin-ghost"}; @@ -322,6 +340,9 @@ VisualElement CreateRoot() ve.RegisterCallback(OnDetachedFromPanel); m_WindowRoot = ve.Q("overlay-window-root"); + + SetOverlaysEnabled(m_OverlaysVisible); + return ve; } @@ -342,11 +363,15 @@ void OnAttachedToPanel(AttachToPanelEvent evt) { //this is used to clamp overlays to floating container bounds. floatingContainer.RegisterCallback(GeometryChanged); + rootVisualElement.RegisterCallback(OnMouseEnter); + rootVisualElement.RegisterCallback(OnMouseLeave); } void OnDetachedFromPanel(DetachFromPanelEvent evt) { floatingContainer.UnregisterCallback(GeometryChanged); + rootVisualElement.UnregisterCallback(OnMouseEnter); + rootVisualElement.UnregisterCallback(OnMouseLeave); } internal void OnContainerWindowDisabled() @@ -355,6 +380,16 @@ internal void OnContainerWindowDisabled() overlay.OnWillBeDestroyed(); } + void OnMouseEnter(MouseEnterEvent evt) + { + m_MouseInCurrentCanvas = true; + } + + void OnMouseLeave(MouseLeaveEvent evt) + { + m_MouseInCurrentCanvas = false; + } + internal Rect ClampToOverlayWindow(Rect rect) { return ClampRectToBounds(rootVisualElement.localBound, rect); @@ -422,13 +457,15 @@ internal void HideHoveredOverlay() internal void ShowMenu(bool show, bool atMousePosition = true) { if (show && !menuVisible) - menu.Show(overlays, atMousePosition); + menu.Show(atMousePosition && m_MouseInCurrentCanvas); else if (!show) menu.Hide(); } internal bool menuVisible => menu.isShown; + internal bool IsTransient(Overlay overlay) => m_TransientOverlays.Contains(overlay); + internal void Initialize(EditorWindow window) { Profiler.BeginSample("OverlayCanvas.Initialize"); @@ -475,6 +512,7 @@ internal void ShowOriginGhost(Overlay overlay) m_OriginGhost.style.width = overlay.rootVisualElement.layout.width; m_OriginGhost.style.height = overlay.rootVisualElement.layout.height; overlay.container?.Insert(overlay.container.IndexOf(overlay.rootVisualElement), m_OriginGhost); + } internal void UpdateGhostHover(bool hovered) @@ -520,6 +558,7 @@ public void OnBeforeSerialize() if (!after[i].dontSaveInLayout) WriteOrReplaceSaveData(after[i], i); } + } } } @@ -535,6 +574,11 @@ internal void CopySaveData(out SaveData[] saveData) saveData[i] = new SaveData(saveData[i]); } + internal void SetLastAppliedPresetName(string name) + { + m_LastAppliedPresetName = name; + } + internal void ApplyPreset(OverlayPreset preset) { if (!preset.CanApplyToWindow(containerWindow.GetType())) @@ -544,7 +588,7 @@ internal void ApplyPreset(OverlayPreset preset) return; } - m_LastAppliedPresetName = preset.name; + SetLastAppliedPresetName(preset.name); ApplySaveData(preset.saveData); } @@ -569,20 +613,31 @@ internal void Rebuild() RestoreOverlays(); } - public void Add(Overlay overlay, bool show = true) + [Obsolete("OverlayCanvas.Add(Overlay, bool) is obsolete, use Add(Overlay) overload.", false)] + public void Add(Overlay overlay, bool show) + { + Add(overlay); + overlay.displayed = show; + } + + // Overlays added to the canvas through this method are considered "temporary" and will be shown in separate + // category in the menu. Persistent overlays (i.e., overlays registered through OverlayAttribute) are not + // created using this method. + public void Add(Overlay overlay) { if(m_Overlays.Contains(overlay)) return; overlay.canvas?.Remove(overlay); - AddOverlay(overlay); + AddOverlay(overlay, true); RestoreOverlay(overlay); - overlay.displayed = show; } public bool Remove(Overlay overlay) { if (!m_Overlays.Remove(overlay)) return false; + m_TransientOverlays.Remove(overlay); + overlay.OnWillBeDestroyed(); WriteOrReplaceSaveData(overlay); overlay.container?.RemoveOverlay(overlay); overlay.canvas = null; @@ -595,8 +650,14 @@ public bool Remove(Overlay overlay) } // AddOverlay just registers the Overlay with Canvas. It does not init save data or add to a valid container. - void AddOverlay(Overlay overlay) + void AddOverlay(Overlay overlay, bool transient = false) { + // Don't show an error when attempting to add a null overlay. This means that a persistent Overlay type was + // removed from a project, or moved to a transient overlay. In either case, the user can't do anything + // meaningful with this information. + if (overlay == null) + return; + if(!OverlayUtilities.EnsureValidId(m_Overlays, overlay)) { Debug.LogError($"An overlay with id \"{overlay.id}\" was already registered to window " + @@ -606,6 +667,8 @@ void AddOverlay(Overlay overlay) overlay.canvas = this; m_Overlays.Add(overlay); + if (transient) + m_TransientOverlays.Add(overlay); m_OverlaysByVE[overlay.rootVisualElement] = overlay; overlay.rootVisualElement.RegisterCallback(OnMouseEnterOverlay); overlay.rootVisualElement.RegisterCallback(OnMouseLeaveOverlay); @@ -658,6 +721,7 @@ SaveData FindSaveData(Overlay overlay) data.snapCorner = SnapCorner.TopLeft; data.layout = attrib.defaultLayout; data.displayed = attrib.defaultDisplay; + } } @@ -705,9 +769,14 @@ void RestoreOverlays() if (containers == null) return; - // Clear existing Overlays + // Clear OverlayContainer instances and set Overlay.displayed to false. RestoreOverlay expects that Overlay + // is not present in VisualElement hierarchy. foreach (var overlay in overlays) + { + overlay.displayed = false; overlay.container?.RemoveOverlay(overlay); + } + // Three steps to reinitialize a canvas: // 1. Find and associate all Overlays with SaveData (using default SaveData if necessary) @@ -723,5 +792,6 @@ void RestoreOverlays() afterOverlaysInitialized?.Invoke(); } + } } diff --git a/Editor/Mono/Overlays/OverlayMenu.cs b/Editor/Mono/Overlays/OverlayMenu.cs index 8834fd4e18..5ac90e819f 100644 --- a/Editor/Mono/Overlays/OverlayMenu.cs +++ b/Editor/Mono/Overlays/OverlayMenu.cs @@ -3,7 +3,6 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; -using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.UIElements; @@ -11,7 +10,7 @@ namespace UnityEditor.Overlays { - internal class OverlayMenu : VisualElement + class OverlayMenu : VisualElement { static bool s_KeepAlive = false; static Action s_DelayUntilCanHide; @@ -21,6 +20,7 @@ internal class OverlayMenu : VisualElement static VisualTreeAsset s_TreeAsset; readonly ScrollView m_ListRoot; + int m_ListContentsHash; readonly Toggle m_Toggle; TextElement m_DropdownText; readonly Button m_Dropdown; @@ -63,7 +63,6 @@ public OverlayMenu(OverlayCanvas canvas) canvas.afterOverlaysInitialized += () => { m_DropdownText.text = canvas.lastAppliedPresetName; - RebuildMenu(canvas.overlays); }; RegisterCallback(evt => @@ -78,9 +77,7 @@ public OverlayMenu(OverlayCanvas canvas) }); RegisterCallback(OnCustomStyleResolved); - canvas.overlaysEnabledChanged += OnOverlayEnabledChanged; - focusable = true; Hide(); } @@ -116,25 +113,62 @@ void DropdownOnClicked() menu.DropDown(m_Dropdown.worldBound); } - void RebuildMenu(IEnumerable overlays) + bool RebuildMenu() { m_ListRoot.Clear(); + var previousHash = m_ListContentsHash; + m_ListContentsHash = 13; - foreach (var overlay in overlays) + foreach (var overlay in m_Canvas.overlays) { - if (!overlay.userControlledVisibility || !overlay.hasMenuEntry) + if (!overlay.userControlledVisibility || !overlay.hasMenuEntry || m_Canvas.IsTransient(overlay)) continue; + m_ListRoot.Add(new OverlayMenuItem() { overlay = overlay }); + m_ListContentsHash = Tuple.CombineHashCodes(overlay.GetHashCode(), m_ListContentsHash); + } - var item = new OverlayMenuItem(); - item.overlay = overlay; - m_ListRoot.Add(item); + if (m_Canvas.transientOverlays.Any()) + { + var separator = new VisualElement() { name = "Separator" }; + separator.AddToClassList("unity-separator"); + m_ListRoot.Add(separator); + } + + foreach (var overlay in m_Canvas.transientOverlays) + { + m_ListRoot.Add(new OverlayMenuItem() { overlay = overlay }); + m_ListContentsHash = Tuple.CombineHashCodes(overlay.GetHashCode(), m_ListContentsHash); } + + return m_ListContentsHash != previousHash; } - public void Show(IEnumerable overlays, bool atMousePosition) + public void Show(bool atMousePosition = true) { - var parentBounds = parent.layout; + // If the contents of the scroll view changed, we need to wait until the layout is recalculated before + // placing the popup overlay. If the contents have _not_ changed, this event will not be invoked and thus + // we need to immediately show the popup. + if(RebuildMenu()) + m_ListRoot.RegisterCallback(atMousePosition ? PresentAtMouse : PresentAtCenter); + else + Present(atMousePosition); + } + void PresentAtCenter(GeometryChangedEvent _) + { + m_ListRoot.UnregisterCallback(PresentAtCenter); + Present(false); + } + + void PresentAtMouse(GeometryChangedEvent _) + { + m_ListRoot.UnregisterCallback(PresentAtMouse); + Present(true); + } + + void Present(bool atMousePosition) + { + var parentBounds = parent.layout; var currentLayout = layout; var currentSize = new Vector2(currentLayout.width, currentLayout.height); @@ -172,48 +206,37 @@ public void Show(IEnumerable overlays, bool atMousePosition) // Change `visibility` instead of `display` to ensure that menu size is // recomputed even when it is not shown. style.visibility = Visibility.Visible; - OnOverlayEnabledChanged(m_Canvas.overlaysEnabled); Focus(); } - void ResolveSizeLimits(out float minWidth, out float minHeight, out float maxWidth, out float maxHeight) + internal void AdjustToParentSize() { - if (m_InitialMinWidth.keyword == StyleKeyword.None || m_InitialMinWidth.keyword == StyleKeyword.Auto) - { - minWidth = 0; - } - else - { - minWidth = m_InitialMinWidth.value; - } + // If size limits have changed since the last time this method was called, + // a re-computation of the layout will occur because the code below will update + // some style properties. - if (m_InitialMinHeight.keyword == StyleKeyword.None || m_InitialMinHeight.keyword == StyleKeyword.Auto) - { - minHeight = 0; - } - else - { - minHeight = m_InitialMinHeight.value; - } + // Calling this as soon as the parent size is modified ensures that + // the menu width and height will be appropriate when comes the time to + // display the menu. - if (m_InitialMaxWidth.keyword == StyleKeyword.None) - { - maxWidth = float.MaxValue; - } - else - { - maxWidth = m_InitialMaxWidth.value; - } + float minWidth = m_InitialMinWidth.keyword == StyleKeyword.None || + m_InitialMinWidth.keyword == StyleKeyword.Auto + ? 0 + : m_InitialMinWidth.value; - if (m_InitialMaxHeight.keyword == StyleKeyword.None) - { - maxHeight = float.MaxValue; - } - else - { - maxHeight = m_InitialMaxHeight.value; - } + float minHeight = m_InitialMinHeight.keyword == StyleKeyword.None || + m_InitialMinHeight.keyword == StyleKeyword.Auto + ? 0 + : m_InitialMinHeight.value; + + float maxWidth = m_InitialMaxWidth.keyword == StyleKeyword.None + ? float.MaxValue + : m_InitialMaxWidth.value; + + float maxHeight = m_InitialMaxHeight.keyword == StyleKeyword.None + ? float.MaxValue + : m_InitialMaxHeight.value; var parentBounds = parent.layout; @@ -221,20 +244,7 @@ void ResolveSizeLimits(out float minWidth, out float minHeight, out float maxWid minHeight = Mathf.Min(parentBounds.height, minHeight); maxWidth = Mathf.Min(parentBounds.width, maxWidth); - maxHeight = Mathf.Min(parentBounds.height, maxHeight);; - } - - internal void AdjustToParentSize() - { - // If size limits have changed since the last time this method was called, - // a recomputation of the layout will occur because the code below will update - // some style properties. - - // Calling this as soon as the parent size is modified ensures that - // the menu width and height will be appropriate when comes the time to - // display the menu. - - ResolveSizeLimits(out var minWidth, out var minHeight, out var maxWidth, out var maxHeight); + maxHeight = Mathf.Min(parentBounds.height, maxHeight); style.minWidth = minWidth; style.minHeight = minHeight; @@ -245,7 +255,6 @@ internal void AdjustToParentSize() public void Hide() { style.visibility = Visibility.Hidden; - var overlays = m_ListRoot.Children().OfType(); foreach (var overlay in overlays) overlay.overlay.SetHighlightEnabled(false); diff --git a/Editor/Mono/Overlays/OverlayMenuItem.cs b/Editor/Mono/Overlays/OverlayMenuItem.cs index 1895b8bb01..8d177405f2 100644 --- a/Editor/Mono/Overlays/OverlayMenuItem.cs +++ b/Editor/Mono/Overlays/OverlayMenuItem.cs @@ -34,7 +34,9 @@ public Overlay overlay m_Overlay = value; name = overlay.rootVisualElement.name; - m_Title.text = overlay.displayName; + m_Title.text = string.IsNullOrEmpty(overlay.displayName) + ? m_Overlay.GetType().Name + : overlay.displayName; UpdateIconVisibilityState(); diff --git a/Editor/Mono/Overlays/OverlayPresetManager.cs b/Editor/Mono/Overlays/OverlayPresetManager.cs index 9feddb17e9..3cc9b4da89 100644 --- a/Editor/Mono/Overlays/OverlayPresetManager.cs +++ b/Editor/Mono/Overlays/OverlayPresetManager.cs @@ -351,6 +351,8 @@ public static void GenerateMenu(GenericMenu menu, string pathPrefix, EditorWindo menu.AddItem(EditorGUIUtility.TrTextContent($"{pathPrefix}Delete Preset/{preset.name}"), false, () => { DeletePreset(preset); + window.overlayCanvas.SetLastAppliedPresetName(OverlayCanvas.k_DefaultPresetName); + window.overlayCanvas.afterOverlaysInitialized.Invoke(); }); } @@ -363,6 +365,7 @@ public static void GenerateMenu(GenericMenu menu, string pathPrefix, EditorWindo { RevertPreferencesPresetsToDefault(); ReloadAllPresets(); + window.overlayCanvas.ApplyPreset(GetDefaultPreset(window.GetType())); } }); } diff --git a/Editor/Mono/Overlays/OverlayResizer.cs b/Editor/Mono/Overlays/OverlayResizer.cs index 9bb742c3a2..b01da37b5e 100644 --- a/Editor/Mono/Overlays/OverlayResizer.cs +++ b/Editor/Mono/Overlays/OverlayResizer.cs @@ -13,6 +13,7 @@ class OverlayResizerGroup : VisualElement { const float k_CornerSize = 8; const float k_SideSize = 6; + const float k_MinDistanceFromEdge = 10; [Flags] enum Direction @@ -210,6 +211,7 @@ public OverlayResizerGroup(Overlay overlay) overlay.containerChanged += OnOverlayContainerChanged; overlay.layoutChanged += OnOverlayLayoutChanged; + m_Overlay.rootVisualElement.RegisterCallback(OnOverlayGeometryChanged); UpdateResizerVisibility(); } @@ -223,6 +225,11 @@ void OnOverlayContainerChanged(OverlayContainer container) UpdateResizerVisibility(); } + void OnOverlayGeometryChanged(GeometryChangedEvent evt) + { + UpdateResizerVisibility(); + } + bool ContainerCanShowResizer(OverlayResizer resizer) { var container = m_Overlay.container; @@ -273,6 +280,26 @@ void UpdateResizerVisibility() hide |= Mathf.Approximately(m_Overlay.minSize.y, m_Overlay.maxSize.y); } + if (m_Overlay.canvas != null) + { + var canvas = m_Overlay.canvas.rootVisualElement; + var canvasRect = canvas.rect; + var overlayRect = canvas.WorldToLocal(m_Overlay.rootVisualElement.worldBound); + + if (resizer.HasDirection(Direction.Left)) + hide |= overlayRect.xMin <= k_MinDistanceFromEdge; + + if (resizer.HasDirection(Direction.Right)) + hide |= overlayRect.xMax >= canvasRect.xMax - k_MinDistanceFromEdge; + + if (resizer.HasDirection(Direction.Top)) + hide |= overlayRect.yMin <= k_MinDistanceFromEdge; + + if (resizer.HasDirection(Direction.Bottom)) + hide |= overlayRect.yMax >= canvasRect.yMax - k_MinDistanceFromEdge; + } + + resizer.style.display = hide ? DisplayStyle.None : DisplayStyle.Flex; } } diff --git a/Editor/Mono/Overlays/OverlayUtilities.cs b/Editor/Mono/Overlays/OverlayUtilities.cs index 6ad80b3f8f..b02b735edc 100644 --- a/Editor/Mono/Overlays/OverlayUtilities.cs +++ b/Editor/Mono/Overlays/OverlayUtilities.cs @@ -7,9 +7,7 @@ using System.Linq; using System.Reflection; using UnityEditor.EditorTools; -using UnityEditor.Toolbars; using UnityEngine; -using UnityEngine.UIElements; namespace UnityEditor.Overlays { @@ -73,9 +71,7 @@ internal static List GetOverlaysForType(Type type) for (int i = 0, c = overlays.Length; i < c; i++) { - if (overlays[i].editorWindowType != null - && (overlays[i].editorWindowType.IsAssignableFrom(type) - || type.IsAssignableFrom(overlays[i].editorWindowType))) + if (overlays[i].editorWindowType != null && overlays[i].editorWindowType.IsAssignableFrom(type)) res.Add(overlays[i].overlay); } @@ -113,7 +109,7 @@ public static Overlay CreateOverlay(Type type) if (overlay == null) { - Debug.LogWarning("Overlay of type {type} can not be instantiated. Make sure this type contains a " + + Debug.LogWarning($"Overlay of type {type} can not be instantiated. Make sure this type contains a " + "parameter-less constructor and inherits the Overlay type."); return null; } @@ -154,6 +150,8 @@ internal static string GetSignificantLettersForIcon(string s) internal static bool EnsureValidId(IEnumerable existing, Overlay overlay) { + if (overlay == null) + return false; var id = string.IsNullOrEmpty(overlay.id) ? $"{overlay.GetType()}" : overlay.id; var ret = EnsureUniqueId(existing.Select(x => x.id), id); if (string.IsNullOrEmpty(ret)) diff --git a/Editor/Mono/PerformanceTools/FrameDebugger.bindings.cs b/Editor/Mono/PerformanceTools/FrameDebugger.bindings.cs index fa7b117274..6e9e2a687c 100644 --- a/Editor/Mono/PerformanceTools/FrameDebugger.bindings.cs +++ b/Editor/Mono/PerformanceTools/FrameDebugger.bindings.cs @@ -6,13 +6,13 @@ using UnityEngine.Bindings; using System.Runtime.InteropServices; -namespace UnityEditorInternal +namespace UnityEditorInternal.FrameDebuggerInternal { [NativeHeader("Editor/Mono/PerformanceTools/FrameDebugger.bindings.h")] [StaticAccessor("FrameDebugger", StaticAccessorType.DoubleColon)] internal sealed class FrameDebuggerUtility { - extern public static void SetEnabled(bool enabled, int remotePlayerGUID); + public extern static void SetEnabled(bool enabled, int remotePlayerGUID); public extern static int GetRemotePlayerGUID(); public extern static bool receivingRemoteFrameEventData { [NativeName("IsReceivingRemoteFrameEventData")] get; } public extern static bool locallySupported { [NativeName("IsSupported")] get; } @@ -36,7 +36,7 @@ public static bool GetFrameEventData(int index, FrameDebuggerEventData frameDebu var handle = GCHandle.Alloc(frameDebuggerEventData, GCHandleType.Pinned); GetFrameEventDataImpl(handle.AddrOfPinnedObject()); handle.Free(); - return frameDebuggerEventData.frameEventIndex == index; + return frameDebuggerEventData.m_FrameEventIndex == index; } [FreeFunction("FrameDebuggerBindings::GetFrameEventDataImpl")] diff --git a/Editor/Mono/PerformanceTools/FrameDebugger.cs b/Editor/Mono/PerformanceTools/FrameDebugger.cs index eb64165626..b4439cd664 100644 --- a/Editor/Mono/PerformanceTools/FrameDebugger.cs +++ b/Editor/Mono/PerformanceTools/FrameDebugger.cs @@ -10,10 +10,11 @@ using UnityEngine.Networking.PlayerConnection; using UnityEditorInternal; +using UnityEditorInternal.FrameDebuggerInternal; + using UnityEditor.IMGUI.Controls; using UnityEditor.Networking.PlayerConnection; - namespace UnityEditor { internal class FrameDebuggerWindow : EditorWindow @@ -23,8 +24,11 @@ internal class FrameDebuggerWindow : EditorWindow [SerializeField] private TreeViewState m_TreeViewState; // Private + private int m_EnablingWaitCounter = 0; private int m_RepaintFrames = k_NeedToRepaintFrames; private int m_FrameEventsHash; + private bool m_ShowTabbedErrorBox; + private bool m_HasOpenedPlaymodeView; private Rect m_SearchRect; private string m_SearchString = String.Empty; private IConnectionState m_AttachToPlayerState; @@ -44,93 +48,287 @@ internal class FrameDebuggerWindow : EditorWindow private const int k_NeedToRepaintFrames = 4; // Properties - private bool IsDisabledInEditor => !FrameDebugger.enabled && m_AttachToPlayerState.connectedToTarget == ConnectionTarget.Editor; + private bool IsDisabledInEditor => !FrameDebugger.enabled; + private bool IsEnablingFrameDebugger => m_EnablingWaitCounter < k_NeedToRepaintFrames; private bool HasEventHashChanged => FrameDebuggerUtility.eventsHash != m_FrameEventsHash; + [ShortcutManagement.Shortcut("Analysis/FrameDebugger/Enable")] + public static void OpenWindowAndToggleEnabled() + { + FrameDebuggerWindow frameDebuggerWindow = OpenWindow(); + frameDebuggerWindow.ToggleFrameDebuggerEnabled(); + } + [MenuItem("Window/Analysis/Frame Debugger", false, 10)] - public static FrameDebuggerWindow ShowFrameDebuggerWindow() + public static FrameDebuggerWindow OpenWindow() { var wnd = GetWindow(typeof(FrameDebuggerWindow)) as FrameDebuggerWindow; - wnd.titleContent = EditorGUIUtility.TrTextContent("Frame Debug"); + wnd.titleContent = EditorGUIUtility.TrTextContent("Frame Debugger"); + wnd.minSize = new Vector2(1000f, 500f); return wnd; } + internal void ToggleFrameDebuggerEnabled() + { + if (FrameDebugger.enabled) + DisableFrameDebugger(); + else + EnableFrameDebugger(); + } + + internal override void OnResized() + { + if (PopupWindowWithoutFocus.IsVisible()) + PopupWindowWithoutFocus.Hide(); + + base.OnResized(); + } + + internal void ChangeFrameEventLimit(int newLimit) + { + if (newLimit <= 0 || newLimit > FrameDebuggerUtility.count) + return; + + FrameDebuggerUtility.limit = newLimit; + m_EventDetailsView?.OnNewFrameEventSelected(); + m_TreeView?.SelectFrameEventIndex(newLimit); + } + + internal void ChangeFrameEventLimit(int newLimit, FrameDebuggerTreeView.FrameDebuggerTreeViewItem originalItem) + { + if (newLimit <= 0 || newLimit > FrameDebuggerUtility.count) + return; + + FrameDebuggerUtility.limit = newLimit; + m_EventDetailsView?.OnNewFrameEventSelected(); + m_TreeView?.SelectFrameEventIndex(newLimit); + } + + internal void OnConnectedProfilerChange() + { + DisableFrameDebugger(); + EnableFrameDebugger(); + } + + internal static void RepaintAll() + { + foreach (var fd in s_FrameDebuggers) + fd.Repaint(); + } + + internal void ReselectItemOnCountChange() + { + if (m_TreeView == null) + return; + + m_TreeView.ReselectFrameEventIndex(); + } + + internal void RepaintAllNeededThings() + { + // indicate that editor needs a redraw (mostly to get offscreen cameras rendered) + EditorApplication.SetSceneRepaintDirty(); + + // Note: do NOT add GameView.RepaintAll here; that would cause really confusing + // behaviors when there are offscreen (rendering into RTs) cameras. + + // redraw ourselves + Repaint(); + } + + internal void DrawSearchField(string str) + { + m_SearchString = EditorGUI.ToolbarSearchField(m_SearchRect, str, false); + } + + // OnGUI does not always get called, which causes issues when profiling a remote player. + // We need to call repaint in order to get the data without having to move the mouse + // and to get things like foldouts to open/collapse normally etc. + void Update() + { + if (IsDisabledInEditor) + return; + + if (IsEnablingFrameDebugger || m_AttachToPlayerState.connectedToTarget == ConnectionTarget.Player) + RepaintOnLimitChange(); + } + private void OnGUI() { - FrameDebuggerEvent[] descs = FrameDebuggerUtility.GetFrameEvents(); - Initialize(descs); + // We always draw the top toolbar with the enable/disable/etc... + bool repaint = DrawToolBar(); - int oldLimit = FrameDebuggerUtility.limit; + // If the debugger has not been enabled... + if (IsDisabledInEditor) + DrawDisabledFrameDebugger(); + + // After first clicking the enable button we need to wait a few frames before starting to draw... + else if (IsEnablingFrameDebugger) + HandleEnablingFrameDebugger(); + + // Draw the enabled debugger... + else + DrawEnabledFrameDebugger(repaint); + } + + private bool DrawToolBar() + { + if (m_Toolbar == null) + m_Toolbar = new FrameDebuggerToolbarView(); Profiler.BeginSample("DrawToolbar"); bool repaint = m_Toolbar.DrawToolbar(this, m_AttachToPlayerState); Profiler.EndSample(); - if (IsDisabledInEditor) + return repaint; + } + + private void DrawDisabledFrameDebugger() + { + GUI.enabled = true; + if (!FrameDebuggerUtility.locallySupported) { - GUI.enabled = true; - if (!FrameDebuggerUtility.locallySupported) - { - string warningMessage = (FrameDebuggerHelper.IsOnLinuxOpenGL) ? FrameDebuggerStyles.EventDetails.warningLinuxOpenGLMsg : FrameDebuggerStyles.EventDetails.warningMultiThreadedMsg; - EditorGUILayout.HelpBox(warningMessage, MessageType.Warning, true); - } + string warningMessage = (FrameDebuggerHelper.isOnLinuxOpenGL) ? FrameDebuggerStyles.EventDetails.k_WarningLinuxOpenGLMsg : FrameDebuggerStyles.EventDetails.k_WarningMultiThreadedMsg; + EditorGUILayout.HelpBox(warningMessage, MessageType.Warning, true); + } + + EditorGUILayout.HelpBox(FrameDebuggerStyles.EventDetails.k_DescriptionString, MessageType.Info, true); + + if (m_ShowTabbedErrorBox) + EditorGUILayout.HelpBox(FrameDebuggerStyles.EventDetails.k_TabbedWithPlaymodeErrorString, MessageType.Error, true); + } + + private void HandleEnablingFrameDebugger() + { + // Make sure the PlayMode window is enabled and shown... + if (!OpenPlayModeView()) + return; - EditorGUILayout.HelpBox(FrameDebuggerStyles.EventDetails.descriptionString, MessageType.Info, true); + if (Event.current.type != EventType.Repaint) + return; + + m_EnablingWaitCounter++; + if (m_EnablingWaitCounter == k_NeedToRepaintFrames) + { + FrameDebuggerEvent[] descs = FrameDebuggerUtility.GetFrameEvents(); + m_TreeView = new FrameDebuggerTreeView(descs, m_TreeViewState, this, new Rect(50, 50, 500, 100)); + m_FrameEventsHash = FrameDebuggerUtility.eventsHash; + ChangeFrameEventLimit(FrameDebuggerUtility.count); } - else + } + + private bool CheckIfFDIsDockedWithGameWindow(DockArea da, PlayModeView gameWindow) + { + for (int i = 0; i < da.m_Panes.Count; i++) + if (gameWindow == da.m_Panes[i]) + return true; + return false; + } + + private bool OpenPlayModeView() + { + if (m_HasOpenedPlaymodeView) + return true; + + // When debugging remote players, we can ignore this check as it doesn't render to the Game Window. + if (!FrameDebugger.IsLocalEnabled() && m_AttachToPlayerState.connectedToTarget != ConnectionTarget.Editor) + return true; + + PlayModeView mainGameWindow = PlayModeView.GetMainPlayModeView(); + List allGameWindows = PlayModeView.GetAllPlayModeViewWindows(); + if (mainGameWindow || allGameWindows.Count > 0) { - if (FrameDebugger.IsLocalEnabled()) + PlayModeView gameWindowToUse = mainGameWindow; + + // The Frame Debugger and Game Window can not be docked together in + // the panes list (tabs) as both need to be shown in the Editor. + bool isFDInTheSamePaneAsGameWindow = false; + DockArea da = m_Parent as DockArea; + if (da) + isFDInTheSamePaneAsGameWindow |= CheckIfFDIsDockedWithGameWindow(da, mainGameWindow); + + // If it's docked, check if there are other game windows available to use + if (isFDInTheSamePaneAsGameWindow && allGameWindows.Count > 1) { - PlayModeView playModeView = PlayModeView.GetMainPlayModeView(); - if (playModeView) - playModeView.ShowTab(); + for (int i = 0; i < allGameWindows.Count; i++) + { + if (CheckIfFDIsDockedWithGameWindow(da, allGameWindows[i])) + continue; + + isFDInTheSamePaneAsGameWindow = false; + gameWindowToUse = allGameWindows[i]; + break; + } } - // captured frame event contents have changed, rebuild the tree data - if (HasEventHashChanged) + // When we can't enable the FD debugger, we display an error box informing the + // user to undock the Frame Debugger Window so it's not tabbed with the Game Window. + if (isFDInTheSamePaneAsGameWindow) + { + m_ShowTabbedErrorBox = true; + return false; + } + // Otherwise we show the Game Window + else { - m_TreeView.m_DataSource.SetEvents(descs); - m_FrameEventsHash = FrameDebuggerUtility.eventsHash; + gameWindowToUse.ShowTab(); + m_HasOpenedPlaymodeView = true; + return true; } + } - float toolbarHeight = EditorStyles.toolbar.fixedHeight; - - Rect dragRect = new Rect(m_TreeWidth, toolbarHeight, FrameDebuggerStyles.Window.k_ResizerWidth, position.height - toolbarHeight); - dragRect = EditorGUIUtility.HandleHorizontalSplitter(dragRect, position.width, FrameDebuggerStyles.Window.k_MinTreeWidth, FrameDebuggerStyles.Window.k_MinDetailsWidth); - m_TreeWidth = dragRect.x; - - // Search area - m_SearchRect = EditorGUILayout.GetControlRect(); - m_SearchRect.width = m_TreeWidth - 5; - DrawSearchField(m_SearchString); - - Rect listRect = new Rect( - 0, - toolbarHeight + m_SearchRect.y, - m_TreeWidth, - position.height - toolbarHeight - m_SearchRect.height - 5 - ); - - Rect currentEventRect = new Rect( - m_TreeWidth, - toolbarHeight, - position.width - m_TreeWidth, - position.height - toolbarHeight - ); - - Profiler.BeginSample("DrawTree"); - m_TreeView.m_TreeView.searchString = m_SearchString; - m_TreeView.DrawTree(listRect); - Profiler.EndSample(); - - EditorGUIUtility.DrawHorizontalSplitter(dragRect); - - Profiler.BeginSample("DrawEvent"); - m_EventDetailsView.DrawEvent(currentEventRect, descs, m_AttachToPlayerState.connectedToTarget == ConnectionTarget.Editor); - Profiler.EndSample(); + return false; + } + + private void DrawEnabledFrameDebugger(bool repaint) + { + int oldLimit = FrameDebuggerUtility.limit; + FrameDebuggerEvent[] descs = FrameDebuggerUtility.GetFrameEvents(); + + // captured frame event contents have changed, rebuild the tree data + if (HasEventHashChanged) + { + m_TreeView.m_DataSource.SetEvents(descs); + m_FrameEventsHash = FrameDebuggerUtility.eventsHash; } + float toolbarHeight = EditorStyles.toolbar.fixedHeight; + + Rect dragRect = new Rect(m_TreeWidth, toolbarHeight, FrameDebuggerStyles.Window.k_ResizerWidth, position.height - toolbarHeight); + dragRect = EditorGUIUtility.HandleHorizontalSplitter(dragRect, position.width, FrameDebuggerStyles.Window.k_MinTreeWidth, FrameDebuggerStyles.Window.k_MinDetailsWidth); + m_TreeWidth = dragRect.x; + + // Search area + m_SearchRect = EditorGUILayout.GetControlRect(); + m_SearchRect.width = m_TreeWidth - 5; + DrawSearchField(m_SearchString); + + Rect listRect = new Rect( + 0, + toolbarHeight + m_SearchRect.y, + m_TreeWidth, + position.height - toolbarHeight - m_SearchRect.height - 5 + ); + + Rect currentEventRect = new Rect( + m_TreeWidth, + toolbarHeight, + position.width - m_TreeWidth, + position.height - toolbarHeight + ); + + Profiler.BeginSample("DrawTree"); + m_TreeView.m_TreeView.searchString = m_SearchString; + m_TreeView.DrawTree(listRect); + Profiler.EndSample(); + + EditorGUIUtility.DrawHorizontalSplitter(dragRect); + + Profiler.BeginSample("DrawEventDetails"); + m_EventDetailsView.DrawEventDetails(currentEventRect, descs, m_AttachToPlayerState.connectedToTarget == ConnectionTarget.Editor); + Profiler.EndSample(); + if (repaint || oldLimit != FrameDebuggerUtility.limit) RepaintOnLimitChange(); @@ -142,11 +340,6 @@ private void OnGUI() } } - internal void DrawSearchField(string str) - { - m_SearchString = EditorGUI.ToolbarSearchField(m_SearchRect, str, false); - } - private void OnDidOpenScene() { DisableFrameDebugger(); @@ -177,11 +370,6 @@ private void OnEnable() private void OnDisable() { - if (m_EventDetailsView != null) - m_EventDetailsView.OnDisable(); - - FrameDebuggerStyles.OnDisable(); - m_AttachToPlayerState?.Dispose(); m_AttachToPlayerState = null; @@ -192,136 +380,65 @@ private void OnDisable() DisableFrameDebugger(); } - internal override void OnResized() + private void EnableFrameDebugger() { - if (PopupWindowWithoutFocus.IsVisible()) - PopupWindowWithoutFocus.Hide(); + if (FrameDebugger.enabled) + return; - base.OnResized(); - } + bool enablingLocally = !FrameDebugger.enabled && m_AttachToPlayerState.connectedToTarget == ConnectionTarget.Editor; + if (enablingLocally && !FrameDebuggerUtility.locallySupported) + return; - private void Initialize(FrameDebuggerEvent[] descs) - { - if (m_Toolbar == null) - m_Toolbar = new FrameDebuggerToolbarView(); + m_ShowTabbedErrorBox = false; + m_HasOpenedPlaymodeView = false; + if (!OpenPlayModeView()) + return; - if (m_TreeViewState == null) - m_TreeViewState = new TreeViewState(); + // pause play mode if needed + if (enablingLocally) + if (EditorApplication.isPlaying && !EditorApplication.isPaused) + EditorApplication.isPaused = true; - if (m_TreeView == null) - { - m_TreeView = new FrameDebuggerTreeView(descs, m_TreeViewState, this, new Rect( - 50, - 50, - 500, 100 - )); - m_FrameEventsHash = FrameDebuggerUtility.eventsHash; - m_TreeView.m_DataSource.SetExpanded(m_TreeView.m_DataSource.root, true); + FrameDebuggerUtility.SetEnabled(true, ProfilerDriver.connectedProfiler); - // Expand root's children only - foreach (var treeViewItem in m_TreeView.m_DataSource.root.children) - if (treeViewItem != null) - m_TreeView.m_DataSource.SetExpanded(treeViewItem, true); - } + if (m_TreeViewState == null) + m_TreeViewState = new TreeViewState(); if (m_EventDetailsView == null) m_EventDetailsView = new FrameDebuggerEventDetailsView(this); - } - - internal void ChangeFrameEventLimit(int newLimit) - { - if (newLimit <= 0 || newLimit > FrameDebuggerUtility.count) - return; - FrameDebuggerUtility.limit = newLimit; - m_EventDetailsView?.OnNewFrameEventSelected(); - m_TreeView?.SelectFrameEventIndex(newLimit); + m_EnablingWaitCounter = 0; + m_EventDetailsView.Reset(); + RepaintOnLimitChange(); } - bool hasChosenParent = false; - FrameDebuggerTreeView.FrameDebuggerTreeViewItem parentItem = null; - - internal void ChangeFrameEventLimit(int newLimit, FrameDebuggerTreeView.FrameDebuggerTreeViewItem originalItem) + private void DisableFrameDebugger() { - if (newLimit <= 0 || newLimit > FrameDebuggerUtility.count) + if (!FrameDebugger.enabled) return; - hasChosenParent = (originalItem != null); - parentItem = originalItem; - - FrameDebuggerUtility.limit = newLimit; - m_EventDetailsView?.OnNewFrameEventSelected(); - m_TreeView?.SelectFrameEventIndex(newLimit); - } - - private static void DisableFrameDebugger() - { // if it was true before, we disabled and ask the game scene to repaint if (FrameDebugger.IsLocalEnabled()) EditorApplication.SetSceneRepaintDirty(); FrameDebuggerUtility.SetEnabled(false, FrameDebuggerUtility.GetRemotePlayerGUID()); - } - - internal void EnableIfNeeded() - { - if (FrameDebugger.enabled) - return; - - m_EventDetailsView.Reset(); - ClickEnableFrameDebugger(); - RepaintOnLimitChange(); - } - - internal void ClickEnableFrameDebugger() - { - bool isEnabled = FrameDebugger.enabled; - bool enablingLocally = !isEnabled && m_AttachToPlayerState.connectedToTarget == ConnectionTarget.Editor; - - if (enablingLocally && !FrameDebuggerUtility.locallySupported) - return; - // pause play mode if needed - if (enablingLocally) - if (EditorApplication.isPlaying && !EditorApplication.isPaused) - EditorApplication.isPaused = true; - - if (!isEnabled) - FrameDebuggerUtility.SetEnabled(true, ProfilerDriver.connectedProfiler); - else - FrameDebuggerUtility.SetEnabled(false, FrameDebuggerUtility.GetRemotePlayerGUID()); - - // Make sure game view is visible when enabling frame debugger locally - if (FrameDebugger.IsLocalEnabled()) + if (m_EventDetailsView != null) { - PlayModeView playModeView = PlayModeView.GetMainPlayModeView(); - if (playModeView) - playModeView.ShowTab(); + m_EventDetailsView.OnDisable(); + m_EventDetailsView = null; } - } - internal static void RepaintAll() - { - foreach (var fd in s_FrameDebuggers) - fd.Repaint(); + m_HasOpenedPlaymodeView = false; + FrameDebuggerStyles.OnDisable(); + m_TreeViewState = null; + m_TreeView = null; } - private void RepaintOnLimitChange() + internal void RepaintOnLimitChange() { m_RepaintFrames = k_NeedToRepaintFrames; RepaintAllNeededThings(); } - - internal void RepaintAllNeededThings() - { - // indicate that editor needs a redraw (mostly to get offscreen cameras rendered) - EditorApplication.SetSceneRepaintDirty(); - - // Note: do NOT add GameView.RepaintAll here; that would cause really confusing - // behaviors when there are offscreen (rendering into RTs) cameras. - - // redraw ourselves - Repaint(); - } } } diff --git a/Editor/Mono/PerformanceTools/FrameDebuggerData.cs b/Editor/Mono/PerformanceTools/FrameDebuggerData.cs index 553614df59..f49d508ce5 100644 --- a/Editor/Mono/PerformanceTools/FrameDebuggerData.cs +++ b/Editor/Mono/PerformanceTools/FrameDebuggerData.cs @@ -3,11 +3,12 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Runtime.InteropServices; +using UnityEditor; using UnityEngine; using UnityEngine.Rendering; -using System.Runtime.InteropServices; -namespace UnityEditorInternal +namespace UnityEditorInternal.FrameDebuggerInternal { // match enum FrameEventType on C++ side! // match kFrameEventTypeNames names array! @@ -43,270 +44,242 @@ internal enum FrameEventType BeginSubpass, SRPBatch, HierarchyLevelBreak, - HybridBatch + HybridBatch, + ConfigureFoveatedRendering, // ReSharper restore InconsistentNaming } - internal enum ShowAdditionalInfo + // Match C++ ScriptingShaderKeywordInfo memory layout! + [StructLayout(LayoutKind.Sequential)] + internal struct ShaderKeywordInfo { - ShaderProperties = 0, - Preview = 1, - } + public string m_Name; + public int m_Flags; + public bool m_IsGlobal; + public bool m_IsDynamic; + }; // Match C++ ScriptingShaderFloatInfo memory layout! [StructLayout(LayoutKind.Sequential)] - internal struct ShaderFloatInfo : IComparable + internal struct ShaderFloatInfo { - public string name; - public int flags; - public float value; - - public int CompareTo(ShaderFloatInfo other) - { - return string.Compare(name, other.name); - } + public string m_Name; + public int m_Flags; + public float m_Value; } // Match C++ ScriptingShaderIntInfo memory layout! [StructLayout(LayoutKind.Sequential)] - internal struct ShaderIntInfo : IComparable + internal struct ShaderIntInfo { - public string name; - public int flags; - public int value; - - public int CompareTo(ShaderIntInfo other) - { - return string.Compare(name, other.name); - } + public string m_Name; + public int m_Flags; + public int m_Value; } // Match C++ ScriptingShaderVectorInfo memory layout! [StructLayout(LayoutKind.Sequential)] - internal struct ShaderVectorInfo : IComparable + internal struct ShaderVectorInfo { - public string name; - public int flags; - public Vector4 value; - - public int CompareTo(ShaderVectorInfo other) - { - return string.Compare(name, other.name); - } + public string m_Name; + public int m_Flags; + public Vector4 m_Value; } // Match C++ ScriptingShaderMatrixInfo memory layout! [StructLayout(LayoutKind.Sequential)] - internal struct ShaderMatrixInfo : IComparable + internal struct ShaderMatrixInfo { - public string name; - public int flags; - public Matrix4x4 value; - - public int CompareTo(ShaderMatrixInfo other) - { - return string.Compare(name, other.name); - } + public string m_Name; + public int m_Flags; + public Matrix4x4 m_Value; } // Match C++ ScriptingShaderTextureInfo memory layout! [StructLayout(LayoutKind.Sequential)] - internal struct ShaderTextureInfo : IComparable + internal struct ShaderTextureInfo { - public string name; - public int flags; - public string textureName; - public Texture value; - - public int CompareTo(ShaderTextureInfo other) - { - return string.Compare(name, other.name); - } + public string m_Name; + public int m_Flags; + public string m_TextureName; + public Texture m_Value; } // Match C++ ScriptingShaderBufferInfo memory layout! [StructLayout(LayoutKind.Sequential)] - internal struct ShaderBufferInfo : IComparable + internal struct ShaderBufferInfo { - public string name; - public int flags; - - public int CompareTo(ShaderBufferInfo other) - { - return string.Compare(name, other.name); - } + public string m_Name; + public int m_Flags; } // Match C++ ScriptingShaderBufferInfo memory layout! [StructLayout(LayoutKind.Sequential)] - internal struct ShaderConstantBufferInfo : IComparable + internal struct ShaderConstantBufferInfo { - public string name; - public int flags; - - public int CompareTo(ShaderConstantBufferInfo other) - { - return string.Compare(name, other.name); - } + public string m_Name; + public int m_Flags; } - // Match C++ ScriptingShaderProperties memory layout! + // Match C++ ScriptingShaderInfo memory layout! [StructLayout(LayoutKind.Sequential)] - internal struct ShaderProperties + internal struct ShaderInfo { - public ShaderFloatInfo[] floats; - public ShaderIntInfo[] ints; - public ShaderVectorInfo[] vectors; - public ShaderMatrixInfo[] matrices; - public ShaderTextureInfo[] textures; - public ShaderBufferInfo[] buffers; - public ShaderConstantBufferInfo[] cbuffers; + public ShaderKeywordInfo[] m_Keywords; + public ShaderFloatInfo[] m_Floats; + public ShaderIntInfo[] m_Ints; + public ShaderVectorInfo[] m_Vectors; + public ShaderMatrixInfo[] m_Matrices; + public ShaderTextureInfo[] m_Textures; + public ShaderBufferInfo[] m_Buffers; + public ShaderConstantBufferInfo[] m_CBuffers; } // Match C++ ScriptingFrameDebuggerEventData memory layout! [StructLayout(LayoutKind.Sequential)] internal class FrameDebuggerEventData { - public int frameEventIndex; - public int vertexCount; - public int indexCount; - public int instanceCount; - public int drawCallCount; - public string shaderName; - public string passName; - public string passLightMode; - public int shaderInstanceID; - public int subShaderIndex; - public int shaderPassIndex; + public int m_FrameEventIndex; + public int m_VertexCount; + public int m_IndexCount; + public int m_InstanceCount; + public int m_DrawCallCount; + public string m_OriginalShaderName; + public string m_RealShaderName; + public string m_PassName; + public string m_PassLightMode; + public int m_ShaderInstanceID; + public int m_SubShaderIndex; + public int m_ShaderPassIndex; public string shaderKeywords; - public int componentInstanceID; - public Mesh mesh; - public int meshInstanceID; - public int meshSubset; - public int[] meshInstanceIDs; + public int m_ComponentInstanceID; + public Mesh m_Mesh; + public int m_MeshInstanceID; + public int m_MeshSubset; + public int[] m_MeshInstanceIDs; // state for compute shader dispatches - public int csInstanceID; - public string csName; - public string csKernel; - public int csThreadGroupsX; - public int csThreadGroupsY; - public int csThreadGroupsZ; - public int csGroupSizeX; - public int csGroupSizeY; - public int csGroupSizeZ; + public int m_ComputeShaderInstanceID; + public string m_ComputeShaderName; + public string m_ComputeShaderKernelName; + public int m_ComputeShaderThreadGroupsX; + public int m_ComputeShaderThreadGroupsY; + public int m_ComputeShaderThreadGroupsZ; + public int m_ComputeShaderGroupSizeX; + public int m_ComputeShaderGroupSizeY; + public int m_ComputeShaderGroupSizeZ; // state for ray tracing shader dispatches - public int rtsInstanceID; - public string rtsName; - public string rtsShaderPassName; - public string rtsRayGenShaderName; - public string rtsAccelerationStructureName; - public int rtsMaxRecursionDepth; - public int rtsWidth; - public int rtsHeight; - public int rtsDepth; - public int rtsMissShaderCount; - public int rtsCallableShaderCount; + public int m_RayTracingShaderInstanceID; + public string m_RayTracingShaderName; + public string m_RayTracingShaderPassName; + public string m_RayTracingShaderRayGenShaderName; + public string m_RayTracingShaderAccelerationStructureName; + public int m_RayTracingShaderMaxRecursionDepth; + public int m_RayTracingShaderWidth; + public int m_RayTracingShaderHeight; + public int m_RayTracingShaderDepth; + public int m_RayTracingShaderMissShaderCount; + public int m_RayTracingShaderCallableShaderCount; // active render target info - public string rtName; - public int rtWidth; - public int rtHeight; - public int rtFormat; - public int rtDim; - public int rtFace; - public int rtLoadAction; - public int rtStoreAction; - public int rtDepthLoadAction; - public int rtDepthStoreAction; - public float rtClearColorR; - public float rtClearColorG; - public float rtClearColorB; - public float rtClearColorA; - public float rtClearDepth; - public uint rtClearStencil; - public short rtCount; - public sbyte rtHasDepthTexture; - public sbyte rtMemoryless; - public bool rtIsBackBuffer; - public Texture rtOutput; + public string m_RenderTargetName; + public int m_RenderTargetWidth; + public int m_RenderTargetHeight; + public int m_RenderTargetFormat; + public int m_RenderTargetDimension; + public int m_RenderTargetCubemapFace; + public int m_RenderTargetFoveatedRenderingMode; + public int m_RenderTargetLoadAction; + public int m_RenderTargetStoreAction; + public int m_RenderTargetDepthLoadAction; + public int m_RenderTargetDepthStoreAction; + public float m_RenderTargetClearColorR; + public float m_RenderTargetClearColorG; + public float m_RenderTargetClearColorB; + public float m_RenderTargetClearColorA; + public float m_RenderTargetClearDepth; + public uint m_RenderTargetClearStencil; + public short m_RenderTargetCount; + public sbyte m_RenderTargetHasDepthTexture; + public sbyte m_RenderTargetMemoryless; + public bool m_RenderTargetIsBackBuffer; + public RenderTexture m_RenderTargetRenderTexture; // shader state - public FrameDebuggerBlendState blendState; - public FrameDebuggerRasterState rasterState; - public FrameDebuggerDepthState depthState; - public FrameDebuggerStencilState stencilState; - public int stencilRef; + public FrameDebuggerBlendState m_BlendState; + public FrameDebuggerRasterState m_RasterState; + public FrameDebuggerDepthState m_DepthState; + public FrameDebuggerStencilState m_StencilState; + public int m_StencilRef; // clear event data - public float clearColorR; - public float clearColorG; - public float clearColorB; - public float clearColorA; - public float clearDepth; - public uint clearStencil; + public float m_ClearColorR; + public float m_ClearColorG; + public float m_ClearColorB; + public float m_ClearColorA; + public float m_ClearDepth; + public uint m_ClearStencil; - // shader properties - public int batchBreakCause; - public ShaderProperties shaderProperties; + public int m_BatchBreakCause; + public ShaderInfo m_ShaderInfo; } // Match C++ MonoFrameDebuggerEvent memory layout! [StructLayout(LayoutKind.Sequential)] internal struct FrameDebuggerEvent { - public FrameEventType type; - public UnityEngine.Object obj; + public FrameEventType m_Type; + public UnityEngine.Object m_Obj; } // Match C++ ScriptingFrameDebuggerBlendState memory layout! [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] internal struct FrameDebuggerBlendState { - public uint writeMask; - public BlendMode srcBlend; - public BlendMode dstBlend; - public BlendMode srcBlendAlpha; - public BlendMode dstBlendAlpha; - public BlendOp blendOp; - public BlendOp blendOpAlpha; + public uint m_WriteMask; + public BlendMode m_SrcBlend; + public BlendMode m_DstBlend; + public BlendMode m_SrcBlendAlpha; + public BlendMode m_DstBlendAlpha; + public BlendOp m_BlendOp; + public BlendOp m_BlendOpAlpha; } // Match C++ ScriptingFrameDebuggerRasterState memory layout! [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] internal struct FrameDebuggerRasterState { - public CullMode cullMode; - public int depthBias; - public float slopeScaledDepthBias; - public bool depthClip; - public bool conservative; + public CullMode m_CullMode; + public int m_DepthBias; + public float m_SlopeScaledDepthBias; + public bool m_DepthClip; + public bool m_Conservative; } // Match C++ ScriptingFrameDebuggerDepthState memory layout! [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] internal struct FrameDebuggerDepthState { - public int depthWrite; - public CompareFunction depthFunc; + public int m_DepthWrite; + public CompareFunction m_DepthFunc; } // Match C++ ScriptingFrameDebuggerStencilState memory layout! [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] internal struct FrameDebuggerStencilState { - public bool stencilEnable; - public byte readMask; - public byte writeMask; - public byte padding; - public CompareFunction stencilFuncFront; - public StencilOp stencilPassOpFront; - public StencilOp stencilFailOpFront; - public StencilOp stencilZFailOpFront; - public CompareFunction stencilFuncBack; - public StencilOp stencilPassOpBack; - public StencilOp stencilFailOpBack; - public StencilOp stencilZFailOpBack; + public bool m_StencilEnable; + public byte m_ReadMask; + public byte m_WriteMask; + public byte m_Padding; + public CompareFunction m_StencilFuncFront; + public StencilOp m_StencilPassOpFront; + public StencilOp m_StencilFailOpFront; + public StencilOp m_StencilZFailOpFront; + public CompareFunction m_StencilFuncBack; + public StencilOp m_StencilPassOpBack; + public StencilOp m_StencilFailOpBack; + public StencilOp m_StencilZFailOpBack; } } diff --git a/Editor/Mono/PerformanceTools/FrameDebuggerEventDetailsView.cs b/Editor/Mono/PerformanceTools/FrameDebuggerEventDetailsView.cs index 09de61f797..25f9dc9aa7 100644 --- a/Editor/Mono/PerformanceTools/FrameDebuggerEventDetailsView.cs +++ b/Editor/Mono/PerformanceTools/FrameDebuggerEventDetailsView.cs @@ -6,184 +6,184 @@ using System.Text; using System.Globalization; using System.Collections.Generic; + using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Profiling; using UnityEngine.Experimental.Rendering; + using UnityEditor; using UnityEditor.Rendering; using UnityEditor.AnimatedValues; -namespace UnityEditorInternal +namespace UnityEditorInternal.FrameDebuggerInternal { internal class FrameDebuggerEventDetailsView { // Render target view options [NonSerialized] private int m_RTIndex; - [NonSerialized] private bool m_ForceRebuildStrings = false; - [NonSerialized] private int m_RTIndexLastSet = int.MaxValue; + [NonSerialized] private int m_MeshIndex = 0; + [NonSerialized] private int m_RTIndexLastSet = 0; [NonSerialized] private int m_RTSelectedChannel; [NonSerialized] private float m_RTBlackLevel; [NonSerialized] private float m_RTWhiteLevel = 1.0f; + [NonSerialized] private float m_RTBlackMinLevel; + [NonSerialized] private float m_RTWhiteMaxLevel = 1.0f; // Private private int m_SelectedColorChannel = 0; - private bool m_ShouldShowMeshListFoldout = false; private Vector2 m_ScrollViewVector = Vector2.zero; private Vector4 m_SelectedMask = Vector4.one; - private Material m_TargetTextureMaterial = null; private AnimBool[] m_FoldoutAnimators = null; + private MeshPreview m_Preview; private GUIContent[] m_OutputMeshTabsGuiContents = new[] { new GUIContent("Output"), new GUIContent("Mesh Preview") }; - private List m_KeywordsList = new List(); - private StringBuilder m_TempSB1 = new StringBuilder(); - private StringBuilder m_TempSB2 = new StringBuilder(); - private EventDisplayData m_LastEventData; - private ShowAdditionalInfo m_OutputMeshTabs = ShowAdditionalInfo.ShaderProperties; + private CachedEventDisplayData m_CachedEventData = null; + private OutputMeshTabOptions m_OutputMeshTabs = OutputMeshTabOptions.Output; private FrameDebuggerWindow m_FrameDebugger = null; private Lazy m_CurEventData = new Lazy(() => new FrameDebuggerEventData()); + private RenderTexture m_RenderTargetRenderTextureCopy = null; - // Constants + // Constants / Readonly private const int k_NumberGUISections = 10; - private const int k_ArraySizeBitMask = 0x3FF; - private const int k_ShaderTypeBits = (int)ShaderType.Count; + private readonly string[] k_foldoutKeys = new string[]{ + "FrameDebuggerFoldout0", + "FrameDebuggerFoldout1", + "FrameDebuggerFoldout2", + "FrameDebuggerFoldout3", + "FrameDebuggerFoldout4", + "FrameDebuggerFoldout5", + "FrameDebuggerFoldout6", + "FrameDebuggerFoldout7", + "FrameDebuggerFoldout8", + "FrameDebuggerFoldout9", + }; // Properties private FrameDebuggerEvent curEvent { get; set; } private FrameDebuggerEventData curEventData => m_CurEventData.Value; + private int curEventIndex => FrameDebuggerUtility.limit - 1; - // Structs - - // Shader Property ID's for the shader used to display the output texture - private struct ShaderPropertyIDs - { - public static int _Levels = Shader.PropertyToID("_Levels"); - public static int _MainTex = Shader.PropertyToID("_MainTex"); - public static int _Channels = Shader.PropertyToID("_Channels"); - public static int _ShouldYFlip = Shader.PropertyToID("_ShouldYFlip"); - public static int _UndoOutputSRGB = Shader.PropertyToID("_UndoOutputSRGB"); - } - - // Cached data built from FrameDebuggerEventData. - // Only need to rebuild them when event data actually changes. - private struct EventDisplayData + // Enums + private enum OutputMeshTabOptions { - public uint hash; - public int index; - public bool isValid; - public bool isClearEvent; - public bool isResolveEvent; - public bool isComputeEvent; - public bool isRayTracingEvent; - public string title; - public string detailsLabelsLeftColumn; - public string detailsValuesLeftColumn; - public string detailLabelsRightColumn; - public string detailValuesRightColumn; - public string keywords; - public string shaderName; - public string passAndLightMode; - public string listOfMeshesString; - public string firstMeshName; - public string meshTitle; - public UnityEngine.Object shader; - public FrameEventType type; + Output = 0, + MeshPreview = 1, } - // Public functions - public FrameDebuggerEventDetailsView(FrameDebuggerWindow frameDebugger) + // Internal functions + internal FrameDebuggerEventDetailsView(FrameDebuggerWindow frameDebugger) { m_FrameDebugger = frameDebugger; - - m_LastEventData = new EventDisplayData(); - m_LastEventData.keywords = string.Empty; } - public void Reset() + internal void Reset() { m_RTSelectedChannel = 0; m_SelectedColorChannel = 0; m_RTIndex = 0; m_RTBlackLevel = 0.0f; + m_RTBlackMinLevel = 0.0f; m_RTWhiteLevel = 1.0f; + m_RTWhiteMaxLevel = 1.0f; } - MeshPreview m_Preview; - public void OnNewFrameEventSelected() + internal void OnNewFrameEventSelected() { - m_KeywordsList.Clear(); - m_Preview?.Dispose(); m_Preview = null; } - public void OnDisable() + internal void OnDisable() { + if (m_CachedEventData != null) + m_CachedEventData.OnDisable(); + + // Release the texture... + FrameDebuggerHelper.ReleaseTemporaryTexture(ref m_RenderTargetRenderTextureCopy); + m_Preview?.Dispose(); m_Preview = null; + Reset(); } - // Here is the major performance bottleneck! - public void DrawEvent(Rect rect, FrameDebuggerEvent[] descs, bool isDebuggingEditor) + internal void DrawEventDetails(Rect rect, FrameDebuggerEvent[] descs, bool isDebuggingEditor) { - int curEventIndex = FrameDebuggerUtility.limit - 1; + // Early out if the frame is not valid if (!FrameDebuggerHelper.IsAValidFrame(curEventIndex, descs.Length)) return; - Initialize(curEventIndex, descs, out bool isReceivingFrameEventData, out bool isFrameEventDataValid); + // Making sure we only initialize once. We do that in the Layout event, which is called before repaint + if (Event.current.type == EventType.Layout) + Initialize(curEventIndex, descs); + // Make sure the window is scrollable... GUILayout.BeginArea(rect); - m_ScrollViewVector = GUILayout.BeginScrollView(m_ScrollViewVector); + m_ScrollViewVector = EditorGUILayout.BeginScrollView(m_ScrollViewVector); // Toolbar Profiler.BeginSample("DrawToolbar"); - { - DrawRenderTargetToolbar(); - } + DrawRenderTargetToolbar(); Profiler.EndSample(); // Title Profiler.BeginSample("DrawTitle"); - { - GUILayout.BeginHorizontal(FrameDebuggerStyles.EventDetails.titleHorizontalStyle); - EditorGUILayout.LabelField(m_LastEventData.title, FrameDebuggerStyles.EventDetails.titleStyle); - GUILayout.EndHorizontal(); - } + GUILayout.BeginHorizontal(FrameDebuggerStyles.EventDetails.s_TitleHorizontalStyle); + EditorGUILayout.LabelField(m_CachedEventData.m_Title, FrameDebuggerStyles.EventDetails.s_TitleStyle); + GUILayout.EndHorizontal(); + if (Event.current.type == EventType.ContextClick) + ShaderPropertyCopyValueMenu(GUILayoutUtility.GetLastRect(), FrameDebuggerStyles.EventDetails.s_CopyEventText, () => m_CachedEventData.copyString); Profiler.EndSample(); // Output & Mesh // We disable Output and Mesh for Compute and Ray Tracing events - bool shouldDrawOutputAndMesh = !m_LastEventData.isComputeEvent && !m_LastEventData.isRayTracingEvent; + bool shouldDrawOutputAndMesh = !m_CachedEventData.m_IsComputeEvent && !m_CachedEventData.m_IsRayTracingEvent; Profiler.BeginSample("DrawOutputAndMesh"); - { - DrawOutputAndMesh(rect, shouldDrawOutputAndMesh, isDebuggingEditor); - } + DrawOutputAndMesh(rect, shouldDrawOutputAndMesh, isDebuggingEditor); Profiler.EndSample(); // Event Details - Profiler.BeginSample("DrawEventDetails"); - { - DrawEventDetails(rect); - } + Profiler.BeginSample("DrawDetails"); + DrawDetails(rect); Profiler.EndSample(); - // We disable and hide keywords and shader properties for clear and resolve events. - bool shouldDisplayProperties = !m_LastEventData.isClearEvent && !m_LastEventData.isResolveEvent; - - // Keywords... - Profiler.BeginSample("DrawKeywords"); + ShaderPropertyCollection[] shaderProperties = m_CachedEventData.m_ShaderProperties; + if (shaderProperties != null && shaderProperties.Length > 0) { - DrawKeywords(shouldDisplayProperties); - } - Profiler.EndSample(); + // Shader keywords & properties... + Profiler.BeginSample("DrawKeywords"); + DrawShaderData(ShaderPropertyType.Keyword, 2, FrameDebuggerStyles.EventDetails.s_FoldoutKeywordsText, m_CachedEventData.m_Keywords); + Profiler.EndSample(); - // Properties... - Profiler.BeginSample("DrawProperties"); - { - DrawProperties(shouldDisplayProperties); + Profiler.BeginSample("DrawTextureProperties"); + DrawShaderData(ShaderPropertyType.Texture, 3, FrameDebuggerStyles.EventDetails.s_FoldoutTexturesText, m_CachedEventData.m_Textures); + Profiler.EndSample(); + + Profiler.BeginSample("DrawIntProperties"); + DrawShaderData(ShaderPropertyType.Int, 4, FrameDebuggerStyles.EventDetails.s_FoldoutIntsText, m_CachedEventData.m_Ints); + Profiler.EndSample(); + + Profiler.BeginSample("DrawFloatProperties"); + DrawShaderData(ShaderPropertyType.Float, 5, FrameDebuggerStyles.EventDetails.s_FoldoutFloatsText, m_CachedEventData.m_Floats); + Profiler.EndSample(); + + Profiler.BeginSample("DrawVectorProperties"); + DrawShaderData(ShaderPropertyType.Vector, 6, FrameDebuggerStyles.EventDetails.s_FoldoutVectorsText, m_CachedEventData.m_Vectors); + Profiler.EndSample(); + + Profiler.BeginSample("DrawMatrixProperties"); + DrawShaderData(ShaderPropertyType.Matrix, 7, FrameDebuggerStyles.EventDetails.s_FoldoutMatricesText, m_CachedEventData.m_Matrices); + Profiler.EndSample(); + + Profiler.BeginSample("DrawBufferProperties"); + DrawShaderData(ShaderPropertyType.Buffer, 8, FrameDebuggerStyles.EventDetails.s_FoldoutBuffersText, m_CachedEventData.m_Buffers); + Profiler.EndSample(); + + Profiler.BeginSample("DrawConstantBufferProperties"); + DrawShaderData(ShaderPropertyType.CBuffer, 9, FrameDebuggerStyles.EventDetails.s_FoldoutCBufferText, m_CachedEventData.m_CBuffers); + Profiler.EndSample(); } - Profiler.EndSample(); - GUILayout.EndScrollView(); + EditorGUILayout.EndScrollView(); GUILayout.EndArea(); } @@ -191,123 +191,128 @@ public void DrawEvent(Rect rect, FrameDebuggerEvent[] descs, bool isDebuggingEdi // PRIVATE /////////////////////////////////////////////// - private void Initialize(int curEventIndex, FrameDebuggerEvent[] descs, out bool isReceivingFrameEventData, out bool isFrameEventDataValid) + private void Initialize(int curEventIndex, FrameDebuggerEvent[] descs) { + Profiler.BeginSample("Initialize"); + uint eventDataHash = FrameDebuggerUtility.eventDataHash; - isReceivingFrameEventData = FrameDebugger.IsRemoteEnabled() && FrameDebuggerUtility.receivingRemoteFrameEventData; - isFrameEventDataValid = curEventIndex == curEventData.frameEventIndex; + bool isReceivingFrameEventData = FrameDebugger.IsRemoteEnabled() && FrameDebuggerUtility.receivingRemoteFrameEventData; + bool isFrameEventDataValid = curEventIndex == curEventData.m_FrameEventIndex; - if (!isFrameEventDataValid || (eventDataHash != 0 && (eventDataHash != m_LastEventData.hash || m_ForceRebuildStrings))) + if (!isFrameEventDataValid + || m_CachedEventData == null + || !m_CachedEventData.m_IsValid + || m_CachedEventData.m_Index != curEventIndex + || (eventDataHash != 0 && (eventDataHash != m_CachedEventData.m_Hash)) + ) { + if (m_CachedEventData == null) + m_CachedEventData = new CachedEventDisplayData(); + + // Release the texture... + FrameDebuggerHelper.ReleaseTemporaryTexture(ref m_RenderTargetRenderTextureCopy); + isFrameEventDataValid = FrameDebuggerUtility.GetFrameEventData(curEventIndex, curEventData); - m_LastEventData.hash = eventDataHash; - m_LastEventData.isValid = false; - m_ForceRebuildStrings = false; + m_CachedEventData.m_IsValid = false; } // event type and draw call info curEvent = descs[curEventIndex]; - FrameEventType eventType = curEvent.type; + FrameEventType eventType = curEvent.m_Type; // Rebuild strings... if (isFrameEventDataValid) - if (!m_LastEventData.isValid || m_LastEventData.index != curEventIndex || m_LastEventData.type != eventType) - BuildCurEventDataStrings(curEvent, curEventData); + { + if (!m_CachedEventData.m_IsValid || m_CachedEventData.m_Index != curEventIndex || m_CachedEventData.m_Type != eventType) + { + m_CachedEventData.m_Hash = eventDataHash; + m_CachedEventData.Initialize(curEvent, curEventData); + } + } if (m_FoldoutAnimators == null || m_FoldoutAnimators.Length == 0) { m_FoldoutAnimators = new AnimBool[k_NumberGUISections]; for (int i = 0; i < m_FoldoutAnimators.Length; i++) - m_FoldoutAnimators[i] = new AnimBool(i < 2); + { + bool val = EditorPrefs.HasKey(k_foldoutKeys[i]) ? EditorPrefs.GetBool(k_foldoutKeys[i]) : false; + m_FoldoutAnimators[i] = new AnimBool(val); + } } - - // TODO: Sort the properties - // TODO: Make the sorting work for both single parameters as well - // TODO: as parameters in arrays. Found an issue in SRP0601_RealtimeLights - // TODO: in Mingwai's CustomSRP project. - /*ShaderProperties data = curEventData.shaderProperties; - if (!data.Equals(default(ShaderProperties))) - { - Array.Sort(data.buffers); - Array.Sort(data.cbuffers); - Array.Sort(data.floats); - Array.Sort(data.ints); - Array.Sort(data.matrices); - Array.Sort(data.textures); - Array.Sort(data.vectors); - }*/ + Profiler.EndSample(); } private void DrawRenderTargetToolbar() { - if (m_LastEventData.isRayTracingEvent) + if (m_CachedEventData.m_IsRayTracingEvent) return; - bool isBackBuffer = curEventData.rtIsBackBuffer; - bool isDepthOnlyRT = GraphicsFormatUtility.IsDepthFormat((GraphicsFormat)curEventData.rtFormat); - bool isClearAction = (int)curEvent.type <= 7; - bool hasShowableDepth = (curEventData.rtHasDepthTexture != 0); - int showableRTCount = curEventData.rtCount; + bool isDepthOnlyRT = GraphicsFormatUtility.IsDepthFormat((GraphicsFormat)curEventData.m_RenderTargetFormat); + bool isClearAction = (int)curEvent.m_Type <= 7; + bool hasShowableDepth = (curEventData.m_RenderTargetHasDepthTexture != 0); + int showableRTCount = curEventData.m_RenderTargetCount; if (hasShowableDepth) showableRTCount++; - GUILayout.BeginHorizontal(FrameDebuggerStyles.EventToolbar.toolbarHorizontalStyle); + GUILayout.BeginHorizontal(FrameDebuggerStyles.EventToolbar.s_HorizontalStyle); // MRT to show EditorGUI.BeginChangeCheck(); GUI.enabled = showableRTCount > 1; var rtNames = new GUIContent[showableRTCount]; - for (var i = 0; i < curEventData.rtCount; ++i) - rtNames[i] = FrameDebuggerStyles.EventToolbar.MRTLabels[i]; + for (var i = 0; i < showableRTCount; ++i) + rtNames[i] = FrameDebuggerStyles.EventToolbar.s_MRTLabels[i]; if (hasShowableDepth) - rtNames[curEventData.rtCount] = FrameDebuggerStyles.EventToolbar.depthLabel; + rtNames[curEventData.m_RenderTargetCount] = FrameDebuggerStyles.EventToolbar.s_DepthLabel; // If we showed depth before then try to keep showing depth // otherwise try to keep showing color if (m_RTIndexLastSet == -1) m_RTIndex = hasShowableDepth ? showableRTCount - 1 : 0; - else if (m_RTIndex > curEventData.rtCount) + else if (m_RTIndex > curEventData.m_RenderTargetCount) m_RTIndex = 0; - m_RTIndex = EditorGUILayout.Popup(m_RTIndex, rtNames, FrameDebuggerStyles.EventToolbar.popupLeftStyle, GUILayout.Width(70)); - - - GUI.enabled = !isBackBuffer && !isDepthOnlyRT; + m_RTIndex = EditorGUILayout.Popup(m_RTIndex, rtNames, FrameDebuggerStyles.EventToolbar.s_PopupLeftStyle, GUILayout.Width(70)); + GUI.enabled = !isDepthOnlyRT; // color channels EditorGUILayout.Space(5f); - GUILayout.Label(FrameDebuggerStyles.EventToolbar.channelHeader, FrameDebuggerStyles.EventToolbar.channelHeaderStyle); + GUILayout.Label(FrameDebuggerStyles.EventToolbar.s_ChannelHeader, FrameDebuggerStyles.EventToolbar.s_ChannelHeaderStyle); EditorGUILayout.Space(5f); int channelToDisplay = 0; bool forceUpdate = false; - bool shouldDisableChannelButtons = isDepthOnlyRT || isClearAction || isBackBuffer; - UInt32 componentCount = GraphicsFormatUtility.GetComponentCount((GraphicsFormat)curEventData.rtFormat); + + // Negative RT index: display depth or stencil buffer + bool isDepthOrStencilSelected = m_RTIndexLastSet < 0; + + bool shouldDisableChannelButtons = isDepthOnlyRT || isClearAction || isDepthOrStencilSelected; + UInt32 componentCount = GraphicsFormatUtility.GetComponentCount((GraphicsFormat)curEventData.m_RenderTargetFormat); GUILayout.BeginHorizontal(); { GUI.enabled = !shouldDisableChannelButtons && m_SelectedColorChannel != 0; - if (GUILayout.Button(FrameDebuggerStyles.EventToolbar.channelAll, FrameDebuggerStyles.EventToolbar.channelAllStyle)) { m_RTSelectedChannel = 0; } + if (GUILayout.Button(FrameDebuggerStyles.EventToolbar.s_ChannelAll, FrameDebuggerStyles.EventToolbar.s_ChannelAllStyle)) { m_RTSelectedChannel = 0; } GUI.enabled = !shouldDisableChannelButtons && componentCount > 0 && m_SelectedColorChannel != 1; - if (GUILayout.Button(FrameDebuggerStyles.EventToolbar.channelR, FrameDebuggerStyles.EventToolbar.channelStyle)) { m_RTSelectedChannel = 1; } + if (GUILayout.Button(FrameDebuggerStyles.EventToolbar.s_ChannelR, FrameDebuggerStyles.EventToolbar.s_ChannelStyle)) { m_RTSelectedChannel = 1; } GUI.enabled = !shouldDisableChannelButtons && componentCount > 1 && m_SelectedColorChannel != 2; - if (GUILayout.Button(FrameDebuggerStyles.EventToolbar.channelG, FrameDebuggerStyles.EventToolbar.channelStyle)) { m_RTSelectedChannel = 2; } + if (GUILayout.Button(FrameDebuggerStyles.EventToolbar.s_ChannelG, FrameDebuggerStyles.EventToolbar.s_ChannelStyle)) { m_RTSelectedChannel = 2; } GUI.enabled = !shouldDisableChannelButtons && componentCount > 2 && m_SelectedColorChannel != 3; - if (GUILayout.Button(FrameDebuggerStyles.EventToolbar.channelB, FrameDebuggerStyles.EventToolbar.channelStyle)) { m_RTSelectedChannel = 3; } + if (GUILayout.Button(FrameDebuggerStyles.EventToolbar.s_ChannelB, FrameDebuggerStyles.EventToolbar.s_ChannelStyle)) { m_RTSelectedChannel = 3; } GUI.enabled = !shouldDisableChannelButtons && componentCount > 3 && m_SelectedColorChannel != 4; - if (GUILayout.Button(FrameDebuggerStyles.EventToolbar.channelA, FrameDebuggerStyles.EventToolbar.channelAStyle)) { m_RTSelectedChannel = 4; } + if (GUILayout.Button(FrameDebuggerStyles.EventToolbar.s_ChannelA, FrameDebuggerStyles.EventToolbar.s_ChannelAStyle)) { m_RTSelectedChannel = 4; } // Force the channel to be "All" when: // * Showing the back buffer // * Showing Shadows/Depth/Clear // * Channel index is higher then the number available channels - bool shouldForceAll = isBackBuffer || (m_RTSelectedChannel != 0 && (shouldDisableChannelButtons || m_RTSelectedChannel < 4 && componentCount < m_RTSelectedChannel)); + bool shouldForceAll = (m_RTSelectedChannel != 0 && (shouldDisableChannelButtons || m_RTSelectedChannel < 4 && componentCount < m_RTSelectedChannel)); channelToDisplay = shouldForceAll ? 0 : m_RTSelectedChannel; if (channelToDisplay != m_SelectedColorChannel) @@ -320,13 +325,26 @@ private void DrawRenderTargetToolbar() } GUILayout.EndHorizontal(); - GUI.enabled = !isBackBuffer; + GUI.enabled = true; // levels - GUILayout.BeginHorizontal(FrameDebuggerStyles.EventToolbar.levelsHorizontalStyle); - GUILayout.Label(FrameDebuggerStyles.EventToolbar.levelsHeader); + GUILayout.BeginHorizontal(FrameDebuggerStyles.EventToolbar.s_LevelsHorizontalStyle); + GUILayout.Label(FrameDebuggerStyles.EventToolbar.s_LevelsHeader); + + float blackMinLevel = EditorGUILayout.DelayedFloatField(m_RTBlackMinLevel, GUILayout.MaxWidth(40.0f)); + float blackLevel = m_RTBlackLevel; + float whiteLevel = m_RTWhiteLevel; + EditorGUILayout.MinMaxSlider(ref blackLevel, ref whiteLevel, m_RTBlackMinLevel, m_RTWhiteMaxLevel, GUILayout.MaxWidth(200.0f)); + float whiteMaxLevel = EditorGUILayout.DelayedFloatField(m_RTWhiteMaxLevel, GUILayout.MaxWidth(40.0f)); - EditorGUILayout.MinMaxSlider(ref m_RTBlackLevel, ref m_RTWhiteLevel, 0.0f, 1.0f, GUILayout.MaxWidth(200.0f)); + if (blackMinLevel < whiteMaxLevel && whiteMaxLevel > blackMinLevel) + { + m_RTBlackMinLevel = blackMinLevel; + m_RTWhiteMaxLevel = whiteMaxLevel; + + m_RTBlackLevel = Mathf.Clamp(blackLevel, m_RTBlackMinLevel, whiteLevel); + m_RTWhiteLevel = Mathf.Clamp(whiteLevel, blackLevel, m_RTWhiteMaxLevel); + } int rtIndexToSet = m_RTIndex; if (hasShowableDepth && rtIndexToSet == (showableRTCount - 1)) @@ -348,7 +366,7 @@ private void DrawRenderTargetToolbar() FrameDebuggerUtility.SetRenderTargetDisplayOptions(rtIndexToSet, m_SelectedMask, m_RTBlackLevel, m_RTWhiteLevel); m_FrameDebugger.RepaintAllNeededThings(); m_RTIndexLastSet = rtIndexToSet; - m_ForceRebuildStrings = true; + FrameDebuggerHelper.ReleaseTemporaryTexture(ref m_RenderTargetRenderTextureCopy); } GUILayout.EndHorizontal(); @@ -360,53 +378,42 @@ private void DrawRenderTargetToolbar() private void DrawOutputAndMesh(Rect rect, bool shouldDrawOutputAndMesh, bool isDebuggingEditor) { - if (BeginFoldoutBox(0, shouldDrawOutputAndMesh, FrameDebuggerStyles.EventDetails.foldoutOutputOrMeshText, out float fadePercent)) + if (BeginFoldoutBox(0, shouldDrawOutputAndMesh, FrameDebuggerStyles.EventDetails.s_FoldoutOutputOrMeshText, out float fadePercent, null)) { if (shouldDrawOutputAndMesh) { EditorGUILayout.BeginVertical(); { - float viewportWidth = Mathf.Max(850, rect.width) - 20f; - if (viewportWidth < FrameDebuggerStyles.EventDetails.k_MaxViewportWidth) - return; - + float viewportWidth = rect.width - 30f; float viewportHeightFaded = FrameDebuggerStyles.EventDetails.k_MaxViewportHeight * fadePercent; - float texWidth = viewportWidth; - float texHeight = viewportHeightFaded; - if (curEventData.rtOutput != null) + float renderTargetWidth = m_CachedEventData.m_RenderTargetWidth; + float renderTargetHeight = m_CachedEventData.m_RenderTargetHeight; + + float scaledRenderTargetWidth = renderTargetWidth; + float scaledRenderTargetHeight = renderTargetHeight; + + if (scaledRenderTargetWidth > viewportWidth) { - texWidth = curEventData.rtOutput.width; - texHeight = curEventData.rtOutput.height; - - if (texWidth > viewportWidth) - { - float scale = viewportWidth / texWidth; - texWidth *= scale; - texHeight *= scale; - } - - if (texHeight > FrameDebuggerStyles.EventDetails.k_MaxViewportHeight) - { - float scale = FrameDebuggerStyles.EventDetails.k_MaxViewportHeight / texHeight; - texWidth *= scale; - texHeight = FrameDebuggerStyles.EventDetails.k_MaxViewportHeight; - } + float scale = viewportWidth / scaledRenderTargetWidth; + scaledRenderTargetWidth *= scale; + scaledRenderTargetHeight *= scale; } - EditorGUILayout.BeginHorizontal(); + if (scaledRenderTargetHeight > FrameDebuggerStyles.EventDetails.k_MaxViewportHeight) { - m_OutputMeshTabs = (ShowAdditionalInfo)GUILayout.Toolbar((int)m_OutputMeshTabs, m_OutputMeshTabsGuiContents, FrameDebuggerStyles.EventDetails.outputMeshTabStyle); + float scale = FrameDebuggerStyles.EventDetails.k_MaxViewportHeight / scaledRenderTargetHeight; + scaledRenderTargetWidth *= scale; + scaledRenderTargetHeight = FrameDebuggerStyles.EventDetails.k_MaxViewportHeight; } + + EditorGUILayout.BeginHorizontal(); + m_OutputMeshTabs = (OutputMeshTabOptions)GUILayout.Toolbar((int)m_OutputMeshTabs, m_OutputMeshTabsGuiContents, FrameDebuggerStyles.EventDetails.s_OutputMeshTabStyle); EditorGUILayout.EndHorizontal(); if (m_OutputMeshTabs == 0) - { - DrawTargetTexture(viewportWidth, viewportHeightFaded, texWidth, texHeight, fadePercent, isDebuggingEditor); - } + DrawTargetTexture(rect, viewportWidth, viewportHeightFaded, renderTargetWidth, renderTargetHeight, scaledRenderTargetWidth, scaledRenderTargetHeight, fadePercent, isDebuggingEditor); else - { - DrawEventMesh(viewportWidth, viewportHeightFaded, texWidth); - } + DrawEventMesh(viewportWidth, viewportHeightFaded, scaledRenderTargetWidth); } GUILayout.EndVertical(); } @@ -414,81 +421,103 @@ private void DrawOutputAndMesh(Rect rect, bool shouldDrawOutputAndMesh, bool isD EndFoldoutBox(); } - private void DrawTargetTexture(float viewportWidth, float viewportHeight, float texWidth, float texHeight, float fadePercent, bool isDebuggingEditor) + private void DrawTargetTexture(Rect rect, float viewportWidth, float viewportHeight, float renderTargetWidth, float renderTargetHeight, float scaledRenderTargetWidth, float scaledRenderTargetHeight, float fadePercent, bool isDebuggingEditor) { - if (fadePercent < 1f) - viewportHeight *= fadePercent; - - EditorGUILayout.BeginHorizontal(FrameDebuggerStyles.EventDetails.outputMeshTextureStyle); + EditorGUILayout.BeginHorizontal(FrameDebuggerStyles.EventDetails.s_RenderTargetMeshBackgroundStyle); Rect previewRect = GUILayoutUtility.GetRect(viewportWidth, viewportHeight); - Rect textureRect = new Rect(previewRect.x, previewRect.y, texWidth, texHeight); - if (Event.current.type == EventType.Repaint && curEventData.rtOutput != null && previewRect.height > 1.0f) + // Early out if the texture is so small it can not be drawn... + int scaledTexWidthInt = (int) scaledRenderTargetWidth; + int scaledTexHeightInt = (int) scaledRenderTargetHeight; + if (scaledTexWidthInt <= 0 || scaledTexHeightInt <= 0) { - if (m_TargetTextureMaterial == null) - m_TargetTextureMaterial = Resources.GetBuiltinResource("PerformanceTools/FrameDebuggerRenderTargetDisplay.mat"); - - GraphicsFormat targetTextureFormat = (GraphicsFormat)curEventData.rtFormat; - - uint componentCount = GraphicsFormatUtility.GetComponentCount(targetTextureFormat); - m_TargetTextureMaterial.SetVector(ShaderPropertyIDs._Channels, (componentCount == 1) ? new Vector4(1, 0, 0, 0) : m_SelectedMask); - - bool linearColorSpace = QualitySettings.activeColorSpace == ColorSpace.Linear; - bool textureSRGB = GraphicsFormatUtility.IsSRGBFormat(targetTextureFormat); - float undoOutputSRGB = (isDebuggingEditor && (!linearColorSpace || textureSRGB)) ? 0.0f : 1.0f; - m_TargetTextureMaterial.SetFloat(ShaderPropertyIDs._UndoOutputSRGB, undoOutputSRGB); - - if (curEventData.rtIsBackBuffer && isDebuggingEditor) - { - m_TargetTextureMaterial.SetVector(ShaderPropertyIDs._Levels, new Vector4(0f, 1f, 0f, 0f)); - m_TargetTextureMaterial.SetFloat(ShaderPropertyIDs._ShouldYFlip, 1f); - } - else - { - m_TargetTextureMaterial.SetVector(ShaderPropertyIDs._Levels, new Vector4(m_RTBlackLevel, m_RTWhiteLevel, 0f, 0f)); - m_TargetTextureMaterial.SetFloat(ShaderPropertyIDs._ShouldYFlip, 0f); - } + EditorGUILayout.EndHorizontal(); + return; + } - m_TargetTextureMaterial.SetTexture(ShaderPropertyIDs._MainTex, curEventData.rtOutput); - m_TargetTextureMaterial.SetPass(0); + // We insert a dummy Draw Texture call when not repainting. + if (Event.current.type != EventType.Repaint) + { + GUI.DrawTexture(Rect.zero, null, ScaleMode.ScaleAndCrop, false, (scaledRenderTargetWidth / scaledRenderTargetHeight)); + EditorGUILayout.EndHorizontal(); + return; + } - if (viewportWidth > texWidth) - textureRect.x += (viewportWidth - texWidth) * 0.5f; + float yPos = previewRect.y; + if (viewportHeight > scaledRenderTargetHeight) + yPos += (viewportHeight - scaledRenderTargetHeight) * 0.5f; - if (viewportHeight > texHeight) - textureRect.y += (viewportHeight - texHeight) * 0.5f; + float xPos = 10f + Mathf.Max(viewportWidth * 0.5f - scaledRenderTargetWidth * 0.5f, 0f); - // Remember currently active render texture - RenderTexture currentActiveRT = RenderTexture.active; + // This is a weird one. When opening/closing the foldout, the image + // shifts a tiny bit to the right. This magic code prevents that. + if (fadePercent < 1f) + xPos -= 7f; - // Blit to the Render Texture - RenderTexture targetRenderTexture = RenderTexture.GetTemporary((int)texWidth, (int)previewRect.height); - Graphics.Blit(null, targetRenderTexture, m_TargetTextureMaterial, 0); + Rect textureRect = new Rect(xPos, yPos, scaledRenderTargetWidth, scaledRenderTargetHeight); - // Restore previously active render texture - RenderTexture.active = currentActiveRT; + if (m_RenderTargetRenderTextureCopy) + { + GUI.DrawTexture(textureRect, m_RenderTargetRenderTextureCopy, ScaleMode.ScaleAndCrop, false, (scaledRenderTargetWidth / scaledRenderTargetHeight)); + } + else if (m_CachedEventData.m_RenderTargetRenderTexture && previewRect.height > 1.0f) + { + GraphicsFormat targetTextureFormat = m_CachedEventData.m_RenderTargetFormat; + uint componentCount = GraphicsFormatUtility.GetComponentCount(targetTextureFormat); - // Draw the texture to the screen - GUI.DrawTexture(textureRect, targetRenderTexture, ScaleMode.ScaleAndCrop, false, (texWidth / texHeight)); + // On some devices the backbuffer gives the None Graphics Format. To prevent us + // displaying a black & white texture we force the channels to the selected mask. + bool shouldForceSelectedMask = targetTextureFormat == GraphicsFormat.None && m_CachedEventData.m_RenderTargetIsBackBuffer; + Vector4 channels = (componentCount > 1 || shouldForceSelectedMask) ? m_SelectedMask : new Vector4(1, 0, 0, 0); - // Release - RenderTexture.ReleaseTemporary(targetRenderTexture); + bool linearColorSpace = QualitySettings.activeColorSpace == ColorSpace.Linear; + bool textureSRGB = GraphicsFormatUtility.IsSRGBFormat(targetTextureFormat); + bool undoOutputSRGB = (isDebuggingEditor && (!linearColorSpace || textureSRGB)) ? false : true; + bool shouldYFlip = m_CachedEventData.m_RenderTargetIsBackBuffer && isDebuggingEditor && SystemInfo.graphicsUVStartsAtTop; + Vector4 levels = new Vector4(m_RTBlackLevel, m_RTWhiteLevel, 0f, 0f); + + // Get a temporary texture... + int renderTargetWidthInt = (int)renderTargetWidth; + int renderTargetHeightInt = (int)renderTargetHeight; + m_RenderTargetRenderTextureCopy = RenderTexture.GetTemporary(renderTargetWidthInt, renderTargetHeightInt); + + // Blit with the settings from the toolbar... + FrameDebuggerHelper.BlitToRenderTexture( + ref m_CachedEventData.m_RenderTargetRenderTexture, + ref m_RenderTargetRenderTextureCopy, + renderTargetWidthInt, + renderTargetHeightInt, + channels, + levels, + shouldYFlip, + undoOutputSRGB + ); + + // Draw the texture to the screen... + GUI.DrawTexture(textureRect, m_RenderTargetRenderTextureCopy, ScaleMode.ScaleAndCrop, false, (scaledRenderTargetWidth / scaledRenderTargetHeight)); } + EditorGUILayout.EndHorizontal(); } private void DrawEventMesh(float viewportWidth, float viewportHeight, float texWidth) { - Mesh mesh = curEventData.mesh; if (viewportHeight - FrameDebuggerStyles.EventDetails.k_MeshBottomToolbarHeight < 1.0f) return; + if (m_CachedEventData.m_Meshes == null || m_CachedEventData.m_Meshes.Length == 0) + { + DrawEventMeshBackground(viewportWidth, viewportHeight); + return; + } + + int meshIndex = m_MeshIndex; + meshIndex = Mathf.Min(m_MeshIndex, m_CachedEventData.m_Meshes.Length - 1); + Mesh mesh = m_CachedEventData.m_Meshes[meshIndex]; + if (mesh == null) { - // Draw the background - EditorGUILayout.BeginHorizontal(FrameDebuggerStyles.EventDetails.outputMeshTextureStyle, GUILayout.Width(viewportWidth)); - GUILayoutUtility.GetRect(viewportWidth, viewportHeight); - EditorGUILayout.EndHorizontal(); + DrawEventMeshBackground(viewportWidth, viewportHeight); return; } @@ -501,17 +530,16 @@ private void DrawEventMesh(float viewportWidth, float viewportHeight, float texW Rect previewRect = GUILayoutUtility.GetRect(viewportWidth, viewportHeight - FrameDebuggerStyles.EventDetails.k_MeshBottomToolbarHeight, GUILayout.ExpandHeight(false)); // Rectangle for the buttons... - Rect rect = EditorGUILayout.BeginHorizontal(GUIContent.none, FrameDebuggerStyles.EventDetails.meshPreToolbarStyle, GUILayout.Height(FrameDebuggerStyles.EventDetails.k_MeshBottomToolbarHeight)); + Rect rect = EditorGUILayout.BeginHorizontal(GUIContent.none, EditorStyles.toolbar, GUILayout.Height(FrameDebuggerStyles.EventDetails.k_MeshBottomToolbarHeight)); { GUILayout.FlexibleSpace(); - GUIContent meshName = new GUIContent(mesh.name); - float meshNameWidth = EditorStyles.label.CalcSize(meshName).x + 10f; - Rect meshNameRect = EditorGUILayout.GetControlRect(GUILayout.Width(meshNameWidth)); + Rect meshNameRect = EditorGUILayout.GetControlRect(GUILayout.Width(FrameDebuggerStyles.EventDetails.k_MeshNameWidth)); meshNameRect.y -= 1; meshNameRect.x = 10; - GUI.Label(meshNameRect, meshName, FrameDebuggerStyles.EventDetails.meshPreToolbarLabelStyle); + if (EditorGUI.DropdownButton(meshNameRect, m_CachedEventData.m_MeshNames[meshIndex], FocusType.Passive, EditorStyles.toolbarDropDown)) + DoMeshListPopup(meshNameRect, m_CachedEventData.m_MeshNames, meshIndex, SetMeshIndex); if (FrameDebuggerHelper.IsCurrentEventMouseDown() && FrameDebuggerHelper.IsClickingRect(meshNameRect)) { @@ -523,1129 +551,198 @@ private void DrawEventMesh(float viewportWidth, float viewportHeight, float texW } EditorGUILayout.EndHorizontal(); - m_Preview?.OnPreviewGUI(previewRect, EditorStyles.helpBox); - } - - private void DrawEventDetails(Rect rect) - { - if (BeginFoldoutBox(1, true, FrameDebuggerStyles.EventDetails.foldoutEventDetailsText, out float fadePercent)) + var evt = Event.current; + if (FrameDebuggerHelper.IsHoveringRect(previewRect) || evt.type != EventType.ScrollWheel) { - // Render Target... - EditorGUILayout.BeginHorizontal(); - { - EditorGUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.verticalLabelStyle); - { - EditorGUILayout.LabelField("RenderTarget", FrameDebuggerStyles.EventDetails.labelStyle); - } - EditorGUILayout.EndVertical(); - EditorGUILayout.BeginVertical(); - { - if (m_LastEventData.isComputeEvent || m_LastEventData.isRayTracingEvent) - EditorGUILayout.LabelField(FrameDebuggerStyles.EventDetails.k_NotAvailable, FrameDebuggerStyles.EventDetails.labelStyle); - else - EditorGUILayout.LabelField(curEventData.rtName, FrameDebuggerStyles.EventDetails.labelStyle); - } - EditorGUILayout.EndVertical(); - } - EditorGUILayout.EndHorizontal(); - - // Size, Color Actions, Blending, Z, Stencil... - EditorGUILayout.BeginHorizontal(); - { - EditorGUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.verticalLabelStyle); - { - EditorGUILayout.LabelField(m_LastEventData.detailsLabelsLeftColumn, FrameDebuggerStyles.EventDetails.labelStyle); - } - EditorGUILayout.EndVertical(); - EditorGUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.verticalValueStyle); - { - EditorGUILayout.LabelField(m_LastEventData.detailsValuesLeftColumn, FrameDebuggerStyles.EventDetails.labelStyle); - } - EditorGUILayout.EndVertical(); - EditorGUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.verticalLabelStyle); - { - EditorGUILayout.LabelField(m_LastEventData.detailLabelsRightColumn, FrameDebuggerStyles.EventDetails.labelStyle); - } - EditorGUILayout.EndVertical(); - EditorGUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.verticalValueStyle); - { - EditorGUILayout.LabelField(m_LastEventData.detailValuesRightColumn, FrameDebuggerStyles.EventDetails.labelStyle); - } - EditorGUILayout.EndVertical(); - } - EditorGUILayout.EndHorizontal(); - - // Meshes - EditorGUILayout.BeginHorizontal(); - { - EditorGUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.verticalLabelStyle); - { - EditorGUILayout.LabelField(m_LastEventData.meshTitle, FrameDebuggerStyles.EventDetails.labelStyle); - } - EditorGUILayout.EndVertical(); - EditorGUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.verticalLabelStyle); - { - if (m_LastEventData.listOfMeshesString == null) - { - EditorGUILayout.LabelField(m_LastEventData.firstMeshName, FrameDebuggerStyles.EventDetails.labelStyle); - } - else - { - m_ShouldShowMeshListFoldout = EditorGUILayout.Foldout(m_ShouldShowMeshListFoldout, m_LastEventData.firstMeshName + ((m_ShouldShowMeshListFoldout) ? string.Empty : "...")); - if (m_ShouldShowMeshListFoldout) - { - EditorGUILayout.LabelField(m_LastEventData.listOfMeshesString, FrameDebuggerStyles.EventDetails.labelStyle); - } - } - } - EditorGUILayout.EndVertical(); - } - EditorGUILayout.EndHorizontal(); - - // Batch cause - EditorGUILayout.BeginHorizontal(); - { - EditorGUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.verticalLabelStyle); - { - EditorGUILayout.LabelField(FrameDebuggerStyles.EventDetails.batchCauseText); - } - EditorGUILayout.EndVertical(); - EditorGUILayout.BeginVertical(); - { - EditorGUILayout.LabelField(FrameDebuggerStyles.EventDetails.batchBreakCauses[curEventData.batchBreakCause], FrameDebuggerStyles.EventDetails.labelStyle); - } - EditorGUILayout.EndVertical(); - } - EditorGUILayout.EndHorizontal(); - - // Shader - EditorGUILayout.BeginHorizontal(); - { - EditorGUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.verticalLabelStyle); - { - EditorGUILayout.LabelField(FrameDebuggerStyles.EventDetails.passLightModeText, FrameDebuggerStyles.EventDetails.labelStyle); - EditorGUILayout.LabelField(FrameDebuggerStyles.EventDetails.shaderText, FrameDebuggerStyles.EventDetails.labelStyle); - } - EditorGUILayout.EndVertical(); - EditorGUILayout.BeginVertical(); - { - EditorGUILayout.LabelField($"{m_LastEventData.passAndLightMode}", FrameDebuggerStyles.EventDetails.labelStyle); - if (m_LastEventData.shader != null) - { - GUI.enabled = false; - EditorGUILayout.ObjectField(m_LastEventData.shader, typeof(Shader), true); - GUI.enabled = true; - } - else - { - EditorGUILayout.LabelField(m_LastEventData.shaderName); - } - - } - EditorGUILayout.EndVertical(); - } - EditorGUILayout.EndHorizontal(); + m_Preview?.OnPreviewGUI(previewRect, EditorStyles.helpBox); } - EndFoldoutBox(); } - private void DrawKeywords(bool shouldDisplayProperties) + private void DrawEventMeshBackground(float viewportWidth, float viewportHeight) { - bool hasKeywords = m_LastEventData.keywords.Length != 0; - if (BeginFoldoutBox(2, shouldDisplayProperties && hasKeywords, FrameDebuggerStyles.EventDetails.foldoutKeywordsText, out float fadePercent)) - { - GUILayout.BeginHorizontal(FrameDebuggerStyles.EventDetails.propertiesVerticalStyle); - { - if (shouldDisplayProperties && hasKeywords) - GUILayout.Label(m_LastEventData.keywords, FrameDebuggerStyles.EventDetails.labelStyle); - } - GUILayout.EndHorizontal(); - } - EndFoldoutBox(); + EditorGUILayout.BeginHorizontal(FrameDebuggerStyles.EventDetails.s_RenderTargetMeshBackgroundStyle, GUILayout.Width(viewportWidth)); + GUILayoutUtility.GetRect(viewportWidth, viewportHeight); + EditorGUILayout.EndHorizontal(); } - private void DrawProperties(bool shouldDisplayProperties) + private void SetMeshIndex(object data) { - ShaderProperties props = curEventData.shaderProperties; - - Profiler.BeginSample("DrawTextureProperties"); - { - DrawTextureProperties(3, shouldDisplayProperties && props.textures.Length != 0, props.textures); - } - Profiler.EndSample(); + int popupIndex = (int)data; + if (popupIndex < 0 || popupIndex >= m_CachedEventData.m_MeshNames.Length) + return; - Profiler.BeginSample("DrawIntProperties"); - { - DrawIntProperties(4, shouldDisplayProperties && props.ints.Length != 0, props.ints); - } - Profiler.EndSample(); - Profiler.BeginSample("DrawFloatProperties"); - { - DrawFloatProperties(5, shouldDisplayProperties && props.floats.Length != 0, props.floats); - } - Profiler.EndSample(); - Profiler.BeginSample("DrawVectorProperties"); - { - DrawVectorProperties(6, shouldDisplayProperties && props.vectors.Length != 0, props.vectors); - } - Profiler.EndSample(); - Profiler.BeginSample("DrawMatrixProperties"); - { - DrawMatrixProperties(7, shouldDisplayProperties && props.matrices.Length != 0, props.matrices); - } - Profiler.EndSample(); - Profiler.BeginSample("DrawBufferProperties"); - { - DrawBufferProperties(8, shouldDisplayProperties && props.buffers.Length != 0, props.buffers); - } - Profiler.EndSample(); - Profiler.BeginSample("DrawConstantBufferProperties"); - { - DrawConstantBufferProperties(9, shouldDisplayProperties && props.cbuffers.Length != 0, props.cbuffers); - } - Profiler.EndSample(); + m_MeshIndex = popupIndex; } - private void DrawTextureProperties(int num, bool shouldDrawProperties, ShaderTextureInfo[] textures) + private void DoMeshListPopup(Rect popupRect, GUIContent[] elements, int selectedIndex, GenericMenu.MenuFunction2 func) { - if (BeginFoldoutBox(num, shouldDrawProperties, FrameDebuggerStyles.EventDetails.foldoutTexturesText, out float fadePercent)) + GenericMenu menu = new GenericMenu(); + for (int i = 0; i < elements.Length; i++) { - GUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.propertiesVerticalStyle); - { - int numOfTextures = shouldDrawProperties ? textures.Length : 0; - for (int i = 0; i < numOfTextures; i++) - { - ShaderTextureInfo t = textures[i]; - - GUILayout.BeginHorizontal(); - { - Event evt = Event.current; - - // Parameter name.. - DrawPropName(t.name); - - // Vertex/Fragment/Geometry/Hull.. - DrawShaderPropertyFlags(t.flags); - - Texture texture = t.value; - if (texture != null) - { - // Texture Preview.. - // for 2D textures, we want to display them directly as a preview (this will make render textures display their contents) but0 - // for cube maps and other non-2D types DrawPreview does not do anything useful right now, so get their asset type icon at least - bool isTex2D = texture.dimension == TextureDimension.Tex2D; - Texture previewTexture = isTex2D ? texture : AssetPreview.GetMiniThumbnail(texture); - - Rect previewRect = GUILayoutUtility.GetRect(new GUIContent(previewTexture), FrameDebuggerStyles.EventDetails.textureButtonStyle); - previewRect.x += 1f; - previewRect.y += 4f; - GUI.DrawTexture(previewRect, previewTexture, ScaleMode.StretchToFill, false); - - if (FrameDebuggerHelper.IsCurrentEventMouseDown() && FrameDebuggerHelper.IsClickingRect(previewRect)) - { - PopupWindowWithoutFocus.Show( - previewRect, - new ObjectPreviewPopup(previewTexture), - new[] { PopupLocation.Left, PopupLocation.Below, PopupLocation.Right } - ); - } - - // Dimensions: Tex2D, Tex3D. etc... - GUILayout.Label($"{texture.dimension}", FrameDebuggerStyles.EventDetails.textureDimensionsStyle); - - // Texture Size... - GUILayout.Label($"{texture.width}x{texture.height}", FrameDebuggerStyles.EventDetails.textureSizeStyle); - - // Texture format... - GUILayout.Label(GetGUIContent(FrameDebuggerHelper.GetFormat(texture), FrameDebuggerStyles.EventDetails.k_TextureFormatMaxChars), FrameDebuggerStyles.EventDetails.textureFormatStyle); - - // Texture name... - // Disable the GUI to prevent users from assigning textures to the field - GUI.enabled = false; - EditorGUILayout.ObjectField(texture, typeof(Texture), true); - GUI.enabled = true; - } - else - { - EditorGUILayout.LabelField(t.textureName, FrameDebuggerStyles.EventDetails.labelStyle); - } - } - GUILayout.EndHorizontal(); - } - } - GUILayout.EndVertical(); + var element = elements[i]; + menu.AddItem(element, i == selectedIndex, func, i); } - EndFoldoutBox(); + menu.DropDown(popupRect); } - private void DrawIntProperties(int num, bool shouldDrawProperties, ShaderIntInfo[] ints) + private void DrawDetails(Rect rect) { - if (BeginFoldoutBox(num, shouldDrawProperties, FrameDebuggerStyles.EventDetails.foldoutIntsText, out float fadePercent)) + bool isFoldoutOpen = BeginFoldoutBox(1, true, FrameDebuggerStyles.EventDetails.s_FoldoutEventDetailsText, out float fadePercent, () => m_CachedEventData.detailsCopyString); + if (!isFoldoutOpen) { - GUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.propertiesVerticalStyle); - { - int numOfIntegers = shouldDrawProperties ? ints.Length : 0; - for (int i = 0; i < numOfIntegers;) - { - ShaderIntInfo t = ints[i]; - int numValues = (ints[i].flags >> k_ShaderTypeBits) & k_ArraySizeBitMask; - if (numValues == 0) - break; - - GUILayout.BeginHorizontal(); - if (numValues == 1) - { - DrawPropName(t.name); - DrawShaderPropertyFlags(t.flags); - GUILayout.Label(t.value.ToString(FrameDebuggerStyles.EventDetails.k_IntFormat, CultureInfo.InvariantCulture.NumberFormat), FrameDebuggerStyles.EventDetails.labelStyle); - ShaderPropertyCopyValueMenu(GUILayoutUtility.GetLastRect(), t.value); - } - else - { - DrawPropName($"{t.name} [{numValues}]"); - DrawShaderPropertyFlags(t.flags); - - Rect buttonRect = GUILayoutUtility.GetRect(FrameDebuggerStyles.EventDetails.arrayPopupButtonText, GUI.skin.button); - buttonRect.width = FrameDebuggerStyles.EventDetails.k_ArrayValuePopupBtnWidth; - if (GUI.Button(buttonRect, FrameDebuggerStyles.EventDetails.arrayPopupButtonText)) - { - FrameDebuggerStyles.ArrayValuePopup.GetValueStringDelegate getValueString = - (int index, bool highPrecision) => ints[index].value.ToString(FrameDebuggerStyles.EventDetails.k_IntFormat, CultureInfo.InvariantCulture.NumberFormat); - - PopupWindowWithoutFocus.Show( - buttonRect, - new FrameDebuggerStyles.ArrayValuePopup(i, numValues, 1, 100.0f, getValueString), - new[] { PopupLocation.Left, PopupLocation.Below, PopupLocation.Right } - ); - } - } - - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - - i += numValues; - } - } - GUILayout.EndVertical(); + EndFoldoutBox(); + return; } - EndFoldoutBox(); - } - - private void DrawFloatProperties(int num, bool shouldDrawProperties, ShaderFloatInfo[] floats) - { - if (BeginFoldoutBox(num, shouldDrawProperties, FrameDebuggerStyles.EventDetails.foldoutFloatsText, out float fadePercent)) - { - GUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.propertiesVerticalStyle); - { - int numOfFloats = shouldDrawProperties ? floats.Length : 0; - for (int i = 0; i < numOfFloats;) - { - ShaderFloatInfo t = floats[i]; - int numValues = (floats[i].flags >> k_ShaderTypeBits) & k_ArraySizeBitMask; - if (numValues == 0) - break; - GUILayout.BeginHorizontal(); - if (numValues == 1) - { - DrawPropName(t.name); - DrawShaderPropertyFlags(t.flags); - GUILayout.Label(t.value.ToString(FrameDebuggerStyles.EventDetails.k_FloatFormat, CultureInfo.InvariantCulture.NumberFormat), FrameDebuggerStyles.EventDetails.labelStyle); - ShaderPropertyCopyValueMenu(GUILayoutUtility.GetLastRect(), t.value); - } - else - { - string arrayName = $"{t.name} [{numValues}]"; - DrawPropName(arrayName); - DrawShaderPropertyFlags(t.flags); - - Rect buttonRect = GUILayoutUtility.GetRect(FrameDebuggerStyles.EventDetails.arrayPopupButtonText, GUI.skin.button); - buttonRect.width = FrameDebuggerStyles.EventDetails.k_ArrayValuePopupBtnWidth; - if (GUI.Button(buttonRect, FrameDebuggerStyles.EventDetails.arrayPopupButtonText)) - { - FrameDebuggerStyles.ArrayValuePopup.GetValueStringDelegate getValueString = - (int index, bool highPrecision) => floats[index].value.ToString(FrameDebuggerStyles.EventDetails.k_FloatFormat, CultureInfo.InvariantCulture.NumberFormat); - - PopupWindowWithoutFocus.Show( - buttonRect, - new FrameDebuggerStyles.ArrayValuePopup(i, numValues, 1, 100.0f, getValueString), - new[] { PopupLocation.Left, PopupLocation.Below, PopupLocation.Right }); - } - } - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); + GUIStyle style = FrameDebuggerStyles.EventDetails.s_MonoLabelStyle; - i += numValues; - } - } - GUILayout.EndVertical(); - } - EndFoldoutBox(); - } + // Size, Color Actions, Blending, Z, Stencil... + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField(m_CachedEventData.details, style, GUILayout.MinWidth(m_CachedEventData.m_DetailsGUIWidth), GUILayout.MinHeight(m_CachedEventData.m_DetailsGUIHeight)); + EditorGUILayout.EndHorizontal(); - private void DrawVectorProperties(int num, bool shouldDrawProperties, ShaderVectorInfo[] vectors) - { - if (BeginFoldoutBox(num, shouldDrawProperties, FrameDebuggerStyles.EventDetails.foldoutVectorsText, out float fadePercent)) + // Shader + if (m_CachedEventData.m_ShouldDisplayRealAndOriginalShaders) { - GUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.propertiesVerticalStyle); + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField(FrameDebuggerStyles.EventDetails.s_RealShaderText, style, GUILayout.Width(FrameDebuggerStyles.EventDetails.k_ShaderLabelWidth)); + if (m_CachedEventData.m_RealShader == null) + EditorGUILayout.LabelField(m_CachedEventData.m_RealShaderName, style); + else { - int numOfVectors = shouldDrawProperties ? vectors.Length : 0; - for (int i = 0; i < numOfVectors;) - { - ShaderVectorInfo t = vectors[i]; - int numValues = (vectors[i].flags >> k_ShaderTypeBits) & k_ArraySizeBitMask; - if (numValues == 0) - break; - - GUILayout.BeginHorizontal(); - if (numValues == 1) - { - DrawPropName(t.name); - DrawShaderPropertyFlags(t.flags); - GUILayout.Label(t.value.ToString(FrameDebuggerStyles.EventDetails.k_FloatFormat), FrameDebuggerStyles.EventDetails.labelStyle); - ShaderPropertyCopyValueMenu(GUILayoutUtility.GetLastRect(), t.value); - } - else - { - DrawPropName($"{t.name} [{numValues}]"); - DrawShaderPropertyFlags(t.flags); - - Rect buttonRect = GUILayoutUtility.GetRect(FrameDebuggerStyles.EventDetails.arrayPopupButtonText, GUI.skin.button); - if (GUI.Button(buttonRect, FrameDebuggerStyles.EventDetails.arrayPopupButtonText)) - { - FrameDebuggerStyles.ArrayValuePopup.GetValueStringDelegate getValueString = - (int index, bool highPrecision) => vectors[index].value.ToString(highPrecision ? FrameDebuggerStyles.EventDetails.k_FloatFormat : FrameDebuggerStyles.EventDetails.k_FloatFormat); - - PopupWindowWithoutFocus.Show( - buttonRect, - new FrameDebuggerStyles.ArrayValuePopup(i, numValues, 1, 200.0f, getValueString), - new[] { PopupLocation.Left, PopupLocation.Below, PopupLocation.Right }); - } - } - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - - i += numValues; - } + GUI.enabled = false; + EditorGUILayout.ObjectField(m_CachedEventData.m_RealShader, typeof(Shader), true, GUILayout.Width(FrameDebuggerStyles.EventDetails.k_ShaderObjectFieldWidth)); + GUI.enabled = true; } - GUILayout.EndVertical(); - } - EndFoldoutBox(); - } - - private void DrawMatrixProperties(int num, bool shouldDrawProperties, ShaderMatrixInfo[] matrices) - { - if (BeginFoldoutBox(num, shouldDrawProperties, FrameDebuggerStyles.EventDetails.foldoutMatricesText, out float fadePercent)) - { - GUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.propertiesVerticalStyle); - { - int numOfMatrices = shouldDrawProperties ? matrices.Length : 0; - for (int i = 0; i < numOfMatrices;) - { - ShaderMatrixInfo t = matrices[i]; - int numValues = (matrices[i].flags >> k_ShaderTypeBits) & k_ArraySizeBitMask; - if (numValues == 0) - break; - - GUILayout.BeginHorizontal(); - if (numValues == 1) - { - DrawPropName(t.name); - DrawShaderPropertyFlags(t.flags); - GUILayout.Label(t.value.ToString(FrameDebuggerStyles.EventDetails.k_FloatFormat), FrameDebuggerStyles.EventDetails.labelStyle); - ShaderPropertyCopyValueMenu(GUILayoutUtility.GetLastRect(), t.value); - } - else - { - DrawPropName($"{t.name} [{numValues}]"); - DrawShaderPropertyFlags(t.flags); - - Rect buttonRect = GUILayoutUtility.GetRect(FrameDebuggerStyles.EventDetails.arrayPopupButtonText, GUI.skin.button); - if (GUI.Button(buttonRect, FrameDebuggerStyles.EventDetails.arrayPopupButtonText)) - { - FrameDebuggerStyles.ArrayValuePopup.GetValueStringDelegate getValueString = - (int index, bool highPrecision) => '\n' + matrices[index].value.ToString(FrameDebuggerStyles.EventDetails.k_FloatFormat); - - PopupWindowWithoutFocus.Show( - buttonRect, - new FrameDebuggerStyles.ArrayValuePopup(i, numValues, 5, 200.0f, getValueString), - new[] { PopupLocation.Left, PopupLocation.Below, PopupLocation.Right } - ); - } - } - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - - i += numValues; - } - } - GUILayout.EndVertical(); - } - EndFoldoutBox(); - } + EditorGUILayout.EndHorizontal(); - private void DrawBufferProperties(int num, bool shouldDrawProperties, ShaderBufferInfo[] buffers) - { - if (BeginFoldoutBox(num, shouldDrawProperties, FrameDebuggerStyles.EventDetails.foldoutBuffersText, out float fadePercent)) - { - GUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.propertiesVerticalStyle); + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField(FrameDebuggerStyles.EventDetails.s_OriginalShaderText, GUILayout.Width(FrameDebuggerStyles.EventDetails.k_ShaderLabelWidth)); + if (m_CachedEventData.m_OriginalShader == null) + EditorGUILayout.LabelField(m_CachedEventData.m_OriginalShaderName, style); + else { - if (shouldDrawProperties) - { - foreach (var t in buffers) - { - GUILayout.BeginHorizontal(); - - DrawPropName(t.name); - DrawShaderPropertyFlags(t.flags); - - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - } - } + GUI.enabled = false; + EditorGUILayout.ObjectField(m_CachedEventData.m_OriginalShader, typeof(Shader), true, GUILayout.Width(FrameDebuggerStyles.EventDetails.k_ShaderObjectFieldWidth)); + GUI.enabled = true; } - GUILayout.EndVertical(); + EditorGUILayout.EndHorizontal(); } - EndFoldoutBox(); - } - - private void DrawConstantBufferProperties(int num, bool shouldDrawProperties, ShaderConstantBufferInfo[] cbuffers) - { - if (BeginFoldoutBox(num, shouldDrawProperties, FrameDebuggerStyles.EventDetails.foldoutCBufferText, out float fadePercent)) - { - GUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.propertiesVerticalStyle); - { - if (shouldDrawProperties) - { - foreach (var t in cbuffers) - { - GUILayout.BeginHorizontal(); - DrawPropName(t.name); - DrawShaderPropertyFlags(t.flags); - - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - } - } - } - GUILayout.EndVertical(); - } EndFoldoutBox(); } - private void DrawShaderPropertyFlags(int flags) - { - m_TempSB1.Clear(); - m_TempSB2.Clear(); - - // Lowest bits of flags are set for each shader stage that property is used in; matching ShaderType C++ enum - const int k_VertexShaderFlag = (1 << 1); - const int k_FragmentShaderFlag = (1 << 2); - const int k_GeometryShaderFlag = (1 << 3); - const int k_HullShaderFlag = (1 << 4); - const int k_DomainShaderFlag = (1 << 5); - - int shaderCounter = 0; - if ((flags & k_VertexShaderFlag) != 0) { shaderCounter++; m_TempSB1.Append("v/"); m_TempSB2.Append("vertex & "); } - if ((flags & k_FragmentShaderFlag) != 0) { shaderCounter++; m_TempSB1.Append("f/"); m_TempSB2.Append("fragment & "); } - if ((flags & k_GeometryShaderFlag) != 0) { shaderCounter++; m_TempSB1.Append("g/"); m_TempSB2.Append("geometry & "); } - if ((flags & k_HullShaderFlag) != 0) { shaderCounter++; m_TempSB1.Append("h/"); m_TempSB2.Append("hull & "); } - if ((flags & k_DomainShaderFlag) != 0) { shaderCounter++; m_TempSB1.Append("d/"); m_TempSB2.Append("domain & "); } - - if (shaderCounter > 0) - { - m_TempSB1.Remove(m_TempSB1.Length - 1, 1); // Remove the last / - m_TempSB2.Remove(m_TempSB2.Length - 3, 2); // Remove the last & - m_TempSB2.Insert(0, "Used in "); - m_TempSB2.Append((shaderCounter > 1) ? "shaders" : "shader"); - } - - GUILayout.Label(EditorGUIUtility.TrTextContent(m_TempSB1.ToString(), m_TempSB2.ToString()), FrameDebuggerStyles.EventDetails.propertiesFlagsStyle); - } - - private void ShaderPropertyCopyValueMenu(Rect valueRect, System.Object value) - { - var e = Event.current; - if (e.type == EventType.ContextClick && valueRect.Contains(e.mousePosition)) - { - e.Use(); - GenericMenu menu = new GenericMenu(); - menu.AddItem(FrameDebuggerStyles.EventDetails.copyValueText, false, delegate - { - if (value is Vector4) - { - EditorGUIUtility.systemCopyBuffer = ((Vector4)value).ToString(FrameDebuggerStyles.EventDetails.k_FloatFormat); - } - else if (value is Matrix4x4) - { - EditorGUIUtility.systemCopyBuffer = ((Matrix4x4)value).ToString(FrameDebuggerStyles.EventDetails.k_FloatFormat); - } - else if (value is System.Single) - { - EditorGUIUtility.systemCopyBuffer = ((System.Single)value).ToString(FrameDebuggerStyles.EventDetails.k_FloatFormat); - } - else - { - EditorGUIUtility.systemCopyBuffer = $"{value}"; - } - }); - menu.ShowAsContext(); - } - } - - private GUIContent GetGUIContent(string text, int maxLength) - { - string fullName = text; - const int k_NumberOfDots = 3; - - // If we need to shorten the name, we will add the full name as a tooltip - if (text.Length > maxLength - k_NumberOfDots) - { - string shortName = fullName.Substring(0, maxLength - k_NumberOfDots) + "..."; - return EditorGUIUtility.TrTextContent(shortName, fullName); - } - - return EditorGUIUtility.TrTextContent(fullName); - } - - private void DrawPropName(string name) - { - GUILayout.Label(GetGUIContent(name, FrameDebuggerStyles.EventDetails.k_PropertyNameMaxChars), FrameDebuggerStyles.EventDetails.propertiesNameStyle); - } - - private void BuildCurEventDataStrings(FrameDebuggerEvent curEvent, FrameDebuggerEventData curEventData) + private void DrawShaderData(ShaderPropertyType propType, int foldoutIndex, GUIContent foldoutText, ShaderPropertyCollection shaderProperties) { - m_LastEventData.index = FrameDebuggerUtility.limit - 1; - m_LastEventData.type = curEvent.type; - int eventTypeInt = (int)m_LastEventData.type; - - // Figure out the type of event we have - m_LastEventData.isClearEvent = FrameDebuggerHelper.IsAClearEvent(m_LastEventData.type); - m_LastEventData.isResolveEvent = FrameDebuggerHelper.IsAResolveEvent(m_LastEventData.type); - m_LastEventData.isComputeEvent = FrameDebuggerHelper.IsAComputeEvent(m_LastEventData.type); - m_LastEventData.isRayTracingEvent = FrameDebuggerHelper.IsARayTracingEvent(m_LastEventData.type); - - // Shader Pass name & LightMode tag - GetShaderData(); - string pass = $"{(string.IsNullOrEmpty(curEventData.passName) ? FrameDebuggerStyles.EventDetails.k_NotAvailable : curEventData.passName)} ({curEventData.shaderPassIndex})"; - string lightMode = $"{(string.IsNullOrEmpty(curEventData.passLightMode) ? FrameDebuggerStyles.EventDetails.k_NotAvailable : curEventData.passLightMode)}"; - m_LastEventData.passAndLightMode = $"{pass}\n{lightMode}"; - - // Event title - var eventObj = FrameDebuggerUtility.GetFrameEventObject(m_LastEventData.index); - if (eventObj) - m_LastEventData.title = $"Event #{m_LastEventData.index + 1} {FrameDebuggerStyles.frameEventTypeNames[eventTypeInt]} {eventObj.name}"; - else - m_LastEventData.title = $"Event #{m_LastEventData.index + 1} {FrameDebuggerStyles.frameEventTypeNames[eventTypeInt]}"; - - m_TempSB1.Clear(); - m_TempSB2.Clear(); - - - if (m_LastEventData.isComputeEvent || m_LastEventData.isRayTracingEvent) - { - m_TempSB1.AppendLine("Size"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Format"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Color Actions"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Depth Actions"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - } - else - { - m_TempSB1.AppendLine("Size"); - m_TempSB2.AppendLine($"{curEventData.rtWidth}x{curEventData.rtHeight}"); - - m_TempSB1.AppendLine("Format"); - m_TempSB2.AppendLine($"{(GraphicsFormat)curEventData.rtFormat}"); - - m_TempSB1.AppendLine("Color Actions"); - m_TempSB2.AppendLine((curEventData.rtLoadAction == -1) ? FrameDebuggerStyles.EventDetails.k_NotAvailable : $"{(RenderBufferLoadAction)curEventData.rtLoadAction} / {(RenderBufferStoreAction)curEventData.rtStoreAction}"); - - m_TempSB1.AppendLine("Depth Actions"); - m_TempSB2.AppendLine((curEventData.rtDepthLoadAction == -1) ? FrameDebuggerStyles.EventDetails.k_NotAvailable : $"{(RenderBufferLoadAction)curEventData.rtDepthLoadAction} / {(RenderBufferStoreAction)curEventData.rtDepthStoreAction}"); - } - - m_LastEventData.isValid = true; - if (m_LastEventData.isComputeEvent) - { - m_TempSB1.AppendLine(); - m_TempSB2.AppendLine(); - - m_TempSB1.AppendLine("Kernel"); - m_TempSB2.AppendLine(curEventData.csKernel); - - m_TempSB1.AppendLine("Thread Groups"); - if (curEventData.csThreadGroupsX != 0 || curEventData.csThreadGroupsY != 0 || curEventData.csThreadGroupsZ != 0) - m_TempSB2.AppendLine($"{curEventData.csThreadGroupsX}x{curEventData.csThreadGroupsY}x{curEventData.csThreadGroupsZ}"); - else - m_TempSB2.AppendLine("Indirect dispatch"); - - m_TempSB1.AppendLine("Thread Group Size"); - if (curEventData.csGroupSizeX > 0) - m_TempSB2.AppendLine($"{curEventData.csGroupSizeX}x{curEventData.csGroupSizeY}x{curEventData.csGroupSizeZ}"); - else - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - } - else if (m_LastEventData.isRayTracingEvent) - { - m_TempSB1.AppendLine(); - m_TempSB2.AppendLine(); - - m_TempSB1.AppendLine("Ray Generation Shader"); - m_TempSB2.AppendLine(curEventData.rtsRayGenShaderName); - - m_TempSB1.AppendLine("SubShader Pass"); - m_TempSB2.AppendLine(curEventData.rtsShaderPassName); - - m_TempSB1.AppendLine("Acceleration Structure"); - if (curEventData.rtsAccelerationStructureName.Length > 0) - m_TempSB2.AppendLine(curEventData.rtsAccelerationStructureName); - else - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Dispatch Size"); - m_TempSB2.AppendLine($"{curEventData.rtsWidth} x {curEventData.rtsHeight} x {curEventData.rtsDepth}"); - - m_TempSB1.AppendLine("Max. Recursion Depth"); - m_TempSB2.AppendLine($"{curEventData.rtsMaxRecursionDepth}"); - - m_TempSB1.AppendLine("Miss Shader Count"); - m_TempSB2.AppendLine($"{curEventData.rtsMissShaderCount}"); - - m_TempSB1.AppendLine("Callable Shader Count"); - m_TempSB2.AppendLine($"{curEventData.rtsCallableShaderCount}"); - } - else if (m_LastEventData.isClearEvent || m_LastEventData.isResolveEvent) - { - m_LastEventData.passAndLightMode = $"{FrameDebuggerStyles.EventDetails.k_NotAvailable}\n{FrameDebuggerStyles.EventDetails.k_NotAvailable}"; - - m_TempSB1.AppendLine("Memoryless"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine(); - m_TempSB2.AppendLine(); - - // Colormask - m_TempSB1.AppendLine("ColorMask"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - // Blend state - m_TempSB1.AppendLine("Blend Color"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Blend Alpha "); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); + ShaderPropertyDisplayInfo[] propertyDisplayInfo = shaderProperties.m_Data; - m_TempSB1.AppendLine("BlendOp Color"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); + // We disable and hide keywords and shader properties for clear and resolve events or when we don't have any data. + bool shouldDisplayProperties = !m_CachedEventData.m_IsClearEvent && !m_CachedEventData.m_IsResolveEvent; + bool shouldDraw = shouldDisplayProperties && propertyDisplayInfo != null && propertyDisplayInfo.Length > 0; + bool isFoldoutOpen = BeginFoldoutBox(foldoutIndex, shouldDraw, foldoutText, out float fadePercent, () => shaderProperties.copyString); - m_TempSB1.AppendLine("BlendOp Alpha"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine(); - m_TempSB2.AppendLine(); - - m_TempSB1.AppendLine("Draw Calls"); - m_TempSB2.AppendLine($"{curEventData.drawCallCount}"); - - m_TempSB1.AppendLine("Vertices"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Indices"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine(); - m_TempSB2.AppendLine(); - - int type = (int)curEvent.type; - - m_TempSB1.AppendLine("Clear Color"); - if ((type & 1) != 0 && !m_LastEventData.isResolveEvent) - m_TempSB2.AppendLine($"({curEventData.rtClearColorR:F3}, {curEventData.rtClearColorG:F3}, {curEventData.rtClearColorB:F3}, {curEventData.rtClearColorA:F3})"); - else - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Clear Depth"); - if ((type & 2) != 0) - m_TempSB2.AppendLine(curEventData.clearDepth.ToString("f3")); - else - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Clear Stencil"); - if ((type & 4) != 0) - m_TempSB2.Append(FrameDebuggerHelper.GetStencilString((int)curEventData.clearStencil)); - else - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - } - else + if (!shouldDraw || !isFoldoutOpen) { - m_TempSB1.AppendLine("Memoryless"); - m_TempSB2.AppendLine((curEventData.rtMemoryless != 0) ? "Yes" : "No"); - - m_TempSB1.AppendLine(); - m_TempSB2.AppendLine(); - - FrameDebuggerBlendState blendState = curEventData.blendState; - - // Colormask - m_TempSB1.AppendLine("ColorMask"); - if (blendState.writeMask == 0) - m_TempSB2.AppendLine("0"); - else - { - if ((blendState.writeMask & 8) != 0) - m_TempSB2.Append('R'); - - if ((blendState.writeMask & 4) != 0) - m_TempSB2.Append('G'); - - if ((blendState.writeMask & 2) != 0) - m_TempSB2.Append('B'); - - if ((blendState.writeMask & 1) != 0) - m_TempSB2.Append('A'); - - m_TempSB2.Append("\n"); - } - - // Blend state - m_TempSB1.AppendLine("Blend Color"); - m_TempSB2.AppendLine($"{blendState.srcBlend} {blendState.dstBlend}"); - - m_TempSB1.AppendLine("Blend Alpha "); - m_TempSB2.AppendLine($"{blendState.srcBlendAlpha} {blendState.dstBlendAlpha}"); - - m_TempSB1.AppendLine("BlendOp Color"); - m_TempSB2.AppendLine(blendState.blendOp.ToString()); - - m_TempSB1.AppendLine("BlendOp Alpha"); - m_TempSB2.AppendLine(blendState.blendOpAlpha.ToString()); - - m_TempSB1.AppendLine(); - m_TempSB2.AppendLine(); - - if (curEventData.instanceCount > 1) - { - m_TempSB1.AppendLine("DrawInstanced Calls"); - m_TempSB2.AppendLine($"{curEventData.drawCallCount}"); - - m_TempSB1.AppendLine("Instances"); - m_TempSB2.AppendLine($"{curEventData.instanceCount}"); - } - else - { - m_TempSB1.AppendLine("Draw Calls"); - m_TempSB2.AppendLine($"{curEventData.drawCallCount}"); - } - - m_TempSB1.AppendLine("Vertices"); - m_TempSB2.AppendLine(curEventData.vertexCount.ToString()); - - m_TempSB1.AppendLine("Indices"); - m_TempSB2.AppendLine(curEventData.indexCount.ToString()); - - m_TempSB1.AppendLine(); - m_TempSB2.AppendLine(); - - m_TempSB1.AppendLine("Clear Color"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Clear Depth"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Clear Stencil"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); + EndFoldoutBox(); + return; } - m_LastEventData.detailsLabelsLeftColumn = m_TempSB1.ToString(); - m_LastEventData.detailsValuesLeftColumn = m_TempSB2.ToString(); + GUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.s_PropertiesBottomMarginStyle); - m_TempSB1.Clear(); - m_TempSB2.Clear(); + GUILayout.BeginHorizontal(FrameDebuggerStyles.EventDetails.s_PropertiesLeftMarginStyle); + GUILayout.Label(shaderProperties.m_Header, FrameDebuggerStyles.EventDetails.s_MonoLabelBoldStyle); + GUILayout.EndHorizontal(); - m_LastEventData.firstMeshName = null; - m_LastEventData.listOfMeshesString = null; - if (curEventData.meshInstanceIDs != null && curEventData.meshInstanceIDs.Length > 0) + for (int i = 0; i < propertyDisplayInfo.Length; i++) { - int numOfMeshesAdded = 0; - m_LastEventData.meshTitle = curEventData.meshInstanceIDs.Length < 2 ? "Mesh" : "Meshes"; - for (int i = 0; i < curEventData.meshInstanceIDs.Length; i++) + ShaderPropertyDisplayInfo data = propertyDisplayInfo[i]; + if (!data.m_IsArray) { - int id = curEventData.meshInstanceIDs[i]; - Mesh mesh = EditorUtility.InstanceIDToObject(id) as Mesh; - if (mesh != null) + Texture textureToDisplay = data.m_TextureCopy != null ? data.m_TextureCopy as Texture : data.m_Texture; + if (textureToDisplay == null) { - if (m_LastEventData.firstMeshName == null) - m_LastEventData.firstMeshName = mesh.name; - else - m_TempSB2.AppendLine(" " + mesh.name); - numOfMeshesAdded++; + GUILayout.BeginHorizontal(FrameDebuggerStyles.EventDetails.s_PropertiesLeftMarginStyle); + GUILayout.Label(data.m_PropertyString, FrameDebuggerStyles.EventDetails.s_MonoLabelNoWrapStyle); } - } - - // We keep the meshes string null if it's only one instance - // and just show the firstMesh instead. - if (numOfMeshesAdded > 1) - { - m_LastEventData.listOfMeshesString = m_TempSB2.ToString(); - m_TempSB2.Clear(); - } - } - else - { - m_LastEventData.meshTitle = "Mesh"; - m_LastEventData.firstMeshName = curEventData.mesh == null ? FrameDebuggerStyles.EventDetails.k_NotAvailable : curEventData.mesh.name; - } - - if (m_LastEventData.isClearEvent - || m_LastEventData.isResolveEvent - || m_LastEventData.isComputeEvent - || m_LastEventData.isRayTracingEvent) - { - // Depth state - m_TempSB1.AppendLine("ZClip"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("ZTest"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("ZWrite"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Cull"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Conservative"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Offset"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine(); - m_TempSB2.AppendLine(); - - // Stencil state - m_TempSB1.AppendLine("Stencil"); - m_TempSB2.AppendLine("Disabled"); - - m_TempSB1.AppendLine("Stencil Ref"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Stencil ReadMask"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Stencil WriteMask"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Stencil Comp"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Stencil Pass"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Stencil Fail"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Stencil ZFail"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - } - else - { - FrameDebuggerRasterState rasterState = curEventData.rasterState; - FrameDebuggerDepthState depthState = curEventData.depthState; - - // Depth state - m_TempSB1.AppendLine("ZClip"); - m_TempSB2.AppendLine(rasterState.depthClip.ToString()); - - m_TempSB1.AppendLine("ZTest"); - m_TempSB2.AppendLine(depthState.depthFunc.ToString()); - - m_TempSB1.AppendLine("ZWrite"); - m_TempSB2.AppendLine(depthState.depthWrite == 0 ? "Off" : "On"); - - m_TempSB1.AppendLine("Cull"); - m_TempSB2.AppendLine(rasterState.cullMode.ToString()); - - m_TempSB1.AppendLine("Conservative"); - m_TempSB2.AppendLine(rasterState.conservative.ToString()); - - m_TempSB1.AppendLine("Offset"); - m_TempSB2.AppendLine($"{rasterState.slopeScaledDepthBias}, {rasterState.depthBias}"); - - m_TempSB1.AppendLine(); - m_TempSB2.AppendLine(); - - // Stencil state - if (curEventData.stencilState.stencilEnable) - { - m_TempSB1.AppendLine("Stencil"); - m_TempSB2.AppendLine("Enabled"); - - m_TempSB1.AppendLine("Stencil Ref"); - m_TempSB2.AppendLine(FrameDebuggerHelper.GetStencilString(curEventData.stencilRef)); - - m_TempSB1.AppendLine("Stencil ReadMask"); - m_TempSB2.AppendLine(curEventData.stencilState.readMask != 255 ? FrameDebuggerHelper.GetStencilString(curEventData.stencilState.readMask) : FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Stencil WriteMask"); - m_TempSB2.AppendLine(curEventData.stencilState.writeMask != 255 ? FrameDebuggerHelper.GetStencilString(curEventData.stencilState.writeMask) : FrameDebuggerStyles.EventDetails.k_NotAvailable); - - // Only show *Front states when CullMode is set to Back. - if (curEventData.rasterState.cullMode == CullMode.Back) - { - m_TempSB1.AppendLine("Stencil Comp"); - m_TempSB2.AppendLine($"{curEventData.stencilState.stencilFuncFront}"); - m_TempSB1.AppendLine("Stencil Pass"); - m_TempSB2.AppendLine($"{curEventData.stencilState.stencilPassOpFront}"); - m_TempSB1.AppendLine("Stencil Fail"); - m_TempSB2.AppendLine($"{curEventData.stencilState.stencilFailOpFront}"); - m_TempSB1.AppendLine("Stencil ZFail"); - m_TempSB2.AppendLine($"{curEventData.stencilState.stencilZFailOpFront}"); - } - // Only show *Back states when CullMode is set to Front. - else if (curEventData.rasterState.cullMode == CullMode.Front) - { - m_TempSB1.AppendLine("Stencil Comp"); - m_TempSB2.AppendLine($"{curEventData.stencilState.stencilFuncBack}"); - m_TempSB1.AppendLine("Stencil Pass"); - m_TempSB2.AppendLine($"{curEventData.stencilState.stencilPassOpBack}"); - m_TempSB1.AppendLine("Stencil Fail"); - m_TempSB2.AppendLine($"{curEventData.stencilState.stencilFailOpBack}"); - m_TempSB1.AppendLine("Stencil ZFail"); - m_TempSB2.AppendLine($"{curEventData.stencilState.stencilZFailOpBack}"); - } - // Show both *Front and *Back states for two-sided geometry. else { - m_TempSB1.AppendLine("Stencil Comp"); - m_TempSB2.AppendLine($"{curEventData.stencilState.stencilFuncFront} {curEventData.stencilState.stencilFuncBack}"); - m_TempSB1.AppendLine("Stencil Pass"); - m_TempSB2.AppendLine($"{curEventData.stencilState.stencilPassOpFront} {curEventData.stencilState.stencilPassOpBack}"); - m_TempSB1.AppendLine("Stencil Fail"); - m_TempSB2.AppendLine($"{curEventData.stencilState.stencilFailOpFront} {curEventData.stencilState.stencilFailOpBack}"); - m_TempSB1.AppendLine("Stencil ZFail"); - m_TempSB2.AppendLine($"{curEventData.stencilState.stencilZFailOpFront} {curEventData.stencilState.stencilZFailOpBack}"); + GUILayout.BeginHorizontal(); + + // Texture Preview.. + // for 2D textures, we want to display them directly as a preview (this will make render textures display their contents) but + // for cube maps and other non-2D types DrawPreview does not do anything useful right now, so get their asset type icon at least + bool isTex2D = textureToDisplay.dimension == TextureDimension.Tex2D; + Texture previewTexture = isTex2D ? textureToDisplay : AssetPreview.GetMiniThumbnail(textureToDisplay); + Rect previewRect = GUILayoutUtility.GetRect(10, 10, FrameDebuggerStyles.EventDetails.s_TextureButtonStyle); + previewRect.width = 10; + previewRect.height = 10; + previewRect.x += 4f; + previewRect.y += 6f; + + GUI.DrawTexture(previewRect, previewTexture, ScaleMode.StretchToFill, false); + GUILayout.Label(data.m_PropertyString, FrameDebuggerStyles.EventDetails.s_MonoLabelNoWrapStyle); + + if (FrameDebuggerHelper.IsCurrentEventMouseDown() && FrameDebuggerHelper.IsClickingRect(previewRect)) + { + PopupWindowWithoutFocus.Show( + previewRect, + new ObjectPreviewPopup(textureToDisplay), + new[] { PopupLocation.Left, PopupLocation.Below, PopupLocation.Right } + ); + } } + GUILayout.EndHorizontal(); } else { - m_TempSB1.AppendLine("Stencil"); - m_TempSB2.AppendLine("Disabled"); - - m_TempSB1.AppendLine("Stencil Ref"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Stencil ReadMask"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Stencil WriteMask"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); + GUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.s_PropertiesLeftMarginStyle); - m_TempSB1.AppendLine("Stencil Comp"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); + data.m_IsFoldoutOpen = EditorGUILayout.Foldout(data.m_IsFoldoutOpen, data.m_FoldoutString, FrameDebuggerStyles.EventDetails.s_ArrayFoldoutStyle); + if (data.m_IsFoldoutOpen) + GUILayout.Label(data.m_PropertyString, data.m_ArrayGUIStyle); - m_TempSB1.AppendLine("Stencil Pass"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Stencil Fail"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); - - m_TempSB1.AppendLine("Stencil ZFail"); - m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); + GUILayout.EndVertical(); } - } - - m_LastEventData.detailLabelsRightColumn = m_TempSB1.ToString(); - m_LastEventData.detailValuesRightColumn = m_TempSB2.ToString(); - - // Keywords - m_TempSB2.Clear(); - if (!string.IsNullOrEmpty(curEventData.shaderKeywords)) - { - if (m_KeywordsList.Count == 0) - m_KeywordsList.AddRange(curEventData.shaderKeywords.Split(' ')); - - m_KeywordsList.Sort(); - m_TempSB2.AppendLine(string.Join("\n", m_KeywordsList)); - } - - m_LastEventData.keywords = m_TempSB2.ToString().Trim(); - } - - private void GetShaderData() - { - const string k_ComputeShaderFilter = "t:computeshader"; - const string k_RayTracingShaderFilter = "t:raytracingshader"; + propertyDisplayInfo[i] = data; - // Clear or Resolve events - if (m_LastEventData.isClearEvent || m_LastEventData.isResolveEvent) - { - m_LastEventData.shaderName = FrameDebuggerStyles.EventDetails.k_NotAvailable; - m_LastEventData.shader = null; - return; - } - - // Normal shader events - if (!m_LastEventData.isComputeEvent && !m_LastEventData.isRayTracingEvent) - { - m_LastEventData.shaderName = curEventData.shaderName; - m_LastEventData.shader = Shader.Find(m_LastEventData.shaderName); - return; - } - - // Compute or RayTracing events - m_LastEventData.shader = null; - string filter; - if (m_LastEventData.isComputeEvent) - { - m_LastEventData.shaderName = curEventData.csName; - filter = k_ComputeShaderFilter; - } - else if (m_LastEventData.isRayTracingEvent) - { - m_LastEventData.shaderName = curEventData.rtsName; - filter = k_RayTracingShaderFilter; - } - else - return; - - string[] guids = AssetDatabase.FindAssets($"{m_LastEventData.shaderName} {filter}"); - if (guids.Length > 0) - { - string path = AssetDatabase.GUIDToAssetPath(guids[0]); - UnityEngine.Object shader = AssetDatabase.LoadAssetAtPath(path); - if (shader != null) - m_LastEventData.shader = shader; + if (Event.current.type == EventType.ContextClick) + ShaderPropertyCopyValueMenu(GUILayoutUtility.GetLastRect(), FrameDebuggerStyles.EventDetails.s_CopyPropertyText, () => data.copyString); } + GUILayout.EndVertical(); + EndFoldoutBox(); } - private bool BeginFoldoutBox(int nr, bool hasData, GUIContent header, out float fadePercent) + private bool BeginFoldoutBox(int foldoutIndex, bool hasData, GUIContent header, out float fadePercent, Func copyStringAction = null) { GUI.enabled = hasData; - EditorGUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.foldoutCategoryBoxStyle); + EditorGUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.s_FoldoutCategoryBoxStyle); Rect r = GUILayoutUtility.GetRect(2, 21); EditorGUI.BeginChangeCheck(); - bool expanded = EditorGUI.FoldoutTitlebar(r, header, m_FoldoutAnimators[nr].target, true, EditorStyles.inspectorTitlebarFlat, EditorStyles.inspectorTitlebarText); + bool expanded = EditorGUI.FoldoutTitlebar(r, header, m_FoldoutAnimators[foldoutIndex].target, true, EditorStyles.inspectorTitlebarFlat, EditorStyles.inspectorTitlebarText); if (EditorGUI.EndChangeCheck()) { - m_FoldoutAnimators[nr].target = !m_FoldoutAnimators[nr].target; + bool newState = !m_FoldoutAnimators[foldoutIndex].target; + EditorPrefs.SetBool(k_foldoutKeys[foldoutIndex], newState); + + // If Shift is being held down, we change the state for all of them... + if (Event.current.shift || Event.current.alt) + for (int i = m_FoldoutAnimators.Length - 1; i >= 0; i--) + m_FoldoutAnimators[i].target = newState; + else + m_FoldoutAnimators[foldoutIndex].target = newState; } + if (Event.current.type == EventType.ContextClick) + if (copyStringAction != null && FrameDebuggerStyles.EventDetails.s_FoldoutCopyText[foldoutIndex] != null && copyStringAction != null) + ShaderPropertyCopyValueMenu(r, FrameDebuggerStyles.EventDetails.s_FoldoutCopyText[foldoutIndex], copyStringAction); + GUI.enabled = true; EditorGUI.indentLevel++; - fadePercent = m_FoldoutAnimators[nr].faded; + fadePercent = m_FoldoutAnimators[foldoutIndex].faded; - return EditorGUILayout.BeginFadeGroup(m_FoldoutAnimators[nr].faded); + return EditorGUILayout.BeginFadeGroup(m_FoldoutAnimators[foldoutIndex].faded); } private void EndFoldoutBox() @@ -1654,5 +751,26 @@ private void EndFoldoutBox() EditorGUI.indentLevel--; EditorGUILayout.EndVertical(); } + + private void ShaderPropertyCopyValueMenu(Rect valueRect, GUIContent menuText, Func textToCopy) + { + Profiler.BeginSample("ShaderPropertyCopyValueMenu"); + var e = Event.current; + + // Copy function + if (valueRect.Contains(e.mousePosition)) + { + e.Use(); + + GenericMenu menu = new GenericMenu(); + menu.AddItem(menuText, false, delegate { + if (textToCopy != null) + EditorGUIUtility.systemCopyBuffer = textToCopy(); + }); + menu.ShowAsContext(); + } + + Profiler.EndSample(); + } } } diff --git a/Editor/Mono/PerformanceTools/FrameDebuggerEventDisplayData.cs b/Editor/Mono/PerformanceTools/FrameDebuggerEventDisplayData.cs new file mode 100644 index 0000000000..15e4d942bc --- /dev/null +++ b/Editor/Mono/PerformanceTools/FrameDebuggerEventDisplayData.cs @@ -0,0 +1,1111 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Text; +using System.Collections.Generic; + +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Profiling; +using UnityEngine.Experimental.Rendering; + +using UnityEditor; + +namespace UnityEditorInternal.FrameDebuggerInternal +{ + internal struct ShaderPropertyCollection + { + internal string m_TypeName; + internal string m_Header; + internal string m_HeaderFormat; + internal string m_Format; + internal ShaderPropertyDisplayInfo[] m_Data; + + internal void GetCopyText(ref StringBuilder sb) + { + sb.AppendLine(FrameDebuggerStyles.EventDetails.s_DashesString); + sb.AppendLine(m_TypeName); + sb.AppendLine(FrameDebuggerStyles.EventDetails.s_DashesString); + + if (m_Data.Length == 0) + { + sb.AppendLine(""); + sb.AppendLine(); + return; + } + + sb.AppendLine(m_Header); + for (int i = 0; i < m_Data.Length; i++) + m_Data[i].GetCopyText(ref sb); + sb.AppendLine(); + } + + internal string copyString + { + get + { + StringBuilder sb = new StringBuilder(4096); + GetCopyText(ref sb); + return sb.ToString(); + } + } + + internal void Clear() + { + for (int i = 0; i < m_Data.Length; i++) + m_Data[i].Clear(); + m_Data = null; + } + } + + internal struct ShaderPropertyDisplayInfo + { + internal bool m_IsArray; + internal bool m_IsFoldoutOpen; + internal string m_FoldoutString; + internal string m_PropertyString; + internal Texture m_Texture; + internal GUIStyle m_ArrayGUIStyle; + internal RenderTexture m_TextureCopy; + + internal string copyString + { + get + { + StringBuilder sb = new StringBuilder(4096); + GetCopyText(ref sb); + return sb.ToString(); + } + } + + internal void Clear() + { + // We only need to destroy the copy texture as the other one is + // directly from source so it could be an asset in the project, etc. + if (m_TextureCopy != null) + { + FrameDebuggerHelper.DestroyTexture(ref m_TextureCopy); + m_TextureCopy = null; + } + } + + internal void GetCopyText(ref StringBuilder sb) + { + if (!m_IsArray) + sb.AppendLine(m_PropertyString); + else + { + sb.AppendLine(m_FoldoutString); + sb.AppendLine(m_PropertyString); + } + } + } + + internal enum ShaderPropertyType + { + Keyword = 0, + Texture = 1, + Int = 2, + Float = 3, + Vector = 4, + Matrix = 5, + Buffer = 6, + CBuffer = 7, + } + + // Cached data built from FrameDebuggerEventData. + // Only need to rebuild them when event data actually changes. + internal class CachedEventDisplayData + { + internal int m_Index; + internal int m_RenderTargetWidth; + internal int m_RenderTargetHeight; + internal int m_RenderTargetShowableRTCount; + internal uint m_Hash; + internal bool m_IsValid; + internal bool m_IsClearEvent; + internal bool m_IsResolveEvent; + internal bool m_IsComputeEvent; + internal bool m_IsRayTracingEvent; + internal bool m_IsConfigureFoveatedEvent; + internal bool m_ShouldDisplayRealAndOriginalShaders; + internal bool m_RenderTargetIsBackBuffer; + internal bool m_RenderTargetIsDepthOnlyRT; + internal bool m_RenderTargetHasShowableDepth; + internal float m_DetailsGUIWidth; + internal float m_DetailsGUIHeight; + internal string m_Title; + internal string m_RealShaderName; + internal string m_OriginalShaderName; + internal string m_RayTracingShaderName; + internal string m_RayTracingGenerationShaderName; + internal Mesh[] m_Meshes; + internal GUIContent[] m_MeshNames; + internal RenderTexture m_RenderTargetRenderTexture; + internal FrameEventType m_Type; + internal GraphicsFormat m_RenderTargetFormat; + internal UnityEngine.Object m_RealShader; + internal UnityEngine.Object m_OriginalShader; + + internal string copyString + { + get + { + string detailsString = detailsCopyString; + m_StringBuilder.Clear(); + m_StringBuilder.AppendLine(FrameDebuggerStyles.EventDetails.s_EqualsString); + m_StringBuilder.AppendLine(m_Title); + m_StringBuilder.AppendLine(FrameDebuggerStyles.EventDetails.s_EqualsString); + m_StringBuilder.AppendLine(); + m_StringBuilder.AppendLine(detailsString); + + for (int i = 0; i < m_ShaderProperties.Length; i++) + m_ShaderProperties[i].GetCopyText(ref m_StringBuilder); + + return m_StringBuilder.ToString(); + } + } + + internal string detailsCopyString + { + get + { + StringBuilder sb = new StringBuilder(4096); + sb.AppendLine(FrameDebuggerStyles.EventDetails.s_DashesString); + sb.AppendLine("Details"); + sb.AppendLine(FrameDebuggerStyles.EventDetails.s_DashesString); + sb.AppendLine(details); + + if (m_ShouldDisplayRealAndOriginalShaders) + { + sb.AppendFormat(k_TwoColumnFormat, FrameDebuggerStyles.EventDetails.s_RealShaderText, m_RealShaderName); + sb.AppendLine(); + sb.AppendFormat(k_TwoColumnFormat, FrameDebuggerStyles.EventDetails.s_OriginalShaderText, m_OriginalShaderName); + sb.AppendLine(); + } + return sb.ToString(); + } + } + + private string m_Details; + internal string details + { + get + { + if (string.IsNullOrEmpty(m_Details)) + m_Details = m_DetailsStringBuilder.ToString(); + + return m_Details; + } + } + internal ShaderPropertyCollection[] m_ShaderProperties; + internal ShaderPropertyCollection m_Keywords => m_ShaderProperties[(int)ShaderPropertyType.Keyword]; + internal ShaderPropertyCollection m_Textures => m_ShaderProperties[(int)ShaderPropertyType.Texture]; + internal ShaderPropertyCollection m_Ints => m_ShaderProperties[(int)ShaderPropertyType.Int]; + internal ShaderPropertyCollection m_Floats => m_ShaderProperties[(int)ShaderPropertyType.Float]; + internal ShaderPropertyCollection m_Vectors => m_ShaderProperties[(int)ShaderPropertyType.Vector]; + internal ShaderPropertyCollection m_Matrices => m_ShaderProperties[(int)ShaderPropertyType.Matrix]; + internal ShaderPropertyCollection m_Buffers => m_ShaderProperties[(int)ShaderPropertyType.Buffer]; + internal ShaderPropertyCollection m_CBuffers => m_ShaderProperties[(int)ShaderPropertyType.CBuffer]; + + // Private + private int m_MaxNameLength; + private int m_MaxStageLength; + private int m_MaxTexSizeLength; + private int m_MaxSampleTypeLength; + private int m_MaxColorFormatLength; + private int m_MaxDepthFormatLength; + private int dataTypeEnumLength => Enum.GetNames(typeof(ShaderPropertyType)).Length; + private StringBuilder m_StringBuilder = new StringBuilder(32768); + private StringBuilder m_DetailsStringBuilder = new StringBuilder(4096); + + // Constants + private const int k_MinNameLength = 38; + private const int k_MinStageLength = 5; // "Stage".Length + private const int k_MinTexSizeLength = 11; // "65536x65536".Length + private const int k_MinSampleTypeLength = 12; // "Sampler Type".Length + private const int k_MinColorFormatLength = 12; // "Color Format".Length + private const int k_MinDepthFormatLength = 19; // "DepthStencil Format".Length + private const int k_ColumnSpace = 2; + private const int k_MaxDecimalNumbers = 7; + private const int k_MaxNaturalNumber = 10 + k_MaxDecimalNumbers + 1 + 1; // + decimal point + sign + private const string k_TwoColumnFormat = "{0, -22}{1, -35}"; + private const string k_FourColumnFormat = "{0, -22}{1, -35}{2, -22}{3, -10}"; + private static string s_IntPrecision = k_MaxNaturalNumber + ":F0"; + private static string s_FloatPrecision = k_MaxNaturalNumber + ":F" + k_MaxDecimalNumbers; + private const string k_NotAvailableString = FrameDebuggerStyles.EventDetails.k_NotAvailable; + + // Structs + private struct ShaderPropertySortingInfo : IComparable + { + internal string m_Name; + internal int m_ArrayIndex; + + public int CompareTo(ShaderPropertySortingInfo other) + { + return string.Compare(m_Name, other.m_Name); + } + } + + internal void Initialize(FrameDebuggerEvent curEvent, FrameDebuggerEventData curEventData) + { + Clear(); + + BuildCurEventDataStrings(curEvent, curEventData); + BuildShaderPropertyData(curEventData); + + if (curEventData.m_RenderTargetRenderTexture != null) + { + m_RenderTargetRenderTexture = curEventData.m_RenderTargetRenderTexture; + + RenderTextureDescriptor desc = curEventData.m_RenderTargetRenderTexture.descriptor; + if (desc.width > 0 && desc.height > 0) + { + m_RenderTargetWidth = desc.width; + m_RenderTargetHeight = desc.height; + } + else + { + m_RenderTargetWidth = curEventData.m_RenderTargetWidth; + m_RenderTargetHeight = curEventData.m_RenderTargetHeight; + } + m_IsValid = true; + } + else + { + m_RenderTargetWidth = curEventData.m_RenderTargetWidth; + m_RenderTargetHeight = curEventData.m_RenderTargetHeight; + m_IsValid = m_IsComputeEvent || m_IsRayTracingEvent; + } + } + + private void BuildShaderPropertyData(FrameDebuggerEventData curEventData) + { + ShaderInfo shaderInfo = curEventData.m_ShaderInfo; + m_ShaderProperties= new ShaderPropertyCollection[dataTypeEnumLength]; + m_ShaderProperties[(int)ShaderPropertyType.Keyword] = GetShaderData(ShaderPropertyType.Keyword, "Keywords", ref shaderInfo, shaderInfo.m_Keywords.Length); + m_ShaderProperties[(int)ShaderPropertyType.Float] = GetShaderData(ShaderPropertyType.Float, "Floats", ref shaderInfo, shaderInfo.m_Floats.Length); + m_ShaderProperties[(int)ShaderPropertyType.Int] = GetShaderData(ShaderPropertyType.Int, "Ints", ref shaderInfo, shaderInfo.m_Ints.Length); + m_ShaderProperties[(int)ShaderPropertyType.Vector] = GetShaderData(ShaderPropertyType.Vector, "Vectors", ref shaderInfo, shaderInfo.m_Vectors.Length); + m_ShaderProperties[(int)ShaderPropertyType.Matrix] = GetShaderData(ShaderPropertyType.Matrix, "Matrices", ref shaderInfo, shaderInfo.m_Matrices.Length); + m_ShaderProperties[(int)ShaderPropertyType.Texture] = GetShaderData(ShaderPropertyType.Texture, "Textures", ref shaderInfo, shaderInfo.m_Textures.Length); + m_ShaderProperties[(int)ShaderPropertyType.Buffer] = GetShaderData(ShaderPropertyType.Buffer, "Buffers", ref shaderInfo, shaderInfo.m_Buffers.Length); + m_ShaderProperties[(int)ShaderPropertyType.CBuffer] = GetShaderData(ShaderPropertyType.CBuffer, "Constant Buffers", ref shaderInfo, shaderInfo.m_CBuffers.Length); + } + + private ShaderPropertyCollection GetShaderData(ShaderPropertyType propType, string typeName, ref ShaderInfo shaderInfo, int arrayLength) + { + ShaderPropertyCollection displayInfo = new ShaderPropertyCollection(); + displayInfo.m_TypeName = typeName; + GetFormatAndHeader(propType, ref shaderInfo, ref displayInfo); + + // Clear and Resolve events often have properties from the previous events + // tied to them. We therefore force it to skip them. + // TODO: Fix this properly in C++ land. + if (m_IsClearEvent || m_IsResolveEvent) + arrayLength = 0; + + // Check the data and create structs for valid ones... + List myList = new List(); + for (int index = 0; index < arrayLength; index++) + if (CreateShaderPropertyDisplayInfo(propType, index, ref shaderInfo, out ShaderPropertySortingInfo propDisplayInfo)) + myList.Add(propDisplayInfo); + + // Sort it... + myList.Sort(); + + // Fill the ordered structs with data... + ShaderPropertyDisplayInfo[] myArr = new ShaderPropertyDisplayInfo[myList.Count]; + for (int index = 0; index < myList.Count; index++) + GetShaderPropertyData(propType, myList[index].m_ArrayIndex, myList[index].m_Name, ref displayInfo, ref shaderInfo, ref myArr[index]); + + // Assign the array and return + displayInfo.m_Data = myArr; + return displayInfo; + } + + private void GetFormatAndHeader(ShaderPropertyType dataType, ref ShaderInfo shaderInfo, ref ShaderPropertyCollection displayData) + { + // To keep the lines dynamic that scales with the various properties, we + // count the characters for various fields so we can adjust the text formatting + CountCharacters(dataType, shaderInfo); + + // Every property starts with the name + scope + displayData.m_Format = "{0, " + -m_MaxNameLength + "}{1, " + -m_MaxStageLength + "}"; + displayData.m_HeaderFormat = string.Empty; + + switch (dataType) + { + case ShaderPropertyType.Keyword: + displayData.m_Format += "{2, -12}{3, -9}"; + displayData.m_Header = String.Format(displayData.m_Format, "Name", "Stage", "Scope", "Dynamic"); + break; + case ShaderPropertyType.Float: + displayData.m_Format += "{2, " + s_FloatPrecision + "}"; + displayData.m_Header = String.Format(displayData.m_Format, "Name", "Stage", "Value"); + break; + case ShaderPropertyType.Int: + displayData.m_Format += "{2, " + s_IntPrecision + "}"; + displayData.m_Header = String.Format(displayData.m_Format, "Name", "Stage", "Value"); + break; + case ShaderPropertyType.Vector: + displayData.m_Format += "{2, " + s_FloatPrecision + "}{3, " + s_FloatPrecision + "}{4, " + s_FloatPrecision + "}{5, " + s_FloatPrecision + "}"; + displayData.m_Header = String.Format(displayData.m_Format, "Name", "Stage", "Value(R)", "Value(G)", "Value(B)", "Value(A)"); + break; + case ShaderPropertyType.Matrix: + displayData.m_HeaderFormat = "{0, " + -m_MaxNameLength + "}{1, " + -m_MaxStageLength + "}{2, " + s_FloatPrecision + "}{3, " + s_FloatPrecision + "}{4, " + s_FloatPrecision + "}{5, " + s_FloatPrecision + "}"; + displayData.m_Format = "{0, " + -m_MaxNameLength + "}{1, " + -m_MaxStageLength + "}{2, " + s_FloatPrecision + "}{3, " + s_FloatPrecision + "}{4, " + s_FloatPrecision + "}{5, " + s_FloatPrecision + "}\n" + + "{6, " + -m_MaxNameLength + "}{7, " + -m_MaxStageLength + "}{8, " + s_FloatPrecision + "}{9, " + s_FloatPrecision + "}{10, " + s_FloatPrecision + "}{11, " + s_FloatPrecision + "}\n" + + "{12, " + -m_MaxNameLength + "}{13, " + -m_MaxStageLength + "}{14, " + s_FloatPrecision + "}{15, " + s_FloatPrecision + "}{16, " + s_FloatPrecision + "}{17, " + s_FloatPrecision + "}\n" + + "{18, " + -m_MaxNameLength + "}{19, " + -m_MaxStageLength + "}{20, " + s_FloatPrecision + "}{21, " + s_FloatPrecision + "}{22, " + s_FloatPrecision + "}{23, " + s_FloatPrecision + "}"; + displayData.m_Header = String.Format(displayData.m_HeaderFormat, "Name", "Stage", "Column 0", "Column 1", "Column 2", "Column 3"); + break; + case ShaderPropertyType.Texture: + displayData.m_Format += "{2, " + -m_MaxTexSizeLength + "}{3, " + -m_MaxSampleTypeLength + "}{4, " + -m_MaxColorFormatLength + "}{5, " + -m_MaxDepthFormatLength + "}{6}"; + displayData.m_HeaderFormat = displayData.m_Format; + displayData.m_Header = String.Format(displayData.m_HeaderFormat, "Name", "Stage", "Size", "Sampler Type", "Color Format", "DepthStencil Format", "Texture"); + break; + case ShaderPropertyType.Buffer: + displayData.m_Format = "{0, " + -m_MaxNameLength + "}"; + displayData.m_Header = String.Format(displayData.m_Format, "Name"); + break; + case ShaderPropertyType.CBuffer: + displayData.m_Format = "{0, " + -m_MaxNameLength + "}"; + displayData.m_Header = String.Format(displayData.m_Format, "Name"); + break; + default: + return; + } + } + + private bool CreateShaderPropertyDisplayInfo(ShaderPropertyType dataType, int arrayIndex, ref ShaderInfo shaderInfo, out ShaderPropertySortingInfo data) + { + int flags; + string name; + data = new ShaderPropertySortingInfo(); + switch (dataType) + { + case ShaderPropertyType.Keyword: flags = shaderInfo.m_Keywords[arrayIndex].m_Flags; name = shaderInfo.m_Keywords[arrayIndex].m_Name; break; + case ShaderPropertyType.Float: flags = shaderInfo.m_Floats[arrayIndex].m_Flags; name = shaderInfo.m_Floats[arrayIndex].m_Name; break; + case ShaderPropertyType.Int: flags = shaderInfo.m_Ints[arrayIndex].m_Flags; name = shaderInfo.m_Ints[arrayIndex].m_Name; break; + case ShaderPropertyType.Vector: flags = shaderInfo.m_Vectors[arrayIndex].m_Flags; name = shaderInfo.m_Vectors[arrayIndex].m_Name; break; + case ShaderPropertyType.Matrix: flags = shaderInfo.m_Matrices[arrayIndex].m_Flags; name = shaderInfo.m_Matrices[arrayIndex].m_Name; break; + case ShaderPropertyType.Texture: flags = shaderInfo.m_Textures[arrayIndex].m_Flags; name = shaderInfo.m_Textures[arrayIndex].m_Name; break; + case ShaderPropertyType.Buffer: flags = 1; name = shaderInfo.m_Buffers[arrayIndex].m_Name; break; + case ShaderPropertyType.CBuffer: flags = 1; name = shaderInfo.m_CBuffers[arrayIndex].m_Name; break; + default: return false; + } + + // We get a lot of rubbish data sent in. Needs investigation to why... + if (dataType != ShaderPropertyType.Keyword && dataType != ShaderPropertyType.Texture && dataType != ShaderPropertyType.Buffer && dataType != ShaderPropertyType.CBuffer) + if (FrameDebuggerHelper.GetNumberOfValuesFromFlags(flags) <= 0) + return false; + + data.m_Name = name; + data.m_ArrayIndex = arrayIndex; + return true; + } + + private string GetArrayIndexString(int nameLength, int numOfValues, int currentIndex) + { + int numOfSpaces = nameLength + (FrameDebuggerHelper.CountDigits(numOfValues) - FrameDebuggerHelper.CountDigits(currentIndex)); + return $"{new string(' ', numOfSpaces)}[{currentIndex}]"; + } + + private bool GetShaderPropertyData(ShaderPropertyType dataType, int arrayIndex, string name, ref ShaderPropertyCollection propertyTypeDisplayData, ref ShaderInfo shaderInfo, ref ShaderPropertyDisplayInfo data) + { + m_StringBuilder.Clear(); + data = new ShaderPropertyDisplayInfo(); + + // Create and return the data... + string stage; + int numOfValues; + switch (dataType) + { + case ShaderPropertyType.Keyword: + Profiler.BeginSample("Keyword"); + ShaderKeywordInfo keywordInfo = shaderInfo.m_Keywords[arrayIndex]; + data.m_PropertyString = m_StringBuilder.AppendFormat(propertyTypeDisplayData.m_Format, + name, + FrameDebuggerHelper.GetShaderStageString(keywordInfo.m_Flags), + keywordInfo.m_IsGlobal ? "Global" : "Local", + keywordInfo.m_IsDynamic ? "Yes" : "No").ToString(); + Profiler.EndSample(); + break; + + case ShaderPropertyType.Texture: + Profiler.BeginSample("Texture"); + ShaderTextureInfo textureInfo = shaderInfo.m_Textures[arrayIndex]; + stage = FrameDebuggerHelper.GetShaderStageString(textureInfo.m_Flags); + + Texture texture = textureInfo.m_Value; + string samplerType; + string size; + string colorFormat; + string depthStencilFormat; + string textureName = textureInfo.m_TextureName; + if (texture != null) + { + if (texture.dimension == TextureDimension.Tex2DArray) + samplerType = "Tex2D-Array"; + else if (texture.dimension == TextureDimension.CubeArray) + samplerType = "Cube-Array"; + else + samplerType = $"{texture.dimension}"; + + size = $"{texture.width}x{texture.height}"; + colorFormat = FrameDebuggerHelper.GetColorFormat(ref texture); + depthStencilFormat = FrameDebuggerHelper.GetDepthStencilFormat(ref texture); + + // We need to do a blit for when MSAA is enabled or when trying to show depth. + // The ObjectPreview doesn't currently support RenderTextures with only depth... + int volumeDepth = FrameDebuggerHelper.GetVolumeDepth(ref texture); + int msaaVal = FrameDebuggerHelper.GetMSAAValue(ref texture); + bool isDepthTex = FrameDebuggerHelper.IsADepthTexture(ref texture); + if (msaaVal > 1 || isDepthTex) + { + data.m_TextureCopy = new RenderTexture(texture.width, texture.height, 0, RenderTextureFormat.ARGB32); + FrameDebuggerHelper.BlitToRenderTexture( + ref texture, + ref data.m_TextureCopy, + texture.width, + texture.height, + volumeDepth, + Vector4.one, + new Vector4(0f, 1f, 0f, 0f), + false, + false + ); + } + else + data.m_Texture = texture; + } + else + { + samplerType = FrameDebuggerStyles.EventDetails.k_NotAvailable; + size = FrameDebuggerStyles.EventDetails.k_NotAvailable; + colorFormat = FrameDebuggerStyles.EventDetails.k_NotAvailable; + depthStencilFormat = FrameDebuggerStyles.EventDetails.k_NotAvailable; + } + + data.m_PropertyString = m_StringBuilder.AppendFormat(propertyTypeDisplayData.m_HeaderFormat, name, stage, size, samplerType, colorFormat, depthStencilFormat, textureName).ToString(); + Profiler.EndSample(); + break; + + case ShaderPropertyType.Int: + Profiler.BeginSample("Int"); + ShaderIntInfo intInfo = shaderInfo.m_Ints[arrayIndex]; + numOfValues = FrameDebuggerHelper.GetNumberOfValuesFromFlags(intInfo.m_Flags); + stage = FrameDebuggerHelper.GetShaderStageString(intInfo.m_Flags); + data.m_IsArray = numOfValues > 1; + data.m_ArrayGUIStyle = CreateArrayGUIStyle(numOfValues); + + if (!data.m_IsArray) + data.m_PropertyString = String.Format(propertyTypeDisplayData.m_Format, name, stage, intInfo.m_Value); + else + { + m_StringBuilder.Clear(); + data.m_FoldoutString = String.Format(propertyTypeDisplayData.m_Format, $"{name}[{numOfValues}]", stage, string.Empty, string.Empty, string.Empty, string.Empty); + for (int k = arrayIndex; k < arrayIndex + numOfValues; k++) + { + int value = shaderInfo.m_Ints[k].m_Value; + m_StringBuilder.AppendFormat(propertyTypeDisplayData.m_Format, + GetArrayIndexString(name.Length, numOfValues, k - arrayIndex), + string.Empty, + value).AppendLine(); + } + + // Remove last linebreak + if (m_StringBuilder.Length > 2) + m_StringBuilder.Length--; + + data.m_PropertyString = m_StringBuilder.ToString(); + } + Profiler.EndSample(); + break; + + case ShaderPropertyType.Float: + Profiler.BeginSample("Float"); + ShaderFloatInfo floatInfo = shaderInfo.m_Floats[arrayIndex]; + numOfValues = FrameDebuggerHelper.GetNumberOfValuesFromFlags(floatInfo.m_Flags); + stage = FrameDebuggerHelper.GetShaderStageString(floatInfo.m_Flags); + data.m_IsArray = numOfValues > 1; + data.m_ArrayGUIStyle = CreateArrayGUIStyle(numOfValues); + + if (!data.m_IsArray) + data.m_PropertyString = String.Format(propertyTypeDisplayData.m_Format, name, stage, floatInfo.m_Value); + else + { + m_StringBuilder.Clear(); + + data.m_FoldoutString = m_StringBuilder.AppendFormat(propertyTypeDisplayData.m_Format, $"{name}[{numOfValues}]", stage, string.Empty, string.Empty, string.Empty, string.Empty).ToString(); + m_StringBuilder.Clear(); + for (int k = arrayIndex; k < arrayIndex + numOfValues; k++) + { + float value = shaderInfo.m_Floats[k].m_Value; + m_StringBuilder.AppendFormat(propertyTypeDisplayData.m_Format, + GetArrayIndexString(name.Length, numOfValues, k - arrayIndex), + string.Empty, + value).AppendLine(); + } + + // Remove last linebreak + if (numOfValues > 1 && m_StringBuilder.Length > 2) + m_StringBuilder.Length--; + + data.m_PropertyString = m_StringBuilder.ToString(); + } + Profiler.EndSample(); + break; + + case ShaderPropertyType.Vector: + Profiler.BeginSample("Vector"); + ShaderVectorInfo vectorInfo = shaderInfo.m_Vectors[arrayIndex]; + numOfValues = FrameDebuggerHelper.GetNumberOfValuesFromFlags(vectorInfo.m_Flags); + stage = FrameDebuggerHelper.GetShaderStageString(vectorInfo.m_Flags); + data.m_IsArray = numOfValues > 1; + data.m_ArrayGUIStyle = CreateArrayGUIStyle(numOfValues); + + if (!data.m_IsArray) + data.m_PropertyString = String.Format(propertyTypeDisplayData.m_Format, name, stage, vectorInfo.m_Value.x, vectorInfo.m_Value.y, vectorInfo.m_Value.z, vectorInfo.m_Value.w); + else + { + m_StringBuilder.Clear(); + data.m_FoldoutString = String.Format(propertyTypeDisplayData.m_Format, $"{name}[{numOfValues}]", stage, string.Empty, string.Empty, string.Empty, string.Empty); + for (int k = arrayIndex; k < arrayIndex + numOfValues; k++) + { + Vector4 value = shaderInfo.m_Vectors[k].m_Value; + m_StringBuilder.AppendFormat(propertyTypeDisplayData.m_Format, + GetArrayIndexString(name.Length, numOfValues, k - arrayIndex), + string.Empty, + value.x, + value.y, + value.z, + value.w).AppendLine(); + } + + // Remove last linebreak + if (numOfValues > 1 && m_StringBuilder.Length > 2) + m_StringBuilder.Length--; + + data.m_PropertyString = m_StringBuilder.ToString(); + } + + Profiler.EndSample(); + break; + + case ShaderPropertyType.Matrix: + Profiler.BeginSample("Matrix"); + ShaderMatrixInfo matrixInfo = shaderInfo.m_Matrices[arrayIndex]; + numOfValues = FrameDebuggerHelper.GetNumberOfValuesFromFlags(matrixInfo.m_Flags); + stage = FrameDebuggerHelper.GetShaderStageString(matrixInfo.m_Flags); + data.m_IsArray = numOfValues > 1; + data.m_ArrayGUIStyle = CreateArrayGUIStyle(numOfValues * 4); + + if (!data.m_IsArray) + { + Matrix4x4 value = matrixInfo.m_Value; + data.m_PropertyString = m_StringBuilder.AppendFormat(propertyTypeDisplayData.m_Format, + name, stage, value.m00, value.m01, value.m02, value.m03, + string.Empty, string.Empty, value.m10, value.m11, value.m12, value.m13, + string.Empty, string.Empty, value.m20, value.m21, value.m22, value.m23, + string.Empty, string.Empty, value.m30, value.m31, value.m32, value.m33).ToString(); + } + else + { + m_StringBuilder.Clear(); + data.m_FoldoutString = m_StringBuilder.AppendFormat(propertyTypeDisplayData.m_HeaderFormat, $"{name}[{numOfValues}]", stage, string.Empty, string.Empty, string.Empty, string.Empty).ToString(); + m_StringBuilder.Clear(); + for (int k = arrayIndex; k < arrayIndex + numOfValues; k++) + { + Matrix4x4 value = shaderInfo.m_Matrices[k].m_Value; + m_StringBuilder.AppendFormat(propertyTypeDisplayData.m_Format, + GetArrayIndexString(name.Length, numOfValues, k - arrayIndex), + string.Empty, value.m00, value.m01, value.m02, value.m03, + string.Empty, string.Empty, value.m10, value.m11, value.m12, value.m13, + string.Empty, string.Empty, value.m20, value.m21, value.m22, value.m23, + string.Empty, string.Empty, value.m30, value.m31, value.m32, value.m33).AppendLine(); + } + + // Remove last linebreak + if (numOfValues > 1 && m_StringBuilder.Length > 2) + m_StringBuilder.Length--; + + data.m_PropertyString = m_StringBuilder.ToString(); + } + Profiler.EndSample(); + break; + + case ShaderPropertyType.Buffer: + Profiler.BeginSample("Buffer"); + ShaderBufferInfo bufferInfo = shaderInfo.m_Buffers[arrayIndex]; + data.m_PropertyString = String.Format(propertyTypeDisplayData.m_Format, name); + Profiler.EndSample(); + break; + + case ShaderPropertyType.CBuffer: + Profiler.BeginSample("Constant Buffer"); + ShaderConstantBufferInfo constantBufferInfo = shaderInfo.m_CBuffers[arrayIndex]; + data.m_PropertyString = String.Format(propertyTypeDisplayData.m_Format, name); + Profiler.EndSample(); + break; + + default: + return false; + } + + return true; + } + + private GUIStyle CreateArrayGUIStyle(int numOfValues) + { + // GUIStyle.CalcSizeWithConstraints() is super expensive so to avoid that we set a fixed + // height and width so that function can early out. The width doesn't matter due as the + // Vertical layout above makes sure it doesn't go too far but needs to be set for the early out. + GUIStyle style = new GUIStyle(FrameDebuggerStyles.EventDetails.s_MonoLabelNoWrapStyle); + style.fixedWidth = 1000f; + style.fixedHeight = 16 * numOfValues; + return style; + } + + internal void OnDisable() + { + Clear(); + } + + private void Clear() + { + FrameDebuggerHelper.DestroyTexture(ref m_RenderTargetRenderTexture); + m_StringBuilder.Clear(); + m_DetailsStringBuilder.Clear(); + m_Details = string.Empty; + m_RealShaderName = string.Empty; + m_RealShader = null; + m_OriginalShaderName = string.Empty; + m_OriginalShader = null; + m_ShouldDisplayRealAndOriginalShaders = false; + + if (m_ShaderProperties != null) + { + for (int i = 0; i < m_ShaderProperties.Length; i++) + { + if (!m_ShaderProperties[i].Equals(default(ShaderPropertyCollection))) + m_ShaderProperties[i].Clear(); + } + m_ShaderProperties = null; + } + } + + private void BuildCurEventDataStrings(FrameDebuggerEvent curEvent, FrameDebuggerEventData curEventData) + { + // Initialize some key settings + m_Index = FrameDebuggerUtility.limit - 1; + m_Type = curEvent.m_Type; + + m_IsClearEvent = FrameDebuggerHelper.IsAClearEvent(m_Type); + m_IsResolveEvent = FrameDebuggerHelper.IsAResolveEvent(m_Type); + m_IsComputeEvent = FrameDebuggerHelper.IsAComputeEvent(m_Type); + m_IsRayTracingEvent = FrameDebuggerHelper.IsARayTracingEvent(m_Type); + m_IsConfigureFoveatedEvent = FrameDebuggerHelper.IsAConfigureFoveatedRenderingEvent(m_Type); + + m_RenderTargetIsBackBuffer = curEventData.m_RenderTargetIsBackBuffer; + m_RenderTargetFormat = (GraphicsFormat)curEventData.m_RenderTargetFormat; + m_RenderTargetIsDepthOnlyRT = GraphicsFormatUtility.IsDepthFormat(m_RenderTargetFormat); + m_RenderTargetHasShowableDepth = (curEventData.m_RenderTargetHasDepthTexture != 0); + m_RenderTargetShowableRTCount = curEventData.m_RenderTargetCount; + + // Event title + int eventTypeInt = (int)m_Type; + var eventObj = FrameDebuggerUtility.GetFrameEventObject(m_Index); + if (eventObj) + m_Title = $"Event #{m_Index + 1} {FrameDebuggerStyles.s_FrameEventTypeNames[eventTypeInt]} {eventObj.name}"; + else + m_Title = $"Event #{m_Index + 1} {FrameDebuggerStyles.s_FrameEventTypeNames[eventTypeInt]}"; + + // Collect data into a string builder based on the event type... + if (m_IsComputeEvent) + BuildComputeEventDataStrings(curEvent, curEventData); + else if (m_IsRayTracingEvent) + BuildRayTracingEventDataStrings(curEvent, curEventData); + else if (m_IsClearEvent) + BuildClearEventDataStrings(curEvent, curEventData); + else + BuildDrawCallEventDataStrings(curEvent, curEventData); + + // Create a string out of the string builder + m_Details = m_DetailsStringBuilder.ToString(); + + // Calculate the width and height so the scrollbar functions correctly for the details + GUIContent content = new GUIContent(m_Details); + Vector2 guiContentSize = FrameDebuggerStyles.EventDetails.s_MonoLabelStyle.CalcSize(content); + m_DetailsGUIWidth = guiContentSize.x + 12; // Add small margin to the width + m_DetailsGUIHeight = guiContentSize.y; + } + + private void BuildRayTracingEventDataStrings(FrameDebuggerEvent curEvent, FrameDebuggerEventData curEventData) + { + bool hasAccelerationName = curEventData.m_RayTracingShaderAccelerationStructureName.Length > 0; + string rayTracingMaxRecursionDepth = $"{curEventData.m_RayTracingShaderMaxRecursionDepth}"; + string rayTracingDispatchSize = $"{curEventData.m_RayTracingShaderWidth} x {curEventData.m_RayTracingShaderHeight} x {curEventData.m_RayTracingShaderDepth}"; + string rayTracingAccelerationStructure = hasAccelerationName ? curEventData.m_RayTracingShaderAccelerationStructureName : k_NotAvailableString; + string rayTracingMissShaderCount = $"{curEventData.m_RayTracingShaderMissShaderCount}"; + string rayTracingCallableShaderCount = $"{curEventData.m_RayTracingShaderCallableShaderCount}"; + string rayTracingPassName = $"{curEventData.m_RayTracingShaderPassName}"; + m_RayTracingShaderName = $"{curEventData.m_RayTracingShaderName}"; + m_RayTracingGenerationShaderName = $"{curEventData.m_RayTracingShaderRayGenShaderName}"; + + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Max Recursion Depth", rayTracingMaxRecursionDepth).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Dispatch Size", rayTracingDispatchSize).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Acceleration Structure", rayTracingAccelerationStructure).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Miss Shader Count", rayTracingMissShaderCount).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Callable Shader Count", rayTracingCallableShaderCount).AppendLine(); + m_DetailsStringBuilder.AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Pass", rayTracingPassName).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Ray Tracing Shader", m_RayTracingShaderName).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Ray Generation Shader", m_RayTracingGenerationShaderName).AppendLine(); + } + + private void BuildComputeEventDataStrings(FrameDebuggerEvent curEvent, FrameDebuggerEventData curEventData) + { + bool hasGroups = curEventData.m_ComputeShaderThreadGroupsX != 0 || curEventData.m_ComputeShaderThreadGroupsY != 0 || curEventData.m_ComputeShaderThreadGroupsZ != 0; + string computeThreadGroups = hasGroups ? $"{curEventData.m_ComputeShaderThreadGroupsX}x{curEventData.m_ComputeShaderThreadGroupsY}x{curEventData.m_ComputeShaderThreadGroupsZ}" : "Indirect dispatch"; + string computeThreadGroupSize = curEventData.m_ComputeShaderGroupSizeX > 0 ? $"{curEventData.m_ComputeShaderGroupSizeX}x{curEventData.m_ComputeShaderGroupSizeY}x{curEventData.m_ComputeShaderGroupSizeZ}" : k_NotAvailableString; + string computeShaderKernel = curEventData.m_ComputeShaderKernelName; + string computeShaderName = curEventData.m_ComputeShaderName; + + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Thread Groups", computeThreadGroups).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Thread Group Size", computeThreadGroupSize).AppendLine(); + m_DetailsStringBuilder.AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Kernel", computeShaderKernel).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Compute Shader", computeShaderName).AppendLine(); + } + + private void BuildClearEventDataStrings(FrameDebuggerEvent curEvent, FrameDebuggerEventData curEventData) + { + string target = curEventData.m_RenderTargetName; + + int typeInt = (int)curEvent.m_Type; + string clearColor = (typeInt & 1) != 0 ? $"({curEventData.m_RenderTargetClearColorR:F2}, {curEventData.m_RenderTargetClearColorG:F2}, {curEventData.m_RenderTargetClearColorB:F2}, {curEventData.m_RenderTargetClearColorA:F2})" : k_NotAvailableString; + string clearDepth = (typeInt & 2) != 0 ? curEventData.m_ClearDepth.ToString("f3") : k_NotAvailableString; + string clearStencil = (typeInt & 4) != 0 ? FrameDebuggerHelper.GetStencilString((int)curEventData.m_ClearStencil) : k_NotAvailableString; + + string size = $"{curEventData.m_RenderTargetWidth}x{curEventData.m_RenderTargetHeight}"; + string format = $"{m_RenderTargetFormat}"; + + bool hasColorActions = curEventData.m_RenderTargetLoadAction != -1; + bool hasDepthActions = curEventData.m_RenderTargetDepthLoadAction != -1; + string colorActions = hasColorActions ? $"{(RenderBufferLoadAction)curEventData.m_RenderTargetLoadAction} / {(RenderBufferStoreAction)curEventData.m_RenderTargetStoreAction}" : k_NotAvailableString; + string depthActions = hasDepthActions ? $"{(RenderBufferLoadAction)curEventData.m_RenderTargetDepthLoadAction} / {(RenderBufferStoreAction)curEventData.m_RenderTargetDepthStoreAction}" : k_NotAvailableString; + + m_DetailsStringBuilder.Append(FrameDebuggerStyles.EventDetails.s_RenderTargetText).AppendLine(); + m_DetailsStringBuilder.Append(target).AppendLine(); + m_DetailsStringBuilder.AppendLine(); + + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Size", size).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Format", format).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Color Actions", colorActions).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Depth Actions", depthActions).AppendLine(); + m_DetailsStringBuilder.AppendLine(); + + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Clear Color", clearColor).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Clear Depth", clearDepth).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Clear Stencil", clearStencil).AppendLine(); + } + + private void BuildDrawCallEventDataStrings(FrameDebuggerEvent curEvent, FrameDebuggerEventData curEventData) + { + m_ShouldDisplayRealAndOriginalShaders = true; + + // + FrameDebuggerRasterState rasterState = curEventData.m_RasterState; + FrameDebuggerDepthState depthState = curEventData.m_DepthState; + FrameDebuggerBlendState blendState = curEventData.m_BlendState; + + // Shader names + m_RealShaderName = curEventData.m_RealShaderName; + m_RealShader = Shader.Find(m_RealShaderName); + m_OriginalShaderName = curEventData.m_OriginalShaderName; + m_OriginalShader = Shader.Find(m_OriginalShaderName); + + // + bool isResolveOrConfigureFov = m_IsResolveEvent || m_IsConfigureFoveatedEvent; + bool hasColorActions = curEventData.m_RenderTargetLoadAction != -1; + bool hasDepthActions = curEventData.m_RenderTargetDepthLoadAction != -1; + bool isStencilEnabled = curEventData.m_StencilState.m_StencilEnable; + + // Gather the necessary strings... + string target = curEventData.m_RenderTargetName; + string batchbreakCause = FrameDebuggerStyles.EventDetails.s_BatchBreakCauses[curEventData.m_BatchBreakCause]; + FrameDebuggerHelper.SpliceText(ref batchbreakCause, 85); + + string size = $"{curEventData.m_RenderTargetWidth}x{curEventData.m_RenderTargetHeight}"; + string format = $"{m_RenderTargetFormat}"; + string colorActions = hasColorActions ? $"{(RenderBufferLoadAction)curEventData.m_RenderTargetLoadAction} / {(RenderBufferStoreAction)curEventData.m_RenderTargetStoreAction}" : k_NotAvailableString; + string depthActions = hasDepthActions ? $"{(RenderBufferLoadAction)curEventData.m_RenderTargetDepthLoadAction} / {(RenderBufferStoreAction)curEventData.m_RenderTargetDepthStoreAction}" : k_NotAvailableString; + + string zClip = !isResolveOrConfigureFov ? rasterState.m_DepthClip.ToString() : k_NotAvailableString; + string zTest = !isResolveOrConfigureFov ? depthState.m_DepthFunc.ToString() : k_NotAvailableString; + string zWrite = !isResolveOrConfigureFov ? (depthState.m_DepthWrite == 0 ? "Off" : "On") : k_NotAvailableString; + string cull = !isResolveOrConfigureFov ? rasterState.m_CullMode.ToString() : k_NotAvailableString; + string conservative = !isResolveOrConfigureFov ? rasterState.m_Conservative.ToString() : k_NotAvailableString; + string offset = !isResolveOrConfigureFov ? $"{rasterState.m_SlopeScaledDepthBias}, {rasterState.m_DepthBias}" : k_NotAvailableString; + string memoryless = !isResolveOrConfigureFov ? (curEventData.m_RenderTargetMemoryless != 0 ? "Yes" : "No") : k_NotAvailableString; + string foveatedRendering = FrameDebuggerHelper.GetFoveatedRenderingModeString(curEventData.m_RenderTargetFoveatedRenderingMode); + + bool hasGroups = curEventData.m_ComputeShaderThreadGroupsX != 0 || curEventData.m_ComputeShaderThreadGroupsY != 0 || curEventData.m_ComputeShaderThreadGroupsZ != 0; + string computeShaderKernel = m_IsComputeEvent ? curEventData.m_ComputeShaderKernelName : k_NotAvailableString; + string computeThreadGroups = m_IsComputeEvent && hasGroups ? $"{curEventData.m_ComputeShaderThreadGroupsX}x{curEventData.m_ComputeShaderThreadGroupsY}x{curEventData.m_ComputeShaderThreadGroupsZ}" : m_IsComputeEvent ? "Indirect dispatch" : k_NotAvailableString; + string computeThreadGroupSize = m_IsComputeEvent && curEventData.m_ComputeShaderGroupSizeX > 0 ? $"{curEventData.m_ComputeShaderGroupSizeX}x{curEventData.m_ComputeShaderGroupSizeY}x{curEventData.m_ComputeShaderGroupSizeZ}" : k_NotAvailableString; + + string passName = $"{(string.IsNullOrEmpty(curEventData.m_PassName) ? k_NotAvailableString : curEventData.m_PassName)} ({curEventData.m_ShaderPassIndex})"; + string lightModeName = string.IsNullOrEmpty(curEventData.m_PassLightMode) ? k_NotAvailableString : curEventData.m_PassLightMode; + + // Format them all together... + m_DetailsStringBuilder.Append(FrameDebuggerStyles.EventDetails.s_RenderTargetText).AppendLine(); + m_DetailsStringBuilder.Append(target).AppendLine(); + m_DetailsStringBuilder.AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "Size", size, "ZClip", zClip).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "Format", format, "ZTest", zTest).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "Color Actions", colorActions, "ZWrite", zWrite).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "Depth Actions", depthActions, "Cull", cull).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "Memoryless", memoryless, "Conservative", conservative).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "Foveated Rendering", foveatedRendering, "Offset", offset).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, string.Empty, string.Empty, string.Empty, string.Empty).AppendLine(); + + if (isResolveOrConfigureFov) + { + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "ColorMask", k_NotAvailableString, "Stencil", string.Empty).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "Blend Color", k_NotAvailableString, "Stencil Ref", k_NotAvailableString).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "Blend Alpha", k_NotAvailableString, "Stencil ReadMask", k_NotAvailableString).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "BlendOp Color", k_NotAvailableString, "Stencil WriteMask", k_NotAvailableString).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "BlendOp Alpha", k_NotAvailableString, "Stencil Comp", k_NotAvailableString).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, string.Empty, string.Empty, "Stencil Pass", k_NotAvailableString).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "DrawInstanced Calls", k_NotAvailableString, "Stencil Fail", k_NotAvailableString).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "Instances", k_NotAvailableString, "Stencil ZFail", k_NotAvailableString).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "Draw Calls", "1", string.Empty, string.Empty).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "Vertices", k_NotAvailableString, string.Empty, string.Empty).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "Indices", k_NotAvailableString, string.Empty, string.Empty).AppendLine(); + } + else + { + string colorMask = FrameDebuggerHelper.GetColorMask(blendState.m_WriteMask); + string blendColor = $"{blendState.m_SrcBlend} / {blendState.m_DstBlend}"; + string blendAlpha = $"{blendState.m_SrcBlendAlpha} / {blendState.m_DstBlendAlpha}"; + string blendOpColor = blendState.m_BlendOp.ToString(); + string blendOpAlpha = blendState.m_BlendOpAlpha.ToString(); + + string stencilRef = isStencilEnabled ? FrameDebuggerHelper.GetStencilString(curEventData.m_StencilRef) : k_NotAvailableString; + string stencilReadMask = isStencilEnabled && curEventData.m_StencilState.m_ReadMask != 255 ? FrameDebuggerHelper.GetStencilString(curEventData.m_StencilState.m_ReadMask) : k_NotAvailableString; + string stencilWriteMask = isStencilEnabled && curEventData.m_StencilState.m_WriteMask != 255 ? FrameDebuggerHelper.GetStencilString(curEventData.m_StencilState.m_WriteMask) : k_NotAvailableString; + string stencilComp = k_NotAvailableString; + string stencilPass = k_NotAvailableString; + string stencilFail = k_NotAvailableString; + string stencilZFail = k_NotAvailableString; + + if (isStencilEnabled) + { + CullMode cullMode = curEventData.m_RasterState.m_CullMode; + + // Only show *Front states when CullMode is set to Back. + if (cullMode == CullMode.Back) + { + stencilComp = $"{curEventData.m_StencilState.m_StencilFuncFront}"; + stencilPass = $"{curEventData.m_StencilState.m_StencilPassOpFront}"; + stencilFail = $"{curEventData.m_StencilState.m_StencilFailOpFront}"; + stencilZFail = $"{curEventData.m_StencilState.m_StencilZFailOpFront}"; + } + // Only show *Back states when CullMode is set to Front. + else if (cullMode == CullMode.Front) + { + stencilComp = $"{curEventData.m_StencilState.m_StencilFuncBack}"; + stencilPass = $"{curEventData.m_StencilState.m_StencilPassOpBack}"; + stencilFail = $"{curEventData.m_StencilState.m_StencilFailOpBack}"; + stencilZFail = $"{curEventData.m_StencilState.m_StencilZFailOpBack}"; + } + // Show both *Front and *Back states for two-sided geometry. + else + { + stencilComp = $"{curEventData.m_StencilState.m_StencilFuncFront} / {curEventData.m_StencilState.m_StencilFuncBack}"; + stencilPass = $"{curEventData.m_StencilState.m_StencilPassOpFront} / {curEventData.m_StencilState.m_StencilPassOpBack}"; + stencilFail = $"{curEventData.m_StencilState.m_StencilFailOpFront} / {curEventData.m_StencilState.m_StencilFailOpBack}"; + stencilZFail = $"{curEventData.m_StencilState.m_StencilZFailOpFront} / {curEventData.m_StencilState.m_StencilZFailOpBack}"; + } + } + + string drawInstancedCalls = curEventData.m_InstanceCount > 1 ? $"{curEventData.m_DrawCallCount}" : k_NotAvailableString; + string drawInstances = curEventData.m_InstanceCount > 1 ? $"{curEventData.m_InstanceCount}" : k_NotAvailableString; + string drawCalls = curEventData.m_InstanceCount > 1 ? k_NotAvailableString : $"{curEventData.m_DrawCallCount}"; + + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "ColorMask", colorMask, "Stencil", string.Empty).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "Blend Color", blendColor, "Stencil Ref", stencilRef).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "Blend Alpha", blendAlpha, "Stencil ReadMask", stencilReadMask).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "BlendOp Color", blendOpColor, "Stencil WriteMask", stencilWriteMask).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "BlendOp Alpha", blendOpAlpha, "Stencil Comp", stencilComp).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, string.Empty, string.Empty, "Stencil Pass", stencilPass).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "DrawInstanced Calls", drawInstancedCalls, "Stencil Fail", stencilFail).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "Instances", drawInstances, "Stencil ZFail", stencilZFail).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "Draw Calls", drawCalls, string.Empty, string.Empty).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "Vertices", curEventData.m_VertexCount.ToString(), string.Empty, string.Empty).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, "Indices", curEventData.m_IndexCount.ToString(), string.Empty, string.Empty).AppendLine(); + } + + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, string.Empty, string.Empty, string.Empty, string.Empty).AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_FourColumnFormat, FrameDebuggerStyles.EventDetails.s_BatchCauseText, string.Empty, string.Empty, string.Empty).AppendLine(); + m_DetailsStringBuilder.Append(batchbreakCause).AppendLine(); + m_DetailsStringBuilder.AppendLine(); + + if (curEventData.m_MeshInstanceIDs != null && curEventData.m_MeshInstanceIDs.Length > 0) + { + HashSet meshIDs = new HashSet(); + for (int i = 0; i < curEventData.m_MeshInstanceIDs.Length; i++) + { + int id = curEventData.m_MeshInstanceIDs[i]; + Mesh mesh = EditorUtility.InstanceIDToObject(id) as Mesh; + if (mesh != null) + meshIDs.Add(id); + } + + List meshes = new List(); + foreach (int id in meshIDs) + { + Mesh mesh = EditorUtility.InstanceIDToObject(id) as Mesh; + + if (meshes.Count == 0) + m_DetailsStringBuilder.AppendLine(String.Format(k_TwoColumnFormat, "Meshes", mesh.name)); + else + m_DetailsStringBuilder.AppendLine(String.Format(k_TwoColumnFormat, string.Empty, mesh.name)); + + meshes.Add(mesh); + } + + m_Meshes = meshes.ToArray(); + + m_MeshNames = new GUIContent[meshes.Count]; + for (var i = 0; i < m_Meshes.Length; ++i) + { + m_MeshNames[i] = EditorGUIUtility.TrTextContent(m_Meshes[i].name, string.Empty); + } + + } + else + { + if (curEventData.m_Mesh == null) + { + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Mesh", k_NotAvailableString).AppendLine(); + m_Meshes = null; + m_MeshNames = null; + } + else + { + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Mesh", curEventData.m_Mesh.name).AppendLine(); + m_Meshes = new Mesh[] { curEventData.m_Mesh }; + m_MeshNames = new GUIContent[1]; + m_MeshNames[0] = EditorGUIUtility.TrTextContent(curEventData.m_Mesh.name, string.Empty); + } + } + + m_DetailsStringBuilder.AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "LightMode", lightModeName); + m_DetailsStringBuilder.AppendLine(); + m_DetailsStringBuilder.AppendFormat(k_TwoColumnFormat, "Pass", passName); + } + + private void CountCharacters(ShaderPropertyType dataType, ShaderInfo shaderInfo) + { + m_MaxNameLength = k_MinNameLength; + m_MaxStageLength = k_MinStageLength; + m_MaxTexSizeLength = k_MinTexSizeLength; + m_MaxSampleTypeLength = k_MinSampleTypeLength; + m_MaxColorFormatLength = k_MinColorFormatLength; + m_MaxDepthFormatLength = k_MinDepthFormatLength; + + switch (dataType) + { + case ShaderPropertyType.Keyword: + for (int i = 0; i < shaderInfo.m_Keywords.Length; i++) + { + m_MaxNameLength = Mathf.Max(m_MaxNameLength, shaderInfo.m_Keywords[i].m_Name.Length); + m_MaxStageLength = Mathf.Max(m_MaxStageLength, FrameDebuggerHelper.GetShaderStageString(shaderInfo.m_Keywords[i].m_Flags).Length); + } + break; + case ShaderPropertyType.Texture: + for (int i = 0; i < shaderInfo.m_Textures.Length; i++) + { + m_MaxNameLength = Mathf.Max(m_MaxNameLength, shaderInfo.m_Textures[i].m_Name.Length); + m_MaxStageLength = Mathf.Max(m_MaxStageLength, FrameDebuggerHelper.GetShaderStageString(shaderInfo.m_Textures[i].m_Flags).Length); + + Texture texture = shaderInfo.m_Textures[i].m_Value; + if (texture != null) + { + m_MaxColorFormatLength = Mathf.Max(m_MaxColorFormatLength, FrameDebuggerHelper.GetColorFormat(ref texture).Length); + m_MaxDepthFormatLength = Mathf.Max(m_MaxDepthFormatLength, FrameDebuggerHelper.GetDepthStencilFormat(ref texture).Length); + m_MaxTexSizeLength = Mathf.Max(m_MaxTexSizeLength, $"{texture.width}x{texture.height}".Length); + m_MaxSampleTypeLength = Mathf.Max(m_MaxSampleTypeLength, $"{texture.dimension}".Length); + } + } + break; + case ShaderPropertyType.Int: + for (int i = 0; i < shaderInfo.m_Ints.Length; i++) + { + int numOfValues = FrameDebuggerHelper.GetNumberOfValuesFromFlags(shaderInfo.m_Ints[i].m_Flags); + int arrayChars = numOfValues > 1 ? FrameDebuggerHelper.CountDigits(numOfValues) + 2 : 0; + m_MaxNameLength = Mathf.Max(m_MaxNameLength, shaderInfo.m_Ints[i].m_Name.Length + arrayChars); + m_MaxStageLength = Mathf.Max(m_MaxStageLength, FrameDebuggerHelper.GetShaderStageString(shaderInfo.m_Ints[i].m_Flags).Length); + } + break; + case ShaderPropertyType.Float: + for (int i = 0; i < shaderInfo.m_Floats.Length; i++) + { + int numOfValues = FrameDebuggerHelper.GetNumberOfValuesFromFlags(shaderInfo.m_Floats[i].m_Flags); + int arrayChars = numOfValues > 1 ? FrameDebuggerHelper.CountDigits(numOfValues) + 2 : 0; + m_MaxNameLength = Mathf.Max(m_MaxNameLength, shaderInfo.m_Floats[i].m_Name.Length + arrayChars); + m_MaxStageLength = Mathf.Max(m_MaxStageLength, FrameDebuggerHelper.GetShaderStageString(shaderInfo.m_Floats[i].m_Flags).Length); + } + break; + case ShaderPropertyType.Vector: + for (int i = 0; i < shaderInfo.m_Vectors.Length; i++) + { + int numOfValues = FrameDebuggerHelper.GetNumberOfValuesFromFlags(shaderInfo.m_Vectors[i].m_Flags); + int arrayChars = numOfValues > 1 ? FrameDebuggerHelper.CountDigits(numOfValues) + 2 : 0; + m_MaxNameLength = Mathf.Max(m_MaxNameLength, shaderInfo.m_Vectors[i].m_Name.Length + arrayChars); + m_MaxStageLength = Mathf.Max(m_MaxStageLength, FrameDebuggerHelper.GetShaderStageString(shaderInfo.m_Vectors[i].m_Flags).Length); + } + break; + case ShaderPropertyType.Matrix: + for (int i = 0; i < shaderInfo.m_Matrices.Length; i++) + { + int numOfValues = FrameDebuggerHelper.GetNumberOfValuesFromFlags(shaderInfo.m_Matrices[i].m_Flags); + int arrayChars = numOfValues > 1 ? FrameDebuggerHelper.CountDigits(numOfValues) + 2 : 0; + m_MaxNameLength = Mathf.Max(m_MaxNameLength, shaderInfo.m_Matrices[i].m_Name.Length + arrayChars); + m_MaxStageLength = Mathf.Max(m_MaxStageLength, FrameDebuggerHelper.GetShaderStageString(shaderInfo.m_Matrices[i].m_Flags).Length); + } + break; + case ShaderPropertyType.Buffer: + for (int i = 0; i < shaderInfo.m_Buffers.Length; i++) + { + m_MaxNameLength = Mathf.Max(m_MaxNameLength, shaderInfo.m_Buffers[i].m_Name.Length); + m_MaxStageLength = Mathf.Max(m_MaxStageLength, FrameDebuggerHelper.GetShaderStageString(shaderInfo.m_Buffers[i].m_Flags).Length); + } + break; + case ShaderPropertyType.CBuffer: + for (int i = 0; i < shaderInfo.m_CBuffers.Length; i++) + { + m_MaxNameLength = Mathf.Max(m_MaxNameLength, shaderInfo.m_CBuffers[i].m_Name.Length); + m_MaxStageLength = Mathf.Max(m_MaxStageLength, FrameDebuggerHelper.GetShaderStageString(shaderInfo.m_CBuffers[i].m_Flags).Length); + } + break; + default: + break; + } + + m_MaxNameLength += k_ColumnSpace; + m_MaxStageLength += k_ColumnSpace; + m_MaxTexSizeLength += k_ColumnSpace; + m_MaxSampleTypeLength += k_ColumnSpace; + m_MaxColorFormatLength += k_ColumnSpace; + m_MaxDepthFormatLength += k_ColumnSpace; + } + } +} diff --git a/Editor/Mono/PerformanceTools/FrameDebuggerHelper.cs b/Editor/Mono/PerformanceTools/FrameDebuggerHelper.cs index ec11596e47..854ebfcc9c 100644 --- a/Editor/Mono/PerformanceTools/FrameDebuggerHelper.cs +++ b/Editor/Mono/PerformanceTools/FrameDebuggerHelper.cs @@ -3,23 +3,208 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Text; using UnityEngine; using UnityEngine.Rendering; +using UnityEngine.Experimental.Rendering; +using UnityEditor.Rendering; +using UnityEditor; -namespace UnityEditorInternal +namespace UnityEditorInternal.FrameDebuggerInternal { internal class FrameDebuggerHelper { - internal static bool IsOnLinuxOpenGL => Application.platform == RuntimePlatform.LinuxEditor && SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLCore; + // Constants + private const int k_ArraySizeBitMask = 0x3FF; + private const int k_ShaderTypeBits = (int)ShaderType.Count; + + // Properties + internal static bool isOnLinuxOpenGL => Application.platform == RuntimePlatform.LinuxEditor && SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLCore; + internal static Material frameDebuggerMaterial + { + get { + if (s_Material == null) + s_Material = new Material(Resources.GetBuiltinResource("PerformanceTools/FrameDebuggerRenderTargetDisplay.mat")); + + return s_Material; + } + } + + // Utility functions... internal static bool IsAValidFrame(int curEventIndex, int descsLength) => (curEventIndex >= 0 && curEventIndex < descsLength); internal static bool IsAClearEvent(FrameEventType eventType) => eventType >= FrameEventType.ClearNone && eventType <= FrameEventType.ClearAll; internal static bool IsAResolveEvent(FrameEventType eventType) => eventType == FrameEventType.ResolveRT || eventType == FrameEventType.ResolveDepth; internal static bool IsAComputeEvent(FrameEventType eventType) => eventType == FrameEventType.ComputeDispatch; internal static bool IsARayTracingEvent(FrameEventType eventType) => eventType == FrameEventType.RayTracingDispatch; + internal static bool IsAConfigureFoveatedRenderingEvent(FrameEventType eventType) => eventType == FrameEventType.ConfigureFoveatedRendering; internal static bool IsAHiddenEvent(FrameEventType eventType) => eventType == FrameEventType.BeginSubpass; internal static bool IsAHierarchyLevelBreakEvent(FrameEventType eventType) => eventType == FrameEventType.HierarchyLevelBreak; internal static bool IsCurrentEventMouseDown() => Event.current.type == EventType.MouseDown; internal static bool IsClickingRect(Rect rect) => rect.Contains(Event.current.mousePosition) && Event.current.type == EventType.MouseDown; + internal static bool IsHoveringRect(Rect rect) => rect.Contains(Event.current.mousePosition); + internal static bool IsARenderTexture(ref Texture t) => t != null && (t as RenderTexture) != null; + internal static bool IsADepthTexture(ref Texture t) => IsARenderTexture(ref t) && ((t as RenderTexture).graphicsFormat == GraphicsFormat.None); + + + // Private Static Variables + private static Material s_Material = null; + private static StringBuilder s_StringBuilder = new StringBuilder(); + + // Functions + private struct ShaderPropertyIDs + { + internal const string _MSAA_2 = "_MSAA_2"; + internal const string _MSAA_4 = "_MSAA_4"; + internal const string _MSAA_8 = "_MSAA_8"; + internal const string _TEX2DARRAY = "_TEX2DARRAY"; + internal const string _CUBEMAP = "_CUBEMAP"; + internal static int _Levels = Shader.PropertyToID("_Levels"); + internal static int _MainTex = Shader.PropertyToID("_MainTex"); + internal static int _MainTexDepth = Shader.PropertyToID("_MainTexDepth"); + internal static int _Channels = Shader.PropertyToID("_Channels"); + internal static int _ShouldYFlip = Shader.PropertyToID("_ShouldYFlip"); + internal static int _UndoOutputSRGB = Shader.PropertyToID("_UndoOutputSRGB"); + internal static int _MainTexWidth = Shader.PropertyToID("_MainTexWidth"); + internal static int _MainTexHeight = Shader.PropertyToID("_MainTexHeight"); + } + + internal static void BlitToRenderTexture( + ref Texture t, + ref RenderTexture output, + int width, + int height, + int depth, + Vector4 channels, + Vector4 levels, + bool shouldYFlip, + bool undoOutputSRGB) + { + if (t == null || output == null) + return; + + output.name = t.name; + int msaaValue = GetMSAAValue(ref t); + TextureDimension samplerType = t.dimension; + SetMaterialProperties(width, height, depth, samplerType, msaaValue, channels, levels, shouldYFlip, undoOutputSRGB); + frameDebuggerMaterial.SetTexture(ShaderPropertyIDs._MainTex, t); + + Blit(ref output); + } + + internal static void BlitToRenderTexture( + ref RenderTexture rt, + ref RenderTexture output, + int width, + int height, + Vector4 channels, + Vector4 levels, + bool shouldYFlip, + bool undoOutputSRGB) + { + if (rt == null || output == null) + return; + + output.name = rt.name; + + int msaaValue = GetMSAAValue(ref rt); + TextureDimension samplerType = rt.dimension; + SetMaterialProperties(width, height, rt.volumeDepth, samplerType, msaaValue, channels, levels, shouldYFlip, undoOutputSRGB); + frameDebuggerMaterial.SetTexture(ShaderPropertyIDs._MainTex, rt); + + Blit(ref output); + } + + private static void SetMaterialProperties( + int width, + int height, + int depth, + TextureDimension samplerType, + int msaaValue, + Vector4 channels, + Vector4 levels, + bool shouldYFlip, + bool undoOutputSRGB) + { + Material mat = frameDebuggerMaterial; + + frameDebuggerMaterial.DisableKeyword(ShaderPropertyIDs._TEX2DARRAY); + frameDebuggerMaterial.DisableKeyword(ShaderPropertyIDs._CUBEMAP); + if (samplerType == TextureDimension.Tex2DArray) + frameDebuggerMaterial.EnableKeyword(ShaderPropertyIDs._TEX2DARRAY); + else if (samplerType == TextureDimension.CubeArray) + frameDebuggerMaterial.EnableKeyword(ShaderPropertyIDs._CUBEMAP); + + mat.DisableKeyword(ShaderPropertyIDs._MSAA_2); + mat.DisableKeyword(ShaderPropertyIDs._MSAA_4); + mat.DisableKeyword(ShaderPropertyIDs._MSAA_8); + + if (msaaValue == 2) + mat.EnableKeyword(ShaderPropertyIDs._MSAA_2); + else if (msaaValue == 4) + mat.EnableKeyword(ShaderPropertyIDs._MSAA_4); + else if (msaaValue == 8) + mat.EnableKeyword(ShaderPropertyIDs._MSAA_8); + + // Create the RenderTexture + mat.SetFloat(ShaderPropertyIDs._MainTexWidth, width); + mat.SetFloat(ShaderPropertyIDs._MainTexHeight, height); + mat.SetFloat(ShaderPropertyIDs._MainTexDepth, depth); + mat.SetVector(ShaderPropertyIDs._Channels, channels); + mat.SetVector(ShaderPropertyIDs._Levels, levels); + mat.SetFloat(ShaderPropertyIDs._ShouldYFlip, shouldYFlip ? 1.0f : 0.0f); + mat.SetFloat(ShaderPropertyIDs._UndoOutputSRGB, undoOutputSRGB ? 1.0f : 0.0f); + } + + private static void Blit(ref RenderTexture rt) + { + // Remember currently active render texture + RenderTexture currentActiveRT = RenderTexture.active; + + // Blit to the Render Texture + Graphics.Blit(null, rt, frameDebuggerMaterial, 0); + + // Restore previously active render texture + RenderTexture.active = currentActiveRT; + } + + internal static int GetVolumeDepth(ref Texture t) + { + RenderTexture rt = t as RenderTexture; + return GetVolumeDepth(ref rt); + } + + internal static int GetVolumeDepth(ref RenderTexture rt) + { + if (rt != null) + return rt.volumeDepth; + + return 1; + } + + internal static int GetMSAAValue(ref Texture t) + { + RenderTexture rt = t as RenderTexture; + return GetMSAAValue(ref rt); + } + + internal static int GetMSAAValue(ref RenderTexture rt) + { + if (rt != null && rt.bindTextureMS) + return rt.antiAliasing; + + return 1; + } + + internal static string GetFoveatedRenderingModeString(int fovMode) + { + switch ((FoveatedRenderingMode)fovMode) + { + case FoveatedRenderingMode.Disabled: + return "Disabled"; + default: + return "Enabled"; + } + } internal static string GetStencilString(int stencil) { @@ -27,39 +212,197 @@ internal static string GetStencilString(int stencil) return $"(0b{Convert.ToString(stencil, 2).PadLeft(k_NumOfUsedStencilBits, '0')}) {stencil}"; } - internal static string GetFormat(Texture texture) + internal static string GetColorMask(uint mask) { - if (texture == null) + string colorMask = String.Empty; + if (mask == 0) + colorMask = "0"; + else { - return string.Empty; - } + if ((mask & 8) != 0) + colorMask += 'R'; - if (texture is Texture2D) - { - return (texture as Texture2D).format.ToString(); - } - else if (texture is Cubemap) - { - return (texture as Cubemap).format.ToString(); + if ((mask & 4) != 0) + colorMask += 'G'; + + if ((mask & 2) != 0) + colorMask += 'B'; + + if ((mask & 1) != 0) + colorMask += 'A'; } - else if (texture is Texture2DArray) + + return colorMask; + } + + internal static string GetColorFormat(ref Texture t) + { + if (t == null) + return string.Empty; + + if (t is Texture2D) + return (t as Texture2D).format.ToString(); + + else if (t is Cubemap) + return (t as Cubemap).format.ToString(); + + else if (t is Texture2DArray) + return (t as Texture2DArray).format.ToString(); + + else if (t is Texture3D) + return (t as Texture3D).format.ToString(); + + else if (t is CubemapArray) + return (t as CubemapArray).format.ToString(); + + else if (t is RenderTexture) + return (t as RenderTexture).graphicsFormat.ToString(); + + return string.Empty; + } + + internal static string GetDepthStencilFormat(ref Texture t) + { + // Render Textures are the only ones who have this as they are split in to 2 textures... + if (IsARenderTexture(ref t)) + return (t as RenderTexture).depthStencilFormat.ToString(); + + return FrameDebuggerStyles.EventDetails.k_NotAvailable; + } + + internal static int GetNumberOfValuesFromFlags(int flags) + { + return (flags >> k_ShaderTypeBits) & k_ArraySizeBitMask; + } + + internal static string GetShaderStageString(int flags) + { + s_StringBuilder.Clear(); + + // Lowest bits of flags are set for each shader stage that property is used in; matching ShaderType C++ enum + const int k_VertexShaderFlag = (1 << 1); + const int k_FragmentShaderFlag = (1 << 2); + const int k_GeometryShaderFlag = (1 << 3); + const int k_HullShaderFlag = (1 << 4); + const int k_DomainShaderFlag = (1 << 5); + + if ((flags & k_VertexShaderFlag) != 0) + s_StringBuilder.Append("vs/"); + + if ((flags & k_FragmentShaderFlag) != 0) + s_StringBuilder.Append("fs/"); + + if ((flags & k_GeometryShaderFlag) != 0) + s_StringBuilder.Append("gs/"); + + if ((flags & k_HullShaderFlag) != 0) + s_StringBuilder.Append("hs/"); + + if ((flags & k_DomainShaderFlag) != 0) + s_StringBuilder.Append("ds/"); + + if (s_StringBuilder.Length == 0) + return FrameDebuggerStyles.EventDetails.k_NotAvailable; + + s_StringBuilder.Remove(s_StringBuilder.Length - 1, 1); + return s_StringBuilder.ToString(); + } + + internal static void DestroyTexture(ref Texture t) + { + if (t == null) + return; + + RenderTexture rt = t as RenderTexture; + if (rt != null) + DestroyTexture(ref rt); + else { - return (texture as Texture2DArray).format.ToString(); + Texture.DestroyImmediate(t); + t = null; } - else if (texture is Texture3D) + } + + internal static void DestroyTexture(ref RenderTexture rt) + { + if (rt == null) + return; + + rt.Release(); + RenderTexture.DestroyImmediate(rt); + rt = null; + } + + internal static void ReleaseTemporaryTexture(ref RenderTexture rt) + { + if (rt == null) + return; + + RenderTexture.ReleaseTemporary(rt); + rt = null; + } + + // May look a bit silly but seems to be a pretty fast way of doing this :) + // https://stackoverflow.com/a/51099524 + internal static int CountDigits(int num) + { + if (num >= 0) { - return (texture as Texture3D).format.ToString(); + if (num < 10) return 1; + if (num < 100) return 2; + if (num < 1000) return 3; + if (num < 10000) return 4; + if (num < 100000) return 5; + if (num < 1000000) return 6; + if (num < 10000000) return 7; + if (num < 100000000) return 8; + if (num < 1000000000) return 9; + return 10; } - else if (texture is CubemapArray) + else { - return (texture as CubemapArray).format.ToString(); + if (num > -10) return 2; + if (num > -100) return 3; + if (num > -1000) return 4; + if (num > -10000) return 5; + if (num > -100000) return 6; + if (num > -1000000) return 7; + if (num > -10000000) return 8; + if (num > -100000000) return 9; + return 10; } - else if (texture is RenderTexture) + } + + internal static void SpliceText(ref string text, int maxWidth) + { + if (string.IsNullOrEmpty(text) || text.Length <= maxWidth) + return; + + StringBuilder sb = new StringBuilder(text.Length * 2); + while (text.Length > 0) { - return (texture as RenderTexture).graphicsFormat.ToString(); - } + if (text.Length <= maxWidth) + { + sb.Append(text); + break; + } - return string.Empty; + string chunk = text.Substring(0, maxWidth); + if (char.IsWhiteSpace(text[maxWidth])) + { + sb.AppendLine(chunk); + text = text.Substring(chunk.Length + 1); + } + else + { + int splitIndex = chunk.LastIndexOf(' '); + if (splitIndex != -1) + chunk = chunk.Substring(0, splitIndex); + text = text.Substring(chunk.Length + (splitIndex == -1 ? 0 : 1)); + sb.AppendLine(chunk); + } + } + text = sb.ToString(); } } } diff --git a/Editor/Mono/PerformanceTools/FrameDebuggerStyles.cs b/Editor/Mono/PerformanceTools/FrameDebuggerStyles.cs index 736daf0757..724b270c1e 100644 --- a/Editor/Mono/PerformanceTools/FrameDebuggerStyles.cs +++ b/Editor/Mono/PerformanceTools/FrameDebuggerStyles.cs @@ -6,12 +6,12 @@ using UnityEngine; using UnityEditor; -namespace UnityEditorInternal +namespace UnityEditorInternal.FrameDebuggerInternal { internal static class FrameDebuggerStyles { // match enum FrameEventType on C++ side! - public static readonly string[] frameEventTypeNames = new[] + internal static readonly string[] s_FrameEventTypeNames = new[] { "Clear (nothing)", "Clear (color)", @@ -42,82 +42,83 @@ internal static class FrameDebuggerStyles "Begin Subpass", "SRP Batch", "", // on purpose empty string for kFrameEventHierarchyLevelBreak - "Hybrid Batch Group" + "Hybrid Batch Group", + "Configure Foveated Rendering", }; // General settings for the Frame Debugger Window and layout - public struct Window + internal struct Window { - public const int k_StartWindowWidth = 1024; - public const float k_MinTreeWidth = k_StartWindowWidth * 0.33f; - public const float k_ResizerWidth = 5f; - public const float k_MinDetailsWidth = 200f; + internal const int k_StartWindowWidth = 1024; + internal const float k_MinTreeWidth = k_StartWindowWidth * 0.33f; + internal const float k_ResizerWidth = 5f; + internal const float k_MinDetailsWidth = 200f; } // Tree - public struct Tree + internal struct Tree { - public static readonly GUIStyle rowText = "OL Label"; - public static readonly GUIStyle rowTextRight = "OL RightLabel"; - public const string k_UnknownScopeString = ""; + internal static readonly GUIStyle s_RowText = "OL Label"; + internal static readonly GUIStyle s_RowTextRight = "OL RightLabel"; + internal const string k_UnknownScopeString = ""; } // Top Toolbar - public struct TopToolbar + internal struct TopToolbar { - public static readonly GUIContent recordButtonEnable = EditorGUIUtility.TrTextContent(L10n.Tr("Enable")); - public static readonly GUIContent recordButtonDisable = EditorGUIUtility.TrTextContent(L10n.Tr("Disable")); - public static readonly GUIContent prevFrame = EditorGUIUtility.TrIconContent("Profiler.PrevFrame", "Go back one frame"); - public static readonly GUIContent nextFrame = EditorGUIUtility.TrIconContent("Profiler.NextFrame", "Go one frame forwards"); - public static readonly GUIContent levelsHeader = EditorGUIUtility.TrTextContent("Levels", "Render target display black/white intensity levels"); + internal static readonly GUIContent s_RecordButtonEnable = EditorGUIUtility.TrTextContent(L10n.Tr("Enable")); + internal static readonly GUIContent s_RecordButtonDisable = EditorGUIUtility.TrTextContent(L10n.Tr("Disable")); + internal static readonly GUIContent s_PrevFrame = EditorGUIUtility.TrIconContent("Profiler.PrevFrame", "Go back one frame"); + internal static readonly GUIContent s_NextFrame = EditorGUIUtility.TrIconContent("Profiler.NextFrame", "Go one frame forwards"); + internal static readonly GUIContent s_LevelsHeader = EditorGUIUtility.TrTextContent("Levels", "Render target display black/white intensity levels"); } // Event Toolbar in the Event Details window - public struct EventToolbar + internal struct EventToolbar { private const float k_ToolbarHeight = 22f; - private const float k_ToolbarButtondWidth = 30f; + private const float k_ChannelButtonWidth = 30f; - public static readonly GUIStyle toolbarHorizontalStyle = new GUIStyle(EditorStyles.toolbar) + internal static readonly GUIStyle s_HorizontalStyle = new GUIStyle(EditorStyles.toolbar) { fixedHeight = k_ToolbarHeight + 1f }; - public static readonly GUIStyle channelHeaderStyle = new GUIStyle(EditorStyles.toolbarLabel) + internal static readonly GUIStyle s_ChannelHeaderStyle = new GUIStyle(EditorStyles.toolbarLabel) { fixedHeight = k_ToolbarHeight }; - public static readonly GUIStyle channelStyle = new GUIStyle(EditorStyles.miniButtonMid) + internal static readonly GUIStyle s_ChannelStyle = new GUIStyle(EditorStyles.miniButtonMid) { - fixedWidth = k_ToolbarButtondWidth, + fixedWidth = k_ChannelButtonWidth, }; - public static readonly GUIStyle channelAllStyle = new GUIStyle(EditorStyles.miniButtonLeft) + internal static readonly GUIStyle s_ChannelAllStyle = new GUIStyle(EditorStyles.miniButtonLeft) { - fixedWidth = k_ToolbarButtondWidth, + fixedWidth = k_ChannelButtonWidth, }; - public static readonly GUIStyle channelAStyle = new GUIStyle(EditorStyles.miniButtonRight) + internal static readonly GUIStyle s_ChannelAStyle = new GUIStyle(EditorStyles.miniButtonRight) { - fixedWidth = k_ToolbarButtondWidth, + fixedWidth = k_ChannelButtonWidth, }; - public static readonly GUIStyle popupLeftStyle = new GUIStyle(EditorStyles.toolbarPopupLeft) + internal static readonly GUIStyle s_PopupLeftStyle = new GUIStyle(EditorStyles.toolbarPopupLeft) { fixedHeight = k_ToolbarHeight }; - public static readonly GUIStyle levelsHorizontalStyle = new GUIStyle(EditorStyles.toolbarButton) + internal static readonly GUIStyle s_LevelsHorizontalStyle = new GUIStyle(EditorStyles.toolbarButton) { margin = new RectOffset(4, 4, 0, 0), padding = new RectOffset(4, 4, 0, 0), fixedHeight = k_ToolbarHeight }; - public static readonly GUIContent depthLabel = EditorGUIUtility.TrTextContent("Depth", "Show depth buffer"); - public static readonly GUIContent channelHeader = EditorGUIUtility.TrTextContent("Channels", "Which render target color channels to show"); - public static readonly GUIContent channelAll = EditorGUIUtility.TrTextContent("All"); - public static readonly GUIContent channelR = EditorGUIUtility.TrTextContent("R"); - public static readonly GUIContent channelG = EditorGUIUtility.TrTextContent("G"); - public static readonly GUIContent channelB = EditorGUIUtility.TrTextContent("B"); - public static readonly GUIContent channelA = EditorGUIUtility.TrTextContent("A"); - public static readonly GUIContent levelsHeader = EditorGUIUtility.TrTextContent("Levels", "Render target display black/white intensity levels"); - public static readonly GUIContent[] MRTLabels = new[] + internal static readonly GUIContent s_DepthLabel = EditorGUIUtility.TrTextContent("Depth", "Show depth buffer"); + internal static readonly GUIContent s_ChannelHeader = EditorGUIUtility.TrTextContent("Channels", "Which render target color channels to show"); + internal static readonly GUIContent s_ChannelAll = EditorGUIUtility.TrTextContent("All"); + internal static readonly GUIContent s_ChannelR = EditorGUIUtility.TrTextContent("R"); + internal static readonly GUIContent s_ChannelG = EditorGUIUtility.TrTextContent("G"); + internal static readonly GUIContent s_ChannelB = EditorGUIUtility.TrTextContent("B"); + internal static readonly GUIContent s_ChannelA = EditorGUIUtility.TrTextContent("A"); + internal static readonly GUIContent s_LevelsHeader = EditorGUIUtility.TrTextContent("Levels", "Render target display black/white intensity levels"); + internal static readonly GUIContent[] s_MRTLabels = new[] { EditorGUIUtility.TrTextContent("RT 0", "Show render target #0"), EditorGUIUtility.TrTextContent("RT 1", "Show render target #1"), @@ -131,137 +132,159 @@ public struct EventToolbar } // Event Details Window - public struct EventDetails + internal struct EventDetails { private const int k_Indent1 = 5; private const int k_Indent2 = 20; - public const float k_MaxViewportHeight = 355f; - public const float k_MaxViewportWidth = 125f; + internal const float k_MaxViewportHeight = 355f; - public const int k_PropertyNameMaxChars = 35; - public const int k_TextureFormatMaxChars = 16; + internal const float k_VerticalLabelWidth = 150f; + internal const float k_VerticalValueWidth = 250f; + internal const float k_MeshNameWidth = k_VerticalLabelWidth + k_VerticalValueWidth; + internal const int k_PropertyNameMaxChars = 30; + internal const int k_TextureFormatMaxChars = 19; - public const float k_MeshBottomToolbarHeight = 21f; - public const float k_ArrayValuePopupBtnWidth = 2.0f; + internal const int k_ShaderLabelWidth = 172; + internal const int k_ShaderObjectFieldWidth = 450; - public const string k_FloatFormat = "g7"; - public const string k_IntFormat = "d"; - public const string k_NotAvailable = "-"; + internal const float k_MeshBottomToolbarHeight = 21f; + internal const float k_ArrayValuePopupBtnWidth = 2.0f; + internal const string k_FloatFormat = "F7"; + internal const string k_IntFormat = "d"; + internal const string k_NotAvailable = "-"; + internal static string s_DashesString = new string('-', 30); + internal static string s_EqualsString = new string('=', 30); - public static readonly GUIStyle titleStyle = new GUIStyle(EditorStyles.largeLabel) + + internal static readonly GUIStyle s_ArrayFoldoutStyle = new GUIStyle(EditorStyles.foldout) + { + margin = new RectOffset(-29, 0, 0, 0), + }; + + internal static readonly GUIStyle s_TitleHorizontalStyle = new GUIStyle(EditorStyles.label) + { + margin = new RectOffset(0, 0, 0, 10), + }; + + internal static readonly GUIStyle s_TitleStyle = new GUIStyle(EditorStyles.largeLabel) { padding = new RectOffset(k_Indent1, 0, k_Indent1, 0), fontStyle = FontStyle.Bold, fontSize = 18, fixedHeight = 50, }; - public static readonly GUIStyle foldoutCategoryBoxStyle = new GUIStyle(EditorStyles.helpBox); - public static readonly GUIStyle labelStyle = new GUIStyle(EditorStyles.label) - { - wordWrap = true, - }; - public static readonly GUIStyle titleHorizontalStyle = new GUIStyle(EditorStyles.label) + internal static readonly GUIStyle s_FoldoutCategoryBoxStyle = new GUIStyle(EditorStyles.helpBox); + + internal static readonly GUIStyle s_MonoLabelStyle = new GUIStyle(EditorStyles.label) { - margin = new RectOffset(0, 0, 0, 10), + alignment = TextAnchor.UpperLeft }; - public static readonly GUIStyle verticalLabelStyle = new GUIStyle(EditorStyles.label) + internal static readonly GUIStyle s_MonoLabelNoWrapStyle = new GUIStyle(EditorStyles.label) { - fixedWidth = 175, padding = new RectOffset(0, 0, 0, 0), - margin = new RectOffset(0, 0, 0, 0), + margin = new RectOffset(0, 0, 2, 0), }; - public static readonly GUIStyle verticalValueStyle = new GUIStyle(EditorStyles.label) + internal static readonly GUIStyle s_MonoLabelBoldStyle = new GUIStyle(EditorStyles.label) { - fixedWidth = 250, padding = new RectOffset(0, 0, 0, 0), margin = new RectOffset(0, 0, 0, 0), }; - public static readonly GUIStyle outputMeshTabStyle = new GUIStyle("LargeButton") + internal static readonly GUIStyle s_OutputMeshTabStyle = new GUIStyle("LargeButton") { padding = new RectOffset(0, 0, 0, 0), margin = new RectOffset(-2, 0, 0, 0), }; - public static readonly GUIStyle outputMeshTextureStyle = new GUIStyle(); - public static readonly GUIStyle meshPreToolbarStyle = "toolbar"; - public static readonly GUIStyle meshPreToolbarLabelStyle = EditorStyles.toolbarButton; - public static readonly GUIStyle propertiesVerticalStyle = new GUIStyle(EditorStyles.label) - { - margin = new RectOffset(k_Indent2, 0, 0, 7) - }; - public static readonly GUIStyle propertiesNameStyle = new GUIStyle(EditorStyles.label) - { - fixedWidth = 215, - }; - public static readonly GUIStyle propertiesFlagsStyle = new GUIStyle(EditorStyles.label) - { - fixedWidth = 55f, - }; + internal static readonly GUIStyle s_RenderTargetMeshBackgroundStyle = new GUIStyle(); - public static readonly GUIStyle textureButtonStyle = new GUIStyle() + internal static readonly GUIStyle s_PropertiesBottomMarginStyle = new GUIStyle(EditorStyles.label) { - fixedWidth = 12f, - fixedHeight = 12f, + margin = new RectOffset(0, 0, 0, 10) }; - public static readonly GUIStyle textureDimensionsStyle = new GUIStyle(EditorStyles.label) + + internal static readonly GUIStyle s_PropertiesLeftMarginStyle = new GUIStyle(EditorStyles.label) { - fixedWidth = 65f, + margin = new RectOffset(k_Indent2, 0, 0, 0), + padding = new RectOffset(0, 0, 0, 0), }; - public static readonly GUIStyle textureSizeStyle = new GUIStyle(EditorStyles.label) + + internal static readonly GUIStyle s_TextureButtonStyle = new GUIStyle() { - fixedWidth = 100f, + fixedWidth = 20f, + margin = new RectOffset(0, 10, 0, 0), }; - public static readonly GUIStyle textureFormatStyle = new GUIStyle(EditorStyles.label) + + internal const string k_WarningMultiThreadedMsg = "The Frame Debugger requires multi-threaded renderer. If this error persists, try starting the Editor with -force-gfx-mt command line argument."; + internal const string k_WarningLinuxOpenGLMsg = k_WarningMultiThreadedMsg + " On Linux, the editor does not support a multi-threaded renderer when using OpenGL."; + internal const string k_DescriptionString = "Frame Debugger lets you step through draw calls and see how exactly frame is rendered. Click Enable!"; + internal const string k_TabbedWithPlaymodeErrorString = "Frame Debugger can not be docked with the Game Window when trying to debug the editor."; + internal static readonly GUIContent s_RenderTargetText = EditorGUIUtility.TrTextContent("RenderTarget"); + internal static readonly GUIContent s_CopyEventText = EditorGUIUtility.TrTextContent("Copy Event Info"); + internal static readonly GUIContent s_CopyPropertyText = EditorGUIUtility.TrTextContent("Copy Property"); + internal static readonly GUIContent[] s_FoldoutCopyText = { - fixedWidth = 110f, + EditorGUIUtility.TrTextContent("Copy Output"), + EditorGUIUtility.TrTextContent("Copy All Details"), + EditorGUIUtility.TrTextContent("Copy All Keyword Properties"), + EditorGUIUtility.TrTextContent("Copy All Texture Properties"), + EditorGUIUtility.TrTextContent("Copy All Integer Properties"), + EditorGUIUtility.TrTextContent("Copy All Float Properties"), + EditorGUIUtility.TrTextContent("Copy All Vector Properties"), + EditorGUIUtility.TrTextContent("Copy All Matrix Properties"), + EditorGUIUtility.TrTextContent("Copy All Buffer Properties"), + EditorGUIUtility.TrTextContent("Copy All Constant Buffer Properties") }; - - public const string warningMultiThreadedMsg = "The Frame Debugger requires multi-threaded renderer. If this error persists, try starting the Editor with -force-gfx-mt command line argument."; - public const string warningLinuxOpenGLMsg = warningMultiThreadedMsg + " On Linux, the editor does not support a multi-threaded renderer when using OpenGL."; - public const string descriptionString = "Frame Debugger lets you step through draw calls and see how exactly frame is rendered. Click Enable!"; - public static readonly GUIContent copyValueText = EditorGUIUtility.TrTextContent("Copy value"); - public static readonly GUIContent shaderText = EditorGUIUtility.TrTextContent("Shader"); - public static readonly GUIContent batchCauseText = EditorGUIUtility.TrTextContent("Batch cause"); - public static readonly GUIContent passLightModeText = EditorGUIUtility.TrTextContent("Pass\nLightMode"); - public static readonly GUIContent arrayPopupButtonText = EditorGUIUtility.TrTextContent("..."); - public static readonly GUIContent foldoutOutputOrMeshText = EditorGUIUtility.TrTextContent("Output / Mesh"); - public static readonly GUIContent foldoutEventDetailsText = EditorGUIUtility.TrTextContent("Details"); - public static readonly GUIContent foldoutTexturesText = EditorGUIUtility.TrTextContent("Textures"); - public static readonly GUIContent foldoutKeywordsText = EditorGUIUtility.TrTextContent("Keywords"); - public static readonly GUIContent foldoutFloatsText = EditorGUIUtility.TrTextContent("Floats"); - public static readonly GUIContent foldoutIntsText = EditorGUIUtility.TrTextContent("Ints"); - public static readonly GUIContent foldoutVectorsText = EditorGUIUtility.TrTextContent("Vectors"); - public static readonly GUIContent foldoutMatricesText = EditorGUIUtility.TrTextContent("Matrices"); - public static readonly GUIContent foldoutBuffersText = EditorGUIUtility.TrTextContent("Buffers"); - public static readonly GUIContent foldoutCBufferText = EditorGUIUtility.TrTextContent("CBuffer"); - - public static Texture2D outputMeshTexture = null; - public static readonly string[] batchBreakCauses = FrameDebuggerUtility.GetBatchBreakCauseStrings(); + internal static readonly GUIContent s_RealShaderText = EditorGUIUtility.TrTextContent("Used Shader", "The shader used in this draw call."); + internal static readonly GUIContent s_OriginalShaderText = EditorGUIUtility.TrTextContent("Original Shader", "The shader originally set to be used in this draw call."); + internal static readonly GUIContent s_RayTracingShaderText = EditorGUIUtility.TrTextContent("Ray Tracing Shader", ""); + internal static readonly GUIContent s_RayTracingGenerationShaderText = EditorGUIUtility.TrTextContent("Ray Generation Shader", ""); + internal static readonly GUIContent s_ComputeShaderText = EditorGUIUtility.TrTextContent("Compute Shader", ""); + internal static readonly GUIContent s_BatchCauseText = EditorGUIUtility.TrTextContent("Batch cause"); + internal static readonly GUIContent s_PassLightModeText = EditorGUIUtility.TrTextContent("Pass\nLightMode"); + internal static readonly GUIContent s_ArrayPopupButtonText = EditorGUIUtility.TrTextContent("..."); + internal static readonly GUIContent s_FoldoutOutputOrMeshText = EditorGUIUtility.TrTextContent("Output / Mesh"); + internal static readonly GUIContent s_FoldoutEventDetailsText = EditorGUIUtility.TrTextContent("Details"); + internal static readonly GUIContent s_FoldoutTexturesText = EditorGUIUtility.TrTextContent("Textures"); + internal static readonly GUIContent s_FoldoutKeywordsText = EditorGUIUtility.TrTextContent("Keywords"); + internal static readonly GUIContent s_FoldoutFloatsText = EditorGUIUtility.TrTextContent("Floats"); + internal static readonly GUIContent s_FoldoutIntsText = EditorGUIUtility.TrTextContent("Ints"); + internal static readonly GUIContent s_FoldoutVectorsText = EditorGUIUtility.TrTextContent("Vectors"); + internal static readonly GUIContent s_FoldoutMatricesText = EditorGUIUtility.TrTextContent("Matrices"); + internal static readonly GUIContent s_FoldoutBuffersText = EditorGUIUtility.TrTextContent("Buffers"); + internal static readonly GUIContent s_FoldoutCBufferText = EditorGUIUtility.TrTextContent("Constant Buffers"); + internal static readonly GUIContent s_NotAvailableText = EditorGUIUtility.TrTextContent(k_NotAvailable); + internal static Texture2D s_RenderTargetMeshBackgroundTexture = null; + internal static readonly string[] s_BatchBreakCauses = FrameDebuggerUtility.GetBatchBreakCauseStrings(); } // Constructor static FrameDebuggerStyles() { float greyVal = 0.2196079f; - EventDetails.outputMeshTexture = MakeTex(1, 1, new Color(greyVal, greyVal, greyVal, 1f)); - EventDetails.outputMeshTextureStyle.normal.background = EventDetails.outputMeshTexture; + EventDetails.s_RenderTargetMeshBackgroundTexture = MakeTex(1, 1, new Color(greyVal, greyVal, greyVal, 1f)); + EventDetails.s_RenderTargetMeshBackgroundStyle.normal.background = EventDetails.s_RenderTargetMeshBackgroundTexture; + + Font monospacedFont = EditorGUIUtility.Load("Fonts/RobotoMono/RobotoMono-Regular.ttf") as Font; + Font monospacedBoldFont = EditorGUIUtility.Load("Fonts/RobotoMono/RobotoMono-Bold.ttf") as Font; + EventDetails.s_MonoLabelStyle.font = monospacedFont; + EventDetails.s_MonoLabelBoldStyle.font = monospacedBoldFont; + EventDetails.s_MonoLabelNoWrapStyle.font = monospacedFont; + EventDetails.s_ArrayFoldoutStyle.font = monospacedFont; } private static Texture2D MakeTex(int width, int height, Color col) { Color[] pix = new Color[width * height]; for (int i = 0; i < pix.Length; i++) - { pix[i] = col; - } Texture2D result = new Texture2D(width, height); result.SetPixels(pix); @@ -270,70 +293,10 @@ private static Texture2D MakeTex(int width, int height, Color col) return result; } - public static void OnDisable() - { - UnityEngine.Object.DestroyImmediate(EventDetails.outputMeshTexture); - EventDetails.outputMeshTexture = null; - } - - public class ArrayValuePopup : PopupWindowContent + internal static void OnDisable() { - public delegate string GetValueStringDelegate(int index, bool highPrecision); - private GetValueStringDelegate GetValueString; - private Vector2 m_ScrollPos = Vector2.zero; - private int m_StartIndex; - private int m_NumValues; - private float m_WindowWidth; - private int m_RowCount; - private static readonly GUIStyle m_Style = EditorStyles.miniLabel; - - public ArrayValuePopup(int startIndex, int numValues, int rowCount, float windowWidth, GetValueStringDelegate getValueString) - { - m_StartIndex = startIndex; - m_NumValues = numValues; - m_WindowWidth = windowWidth; - m_RowCount = rowCount; - GetValueString = getValueString; - } - - public override Vector2 GetWindowSize() - { - float lineHeight = m_Style.lineHeight + m_Style.padding.vertical + m_Style.margin.top; - return new Vector2(m_WindowWidth, Math.Min(lineHeight * m_NumValues * m_RowCount, 250.0f)); - } - - public override void OnGUI(Rect rect) - { - m_ScrollPos = EditorGUILayout.BeginScrollView(m_ScrollPos); - - for (int i = 0; i < m_NumValues; ++i) - { - string text = $"[{i}]\t{GetValueString(m_StartIndex + i, false)}"; - GUILayout.Label(text, m_Style); - } - - EditorGUILayout.EndScrollView(); - - // Right click to copy the values to clipboard. - var e = Event.current; - if (e.type == EventType.ContextClick && rect.Contains(e.mousePosition)) - { - e.Use(); - - string allText = string.Empty; - for (int i = 0; i < m_NumValues; ++i) - { - allText += $"[{i}]\t{GetValueString(m_StartIndex + i, true)}"; - } - - var menu = new GenericMenu(); - menu.AddItem(FrameDebuggerStyles.EventDetails.copyValueText, false, delegate - { - EditorGUIUtility.systemCopyBuffer = allText; - }); - menu.ShowAsContext(); - } - } + UnityEngine.Object.DestroyImmediate(EventDetails.s_RenderTargetMeshBackgroundTexture); + EventDetails.s_RenderTargetMeshBackgroundTexture = null; } } } diff --git a/Editor/Mono/PerformanceTools/FrameDebuggerToolbarView.cs b/Editor/Mono/PerformanceTools/FrameDebuggerToolbarView.cs index b1bf0c34a0..eea124a760 100644 --- a/Editor/Mono/PerformanceTools/FrameDebuggerToolbarView.cs +++ b/Editor/Mono/PerformanceTools/FrameDebuggerToolbarView.cs @@ -3,13 +3,18 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; + using UnityEngine; -using UnityEditorInternal; +using UnityEngine.Profiling; using UnityEngine.Networking.PlayerConnection; + +using UnityEditor; +using UnityEditorInternal; +using UnityEditorInternal.FrameDebuggerInternal; using UnityEditor.Networking.PlayerConnection; -using UnityEngine.Profiling; -namespace UnityEditor + +namespace UnityEditorInternal.FrameDebuggerInternal { internal class FrameDebuggerToolbarView { @@ -20,32 +25,24 @@ internal class FrameDebuggerToolbarView // Returns true if repaint is needed public bool DrawToolbar(FrameDebuggerWindow frameDebugger, IConnectionState m_AttachToPlayerState) { + Profiler.BeginSample("DrawToolbar"); GUILayout.BeginHorizontal(EditorStyles.toolbar); - Profiler.BeginSample("DrawEnableDisableButton"); DrawEnableDisableButton(frameDebugger, m_AttachToPlayerState, out bool needsRepaint); - Profiler.EndSample(); - - Profiler.BeginSample("DrawConnectionDropdown"); DrawConnectionDropdown(frameDebugger, m_AttachToPlayerState, out bool isEnabled); - Profiler.EndSample(); GUI.enabled = isEnabled; - Profiler.BeginSample("DrawEventLimitSlider"); DrawEventLimitSlider(frameDebugger, out int newLimit); - Profiler.EndSample(); - - Profiler.BeginSample("DrawPrevNextButtons"); DrawPrevNextButtons(frameDebugger, ref newLimit); - Profiler.EndSample(); GUILayout.EndHorizontal(); + Profiler.EndSample(); return needsRepaint; } - private void DrawEnableDisableButton(FrameDebuggerWindow frameDebugger, IConnectionState m_AttachToPlayerState, out bool needsRepaint) + private void DrawEnableDisableButton(FrameDebuggerWindow frameDebuggerWindow, IConnectionState m_AttachToPlayerState, out bool needsRepaint) { needsRepaint = false; @@ -53,30 +50,29 @@ private void DrawEnableDisableButton(FrameDebuggerWindow frameDebugger, IConnect bool wasEnabled = GUI.enabled; GUI.enabled = m_AttachToPlayerState.connectedToTarget != ConnectionTarget.Editor || FrameDebuggerUtility.locallySupported; - GUIContent button = (FrameDebugger.enabled) ? FrameDebuggerStyles.TopToolbar.recordButtonDisable : FrameDebuggerStyles.TopToolbar.recordButtonEnable; + GUIContent button = (FrameDebugger.enabled) ? FrameDebuggerStyles.TopToolbar.s_RecordButtonDisable : FrameDebuggerStyles.TopToolbar.s_RecordButtonEnable; GUILayout.Toggle(FrameDebugger.enabled, button, EditorStyles.toolbarButtonLeft, GUILayout.MinWidth(80)); GUI.enabled = wasEnabled; if (EditorGUI.EndChangeCheck()) { - frameDebugger.ClickEnableFrameDebugger(); + frameDebuggerWindow.ToggleFrameDebuggerEnabled(); needsRepaint = true; } } - private void DrawConnectionDropdown(FrameDebuggerWindow frameDebugger, IConnectionState m_AttachToPlayerState, out bool isEnabled) + private void DrawConnectionDropdown(FrameDebuggerWindow frameDebuggerWindow, IConnectionState m_AttachToPlayerState, out bool isEnabled) { PlayerConnectionGUILayout.ConnectionTargetSelectionDropdown(m_AttachToPlayerState, EditorStyles.toolbarDropDown); isEnabled = FrameDebugger.enabled; if (isEnabled && ProfilerDriver.connectedProfiler != FrameDebuggerUtility.GetRemotePlayerGUID()) { // Switch from local to remote debugger or vice versa - FrameDebuggerUtility.SetEnabled(false, FrameDebuggerUtility.GetRemotePlayerGUID()); - FrameDebuggerUtility.SetEnabled(true, ProfilerDriver.connectedProfiler); + frameDebuggerWindow.OnConnectedProfilerChange(); } } - private void DrawEventLimitSlider(FrameDebuggerWindow frameDebugger, out int newLimit) + private void DrawEventLimitSlider(FrameDebuggerWindow frameDebuggerWindow, out int newLimit) { newLimit = 0; @@ -84,30 +80,41 @@ private void DrawEventLimitSlider(FrameDebuggerWindow frameDebugger, out int new bool wasEnabled = GUI.enabled; GUI.enabled = FrameDebuggerUtility.count > 1; - newLimit = EditorGUILayout.IntSlider(FrameDebuggerUtility.limit, 1, FrameDebuggerUtility.count, 1, EditorStyles.toolbarSlider); + + // We need to use Slider instead of IntSlider due to a bug where the invisible label makes + // the mouse cursor different when hovering over the leftmost 10-20% of the slider (UUM-17184) + // We add 0.5 to make it switch between frames like when we use a IntSlider. + newLimit = (int) (0.5f + EditorGUILayout.Slider(FrameDebuggerUtility.limit, 1, FrameDebuggerUtility.count)); + GUI.enabled = wasEnabled; if (EditorGUI.EndChangeCheck()) - frameDebugger.ChangeFrameEventLimit(newLimit); + frameDebuggerWindow.ChangeFrameEventLimit(newLimit); } - private void DrawPrevNextButtons(FrameDebuggerWindow frameDebugger, ref int newLimit) + private void DrawPrevNextButtons(FrameDebuggerWindow frameDebuggerWindow, ref int newLimit) { bool wasEnabled = GUI.enabled; GUI.enabled = newLimit > 1; - if (GUILayout.Button(FrameDebuggerStyles.TopToolbar.prevFrame, EditorStyles.toolbarButton)) - frameDebugger.ChangeFrameEventLimit(newLimit - 1); + if (GUILayout.Button(FrameDebuggerStyles.TopToolbar.s_PrevFrame, EditorStyles.toolbarButton)) + frameDebuggerWindow.ChangeFrameEventLimit(newLimit - 1); GUI.enabled = newLimit < FrameDebuggerUtility.count; - if (GUILayout.Button(FrameDebuggerStyles.TopToolbar.nextFrame, EditorStyles.toolbarButtonRight)) - frameDebugger.ChangeFrameEventLimit(newLimit + 1); + if (GUILayout.Button(FrameDebuggerStyles.TopToolbar.s_NextFrame, EditorStyles.toolbarButtonRight)) + frameDebuggerWindow.ChangeFrameEventLimit(newLimit + 1); // If we had last event selected, and something changed in the scene so that // number of events is different - then try to keep the last event selected. if (m_PrevEventsLimit == m_PrevEventsCount) if (FrameDebuggerUtility.count != m_PrevEventsCount && FrameDebuggerUtility.limit == m_PrevEventsLimit) - frameDebugger.ChangeFrameEventLimit(FrameDebuggerUtility.count); + frameDebuggerWindow.ChangeFrameEventLimit(FrameDebuggerUtility.count); + + // The number of events has changed... + if (FrameDebuggerUtility.count != m_PrevEventsCount) + { + frameDebuggerWindow.ReselectItemOnCountChange(); + } m_PrevEventsLimit = FrameDebuggerUtility.limit; m_PrevEventsCount = FrameDebuggerUtility.count; diff --git a/Editor/Mono/PerformanceTools/FrameDebuggerTreeView.cs b/Editor/Mono/PerformanceTools/FrameDebuggerTreeView.cs index 97c873656f..7372345ef5 100644 --- a/Editor/Mono/PerformanceTools/FrameDebuggerTreeView.cs +++ b/Editor/Mono/PerformanceTools/FrameDebuggerTreeView.cs @@ -9,8 +9,7 @@ using UnityEditor.IMGUI.Controls; using Object = UnityEngine.Object; - -namespace UnityEditorInternal +namespace UnityEditorInternal.FrameDebuggerInternal { internal class FrameDebuggerTreeView { @@ -76,21 +75,37 @@ private void PingFrameEventObject(int selectedID) m_FrameDebugger.DrawSearchField(string.Empty); } + public void ReselectFrameEventIndex() + { + FrameDebuggerTreeViewItem item = GetSelectedTreeViewItem(); + if (item != null) + SetSelection(item.m_EventIndex); + } + public void SelectFrameEventIndex(int eventIndex) { // Check if we'd end up selecting same "frame event": // different tree nodes could result in the same frame debugger event // limit, e.g. a hierarchy node sets last child event as the limit. // If the limit event is the same, then do not change the currently selected item. + FrameDebuggerTreeViewItem item = GetSelectedTreeViewItem(); + if (item == null || item.m_EventIndex != eventIndex) + SetSelection(eventIndex); + } + + private FrameDebuggerTreeViewItem GetSelectedTreeViewItem() + { int[] selection = m_TreeView.GetSelection(); if (selection.Length > 0) - { - FrameDebuggerTreeViewItem item = m_TreeView.FindItem(selection[0]) as FrameDebuggerTreeViewItem; - if (item != null && eventIndex == item.m_EventIndex) - return; - } + return m_TreeView.FindItem(selection[0]) as FrameDebuggerTreeViewItem; + + return null; + } + private void SetSelection(int eventIndex) + { m_TreeView.SetSelection(new[] { eventIndex }, true); + m_FrameDebugger.RepaintOnLimitChange(); } public void DrawTree(Rect rect) @@ -136,9 +151,7 @@ protected override Texture GetIconForItem(TreeViewItem item) protected override void OnContentGUI(Rect rect, int row, TreeViewItem itemRaw, string label, bool selected, bool focused, bool useBoldFont, bool isPinging) { if (Event.current.type != EventType.Repaint) - { return; - } FrameDebuggerTreeViewItem item = (FrameDebuggerTreeViewItem)itemRaw; string text; @@ -150,7 +163,7 @@ protected override void OnContentGUI(Rect rect, int row, TreeViewItem itemRaw, s childCounter = (isParent) ? 1 : (childCounter + 1); // Draw background - style = FrameDebuggerStyles.Tree.rowText; + style = FrameDebuggerStyles.Tree.s_RowText; tempContent = EditorGUIUtility.TempContent(""); style.Draw(rect, tempContent, false, false, false, false); @@ -165,7 +178,7 @@ protected override void OnContentGUI(Rect rect, int row, TreeViewItem itemRaw, s text = item.m_ChildEventCount.ToString(CultureInfo.InvariantCulture); tempContent = EditorGUIUtility.TempContent(text); - style = FrameDebuggerStyles.Tree.rowTextRight; + style = FrameDebuggerStyles.Tree.s_RowTextRight; style.fontStyle = fontStyle; Rect r = rect; @@ -176,7 +189,7 @@ protected override void OnContentGUI(Rect rect, int row, TreeViewItem itemRaw, s rect.width -= style.CalcSize(tempContent).x + kSmallMargin * 2; } - style = FrameDebuggerStyles.Tree.rowText; + style = FrameDebuggerStyles.Tree.s_RowText; style.fontStyle = fontStyle; // draw event name @@ -200,6 +213,9 @@ internal class FrameDebuggerTreeViewDataSource : TreeViewDataSource { private FrameDebuggerEvent[] m_FrameEvents; + public override bool IsRenamingItemAllowed(TreeViewItem item) => false; + public override bool CanBeMultiSelected(TreeViewItem item) => false; + public FrameDebuggerTreeViewDataSource(TreeViewController treeView, FrameDebuggerEvent[] frameEvents) : base(treeView) { m_FrameEvents = frameEvents; @@ -221,16 +237,6 @@ public void SetEvents(FrameDebuggerEvent[] frameEvents) SetExpandedWithChildren(m_RootItem, true); } - public override bool IsRenamingItemAllowed(TreeViewItem item) - { - return false; - } - - public override bool CanBeMultiSelected(TreeViewItem item) - { - return false; - } - // Used while building the tree data source; represents current tree hierarchy level private class FrameDebuggerTreeHierarchyLevel { @@ -286,33 +292,33 @@ public override void FetchData() while (eventStack.Count > 0 && eventStack.Count > level) CloseLastHierarchyLevel(eventStack, i); - if (FrameDebuggerHelper.IsAHierarchyLevelBreakEvent(m_FrameEvents[i].type)) + if (FrameDebuggerHelper.IsAHierarchyLevelBreakEvent(m_FrameEvents[i].m_Type)) continue; // add all further levels for current event for (int j = level; j < names.Length; ++j) { - var parent = eventStack[eventStack.Count - 1]; - var newLevel = new FrameDebuggerTreeHierarchyLevel(eventStack.Count - 1, --hierarchyIDCounter, names[j], parent.item); + FrameDebuggerTreeHierarchyLevel parent = eventStack[eventStack.Count - 1]; + FrameDebuggerTreeHierarchyLevel newLevel = new FrameDebuggerTreeHierarchyLevel(eventStack.Count - 1, --hierarchyIDCounter, names[j], parent.item); parent.children.Add(newLevel.item); eventStack.Add(newLevel); } - if (FrameDebuggerHelper.IsAHiddenEvent(m_FrameEvents[i].type)) + if (FrameDebuggerHelper.IsAHiddenEvent(m_FrameEvents[i].m_Type)) continue; // add leaf event to current level - var eventObj = FrameDebuggerUtility.GetFrameEventObject(i); - var displayName = string.Empty; + Object eventObj = FrameDebuggerUtility.GetFrameEventObject(i); + string displayName = FrameDebuggerStyles.s_FrameEventTypeNames[(int)m_FrameEvents[i].m_Type]; if (eventObj) - displayName = " " + eventObj.name; - else - displayName = FrameDebuggerStyles.frameEventTypeNames[(int)m_FrameEvents[i].type]; + displayName += " " + eventObj.name; FrameDebuggerTreeHierarchyLevel parentEvent = eventStack[eventStack.Count - 1]; + int leafEventID = i + 1; FrameDebuggerTreeViewItem item = new FrameDebuggerTreeViewItem(leafEventID, eventStack.Count - 1, parentEvent.item, displayName); + item.m_FrameEvent = m_FrameEvents[i]; parentEvent.children.Add(item); ++parentEvent.item.m_ChildEventCount; diff --git a/Editor/Mono/PlatformSupport/ProvisioningProfile.cs b/Editor/Mono/PlatformSupport/ProvisioningProfile.cs index 3cceabaf9e..ccf96aeeec 100644 --- a/Editor/Mono/PlatformSupport/ProvisioningProfile.cs +++ b/Editor/Mono/PlatformSupport/ProvisioningProfile.cs @@ -18,8 +18,10 @@ internal class iOSEditorPrefKeys public readonly static string kDefaultiOSProvisioningProfileUUID = "DefaultiOSProvisioningProfileUUID"; public readonly static string kDefaulttvOSProvisioningProfileUUID = "DefaulttvOSProvisioningProfileUUID"; + public readonly static string kDefaultVisionOSProvisioningProfileUUID = "DefaultVisionOSProvisioningProfileUUID"; public readonly static string kDefaultiOSProvisioningProfileType = "kDefaultiOSProvisioningProfileType"; public readonly static string kDefaulttvOSProvisioningProfileType = "kDefaulttvOSProvisioningProfileType"; + public readonly static string kDefaultVisionOSProvisioningProfileType = "kDefaultVisionOSProvisioningProfileType"; } internal class ProvisioningProfile diff --git a/Editor/Mono/PlatformSupport/ProvisioningProfileGUI.cs b/Editor/Mono/PlatformSupport/ProvisioningProfileGUI.cs index b7540728fb..d55080382d 100644 --- a/Editor/Mono/PlatformSupport/ProvisioningProfileGUI.cs +++ b/Editor/Mono/PlatformSupport/ProvisioningProfileGUI.cs @@ -192,7 +192,7 @@ private static int GetDefaultAutomaticSigningValue(SerializedProperty prop, stri int val = prop.intValue; if (val == (int)iOSAutomaticallySignValue.AutomaticallySignValueNotSet) { - val = (int)(EditorPrefs.GetBool(editorPropKey, true) ? iOSAutomaticallySignValue.AutomaticallySignValueTrue : iOSAutomaticallySignValue.AutomaticallySignValueFalse); + val = (int)(EditorPrefs.GetBool(editorPropKey) ? iOSAutomaticallySignValue.AutomaticallySignValueTrue : iOSAutomaticallySignValue.AutomaticallySignValueFalse); } return val; } diff --git a/Editor/Mono/PlayModeView/PlayModeView.cs b/Editor/Mono/PlayModeView/PlayModeView.cs index fd447550bc..46c697537f 100644 --- a/Editor/Mono/PlayModeView/PlayModeView.cs +++ b/Editor/Mono/PlayModeView/PlayModeView.cs @@ -10,6 +10,7 @@ using UnityEditor.Modules; using UnityEditorInternal; using UnityEngine; +using UnityEngine.Bindings; using UnityEngine.Experimental.Rendering; using UnityEngine.Scripting; @@ -46,11 +47,9 @@ internal abstract class PlayModeView : EditorWindow [SerializeField] HideFlags m_TextureHideFlags = HideFlags.HideAndDontSave; [SerializeField] bool m_RenderIMGUI; [SerializeField] EnterPlayModeBehavior m_EnterPlayModeBehavior; - [SerializeField] int m_fullscreenMonitorIdx = 0; - [SerializeField] int m_playModeBehaviorIdx = 0; [SerializeField] bool m_UseMipMap; - [SerializeField] bool m_isFullscreen; - [SerializeField] bool m_suppressRenderingForFullscreen; + + protected bool m_SwitchingPlayModeViewType = false; private const int k_MaxSupportedDisplays = 8; @@ -143,18 +142,6 @@ public EnterPlayModeBehavior enterPlayModeBehavior public const int kFullscreenInvalidIdx = -1; public const int kFullscreenNone = 0; - public int fullscreenMonitorIdx - { - get => m_fullscreenMonitorIdx; - set => m_fullscreenMonitorIdx = value; - } - - public int playModeBehaviorIdx - { - get => m_playModeBehaviorIdx; - set => m_playModeBehaviorIdx = value; - } - [Obsolete("PlayModeView.maximizeOnPlay is obsolete. Use PlayModeView.enterPlayModeBehavior instead")] public bool maximizeOnPlay { @@ -162,21 +149,6 @@ public bool maximizeOnPlay set { m_EnterPlayModeBehavior = value ? EnterPlayModeBehavior.PlayMaximized : EnterPlayModeBehavior.PlayFocused; } } - public bool isFullscreen - { - get { return m_isFullscreen; } - set { m_isFullscreen = value; } - } - - internal bool suppressRenderingForFullscreen - { - get { return m_suppressRenderingForFullscreen; } - set - { - m_suppressRenderingForFullscreen = value; - } - } - protected bool useMipMap { get { return m_UseMipMap; } @@ -199,6 +171,14 @@ public static bool openWindowOnEnteringPlayMode RenderTexture m_TargetTexture; ColorSpace m_CurrentColorSpace = ColorSpace.Uninitialized; + [FreeFunction] + extern protected static bool NeedToPerformRendering(); + + protected static bool renderViewCallNeededInOnGUI => + !EditorApplication.isPlaying || + (EditorApplication.isPlaying && NeedToPerformRendering() && !Unsupported.IsEditorPlayerLoopWaiting()); + + class RenderingView : IDisposable { bool disposed = false; @@ -241,9 +221,15 @@ protected RenderTexture RenderView(Vector2 mousePosition, bool clearTexture) using (var renderingView = new RenderingView(this)) { SetPlayModeViewSize(targetSize); - // This should be called configure virtual display or sth - EditorDisplayUtility.AddVirtualDisplay(targetDisplay, (int)targetSize.x, (int)targetSize.y); - // EditorDisplayManager.UpdateVirtualDisplay(this); + + // The target size and GUI rects are communicated to C++ as floats throughout the engine. This means the final size in pixels + // of the view will be calculated in C++ so we have to ensure we use the C++ float rounding conventions here. Failure to do so + // may result in subtly differences and subtle bugs (like the Screen class thinking something sized 640.5 is 641 pixels but the + // RenderTexture being allocated here only being 640 pixels in size with standard C# rounding used by Mathf.RoundToInt. + // So we use the c++ AwayFromZero convention when rounding here. + int width = (int)Math.Round(targetSize.x, MidpointRounding.AwayFromZero); + int height = (int)Math.Round(targetSize.y, MidpointRounding.AwayFromZero); + var currentTargetDisplay = 0; if (ModuleManager.ShouldShowMultiDisplayOption()) { @@ -253,7 +239,7 @@ protected RenderTexture RenderView(Vector2 mousePosition, bool clearTexture) } bool hdr = (m_Parent != null && m_Parent.actualView == this && m_Parent.hdrActive); - ConfigureTargetTexture((int)targetSize.x, (int)targetSize.y, clearTexture, playModeViewName, hdr); + ConfigureTargetTexture(width, height, clearTexture, playModeViewName, hdr); if (Event.current == null || Event.current.type != EventType.Repaint) return m_TargetTexture; @@ -306,8 +292,6 @@ protected internal void SwapMainWindow(Type type) throw new ArgumentException("Type should derive from " + typeof(PlayModeView).Name); if (type.Name != GetType().Name) { - EditorFullscreenController.SetMainDisplayPlayModeViewType(type); - var serializedViews = ListsToDictionary(m_SerializedViewNames, m_SerializedViewValues); // Clear serialized views so they wouldn't be serialized again @@ -343,6 +327,11 @@ protected internal void SwapMainWindow(Type type) if (m_Parent is DockArea dockAreaParent) { + // Mark that this view is the one switching out so when we do the tab management we don't have + // the old tab rewrite the desired size when GameView.OnBackgroundResized() is called during + // SplitView.Reflow(). + m_SwitchingPlayModeViewType = true; + dockAreaParent.AddTab(window); dockAreaParent.RemoveTab(this); DestroyImmediate(this, true); @@ -353,6 +342,7 @@ protected internal void SwapMainWindow(Type type) DestroyImmediate(this, true); } } + RemoveDisabledWindows(); } private void ClearTargetTexture() @@ -500,9 +490,6 @@ protected void SetVSync(bool enable) protected void SetFocus(bool focused) { - if (suppressRenderingForFullscreen) - return; //suppressed views should not grab "play mode" focus - if (!focused && s_LastFocused == this) { InternalEditorUtility.OnGameViewFocus(false); @@ -520,21 +507,6 @@ protected void SetFocus(bool focused) SetDisplayViewSize(m_TargetDisplay, m_TargetSize); } - internal virtual void ApplyEditorDisplayFullscreenSetting(IPlayModeViewFullscreenSettings settings) - { - m_isFullscreen = true; - - if (ModuleManager.ShouldShowMultiDisplayOption()) - { - if (targetDisplay != settings.DisplayNumber) - { - targetDisplay = settings.DisplayNumber; - } - } - - SetVSync(settings.VsyncEnabled); - } - [RequiredByNativeCode] internal static void IsPlayModeViewOpen(out bool isPlayModeViewOpen) { @@ -554,55 +526,32 @@ public enum EnterPlayModeBehavior { PlayFocused, PlayMaximized, - PlayUnfocused, - PlayFullscreen + PlayUnfocused } void SetPlayModeWindowsStates(EnterPlayModeBehavior behavior) { - var isFullscreen = (behavior == EnterPlayModeBehavior.PlayFullscreen); + if(m_EnterPlayModeBehavior == behavior) + return; - this.m_EnterPlayModeBehavior = behavior; - this.fullscreenMonitorIdx = isFullscreen - ? GameViewOnPlayMenu.SelectedIndexToDisplayIndex(this.playModeBehaviorIdx) - : -1; + m_EnterPlayModeBehavior = behavior; + OnEnterPlayModeBehaviorChange(); + + if (behavior != EnterPlayModeBehavior.PlayMaximized) + return; foreach (var view in s_PlayModeViews) { - if (view == this) - { + if (view == this || view.m_EnterPlayModeBehavior != EnterPlayModeBehavior.PlayMaximized) continue; - } - if (behavior == EnterPlayModeBehavior.PlayMaximized && view.m_EnterPlayModeBehavior == EnterPlayModeBehavior.PlayMaximized) - { - // Only one play mode view can be maximized at a time. - view.m_EnterPlayModeBehavior = EnterPlayModeBehavior.PlayUnfocused; - view.playModeBehaviorIdx = 0; - view.fullscreenMonitorIdx = PlayModeView.kFullscreenNone; - } - else if (behavior == EnterPlayModeBehavior.PlayFullscreen && view.m_EnterPlayModeBehavior == EnterPlayModeBehavior.PlayFullscreen) - { - // We can have multiple fullscreen views, so long as they're not on the same monitor - if (this.fullscreenMonitorIdx == view.fullscreenMonitorIdx) - { - view.m_EnterPlayModeBehavior = EnterPlayModeBehavior.PlayUnfocused; - view.playModeBehaviorIdx = 0; - view.fullscreenMonitorIdx = PlayModeView.kFullscreenNone; - } - } + // Only one play mode view can be maximized at a time. + view.m_EnterPlayModeBehavior = EnterPlayModeBehavior.PlayUnfocused; view.OnEnterPlayModeBehaviorChange(); view.Repaint(); } } protected virtual void OnEnterPlayModeBehaviorChange() {} - internal static PlayModeView GetAssociatedViewForTargetDisplay(int targetDisplay) - { - return s_PlayModeViews.Where(v => v.targetDisplay == targetDisplay && !v.m_suppressRenderingForFullscreen) - .OrderByDescending(v => v.isFullscreen) - .ThenByDescending(v => v.hasFocus) - .FirstOrDefault(); - } } } diff --git a/Editor/Mono/PlayerSettings.bindings.cs b/Editor/Mono/PlayerSettings.bindings.cs index 70b2e52dca..3551e9a14e 100644 --- a/Editor/Mono/PlayerSettings.bindings.cs +++ b/Editor/Mono/PlayerSettings.bindings.cs @@ -256,7 +256,8 @@ internal enum TextureCompressionFormat ASTC = 3, PVRTC = 4, DXTC = 5, - BPTC = 6 + BPTC = 6, + DXTC_RGTC = 7 } public enum InsecureHttpOption @@ -266,6 +267,20 @@ public enum InsecureHttpOption AlwaysAllowed = 2, } + // Windows platform input APIs + // Keep in sync with WindowsGamepadBackendHint enum from PlayerSettings.h + public enum WindowsGamepadBackendHint + { + // Backend selected automatically based on platform support + WindowsGamepadBackendHintDefault = 0, + + // XInput + WindowsGamepadBackendHintXInput = 1, + + // GameInput + WindowsGamepadBackendHintWindowsGamingInput = 2 + } + // Player Settings is where you define various parameters for the final game that you will build in Unity. Some of these values are used in the Resolution Dialog that launches when you open a standalone game. [NativeClass(null)] [NativeHeader("Editor/Mono/PlayerSettings.bindings.h")] @@ -514,6 +529,9 @@ public static Guid productGUID // Enable receipt validation for the Mac App Store. public static extern bool useMacAppStoreValidation { get; set; } + // Enable advanced optimiztions for Dedicated Server builds + public static extern bool dedicatedServerOptimizations { get; set; } + // Define how to handle fullscreen mode in Mac OS X standalones [Obsolete("macFullscreenMode is deprecated, use fullScreenMode instead")] [StaticAccessor("PlayerSettingsBindings", StaticAccessorType.DoubleColon)] @@ -568,10 +586,13 @@ public static bool singlePassStereoRendering public static extern bool enableFrameTimingStats { get; set; } public static extern bool enableOpenGLProfilerGPURecorders { get; set; } + public static extern bool allowHDRDisplaySupport { get; set; } public static extern bool useHDRDisplay { get; set; } - - public static extern D3DHDRDisplayBitDepth D3DHDRBitDepth { get; set; } - + + [Obsolete("D3DHDRBitDepth has been replaced by hdrBitDepth. (UnityUpgradable) -> hdrBitDepth", true)] + public static extern D3DHDRDisplayBitDepth D3DHDRBitDepth { [NativeName("GetHDRBitDepthForObseleteEnum")] get; [NativeName("SetHDRBitDepthForObseleteEnum")] set; } + + public static extern HDRDisplayBitDepth hdrBitDepth { get; set; } // What happens with the fullscreen Window when it runs in the background @@ -701,6 +722,36 @@ internal static string GetPlatformName(BuildTargetGroup targetGroup) [StaticAccessor("PlayerSettingsBindings", StaticAccessorType.DoubleColon)] internal static extern void SetBatchingForPlatform(BuildTarget platform, int staticBatching, int dynamicBatching); + [StaticAccessor("PlayerSettingsBindings", StaticAccessorType.DoubleColon)] + public static extern int GetShaderChunkSizeInMBForPlatform(BuildTarget buildTarget); + + [StaticAccessor("PlayerSettingsBindings", StaticAccessorType.DoubleColon)] + public static extern void SetShaderChunkSizeInMBForPlatform(BuildTarget buildTarget, int sizeInMegabytes); + + [StaticAccessor("PlayerSettingsBindings", StaticAccessorType.DoubleColon)] + public static extern int GetShaderChunkCountForPlatform(BuildTarget buildTarget); + + [StaticAccessor("PlayerSettingsBindings", StaticAccessorType.DoubleColon)] + public static extern void SetShaderChunkCountForPlatform(BuildTarget buildTarget, int chunkCount); + + [StaticAccessor("PlayerSettingsBindings", StaticAccessorType.DoubleColon)] + public static extern int GetDefaultShaderChunkSizeInMB(); + + [StaticAccessor("PlayerSettingsBindings", StaticAccessorType.DoubleColon)] + public static extern void SetDefaultShaderChunkSizeInMB(int sizeInMegabytes); + + [StaticAccessor("PlayerSettingsBindings", StaticAccessorType.DoubleColon)] + public static extern int GetDefaultShaderChunkCount(); + + [StaticAccessor("PlayerSettingsBindings", StaticAccessorType.DoubleColon)] + public static extern void SetDefaultShaderChunkCount(int chunkCount); + + [StaticAccessor("PlayerSettingsBindings", StaticAccessorType.DoubleColon)] + public static extern bool GetOverrideShaderChunkSettingsForPlatform(BuildTarget buildTarget); + + [StaticAccessor("PlayerSettingsBindings", StaticAccessorType.DoubleColon)] + public static extern void SetOverrideShaderChunkSettingsForPlatform(BuildTarget buildTarget, bool value); + [NativeMethod("GetLightmapEncodingQuality")] internal static extern LightmapEncodingQuality GetLightmapEncodingQualityForPlatformGroup(BuildTargetGroup platformGroup); @@ -772,46 +823,6 @@ internal static extern string spritePackerPolicy set; } - internal static readonly char[] defineSplits = new[] { ';', ',', ' ' }; - - internal static string[] ConvertScriptingDefineStringToArray(string defines) - { - return defines.Split(defineSplits, StringSplitOptions.RemoveEmptyEntries); - } - - internal static string ConvertScriptingDefineArrayToString(string[] defines) - { - List list = new List(); - foreach (var define in defines) - { - string[] split = define.Split(' ', ';'); - - // Split each define element, since there can be multiple defines added - foreach (var item in split) - { - if (!string.IsNullOrEmpty(item)) - { - list.Add(item); - } - } - } - - // Remove duplicates - defines = list.Distinct().ToArray(); - - // Join all defines to one string - var joined = new StringBuilder(); - foreach (var define in defines) - { - if (joined.Length != 0) - joined.Append(';'); - - joined.Append(define); - } - - return joined.ToString(); - } - // TargetGroup no longer defines the entire build targets space. Now build targets are better // identified by a string key (wrapped into NamedBuildTarget), so all the following methods are being replaced. // We need to mark them as obsolete once (if) BuildTargetGroup is completely removed. @@ -822,7 +833,7 @@ public static string GetScriptingDefineSymbolsForGroup(BuildTargetGroup targetGr // [Obsolete("Use GetScriptingDefineSymbols(NamedBuildTarget buildTarget, out string[] defines) instead")] public static void GetScriptingDefineSymbolsForGroup(BuildTargetGroup targetGroup, out string[] defines) => - defines = ConvertScriptingDefineStringToArray(GetScriptingDefineSymbolsForGroup(targetGroup)); + defines = ScriptingDefinesHelper.ConvertScriptingDefineStringToArray(GetScriptingDefineSymbolsForGroup(targetGroup)); // [Obsolete("Use SetScriptingDefineSymbols(NamedBuildTarget buildTarget, string defines) instead")] public static void SetScriptingDefineSymbolsForGroup(BuildTargetGroup targetGroup, string defines) => @@ -925,13 +936,13 @@ public static void SetNormalMapEncoding(BuildTargetGroup platform, NormalMapEnco public static string GetScriptingDefineSymbols(NamedBuildTarget buildTarget) => GetScriptingDefineSymbolsInternal(buildTarget.TargetName); public static void GetScriptingDefineSymbols(NamedBuildTarget buildTarget, out string[] defines) => - defines = ConvertScriptingDefineStringToArray(GetScriptingDefineSymbols(buildTarget)); + defines = ScriptingDefinesHelper.ConvertScriptingDefineStringToArray(GetScriptingDefineSymbols(buildTarget)); // Set user-specified symbols for script compilation for the given build target group. public static void SetScriptingDefineSymbols(NamedBuildTarget buildTarget, string defines) { if (!string.IsNullOrEmpty(defines)) - defines = string.Join(";", ConvertScriptingDefineStringToArray(defines)); + defines = string.Join(";", ScriptingDefinesHelper.ConvertScriptingDefineStringToArray(defines)); SetScriptingDefineSymbolsInternal(buildTarget.TargetName, defines); } @@ -941,7 +952,7 @@ public static void SetScriptingDefineSymbols(NamedBuildTarget buildTarget, strin if (defines == null) throw new ArgumentNullException(nameof(defines)); - SetScriptingDefineSymbols(buildTarget, ConvertScriptingDefineArrayToString(defines)); + SetScriptingDefineSymbols(buildTarget, ScriptingDefinesHelper.ConvertScriptingDefineArrayToString(defines)); } [NativeThrows] @@ -1191,15 +1202,6 @@ public static bool useReferenceAssemblies set {} } - internal static extern bool EnableRoslynAnalyzers - { - [StaticAccessor("GetPlayerSettings().GetEditorOnly()")] - get; - - [StaticAccessor("GetPlayerSettings().GetEditorOnlyForUpdate()")] - set; - } - internal static extern bool gcWBarrierValidation { [StaticAccessor("GetPlayerSettings().GetEditorOnly()")] @@ -1406,6 +1408,14 @@ public static string applicationIdentifier } } + // Application bundle version for the VisionOS platform + [NativeProperty("VisionOSApplicationVersion")] + public static extern string visionOSBundleVersion { get; set; } + + // Application bundle version for the TVOS platform + [NativeProperty("TVOSApplicationVersion")] + public static extern string tvOSBundleVersion { get; set; } + // Application bundle version shared between iOS & Android platforms [NativeProperty("ApplicationVersion")] public static extern string bundleVersion { get; set; } @@ -1518,6 +1528,8 @@ public static bool useDirect3D11 // Defines whether the application will request audio focus, muting all other audio sources. public static extern bool muteOtherAudioSources { get; set; } + public static extern AudioSpatialExperience audioSpatialExperience { get; set; } + internal static extern bool playModeTestRunnerEnabled { get; set; } internal static extern bool runPlayModeTestAsEditModeTest { get; set; } @@ -1562,6 +1574,19 @@ public static extern bool enableMetalAPIValidation [FreeFunction("GetPlayerSettings().GetDisableOldInputManagerSupport")] internal static extern bool GetDisableOldInputManagerSupport(); + [FreeFunction("GetPlayerSettings().GetWindowsGamepadBackendHint")] + internal static extern WindowsGamepadBackendHint GetWindowsGamepadBackendHint(); + + [FreeFunction("GetPlayerSettings().SetWindowsGamepadBackendHint")] + internal static extern void SetWindowsGamepadBackendHint(WindowsGamepadBackendHint value); + + // The input API backend used on Windows platforms + public static WindowsGamepadBackendHint windowsGamepadBackendHint + { + get { return GetWindowsGamepadBackendHint(); } + set { SetWindowsGamepadBackendHint(value); } + } + [StaticAccessor("GetPlayerSettings()")] [NativeMethod("GetVirtualTexturingSupportEnabled")] public static extern bool GetVirtualTexturingSupportEnabled(); @@ -1610,5 +1635,7 @@ internal static extern bool iosCopyPluginsCodeInsteadOfSymlink [StaticAccessor("GetPlayerSettings().GetEditorOnlyForUpdate()", StaticAccessorType.Dot)] set; } + + internal static extern bool platformRequiresReadableAssets { get; set; } } } diff --git a/Editor/Mono/PlayerSettings.deprecated.cs b/Editor/Mono/PlayerSettings.deprecated.cs index 3d8e4f326e..3c7deb6721 100644 --- a/Editor/Mono/PlayerSettings.deprecated.cs +++ b/Editor/Mono/PlayerSettings.deprecated.cs @@ -164,6 +164,9 @@ public static AndroidTargetDevice targetDevice targetArchitectures = AndroidArchitecture.ARMv7; } } + + [Obsolete("minifyWithR8 is obsolete and has no effect anymore, since Android Gradle Plugin 7.0 always uses R8", false)] + public static bool minifyWithR8 { get { return true; } set {} } } partial class iOS diff --git a/Editor/Mono/PlayerSettingsAndroid.bindings.cs b/Editor/Mono/PlayerSettingsAndroid.bindings.cs index 3dae16de80..f826a04d40 100644 --- a/Editor/Mono/PlayerSettingsAndroid.bindings.cs +++ b/Editor/Mono/PlayerSettingsAndroid.bindings.cs @@ -95,6 +95,24 @@ public enum AndroidSdkVersions // Android 11.0, API level 30 AndroidApiLevel30 = 30, + + // Android 12.0, API level 31 + AndroidApiLevel31 = 31, + + // Android 12L, API level 32 + AndroidApiLevel32 = 32, + + // Android 13.0, API level 33 + AndroidApiLevel33 = 33, + + // Android 14.0, API level 34 + AndroidApiLevel34 = 34, + + // Android 15.0, API level 35 + AndroidApiLevel35 = 35, + + // Android 16.0, API level 36 + AndroidApiLevel36 = 36, } // Preferred application install location @@ -175,6 +193,12 @@ internal struct AndroidBanner public Texture2D banner; } + public enum AndroidAutoRotationBehavior + { + User = 1, + Sensor = 2, + } + // Player Settings is where you define various parameters for the final game that you will build in Unity. Some of these values are used in the Resolution Dialog that launches when you open a standalone game. public partial class PlayerSettings : UnityEngine.Object { @@ -248,6 +272,14 @@ public static extern FullScreenMode fullscreenMode set; } + public static extern AndroidAutoRotationBehavior autoRotationBehavior + { + [NativeMethod("GetAndroidAutoRotationBehavior")] + get; + [NativeMethod("SetAndroidAutoRotationBehavior")] + set; + } + // Android bundle version code public static extern int bundleVersionCode { @@ -528,9 +560,6 @@ public static extern bool renderOutsideSafeArea set; } - // Minify java code using R8 if set to true, otherwise use proguard for minification - [NativeProperty("AndroidMinifyWithR8", TargetType.Function)] - public static extern bool minifyWithR8 { get; set; } // Minify java code in release build [NativeProperty("AndroidMinifyRelease", TargetType.Function)] public static extern bool minifyRelease { get; set; } @@ -551,6 +580,10 @@ public static extern bool optimizedFramePacing [NativeMethod("SetAndroidUseSwappy")] set; } + + // Add enableOnBackInvokedCallback flag to AndroidManifest + [NativeProperty("AndroidPredictiveBackSupport", TargetType.Function)] + public static extern bool predictiveBackSupport { get; set; } } } } diff --git a/Editor/Mono/PlayerSettingsEmbeddedLinux.bindings.cs b/Editor/Mono/PlayerSettingsEmbeddedLinux.bindings.cs index c4e8ef8f36..2ac5733385 100644 --- a/Editor/Mono/PlayerSettingsEmbeddedLinux.bindings.cs +++ b/Editor/Mono/PlayerSettingsEmbeddedLinux.bindings.cs @@ -2,6 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using UnityEngine; using UnityEngine.Bindings; using UnityObject = UnityEngine.Object; @@ -14,11 +15,23 @@ public partial class PlayerSettings : UnityObject public sealed partial class EmbeddedLinux { // Custom path to store data files - [NativeProperty("PlayerDataPath")] + [NativeProperty("HmiPlayerDataPath")] public static extern string playerDataPath { get; set; } - [NativeProperty("ForceSRGBBlit")] + [NativeProperty("HmiForceSRGBBlit")] public static extern bool forceSRGBBlit { get; set; } + + [NativeProperty("EmbeddedLinuxEnableGamepadInput")] + public static extern bool enableGamepadInput { get; set; } + + [NativeProperty("HmiCpuConfiguration")] + public static extern int[] cpuConfiguration { get; set; } + + [NativeProperty("HmiLoadingImage")] + public static extern Texture2D hmiLoadingImage { get; set; } + + [NativeProperty("HmiLogStartupTiming")] + public static extern bool hmiLogStartupTiming { get; set; } } } } diff --git a/Editor/Mono/PlayerSettingsIOS.bindings.cs b/Editor/Mono/PlayerSettingsIOS.bindings.cs index ff21f94e91..e27bbc605e 100644 --- a/Editor/Mono/PlayerSettingsIOS.bindings.cs +++ b/Editor/Mono/PlayerSettingsIOS.bindings.cs @@ -19,6 +19,7 @@ namespace UnityEditor { // AppleMobile CPU architecture. // Matches enum in EditorOnlyPlayerSettings.h. + // For Device [Flags] public enum AppleMobileArchitecture : uint { @@ -30,6 +31,14 @@ public enum AppleMobileArchitecture : uint Universal = 1 << 1 } + // For Simulator + public enum AppleMobileArchitectureSimulator + { + X86_64 = 0, + ARM64 = 1 << 0, + Universal = 1 << 1 + } + // Supported iOS SDK versions public enum iOSSdkVersion { @@ -159,7 +168,7 @@ internal partial class iOSDeviceRequirementGroup private string m_VariantName; [FreeFunction("PlayerSettingsIOSBindings::SetOrAddDeviceRequirementForVariantNameImpl")] - extern private static void SetOrAddDeviceRequirementForVariantNameImpl(string name, int index, string[] keys, string[] values); + extern private static void SetOrAddDeviceRequirementForVariantNameImpl(string name, int index, [Unmarshalled] string[] keys, [Unmarshalled] string[] values); [NativeMethod(Name = "GetIOSDeviceRequirementCountForVariantName")] [StaticAccessor("GetPlayerSettings()", StaticAccessorType.Dot)] @@ -254,12 +263,13 @@ private extern static int scriptCallOptimizationInternal [NativeMethod("SetiPhoneScriptCallOptimization")] set; } - public static ScriptCallOptimizationLevel scriptCallOptimization { get { return (ScriptCallOptimizationLevel)scriptCallOptimizationInternal; } set { scriptCallOptimizationInternal = (int)value; } } + + // Active iOS SDK version used for build private extern static int sdkVersionInternal { [NativeMethod("GetiPhoneSdkVersion")] @@ -267,14 +277,26 @@ private extern static int sdkVersionInternal [NativeMethod("SetiPhoneSdkVersion")] set; } - - // Active iOS SDK version used for build public static iOSSdkVersion sdkVersion { get { return (iOSSdkVersion)sdkVersionInternal; } set { sdkVersionInternal = (int)value; } } + // Simulator Architectures + private extern static int simulatorSdkArchitectureInternal + { + [NativeMethod("GetiOSSimulatorArchitecture")] + get; + [NativeMethod("SetiOSSimulatorArchitecture")] + set; + } + public static AppleMobileArchitectureSimulator simulatorSdkArchitecture + { + get { return (AppleMobileArchitectureSimulator)simulatorSdkArchitectureInternal; } + set { simulatorSdkArchitectureInternal = (int)value; } + } + [FreeFunction] private extern static string iOSTargetOSVersionObsoleteEnumToString(int val); @@ -362,7 +384,7 @@ public static UnityEngine.iOS.SystemGestureDeferMode deferSystemGesturesMode } [NativeProperty("HideHomeButton")] - public static bool hideHomeButton { get; set; } + public extern static bool hideHomeButton { get; set; } [NativeProperty("IOSAppInBackgroundBehavior")] private extern static int appInBackgroundBehaviorInternal @@ -472,6 +494,28 @@ public static string tvOSManualProvisioningProfileID } + [NativeProperty("VisionOSManualProvisioningProfileID")] + private extern static string VisionOSManualProvisioningProfileIDInternal + { + [StaticAccessor("GetPlayerSettings().GetEditorOnly()", StaticAccessorType.Dot)] + get; + [StaticAccessor("GetPlayerSettings().GetEditorOnlyForUpdate()", StaticAccessorType.Dot)] + set; + } + + public static string VisionOSManualProvisioningProfileID + { + get + { + return String.IsNullOrEmpty(VisionOSManualProvisioningProfileIDInternal) ? + EditorPrefs.GetString("DefaultVisionOSProvisioningProfileUUID") : VisionOSManualProvisioningProfileIDInternal; + } + set + { + VisionOSManualProvisioningProfileIDInternal = value; + } + } + [NativeProperty("tvOSManualProvisioningProfileType")] public static extern ProvisioningProfileType tvOSManualProvisioningProfileType { @@ -490,6 +534,15 @@ public static extern ProvisioningProfileType iOSManualProvisioningProfileType set; } + [NativeProperty("VisionOSManualProvisioningProfileType")] + public static extern ProvisioningProfileType VisionOSManualProvisioningProfileType + { + [StaticAccessor("GetPlayerSettings().GetEditorOnly()", StaticAccessorType.Dot)] + get; + [StaticAccessor("GetPlayerSettings().GetEditorOnlyForUpdate()", StaticAccessorType.Dot)] + set; + } + [NativeProperty("AppleEnableAutomaticSigning")] private extern static int appleEnableAutomaticSigningInternal { diff --git a/Editor/Mono/PlayerSettingsPS4.bindings.cs b/Editor/Mono/PlayerSettingsPS4.bindings.cs index 11599b4d7f..da6970aff9 100644 --- a/Editor/Mono/PlayerSettingsPS4.bindings.cs +++ b/Editor/Mono/PlayerSettingsPS4.bindings.cs @@ -197,7 +197,7 @@ public static string SdkOverride [NativeProperty("ps4ReprojectionSupport", false, TargetType.Field)] extern public static bool reprojectionSupport { get; set; } [NativeProperty("ps4UseAudio3dBackend", false, TargetType.Field)] extern public static bool useAudio3dBackend { get; set; } [NativeProperty("ps4Audio3dVirtualSpeakerCount", false, TargetType.Field)] extern public static int audio3dVirtualSpeakerCount { get; set; } - [NativeProperty("ps4ScriptOptimizationLevel", false, TargetType.Field)] extern public static int scriptOptimizationLevel { get; set; } + [Obsolete("Use PlayerSettings.SetIl2CppCompilerConfiguration() instead.")] public static int scriptOptimizationLevel { get; set; } [NativeProperty("ps4UseLowGarlicFragmentationMode", true, TargetType.Field)] extern public static bool useLowGarlicFragmentationMode { get; set; } [NativeProperty("ps4SocialScreenEnabled", false, TargetType.Field)] extern public static int socialScreenEnabled { get; set; } [NativeProperty("ps4attribUserManagement", false, TargetType.Field)] extern public static bool attribUserManagement { get; set; } diff --git a/Editor/Mono/PlayerSettingsQNX.bindings.cs b/Editor/Mono/PlayerSettingsQNX.bindings.cs new file mode 100644 index 0000000000..d81e1948bf --- /dev/null +++ b/Editor/Mono/PlayerSettingsQNX.bindings.cs @@ -0,0 +1,31 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using UnityEngine; +using UnityEngine.Bindings; +using UnityObject = UnityEngine.Object; + +namespace UnityEditor +{ + public partial class PlayerSettings : UnityObject + { + [NativeHeader("Runtime/Misc/PlayerSettings.h")] + [StaticAccessor("GetPlayerSettings()", StaticAccessorType.Dot)] + public sealed partial class QNX + { + // Custom path to store data files + [NativeProperty("HmiPlayerDataPath")] + public static extern string playerDataPath { get; set; } + + [NativeProperty("HmiForceSRGBBlit")] + public static extern bool forceSRGBBlit { get; set; } + + [NativeProperty("HmiCpuConfiguration")] + public static extern int[] cpuConfiguration { get; set; } + + [NativeProperty("HmiLoadingImage")] + public static extern Texture2D hmiLoadingImage { get; set; } + } + } +} diff --git a/Editor/Mono/PlayerSettingsSwitch.bindings.cs b/Editor/Mono/PlayerSettingsSwitch.bindings.cs index d210b19bd2..841b6be3d0 100644 --- a/Editor/Mono/PlayerSettingsSwitch.bindings.cs +++ b/Editor/Mono/PlayerSettingsSwitch.bindings.cs @@ -60,13 +60,6 @@ public enum RequiredWithNetworkServiceAccountAvailable = 2 } - public enum TouchScreenUsage - { - Supported = 0, - Required = 1, - None = 2 - } - public enum LogoHandling { Auto = 0, @@ -139,14 +132,14 @@ public enum SupportedNpadStyle [NativeProperty("switchUseCPUProfiler", TargetType.Field)] extern public static bool useSwitchCPUProfiler { get; set; } + // Whether to enable file system trace on Nintendo Switch CPU Profiler. + [NativeProperty("switchEnableFileSystemTrace", TargetType.Field)] + extern public static bool enableFileSystemTrace { get; set; } + // What LTO setting to use on Switch. [NativeProperty("switchLTOSetting", TargetType.Field)] extern public static int switchLTOSetting { get; set; } - // Whether to enable use of the old Nintendo GOLD linker. - [NativeProperty("switchUseGOLDLinker", TargetType.Field)] - extern public static bool useSwitchGOLDLinker { get; set; } - // System Memory (used for virtual memory mapping). [NativeProperty("switchSystemResourceMemory", TargetType.Field)] extern public static int systemResourceMemory { get; set; } @@ -275,6 +268,15 @@ extern public static int NVNGraphicsFirmwareMemory [StaticAccessor("PlayerSettings", StaticAccessorType.DoubleColon)] extern public static int maximumSwitchNVNGraphicsFirmwareMemory { get; } + [StaticAccessor("GetPlayerSettings()", StaticAccessorType.Dot)] + extern public static int switchMaxWorkerMultiple + { + [NativeMethod("GetSwitchKMaxWorkerMultiple")] + get; + [NativeMethod("SetSwitchKMaxWorkerMultiple")] + set; + } + // Controls the behavior of Switch's auto-changing screen resolution [NativeProperty("switchScreenResolutionBehavior", TargetType.Field)] extern public static ScreenResolutionBehavior screenResolutionBehavior { get; set; } @@ -469,9 +471,6 @@ extern public static string releaseVersion [NativeProperty("switchStartupUserAccount", TargetType.Field)] extern public static StartupUserAccount startupUserAccount { get; set; } - [NativeProperty("switchTouchScreenUsage", TargetType.Field)] - extern public static TouchScreenUsage touchScreenUsage { get; set; } - [NativeProperty("switchSupportedLanguagesMask", TargetType.Field)] extern public static int supportedLanguages { get; set; } @@ -583,6 +582,9 @@ extern public static int[] ratingAgeArray [NativeProperty("switchSupportedNpadCount", TargetType.Field)] extern public static int supportedNpadCount { get; set; } + [NativeProperty("switchEnableTouchScreen", TargetType.Field)] + extern public static bool enableTouchScreen { get; set; } + // SocketConfigEnabled [NativeProperty("switchSocketConfigEnabled", TargetType.Field)] extern public static bool socketConfigEnabled { get; set; } @@ -623,14 +625,18 @@ extern public static int[] ratingAgeArray [NativeProperty("switchNetworkInterfaceManagerInitializeEnabled", TargetType.Field)] extern public static bool networkInterfaceManagerInitializeEnabled { get; set; } - // Player Connection Enabled - [NativeProperty("switchPlayerConnectionEnabled", TargetType.Field)] - extern public static bool playerConnectionEnabled { get; set; } + // HTCS for player connection + [NativeProperty("switchDisableHTCSPlayerConnection", TargetType.Field)] + extern public static bool disableHTCSPlayerConnection { get; set; } // Using the new path style system [NativeProperty("switchUseNewStyleFilepaths", TargetType.Field)] extern public static bool useNewStyleFilepaths { get; set; } + // Forces all FMOD threads to use nn::os::LowestThreadPriority + [NativeProperty("switchUseLegacyFmodPriorities", TargetType.Field)] + extern public static bool switchUseLegacyFmodPriorities { get; set; } + // Controls if calls to nn::os::YieldThread are swapped with calls to nn::os::SleepThread({switchMicroSleepForYieldTime}us) [NativeProperty("switchUseMicroSleepForYield", TargetType.Field)] extern public static bool switchUseMicroSleepForYield { get; set; } diff --git a/Editor/Mono/PlayerSettingsTVOS.bindings.cs b/Editor/Mono/PlayerSettingsTVOS.bindings.cs index 00e442e0ae..dc30c8e224 100644 --- a/Editor/Mono/PlayerSettingsTVOS.bindings.cs +++ b/Editor/Mono/PlayerSettingsTVOS.bindings.cs @@ -33,6 +33,7 @@ public partial class PlayerSettings : UnityEngine.Object [StaticAccessor("GetPlayerSettings()")] public partial class tvOS { + // Sdk Version private static extern int sdkVersionInt { [NativeMethod("GettvOSSdkVersion")] @@ -40,13 +41,26 @@ private static extern int sdkVersionInt [NativeMethod("SettvOSSdkVersion")] set; } - public static tvOSSdkVersion sdkVersion { get { return (tvOSSdkVersion)sdkVersionInt; } set { sdkVersionInt = (int)value; } } + // Simulator Architectures + private extern static int simulatorSdkArchitectureInternal + { + [NativeMethod("GettvOSSimulatorArchitecture")] + get; + [NativeMethod("SettvOSSimulatorArchitecture")] + set; + } + public static AppleMobileArchitectureSimulator simulatorSdkArchitecture + { + get { return (AppleMobileArchitectureSimulator)simulatorSdkArchitectureInternal; } + set { simulatorSdkArchitectureInternal = (int)value; } + } + // tvOS bundle build number public static string buildNumber { diff --git a/Editor/Mono/PlayerSettingsVisionOS.bindings.cs b/Editor/Mono/PlayerSettingsVisionOS.bindings.cs new file mode 100644 index 0000000000..90c053c6e3 --- /dev/null +++ b/Editor/Mono/PlayerSettingsVisionOS.bindings.cs @@ -0,0 +1,64 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using UnityEditor.Build; +using UnityEngine; +using UnityEngine.Bindings; + +namespace UnityEditor +{ + public enum VisionOSSdkVersion + { + Device = 0, + Simulator = 1 + } + + // Player Settings is where you define various parameters for the final game that you will build in Unity. Some of these values are used in the Resolution Dialog that launches when you open a standalone game. + public partial class PlayerSettings : UnityEngine.Object + { + // VisionOS specific player settings + [NativeHeader("Runtime/Misc/PlayerSettings.h")] + [NativeHeader("Editor/Src/EditorUserBuildSettings.h")] + [StaticAccessor("GetPlayerSettings()")] + public sealed partial class VisionOS + { + private static extern int sdkVersionInt + { + [NativeMethod("GetVisionOSSdkVersion")] + get; + [NativeMethod("SetVisionOSSdkVersion")] + set; + } + + public static VisionOSSdkVersion sdkVersion + { + get { return (VisionOSSdkVersion)sdkVersionInt; } + set { sdkVersionInt = (int)value; } + } + + // visionos bundle build number + public static string buildNumber + { + get { return PlayerSettings.GetBuildNumber(NamedBuildTarget.VisionOS.TargetName); } + set { PlayerSettings.SetBuildNumber(NamedBuildTarget.VisionOS.TargetName, value); } + } + + [StaticAccessor("GetPlayerSettings().GetEditorOnly()", StaticAccessorType.Dot)] + [NativeMethod("GetVisionOSMinimumVersionString")] + static extern string GetMinimumVersionString(); + + internal static readonly Version minimumOsVersion = new Version(GetMinimumVersionString()); + + public static extern string targetOSVersionString + { + [NativeMethod("GetVisionOSTargetOSVersion")] + get; + [NativeMethod("SetVisionOSTargetOSVersion")] + set; + } + } + } +} diff --git a/Editor/Mono/PlayerSettingsWSA.bindings.cs b/Editor/Mono/PlayerSettingsWSA.bindings.cs index 3daa66e699..2fbefa6be2 100644 --- a/Editor/Mono/PlayerSettingsWSA.bindings.cs +++ b/Editor/Mono/PlayerSettingsWSA.bindings.cs @@ -338,6 +338,16 @@ private static extern Color splashScreenBackgroundColorRaw set; } + [NativeProperty("syncCapabilities", TargetType.Field)] + public static extern bool syncCapabilities + { + [StaticAccessor("GetPlayerSettings().GetEditorOnly()", StaticAccessorType.Dot)] + get; + + [StaticAccessor("GetPlayerSettings().GetEditorOnlyForUpdate()", StaticAccessorType.Dot)] + set; + } + private static extern void InternalSetCapability(string name, string value); private static extern string InternalGetCapability(string name); diff --git a/Editor/Mono/PlayerSettingsWebGL.bindings.cs b/Editor/Mono/PlayerSettingsWebGL.bindings.cs index b5895b6ad6..00f9bad2f9 100644 --- a/Editor/Mono/PlayerSettingsWebGL.bindings.cs +++ b/Editor/Mono/PlayerSettingsWebGL.bindings.cs @@ -52,6 +52,13 @@ public enum WebGLMemoryGrowthMode Geometric = 2 } + public enum WebGLPowerPreference + { + Default = 0, + LowPower = 1, + HighPerformance = 2 + } + public sealed partial class PlayerSettings : UnityEngine.Object { [NativeHeader("Editor/Mono/PlayerSettingsWebGL.bindings.h")] @@ -232,6 +239,13 @@ public extern static int memoryGeometricGrowthCap [StaticAccessor("GetPlayerSettings().GetEditorOnly()", StaticAccessorType.Dot)] get; [StaticAccessor("GetPlayerSettings().GetEditorOnlyForUpdate()", StaticAccessorType.Dot)] set; } + + [NativeProperty("webGLPowerPreference", TargetType.Field)] + public extern static WebGLPowerPreference powerPreference + { + [StaticAccessor("GetPlayerSettings().GetEditorOnly()", StaticAccessorType.Dot)] get; + [StaticAccessor("GetPlayerSettings().GetEditorOnlyForUpdate()", StaticAccessorType.Dot)] set; + } } } } diff --git a/Editor/Mono/Prefabs/PrefabImporter.bindings.cs b/Editor/Mono/Prefabs/PrefabImporter.bindings.cs index 2b7c1373ba..30c38db065 100644 --- a/Editor/Mono/Prefabs/PrefabImporter.bindings.cs +++ b/Editor/Mono/Prefabs/PrefabImporter.bindings.cs @@ -11,6 +11,7 @@ namespace UnityEditor { [NativeHeader("Editor/Src/AssetPipeline/PrefabImporter.h")] [ExcludeFromPreset] + [HelpURL("https://docs.unity3d.com/2022.3/Documentation/Manual/Prefabs.html")] internal class PrefabImporter : AssetImporter { } diff --git a/Editor/Mono/Prefabs/PrefabImporterEditor.cs b/Editor/Mono/Prefabs/PrefabImporterEditor.cs index 7919ffb1ab..1156f63c28 100644 --- a/Editor/Mono/Prefabs/PrefabImporterEditor.cs +++ b/Editor/Mono/Prefabs/PrefabImporterEditor.cs @@ -2,10 +2,11 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License -using UnityEngine; +using System.Collections.Generic; using UnityEditor.AssetImporters; using UnityEditor.SceneManagement; -using System.Collections.Generic; +using UnityEditor.UIElements; +using UnityEngine; namespace UnityEditor { @@ -40,8 +41,9 @@ struct TrackedAsset public Hash128 hash; } - List m_TempComponentsResults = new List(); - List m_DirtyPrefabAssets = new List(); + readonly List m_TempComponentsResults = new List(); + readonly List m_DirtyPrefabAssets = new List(); + internal bool m_HasPendingChanges; public override bool showImportedObject { get { return !hasMissingScripts; } } @@ -53,7 +55,7 @@ bool isTextFieldCaretShowing get { return EditorGUI.IsEditingTextField() && !EditorGUIUtility.textFieldHasSelection; } } - bool readyToAutoSave + internal bool readyToAutoSave { get { return !m_SavingHasFailed && !hasMissingScripts && GUIUtility.hotControl == 0 && !isTextFieldCaretShowing && !EditorApplication.isCompiling; } } @@ -66,15 +68,61 @@ bool hasMissingScripts public override void OnEnable() { base.OnEnable(); - EditorApplication.update += Update; + ObjectChangeEvents.changesPublished += ObjectChangeEventPublished; } public override void OnDisable() { - EditorApplication.update -= Update; + if (m_HasPendingChanges) + { + EditorApplication.update -= WaitToApplyChanges; + m_HasPendingChanges = false; + SaveDirtyPrefabAssets(false); + } + + ObjectChangeEvents.changesPublished -= ObjectChangeEventPublished; base.OnDisable(); } + private void ObjectChangeEventPublished(ref ObjectChangeEventStream stream) + { + if (m_HasPendingChanges) + return; + + for (int i = 0; i < stream.length; ++i) + { + int instanceId = 0; + if (stream.GetEventType(i) == ObjectChangeKind.ChangeGameObjectOrComponentProperties) + { + stream.GetChangeGameObjectOrComponentPropertiesEvent(i, out var changeGameObjectOrComponent); + instanceId = changeGameObjectOrComponent.instanceId; + } + else if (stream.GetEventType(i) == ObjectChangeKind.ChangeGameObjectStructure) + { + stream.GetChangeGameObjectStructureEvent(i, out var changeGameObject); + instanceId = changeGameObject.instanceId; + } + + if (instanceId == 0) + continue; + + var asset = EditorUtility.InstanceIDToObject(instanceId); + if (IsTargetAsset(asset)) + { + if (!EditorFocusMonitor.AreBindableElementsSelected() && readyToAutoSave) + { + SaveDirtyPrefabAssets(true); + } + else + { + m_HasPendingChanges = true; + EditorApplication.update += WaitToApplyChanges; + } + return; + } + } + } + protected override void Awake() { base.Awake(); @@ -102,14 +150,14 @@ void OnDestroy() SaveDirtyPrefabAssets(false); } - void Update() + void WaitToApplyChanges() { var time = EditorApplication.timeSinceStartup; if (time > m_NextUpdate) { m_NextUpdate = time + 0.2; - if (readyToAutoSave && HasDirtyPrefabAssets()) + if (!EditorFocusMonitor.AreBindableElementsSelected() && readyToAutoSave) SaveDirtyPrefabAssets(true); } } @@ -190,22 +238,31 @@ internal void SaveDirtyPrefabAssets(bool reloadInspectors) } } } + + // We reset the flag after the changes have been applied in order to avoid a new change being detected from the save operation. + if (m_HasPendingChanges) + { + EditorApplication.update -= WaitToApplyChanges; + m_HasPendingChanges = false; + } } } } - internal bool HasDirtyPrefabAssets() + bool IsTargetAsset(Object obj) { - if (assetTarget == null) + if (assetTargets == null) return false; - if (typeof(GameObject) != assetTarget.GetType()) - return false; + if (obj is Component component) + obj = component.gameObject; - // We just check one target since we assume that a multi-edit will - // always edit that target. So no need to spend resources on checking - // all targets in multiselection. - return IsDirty((GameObject)assetTarget); + foreach (var asset in assetTargets) + { + if (obj == asset) + return true; + } + return false; } bool IsDirty(GameObject prefabAssetRoot) diff --git a/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesTreeView.cs b/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesTreeView.cs index d7613b32bf..684438c531 100644 --- a/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesTreeView.cs +++ b/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesTreeView.cs @@ -7,8 +7,10 @@ using System.Collections.Generic; using UnityEditor.IMGUI.Controls; using UnityEditor.SceneManagement; +using UnityEditor.UIElements; +using UnityEngine.UIElements; using Object = UnityEngine.Object; - +using TreeView = UnityEditor.IMGUI.Controls.TreeView; namespace UnityEditor { @@ -613,23 +615,22 @@ class ComparisonViewPopup : PopupWindowContent readonly PrefabOverride m_Modification; readonly Object m_Source; readonly Object m_Instance; - readonly Editor m_SourceEditor; - readonly Editor m_InstanceEditor; readonly bool m_Unappliable; - const float k_HeaderHeight = 25f; - const float k_ScrollbarWidth = 13; - Vector2 m_PreviewSize = new Vector2(600f, 0); - Vector2 m_Scroll; - bool m_RenderOverlayAfterResizeChange; bool m_OwnerNeedsRefresh; static class Styles { - public static GUIStyle borderStyle = new GUIStyle("grey_border"); - public static GUIStyle centeredLabelStyle = new GUIStyle(EditorStyles.label); + public const string ussPath = "StyleSheets/Prefab/ComparisonViewPopup.uss"; + public const string rootClass = "unity-prefab-compare__root"; + public const string dualViewClass = "unity-prefab-compare-dual"; + public const string headerClass = "unity-prefab-compare__header"; + public const string cellClass = "unity-prefab-compare__cell"; + public const string contentClass = "unity-prefab-compare__content"; + public const string rightClass = "unity-prefab-compare__right"; + public const string headerButtonClass = "unity-prefab-compare__header-buttons"; + public static GUIStyle headerGroupStyle = new GUIStyle(); - public static GUIStyle headerStyle = new GUIStyle(EditorStyles.boldLabel); public static GUIContent sourceContent = EditorGUIUtility.TrTextContent("Prefab Source"); public static GUIContent instanceContent = EditorGUIUtility.TrTextContent("Override"); public static GUIContent removedContent = EditorGUIUtility.TrTextContent("Removed"); @@ -640,14 +641,7 @@ static class Styles static Styles() { - centeredLabelStyle.alignment = TextAnchor.UpperCenter; - centeredLabelStyle.padding = new RectOffset(3, 3, 3, 3); - headerGroupStyle.padding = new RectOffset(0, 0, 3, 3); - - headerStyle.alignment = TextAnchor.MiddleLeft; - headerStyle.padding.left = 5; - headerStyle.padding.top = 1; } } @@ -658,25 +652,9 @@ public ComparisonViewPopup(Object source, Object instance, PrefabOverride modifi m_Instance = instance; m_Modification = modification; if (modification != null) - { m_Unappliable = !PrefabUtility.IsPartOfPrefabThatCanBeAppliedTo(modification.GetAssetObject()); - } else - { m_Unappliable = false; - } - - if (m_Source != null) - { - m_SourceEditor = Editor.CreateEditor(m_Source); - } - if (m_Instance != null) - { - m_InstanceEditor = Editor.CreateEditor(m_Instance); - } - - if (m_Source == null || m_Instance == null || m_Modification == null) - m_PreviewSize.x /= 2; if (modification is ObjectOverride) Undo.postprocessModifications += RecheckOverrideStatus; @@ -686,12 +664,7 @@ public override void OnClose() { Undo.postprocessModifications -= RecheckOverrideStatus; m_Owner.ComparisonPopupClosed(m_Instance, m_OwnerNeedsRefresh); - base.OnClose(); - if (m_SourceEditor != null) - Object.DestroyImmediate(m_SourceEditor); - if (m_InstanceEditor != null) - Object.DestroyImmediate(m_InstanceEditor); } UndoPropertyModification[] RecheckOverrideStatus(UndoPropertyModification[] modifications) @@ -714,125 +687,118 @@ void UpdateAndCloseOnNextTick() UpdateAndClose(); } - bool UpdatePreviewHeight(float height) + internal override VisualElement CreateGUI() { - if (height > 0 && m_PreviewSize.y != height) - { - m_PreviewSize.y = height; - return true; - } - return false; - } + var root = new VisualElement(); + root.AddStyleSheetPath(Styles.ussPath); + root.AddToClassList(Styles.rootClass); + if (m_Modification != null && m_Source != null && m_Instance != null) + root.AddToClassList(Styles.dualViewClass); - public override void OnGUI(Rect rect) - { - bool scroll = (m_PreviewSize.y > rect.height - k_HeaderHeight); - if (scroll) - rect.width -= k_ScrollbarWidth + 1; - else - // We overdraw border by one pixel to the right, so subtract here to account for that. - rect.width -= 1; + root.Add(CreateHeader()); + + if (m_Modification != null) + root.Add(CreateComparisonView()); - EditorGUIUtility.wideMode = true; - EditorGUIUtility.labelWidth = 120; - int middleCol = Mathf.RoundToInt((rect.width - 1) * 0.5f); + return root; + } - if (Event.current.type == EventType.Repaint) - EditorStyles.viewBackground.Draw(rect, GUIContent.none, 0); + VisualElement CreateHeader() + { + var container = new VisualElement(); + container.AddToClassList(Styles.headerClass); if (m_Modification == null) { - DrawHeader( - new Rect(rect.x, rect.y, rect.width, k_HeaderHeight), - Styles.noModificationsContent); - m_PreviewSize.y = 0; - return; + container.Add(CreateHeaderCell(Styles.noModificationsContent.text)); + return container; } - Rect scrollRectPosition = - new Rect( - rect.x, - rect.y + k_HeaderHeight, - rect.width + (scroll ? k_ScrollbarWidth : 0), - rect.height - k_HeaderHeight); - Rect viewPosition = new Rect(0, 0, rect.width, m_PreviewSize.y); - if (m_Source != null && m_Instance != null) { - Rect sourceHeaderRect = new Rect(rect.x, rect.y, middleCol, k_HeaderHeight); - Rect instanceHeaderRect = new Rect(rect.x + middleCol, rect.y, rect.xMax - middleCol + (scroll ? k_ScrollbarWidth : 0), k_HeaderHeight); - DrawHeader(sourceHeaderRect, Styles.sourceContent); - DrawHeader(instanceHeaderRect, Styles.instanceContent); - - DrawRevertApplyButtons(instanceHeaderRect); + var sourceHeader = CreateHeaderCell(Styles.sourceContent.text); + var instanceHeader = CreateHeaderCell(Styles.instanceContent.text); + instanceHeader.AddToClassList(Styles.rightClass); + instanceHeader.Add(CreateHeaderButtons()); - m_Scroll = GUI.BeginScrollView(scrollRectPosition, m_Scroll, viewPosition); - { - var leftColumnHeight = DrawEditor(new Rect(0, 0, middleCol, m_PreviewSize.y), m_SourceEditor, true, EditorGUIUtility.ComparisonViewMode.Original); - - var rightColumnHeight = DrawEditor(new Rect(middleCol, 0, rect.xMax - middleCol, m_PreviewSize.y), m_InstanceEditor, false, EditorGUIUtility.ComparisonViewMode.Modified); - - if (UpdatePreviewHeight(Math.Max(leftColumnHeight, rightColumnHeight))) - m_RenderOverlayAfterResizeChange = true; - } - GUI.EndScrollView(); + container.Add(sourceHeader); + container.Add(instanceHeader); } else { - GUIContent headerContent; - Editor editor; - bool disable; - if (m_Source != null) - { - headerContent = Styles.removedContent; - editor = m_SourceEditor; - disable = true; - } - else - { - headerContent = Styles.addedContent; - editor = m_InstanceEditor; - disable = false; - } + string headerContentText = m_Source != null ? Styles.removedContent.text : Styles.addedContent.text; + var header = CreateHeaderCell(headerContentText); + header.Add(CreateHeaderButtons()); + container.Add(header); + } - Rect headerRect = new Rect(rect.x, rect.y, rect.width, k_HeaderHeight); - DrawHeader(headerRect, headerContent); + return container; + } - DrawRevertApplyButtons(headerRect); + VisualElement CreateHeaderButtons() + { + var container = new IMGUIContainer(DrawRevertApplyButtons); + container.AddToClassList(Styles.headerButtonClass); + return container; + } - m_Scroll = GUI.BeginScrollView(scrollRectPosition, m_Scroll, viewPosition); + VisualElement CreateComparisonView() + { + var comparisonView = new ScrollView + { + mode = ScrollViewMode.Vertical, + verticalScrollerVisibility = ScrollerVisibility.Auto + }; + comparisonView.RegisterCallback(OnObjectChanged); - float columnHeight = DrawEditor(new Rect(0, 0, rect.width, m_PreviewSize.y), editor, disable, EditorGUIUtility.ComparisonViewMode.Modified); - if (UpdatePreviewHeight(columnHeight)) - m_RenderOverlayAfterResizeChange = true; + if (m_Source != null && m_Instance != null) + comparisonView.Add(CreateDualObjectView()); + else + comparisonView.Add(CreateSingleObjectView()); - GUI.EndScrollView(); - } + return comparisonView; + } - if (m_RenderOverlayAfterResizeChange && Event.current.type == EventType.Repaint) + void OnObjectChanged(SerializedObjectChangeEvent obj) + { + m_OwnerNeedsRefresh = true; + } + + VisualElement CreateSingleObjectView() + { + if (m_Source != null) + { + var inspector = CreateObjectInspector(m_Source, EditorGUIUtility.ComparisonViewMode.Original); + inspector.SetEnabled(false); + return inspector; + } + else { - m_RenderOverlayAfterResizeChange = false; - // The comparison view resizes a frame delayed due to having to wait for the first render to - // layout the contents. This creates a distorted rendering because the last frame rendered is rendered - // to the new window size. We therefore 'clear' the comparison view after a resize change by rendering - // a quad on top with the background color so the distorted rendering is not shown to the user. - // Fixes case 1069062. - GUI.Label(rect, GUIContent.none, EditorStyles.viewBackground); - editorWindow.Repaint(); + var inspector = CreateObjectInspector(m_Instance, EditorGUIUtility.ComparisonViewMode.Original); + inspector.SetEnabled(true); + return inspector; } } - void DrawHeader(Rect rect, GUIContent label) + VisualElement CreateDualObjectView() { - EditorGUI.LabelField(rect, label, Styles.headerStyle); - // Overdraw border by one pixel to the right, so adjacent borders overlap. - // Don't overdraw down, since overlapping scroll view can make controls overlap divider line. - GUI.Label(new Rect(rect.x, rect.y, rect.width + 1, rect.height), GUIContent.none, Styles.borderStyle); + var sourceInspector = CreateObjectInspector(m_Source, EditorGUIUtility.ComparisonViewMode.Original); + var instanceInspector = CreateObjectInspector(m_Instance, EditorGUIUtility.ComparisonViewMode.Modified); + + sourceInspector.SetEnabled(false); + sourceInspector.AddToClassList(Styles.cellClass); + instanceInspector.AddToClassList(Styles.cellClass); + instanceInspector.AddToClassList(Styles.rightClass); + + var container = new VisualElement(); + container.AddToClassList(Styles.contentClass); + container.Add(sourceInspector); + container.Add(instanceInspector); + return container; } - void DrawRevertApplyButtons(Rect rect) + void DrawRevertApplyButtons() { - GUILayout.BeginArea(rect); GUILayout.BeginHorizontal(Styles.headerGroupStyle); GUILayout.FlexibleSpace(); @@ -855,7 +821,6 @@ void DrawRevertApplyButtons(Rect rect) } GUILayout.EndHorizontal(); - GUILayout.EndArea(); } void Apply(object prefabAssetPathObject) @@ -875,61 +840,37 @@ void UpdateAndClose() editorWindow?.Close(); } - float DrawEditor(Rect rect, Editor editor, bool disabled, EditorGUIUtility.ComparisonViewMode comparisonViewMode) + static VisualElement CreateObjectInspector(Object obj, EditorGUIUtility.ComparisonViewMode viewMode) { - rect.xMin += 1; - EditorGUIUtility.ResetGUIState(); - - EditorGUIUtility.wideMode = true; - EditorGUIUtility.labelWidth = 120; - EditorGUIUtility.comparisonViewMode = comparisonViewMode; - EditorGUIUtility.leftMarginCoord = rect.x; - - GUILayout.BeginArea(rect); - Rect editorRect = EditorGUILayout.BeginVertical(); - { - using (new EditorGUI.DisabledScope(disabled)) - { - if (editor == null) - { - GUI.enabled = true; - GUILayout.Label("None - this should not happen.", Styles.centeredLabelStyle); - } - else - { - EditorGUI.BeginChangeCheck(); - if (editor.target is GameObject) - { - editor.DrawHeader(); - } - else - { - EditorGUIUtility.hierarchyMode = true; - EditorGUILayout.InspectorTitlebar(true, editor); - EditorGUILayout.BeginVertical(EditorStyles.inspectorDefaultMargins); - editor.OnInspectorGUI(); - EditorGUILayout.Space(); - EditorGUILayout.EndVertical(); - } - - if (EditorGUI.EndChangeCheck()) - m_OwnerNeedsRefresh = true; - } - } - } + var container = new VisualElement(); + var inspector = new InspectorElement(obj) { comparisonViewMode = viewMode }; + if (inspector.boundObject != null) + inspector.TrackSerializedObjectValue(inspector.boundObject); - EditorGUILayout.EndVertical(); - GUILayout.EndArea(); - - // Overdraw border by one pixel in all directions. - GUI.Label(new Rect(rect.x - 1, -1, rect.width + 2, m_PreviewSize.y + 2), GUIContent.none, Styles.borderStyle); + container.Add(new IMGUIContainer(() => DrawObjectHeader(inspector.editor, viewMode))); + container.Add(inspector); + return container; + } - return editorRect.height; + static VisualElement CreateHeaderCell(string label) + { + var headerCell = new VisualElement(); + headerCell.AddToClassList(Styles.cellClass); + headerCell.Add(new Label(label)); + return headerCell; } - public override Vector2 GetWindowSize() + static void DrawObjectHeader(Editor editor, EditorGUIUtility.ComparisonViewMode viewMode) { - return new Vector2(m_PreviewSize.x, m_PreviewSize.y + k_HeaderHeight + 1f); + if (editor == null) return; + if (editor.target is GameObject) + editor.DrawHeader(); + else + { + EditorGUIUtility.comparisonViewMode = viewMode; + EditorGUIUtility.hierarchyMode = true; + EditorGUILayout.InspectorTitlebar(true, editor); + } } } } diff --git a/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesWindow.cs b/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesWindow.cs index fec46e0c33..b4ad8a142f 100644 --- a/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesWindow.cs +++ b/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesWindow.cs @@ -20,6 +20,7 @@ internal class PrefabOverridesWindow : PopupWindowContent RectOffset k_TreeViewPadding = new RectOffset(0, 0, 4, 4); const float k_HeaderHeight = 60f; const float k_ButtonWidth = 120; + const float k_ButtonWidthVariant = 201; const float k_ButtonWidthPadding = 10; //Padding in case the text is long to give the window a bit of margins around the buttons const float k_HeaderLeftMargin = 6; const float k_NoOverridesLabelHeight = 26f; @@ -451,15 +452,12 @@ public override void OnGUI(Rect rect) } else { - using (new EditorGUI.DisabledScope(!m_HasApplicableOverrides)) + if (GUILayout.Button(m_RevertAllContent, GUILayout.Width(m_ButtonWidth))) { - if (GUILayout.Button(m_RevertAllContent, GUILayout.Width(m_ButtonWidth))) + if (RevertAll() && editorWindow != null) { - if (RevertAll() && editorWindow != null) - { - editorWindow.Close(); - GUIUtility.ExitGUI(); - } + editorWindow.Close(); + GUIUtility.ExitGUI(); } } @@ -544,17 +542,7 @@ bool ApplyAll() if (!PrefabUtility.PromptAndCheckoutPrefabIfNeeded(prefabAssetPaths.ToArray(), PrefabUtility.SaveVerb.Apply)) return false; - // Apply sequentially. - AssetDatabase.StartAssetEditing(); - try - { - foreach (var t in m_SelectedGameObjects) - PrefabUtility.ApplyPrefabInstance(t, InteractionMode.UserAction); - } - finally - { - AssetDatabase.StopAssetEditing(); - } + PrefabUtility.ApplyPrefabInstances(m_SelectedGameObjects, InteractionMode.UserAction); EditorUtility.ForceRebuildInspectors(); return true; @@ -630,7 +618,7 @@ void UpdateText(Texture assetIcon, string assetName) if (stage is PrefabStage && PrefabUtility.IsPartOfVariantPrefab(AssetDatabase.LoadAssetAtPath(stage.assetPath))) { applyAllContent = Styles.applyAllToBaseContent; - m_ApplyButtonWidth = GUI.skin.button.CalcSize(applyAllContent).x; + m_ApplyButtonWidth = k_ButtonWidthVariant; applySelectedContent = Styles.applySelectedToBaseContent; } diff --git a/Editor/Mono/Prefabs/PrefabReplaceUtility.cs b/Editor/Mono/Prefabs/PrefabReplaceUtility.cs new file mode 100644 index 0000000000..89989f79ef --- /dev/null +++ b/Editor/Mono/Prefabs/PrefabReplaceUtility.cs @@ -0,0 +1,382 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UnityEditor +{ + static class PrefabReplaceUtility + { + static bool s_CreateOverrides = false; + static PrefabOverridesOptions s_PrefabOverideOptions = PrefabOverridesOptions.KeepAllPossibleOverrides; + + internal static void ReplaceCurrentSelectionWithPrefabUsingObjectPicker(object userdata) + { + var args = ((GameObject, PrefabOverridesOptions, bool))userdata; + var contextClickedGameObject = args.Item1; + var prefabOverrideOptions = args.Item2; + var createOverrides = args.Item3; + + s_CreateOverrides = createOverrides; + s_PrefabOverideOptions = prefabOverrideOptions; + ObjectSelector.get.Show(null, typeof(GameObject), contextClickedGameObject, false, null, null, OnObjectSelectorSelectionUpdated, false); + } + + private static void OnObjectSelectorSelectionUpdated(UnityEngine.Object obj) + { + var selectedPrefabAsset = (GameObject)obj; + if (selectedPrefabAsset == null) + return; + + if (!EditorUtility.IsPersistent(selectedPrefabAsset)) + return; + + var contextClickedGameObject = ObjectSelector.get.objectBeingEdited as GameObject; + List listOfInstanceRoots; + List listOfPlainGameObjects; + FindGameObjectsToReplace(contextClickedGameObject, out listOfPlainGameObjects, out listOfInstanceRoots); + + if (listOfInstanceRoots.Count == 0 && listOfPlainGameObjects.Count == 0) + return; + + if (listOfInstanceRoots.Count > 0) + PrefabUtility.ReplacePrefabAssetOfPrefabInstances(listOfInstanceRoots.ToArray(), selectedPrefabAsset, GetPrefabReplacingSettingsForUI(s_PrefabOverideOptions), InteractionMode.UserAction); + if (listOfPlainGameObjects.Count > 0) + PrefabUtility.ConvertToPrefabInstances(listOfPlainGameObjects.ToArray(), selectedPrefabAsset, GetConvertToPrefabInstanceSettingsForUI(s_CreateOverrides), InteractionMode.UserAction); + + if (contextClickedGameObject != null && !Selection.Contains(contextClickedGameObject)) + Selection.activeGameObject = contextClickedGameObject; + } + + internal static bool HasInstancesAnyOverrides(List listOfInstanceRoots) + { + foreach (var root in listOfInstanceRoots) + if (PrefabUtility.HasPrefabInstanceAnyOverrides(root, false)) + return true; + + return false; + } + + internal static bool AddReplaceMenuItemsToMenuBasedOnCurrentSelection(GenericMenu menu, string parentMenuItemPath, GameObject contextGameObject, List listOfInstanceRoots, List listOfPlainGameObjects, GameObject prefabAsset) + { + if (listOfInstanceRoots.Count == 0 && listOfPlainGameObjects.Count == 0) + return false; + + string GetMenuItemText(string text, string parentMenuItemPath, bool needsObjectSelector) + { + return string.Format("{0}{1}{2}", parentMenuItemPath, text, (needsObjectSelector ? "..." : "")); + } + + bool needObjectSelector = prefabAsset == null; + var replaceText = L10n.Tr("Replace"); + var replaceAndKeepOverridesText = L10n.Tr("Replace and Keep Overrides"); + var reconnectPrefabText = L10n.Tr("Reconnect Prefab"); + var replaceAndReconnectPrefabText = L10n.Tr("Replace and Reconnect Prefab"); + + if (listOfInstanceRoots.Count > 0) + { + var hasAnyOverrides = HasInstancesAnyOverrides(listOfInstanceRoots); + + if (hasAnyOverrides) + { + var text1 = new GUIContent(GetMenuItemText(replaceText, parentMenuItemPath, needObjectSelector)); + var text2 = new GUIContent(GetMenuItemText(replaceAndKeepOverridesText, parentMenuItemPath, needObjectSelector)); + + if (needObjectSelector) + { + menu.AddItem(text1, false, ReplaceCurrentSelectionWithPrefabUsingObjectPicker, (contextGameObject, PrefabOverridesOptions.ClearAllNonDefaultOverrides, true)); + menu.AddItem(text2, false, ReplaceCurrentSelectionWithPrefabUsingObjectPicker, (contextGameObject, PrefabOverridesOptions.KeepAllPossibleOverrides, false)); + } + else + { + menu.AddItem(text1, false, MenuActionForConvertAndReplacePrefabInstance, (contextGameObject, listOfInstanceRoots, PrefabOverridesOptions.ClearAllNonDefaultOverrides, listOfPlainGameObjects, true, prefabAsset)); + menu.AddItem(text2, false, MenuActionForConvertAndReplacePrefabInstance, (contextGameObject, listOfInstanceRoots, PrefabOverridesOptions.KeepAllPossibleOverrides, listOfPlainGameObjects, false, prefabAsset)); + } + } + else // no overrides on instances + { + var text1 = new GUIContent(GetMenuItemText(replaceText, parentMenuItemPath, needObjectSelector)); + GUIContent text2 = null; + if (listOfPlainGameObjects.Count > 0) + text2 = new GUIContent(GetMenuItemText(replaceAndReconnectPrefabText, parentMenuItemPath, needObjectSelector)); + else + text2 = new GUIContent(GetMenuItemText(replaceAndKeepOverridesText, parentMenuItemPath, needObjectSelector)); + + if (needObjectSelector) + { + menu.AddItem(text1, false, ReplaceCurrentSelectionWithPrefabUsingObjectPicker, (contextGameObject, PrefabOverridesOptions.KeepAllPossibleOverrides, false)); + if (listOfPlainGameObjects.Count > 0) + menu.AddItem(text2, false, ReplaceCurrentSelectionWithPrefabUsingObjectPicker, (contextGameObject, PrefabOverridesOptions.ClearAllNonDefaultOverrides, true)); + else + menu.AddDisabledItem(text2, false); + } + else + { + menu.AddItem(text1, false, MenuActionForConvertAndReplacePrefabInstance, (contextGameObject, listOfInstanceRoots, PrefabOverridesOptions.KeepAllPossibleOverrides, listOfPlainGameObjects, false, prefabAsset)); + if (listOfPlainGameObjects.Count > 0) + menu.AddItem(text2, false, MenuActionForConvertAndReplacePrefabInstance, (contextGameObject, listOfInstanceRoots, PrefabOverridesOptions.ClearAllNonDefaultOverrides, listOfPlainGameObjects, true, prefabAsset)); + else + menu.AddDisabledItem(text2, false); + } + } + } + else if (listOfPlainGameObjects.Count > 0) + { + // Only plain gameobjects + var text1 = new GUIContent(GetMenuItemText(replaceText, parentMenuItemPath, needObjectSelector)); + var text2 = new GUIContent(GetMenuItemText(reconnectPrefabText, parentMenuItemPath, needObjectSelector)); + + if (needObjectSelector) + { + menu.AddItem(text1, false, ReplaceCurrentSelectionWithPrefabUsingObjectPicker, (contextGameObject, PrefabOverridesOptions.KeepAllPossibleOverrides, false)); + menu.AddItem(text2, false, ReplaceCurrentSelectionWithPrefabUsingObjectPicker, (contextGameObject, PrefabOverridesOptions.ClearAllNonDefaultOverrides, true)); + } + else + { + menu.AddItem(text1, false, MenuActionForConvertAndReplacePrefabInstance, (contextGameObject, listOfInstanceRoots, PrefabOverridesOptions.KeepAllPossibleOverrides, listOfPlainGameObjects, false, prefabAsset)); + menu.AddItem(text2, false, MenuActionForConvertAndReplacePrefabInstance, (contextGameObject, listOfInstanceRoots, PrefabOverridesOptions.ClearAllNonDefaultOverrides, listOfPlainGameObjects, true, prefabAsset)); + } + } + + return true; + } + + // Returns true if this this function uses the event and propagation of the drag should be stopped after this + internal static bool GetDragVisualModeAndShowMenuWithReplaceMenuItemsWhenNeeded(GameObject droppedUponGameObject, bool draggingUpon, bool perform, bool addAsChildMenuItem, bool dragInHierarchy, out DragAndDropVisualMode cursorVisualMode) + { + cursorVisualMode = DragAndDropVisualMode.None; + + GameObject prefabAsset = TryGetSinglePrefabAssetFromDraggedObjects(); + if (prefabAsset == null) + return false; + + if (Event.current == null) + return false; + + if (!dragInHierarchy || EditorGUI.actionKey) + { + if (!draggingUpon) + { + cursorVisualMode = DragAndDropVisualMode.Rejected; + return true; + } + + List listOfInstanceRoots; + List listOfPlainGameObjects; + FindGameObjectsToReplace(droppedUponGameObject, out listOfPlainGameObjects, out listOfInstanceRoots); + + if (listOfInstanceRoots.Count == 0 && listOfPlainGameObjects.Count == 0) + { + cursorVisualMode = DragAndDropVisualMode.Rejected; + return true; + } + + cursorVisualMode = DragAndDropVisualMode.Link; + + if (perform) + { + if (!dragInHierarchy && listOfInstanceRoots.Count > 0 && listOfPlainGameObjects.Count == 0 && !HasInstancesAnyOverrides(listOfInstanceRoots)) + { + return false; // don't show the drag menu when we know we will only have one menuitem over the objectfield in the Inspector + } + + Event.current.Use(); + + var menu = new GenericMenu(); + + AddReplaceMenuItemsToMenuBasedOnCurrentSelection(menu, "", droppedUponGameObject, listOfInstanceRoots, listOfPlainGameObjects, prefabAsset); + if (addAsChildMenuItem) + { + menu.AddSeparator(""); + menu.AddItem(EditorGUIUtility.TrTextContent("Add as Child"), false, MenuActionForInstantiateDraggedPrefabAsChild, (droppedUponGameObject, prefabAsset)); + } + menu.AddSeparator(""); + menu.AddItem(EditorGUIUtility.TrTextContent("Cancel"), false, () => {/*nop*/ }); + menu.ShowAsContext(); + } + return true; + } + return false; + } + + internal static GameObject TryGetSinglePrefabAssetFromDraggedObjects() + { + var draggedObjects = DragAndDrop.objectReferences; + GameObject prefabAsset = null; + foreach (var draggedObject in draggedObjects) + { + if (draggedObject is GameObject && EditorUtility.IsPersistent(draggedObject)) + { + if (prefabAsset == null) + prefabAsset = (GameObject)draggedObject; + else + return null; + } + } + return prefabAsset; + } + + internal static void FindGameObjectsToReplace(GameObject droppedUponGameObject, out List listOfPlainGameObjects, out List listOfInstanceRoots) + { + listOfInstanceRoots = new List(); + listOfPlainGameObjects = new List(); + + // Dropping on single + if (droppedUponGameObject != null && !Selection.gameObjects.Contains(droppedUponGameObject)) + { + if (PrefabUtility.IsOutermostPrefabInstanceRoot(droppedUponGameObject)) + listOfInstanceRoots.Add(droppedUponGameObject); + else if (!PrefabUtility.IsPartOfAnyPrefab(droppedUponGameObject)) + listOfPlainGameObjects.Add(droppedUponGameObject); + + return; + } + + // Dropping on selection: need to find the common roots + var selectedGameObjects = Selection.GetFiltered(SelectionMode.TopLevel | SelectionMode.ExcludePrefab | SelectionMode.Editable); + foreach (var go in selectedGameObjects) + { + if (PrefabUtility.IsOutermostPrefabInstanceRoot(go)) + listOfInstanceRoots.Add(go); + else if (!PrefabUtility.IsPartOfNonAssetPrefabInstance(go)) + listOfPlainGameObjects.Add(go); + } + } + + static void MenuActionForConvertAndReplacePrefabInstance(object userData) + { + var args = ((GameObject, List, PrefabOverridesOptions, List, bool, GameObject))userData; + var contextGameObject = args.Item1; + var droppedOnInstances = args.Item2; + var overrideOptionsOnReplace = args.Item3; + var droppedOnGameObjects = args.Item4; + var createOverridesOnConvert = args.Item5; + var prefabAssetRoot = args.Item6; + + Undo.IncrementCurrentGroup(); + + if (droppedOnInstances.Count > 0) + PrefabUtility.ReplacePrefabAssetOfPrefabInstances(droppedOnInstances.ToArray(), prefabAssetRoot, GetPrefabReplacingSettingsForUI(overrideOptionsOnReplace), InteractionMode.UserAction); + if (droppedOnGameObjects.Count > 0) + PrefabUtility.ConvertToPrefabInstances(droppedOnGameObjects.ToArray(), prefabAssetRoot, GetConvertToPrefabInstanceSettingsForUI(createOverridesOnConvert), InteractionMode.UserAction); + + if (contextGameObject != null && !Selection.Contains(contextGameObject)) + Selection.activeGameObject = contextGameObject; + + Undo.IncrementCurrentGroup(); + } + + static void MenuActionForInstantiateDraggedPrefabAsChild(object userData) + { + var args = ((GameObject, GameObject))userData; + var droppedUponGameObject = args.Item1; + var prefabAssetRoot = args.Item2; + + Undo.IncrementCurrentGroup(); + + var childInstanceIDs = GetChildrenInstanceIDs(droppedUponGameObject); + PrefabUtility.InstantiateDraggedPrefabUpon(droppedUponGameObject, prefabAssetRoot); + var newChildren = GetNewChildren(droppedUponGameObject, childInstanceIDs); + if (newChildren != null && newChildren.Count > 0) + Selection.instanceIDs = newChildren.ToArray(); + + Undo.IncrementCurrentGroup(); + } + + static HashSet GetChildrenInstanceIDs(GameObject parent) + { + var childInstanceIDs = new HashSet(); + var transform = parent.transform; + var childCount = transform.childCount; + for (int i = 0; i < childCount; i++) + { + var childInstanceID = transform.GetChild(i).gameObject.GetInstanceID(); + childInstanceIDs.Add(childInstanceID); + } + return childInstanceIDs; + } + + static List GetNewChildren(GameObject parent, HashSet oldChildren) + { + var newChildren = new List(); + var transform = parent.transform; + var childCount = transform.childCount; + for (int i = 0; i < childCount; i++) + { + var childInstanceID = transform.GetChild(i).gameObject.GetInstanceID(); + if (!oldChildren.Contains(childInstanceID)) + newChildren.Add(childInstanceID); + } + return newChildren; + } + + public static ConvertToPrefabInstanceSettings GetConvertToPrefabInstanceSettingsForUI(bool createOverrides) + { + return new ConvertToPrefabInstanceSettings + { + objectMatchMode = ObjectMatchMode.ByHierarchy, + componentsNotMatchedBecomesOverride = createOverrides, + gameObjectsNotMatchedBecomesOverride = createOverrides, + recordPropertyOverridesOfMatches = createOverrides, + changeRootNameToAssetName = true, + logInfo = false + }; + } + + public static PrefabReplacingSettings GetPrefabReplacingSettingsForUI(PrefabOverridesOptions prefabOverridesOptions) + { + return new PrefabReplacingSettings + { + objectMatchMode = ObjectMatchMode.ByHierarchy, + prefabOverridesOptions = prefabOverridesOptions, + changeRootNameToAssetName = true, + logInfo = false + }; + } + + } + + internal static class PrefabReplaceSettingsUserPreferences + { + static SavedInt m_ObjectMatchMode = new SavedInt("PrefabReplace.ObjectMatchMode", (int)ObjectMatchMode.ByHierarchy); + static SavedBool m_GameObjectsNotMatchedBecomesOverride = new SavedBool("PrefabReplace.GameObjectsNotMatchedBecomesOverride", true); + static SavedBool m_ComponentsNotMatchedBecomesOverride = new SavedBool("PrefabReplace.ComponentsNotMatchedBecomesOverride", true); + static SavedInt m_ClearOverridesOptions = new SavedInt("PrefabReplace.ClearOverridesOptions", (int)PrefabOverridesOptions.KeepAllPossibleOverrides); + static SavedBool m_UseAssetName = new SavedBool("PrefabReplace.UseAssetName", true); + static SavedBool m_LogInfo = new SavedBool("PrefabReplace.LogInfo", false); + + public static ObjectMatchMode objectMatchMode { get { return (ObjectMatchMode)m_ObjectMatchMode.value; } set { m_ObjectMatchMode.value = Convert.ToInt32(value); } } + public static PrefabOverridesOptions prefabOverridesOptions { get { return (PrefabOverridesOptions)m_ClearOverridesOptions.value; } set { m_ClearOverridesOptions.value = Convert.ToInt32(value); } } + + public static bool gameObjectsNotMatchedBecomesOverride { get { return m_GameObjectsNotMatchedBecomesOverride.value; } set { m_GameObjectsNotMatchedBecomesOverride.value = value; } } + public static bool componentsNotMatchedBecomesOverride { get { return m_ComponentsNotMatchedBecomesOverride.value; } set { m_ComponentsNotMatchedBecomesOverride.value = value; } } + public static bool useAssetName { get { return m_UseAssetName.value; } set { m_UseAssetName.value = value; } } + public static bool logInfo { get { return m_LogInfo.value; } set { m_LogInfo.value = value; } } + + public static ConvertToPrefabInstanceSettings GetConvertToPrefabInstancePreferences() + { + return new ConvertToPrefabInstanceSettings + { + objectMatchMode = objectMatchMode, + componentsNotMatchedBecomesOverride = componentsNotMatchedBecomesOverride, + gameObjectsNotMatchedBecomesOverride = gameObjectsNotMatchedBecomesOverride, + changeRootNameToAssetName = useAssetName, + logInfo = logInfo + }; + } + + public static PrefabReplacingSettings GetPrefabReplacingPreferences() + { + return new PrefabReplacingSettings + { + objectMatchMode = objectMatchMode, + prefabOverridesOptions = prefabOverridesOptions, + logInfo = logInfo + }; + } + } +} diff --git a/Editor/Mono/Prefabs/PrefabUtility.bindings.cs b/Editor/Mono/Prefabs/PrefabUtility.bindings.cs index 9cf715b8e7..d30b6f57a2 100644 --- a/Editor/Mono/Prefabs/PrefabUtility.bindings.cs +++ b/Editor/Mono/Prefabs/PrefabUtility.bindings.cs @@ -20,6 +20,12 @@ namespace UnityEditor [NativeHeader("Editor/Mono/Prefabs/PrefabUtility.bindings.h")] public sealed partial class PrefabUtility { + [StaticAccessor("PrefabInstance", StaticAccessorType.DoubleColon)] + extern internal static int defaultOverridesCount { get; } + + [StaticAccessor("PrefabInstance", StaticAccessorType.DoubleColon)] + extern internal static int defaultOverridesCountUsingRectTransform { get; } + // Returns the corresponding GameObject/Component from /source/, or null if it can't be found. [StaticAccessor("PrefabUtilityBindings", StaticAccessorType.DoubleColon)] extern private static Object GetCorrespondingObjectFromSource_internal([NotNull] Object obj); @@ -103,9 +109,8 @@ public sealed partial class PrefabUtility extern public static bool HasPrefabInstanceAnyOverrides(GameObject instanceRoot, bool includeDefaultOverrides); // Instantiate an asset that is referenced by a prefab and use it on the prefab instance. - [FreeFunction] - [NativeHeader("Editor/Src/Prefabs/AttachedPrefabAsset.h")] - extern public static Object InstantiateAttachedAsset([NotNull("NullExceptionObject")] Object targetObject); + [Obsolete("InstantiateAttachedAsset is deprecated")] + public static Object InstantiateAttachedAsset(Object targetObject) { return null; } // Force record property modifications by comparing against the parent prefab. [FreeFunction] @@ -143,13 +148,32 @@ public sealed partial class PrefabUtility [StaticAccessor("PrefabUtilityBindings", StaticAccessorType.DoubleColon)] [NativeThrows] - extern public static void LoadPrefabContentsIntoPreviewScene(string prefabPath, Scene scene); + extern static private bool ConvertToPrefabInstance_Internal([NotNull] GameObject plainGameObject, [NotNull] GameObject assetRootRoot, ConvertToPrefabInstanceSettings settings); - // Connect the source prefab to the game object, which replaces the instance content with the content of the prefab - [Obsolete("Use RevertPrefabInstance. Prefabs instances can no longer be connected to Prefab Assets they are not an instance of to begin with.")] [StaticAccessor("PrefabUtilityBindings", StaticAccessorType.DoubleColon)] [NativeThrows] - extern public static GameObject ConnectGameObjectToPrefab([NotNull] GameObject go, [NotNull] GameObject sourcePrefab); + extern static private bool InstantiateDraggedPrefabUpon_Internal([NotNull] GameObject draggedUponGameObject, [NotNull] GameObject assetRootRoot); + + [StaticAccessor("PrefabUtilityBindings", StaticAccessorType.DoubleColon)] + [NativeThrows] + extern public static void LoadPrefabContentsIntoPreviewScene(string prefabPath, Scene scene); + + [Obsolete("Use ConvertToPrefabInstance() or ReplacePrefabAssetOfPrefabInstance() which has settings for better control.")] + public static GameObject ConnectGameObjectToPrefab(GameObject go, GameObject sourcePrefab) + { + if (GetPrefabInstanceStatus(go) == PrefabInstanceStatus.NotAPrefab) + { + var settings = new ConvertToPrefabInstanceSettings(); + ConvertToPrefabInstance(go, sourcePrefab, settings, InteractionMode.AutomatedAction); + } + else if (IsOutermostPrefabInstanceRoot(go)) + { + var settings = new PrefabReplacingSettings(); + ReplacePrefabAssetOfPrefabInstance(go, sourcePrefab, settings, InteractionMode.AutomatedAction); + } + + return go; + } // Returns the topmost game object that has the same prefab parent as /target/ [FreeFunction] @@ -320,6 +344,9 @@ internal static void AddGameObjectsToPrefabAndConnect(GameObject[] gameObjects, [FreeFunction] extern public static bool IsPartOfAnyPrefab([NotNull] Object componentOrGameObject); + [FreeFunction] + extern internal static void SetHasSubscribersToAllowRecordingPrefabPropertyOverrides(bool hasSubscribers); + // Returns true if the object is an asset, // does not matter if the asset is a regular prefab, a variant or Model // Is false for all non-persistent objects diff --git a/Editor/Mono/Prefabs/PrefabUtility.cs b/Editor/Mono/Prefabs/PrefabUtility.cs index b533636363..7f0b0b1c99 100644 --- a/Editor/Mono/Prefabs/PrefabUtility.cs +++ b/Editor/Mono/Prefabs/PrefabUtility.cs @@ -14,7 +14,7 @@ using Object = UnityEngine.Object; using RequiredByNativeCodeAttribute = UnityEngine.Scripting.RequiredByNativeCodeAttribute; using UnityEngine.Bindings; - +using UnityEngine.Assertions; namespace UnityEditor { @@ -84,6 +84,7 @@ public enum ObjectMatchMode { NoMatchingPerformed = 0, ByName = 1, + ByHierarchy = 2 } [Flags] @@ -105,6 +106,19 @@ public class PrefabReplacingSettings { public ObjectMatchMode objectMatchMode { get; set; } = ObjectMatchMode.ByName; public PrefabOverridesOptions prefabOverridesOptions { get; set; } = PrefabOverridesOptions.KeepAllPossibleOverrides; + public bool changeRootNameToAssetName { get; set; } = true; + public bool logInfo { get; set; } = false; + } + + [StructLayout(LayoutKind.Sequential)] + [NativeAsStruct] + public class ConvertToPrefabInstanceSettings + { + public ObjectMatchMode objectMatchMode { get; set; } = ObjectMatchMode.ByHierarchy; + public bool componentsNotMatchedBecomesOverride { get; set; } = false; + public bool gameObjectsNotMatchedBecomesOverride { get; set; } = false; + public bool recordPropertyOverridesOfMatches { get; set; } = false; + public bool changeRootNameToAssetName { get; set; } = true; public bool logInfo { get; set; } = false; } @@ -270,6 +284,11 @@ public static void RevertPrefabInstance(GameObject instanceRoot, InteractionMode } public static void ApplyPrefabInstance(GameObject instanceRoot, InteractionMode action) + { + ApplyPrefabInstance(instanceRoot, action, undoFlushTrackedObjects:true); + } + + static void ApplyPrefabInstance(GameObject instanceRoot, InteractionMode action, bool undoFlushTrackedObjects) { DateTime startTime = DateTime.UtcNow; @@ -288,9 +307,9 @@ public static void ApplyPrefabInstance(GameObject instanceRoot, InteractionMode Undo.RegisterFullObjectHierarchyUndo(prefabInstanceRoot, actionName); } - PrefabUtility.ApplyPrefabInstance(prefabInstanceRoot); + ApplyPrefabInstance(prefabInstanceRoot); - if (action == InteractionMode.UserAction) + if (undoFlushTrackedObjects && action == InteractionMode.UserAction) Undo.FlushTrackedObjects(); } @@ -304,6 +323,34 @@ public static void ApplyPrefabInstance(GameObject instanceRoot, InteractionMode ); } + public static void ApplyPrefabInstances(GameObject[] instanceRoots, InteractionMode action) + { + if (instanceRoots == null) + throw new ArgumentNullException(nameof(instanceRoots)); + + foreach (var instanceRoot in instanceRoots) + { + ThrowExceptionIfNotValidPrefabInstanceObject(instanceRoot, true); + } + + // Apply sequentially but import after all input have been saved to disk + AssetDatabase.StartAssetEditing(); + try + { + foreach (var instanceRoot in instanceRoots) + { + ApplyPrefabInstance(instanceRoot, action, undoFlushTrackedObjects:false); + } + } + finally + { + AssetDatabase.StopAssetEditing(); + } + + if (action == InteractionMode.UserAction) + Undo.FlushTrackedObjects(); // Needs to be called after StopAssetEditing() to fix UUM-6917 + } + private static void MapObjectReferencePropertyToSourceIfApplicable(SerializedProperty property, Object prefabSourceObject) { var referencedObject = property.objectReferenceValue; @@ -406,6 +453,60 @@ static void ApplyPropertyOverrides(Object prefabInstanceObject, SerializedProper var serializedObjects = new List(); var changedObjects = new HashSet(); + HandleApplySingleProperties(prefabInstanceObject, optionalSingleInstanceProperty, assetPath, prefabSourceObject, prefabSourceSerializedObject, serializedObjects, changedObjects, allowApplyDefaultOverride, action); + + // Ensure importing of saved Prefab Assets only kicks in after all Prefab Asset have been saved + AssetDatabase.StartAssetEditing(); + + try + { + Action saveIfChanged = (SerializedObject serializedObject) => + { + if (changedObjects.Contains(serializedObject)) + { + bool applySuccess = action == InteractionMode.UserAction ? serializedObject.ApplyModifiedProperties() : serializedObject.ApplyModifiedPropertiesWithoutUndo(); + if (applySuccess) + { + SaveChangesToPrefabFileIfPersistent(serializedObject); + + if (action == InteractionMode.UserAction) + { + Undo.FlushUndoRecordObjects(); // flush'es ensure that SavePrefab() on undo/redo on the source happens in the right order + } + } + } + }; + + // Case 1292519 + // The Prefab to which the changes are applied is added first in serializedObjects by ApplySingleProperty - it must be saved first + // The rest of the objects are collected in reverse dependency order in ApplySingleProperty - starting from the instance where the changes are made down to the original Prefab + // Apply the changes and save them in dependency order (reversed array order) to make sure dependent values are saved after the values they depend upon + if (serializedObjects.Count > 0) + { + saveIfChanged(serializedObjects[0]); + for (int i = serializedObjects.Count - 1; i > 0; i--) + { + saveIfChanged(serializedObjects[i]); + } + } + } + finally + { + AssetDatabase.StopAssetEditing(); + } + } + + static void HandleApplySingleProperties( + Object prefabInstanceObject, + SerializedProperty optionalSingleInstanceProperty, + string assetPath, Object prefabSourceObject, + SerializedObject prefabSourceSerializedObject, + List serializedObjects, + HashSet changedObjects, + bool allowApplyDefaultOverride, + InteractionMode action + ) + { bool isObjectOnRootInAsset = IsObjectOnRootInAsset(prefabInstanceObject, assetPath); SerializedProperty property = null; @@ -437,14 +538,29 @@ static void ApplyPropertyOverrides(Object prefabInstanceObject, SerializedProper property = so.GetIterator(); } + bool allowWarnAboutApplyingPartsOfManagedReferences = property.isReferencingAManagedReferenceField; + if (!property.hasVisibleChildren) { if (property.prefabOverride) - ApplySingleProperty(property, prefabSourceSerializedObject, prefabSourceObject, isObjectOnRootInAsset, true, allowApplyDefaultOverride, serializedObjects, changedObjects, action); + ApplySinglePropertyAndRemoveOverride(property, prefabSourceSerializedObject, prefabSourceObject, isObjectOnRootInAsset, true, allowWarnAboutApplyingPartsOfManagedReferences, allowApplyDefaultOverride, serializedObjects, changedObjects, action, out _); } else { - while (property.Next(property.hasVisibleChildren) && (endProperty == null || !SerializedProperty.EqualContents(property, endProperty))) + if (property.prefabOverride && property.propertyType == SerializedPropertyType.ManagedReference) + { + bool skipRestOfProperties = false; + ApplySinglePropertyAndRemoveOverride(property, prefabSourceSerializedObject, prefabSourceObject, isObjectOnRootInAsset, false, allowWarnAboutApplyingPartsOfManagedReferences, allowApplyDefaultOverride, serializedObjects, changedObjects, action, out skipRestOfProperties); + if (skipRestOfProperties) + return; + + allowWarnAboutApplyingPartsOfManagedReferences = false; // The managed reference was applied to the Asset so do not check for sub properties + } + + var visitedManagedReferenceProperties = new HashSet(); + bool visitChildren = property.hasVisibleChildren; + + while (property.Next(visitChildren) && (endProperty == null || !SerializedProperty.EqualContents(property, endProperty))) { // If we apply a property that has child properties that are object references, and if they // reference non-asset objects, those references will get lost, since ApplySingleProperty @@ -465,50 +581,28 @@ static void ApplyPropertyOverrides(Object prefabInstanceObject, SerializedProper // NOTE: all property modifications are leafs except in the context of managed references. // Managed references can be overriden (and have visible children). - if (property.prefabOverride && (property.propertyType == SerializedPropertyType.ManagedReference || !property.hasVisibleChildren)) - ApplySingleProperty(property, prefabSourceSerializedObject, prefabSourceObject, isObjectOnRootInAsset, false, allowApplyDefaultOverride, serializedObjects, changedObjects, action); - } - } + bool isManagedReferenceRoot = property.propertyType == SerializedPropertyType.ManagedReference; + bool skipRestOfProperties = false; + if (property.prefabOverride && (isManagedReferenceRoot || !property.hasVisibleChildren)) + ApplySinglePropertyAndRemoveOverride(property, prefabSourceSerializedObject, prefabSourceObject, isObjectOnRootInAsset, false, allowWarnAboutApplyingPartsOfManagedReferences, allowApplyDefaultOverride, serializedObjects, changedObjects, action, out skipRestOfProperties); - // Ensure importing of saved Prefab Assets only kicks in after all Prefab Asset have been saved - AssetDatabase.StartAssetEditing(); + if (skipRestOfProperties) + break; - try - { - Action saveIfChanged = (SerializedObject serializedObject) => - { - if (changedObjects.Contains(serializedObject)) + // Avoid cyclic mangaged references + if (isManagedReferenceRoot) { - bool applySuccess = action == InteractionMode.UserAction ? serializedObject.ApplyModifiedProperties() : serializedObject.ApplyModifiedPropertiesWithoutUndo(); - if (applySuccess) - { - SaveChangesToPrefabFileIfPersistent(serializedObject); - - if (action == InteractionMode.UserAction) - { - Undo.FlushUndoRecordObjects(); // flush'es ensure that SavePrefab() on undo/redo on the source happens in the right order - } - } + if (visitedManagedReferenceProperties.Add(property.managedReferenceId)) + visitChildren = property.hasVisibleChildren; // First time seeing managed reference, so allow entering children if needed + else + visitChildren = false; } - }; - - // Case 1292519 - // The Prefab to which the changes are applied is added first in serializedObjects by ApplySingleProperty - it must be saved first - // The rest of the objects are collected in reverse dependency order in ApplySingleProperty - starting from the instance where the changes are made down to the original Prefab - // Apply the changes and save them in dependency order (reversed array order) to make sure dependent values are saved after the values they depend upon - if (serializedObjects.Count > 0) - { - saveIfChanged(serializedObjects[0]); - for (int i = serializedObjects.Count - 1; i > 0; i--) + else { - saveIfChanged(serializedObjects[i]); + visitChildren = property.hasVisibleChildren; } } } - finally - { - AssetDatabase.StopAssetEditing(); - } } static void SaveChangesToPrefabFileIfPersistent(SerializedObject serializedObject) @@ -564,6 +658,16 @@ static SerializedProperty GetArrayPropertyIfGivenPropertyIsPartOfArrayElementInI // Check if found array has different length on instance than on Asset. SerializedProperty arrayPropertyOnInstance = optionalSingleInstanceProperty.serializedObject.FindProperty(arrayPropertyPath); SerializedProperty arrayPropertyOnAsset = prefabSourceSerializedObject.FindProperty(arrayPropertyPath); + + // With the ManagedReferences feature we no longer guarantee that the Prefab Asset will have the same property so we need to check + // if it exists (could be a base class without any properties) + if (arrayPropertyOnAsset == null) + { + WarnIfApplyingManagedReferenceFieldIsNotPossible(optionalSingleInstanceProperty, null, action); + cancel = true; + return null; + } + if (arrayPropertyOnInstance.arraySize > arrayPropertyOnAsset.arraySize) { // Array size mismatches. Check if the property the user is attempting to apply @@ -603,17 +707,21 @@ static SerializedProperty GetArrayPropertyIfGivenPropertyIsPartOfArrayElementInI // That may be thousands of times if a component has lots of array data. // We provide as much information as possible to the method as parameters // so we don't have to recalculate it for each call. - static void ApplySingleProperty( + static void ApplySinglePropertyAndRemoveOverride( SerializedProperty instanceProperty, SerializedObject prefabSourceSerializedObject, Object applyTarget, bool isObjectOnRootInAsset, bool singlePropertyOnly, + bool allowWarnAboutApplyingPartsOfManagedReferences, bool allowApplyDefaultOverride, List serializedObjects, HashSet changedObjects, - InteractionMode action) + InteractionMode action, + out bool skipRestOfProperties) { + skipRestOfProperties = false; + if (!allowApplyDefaultOverride && isObjectOnRootInAsset && IsPropertyOverrideDefaultOverrideComparedToAnySource(instanceProperty)) { if (singlePropertyOnly) @@ -634,6 +742,7 @@ static void ApplySingleProperty( SerializedProperty sourceProperty = prefabSourceSerializedObject.FindProperty(instanceProperty.propertyPath); if (sourceProperty == null) { + // Special handling for arrays bool cancel; var instanceArrayProperty = GetArrayPropertyIfGivenPropertyIsPartOfArrayElementInInstanceWhichDoesNotExistInAsset(instanceProperty, prefabSourceSerializedObject, InteractionMode.AutomatedAction, out cancel); if (instanceArrayProperty != null) @@ -648,15 +757,34 @@ static void ApplySingleProperty( return; } } - else - { - Debug.LogError($"ApplySingleProperty copy state error: SerializedProperty could not be found for {instanceProperty.propertyPath}. Please report a bug."); - return; - } + } + + if (allowWarnAboutApplyingPartsOfManagedReferences && WarnIfApplyingManagedReferenceFieldIsNotPossible(instanceProperty, sourceProperty, action)) + { + skipRestOfProperties = true; + return; + } + + if (sourceProperty == null) + { + // If we reach here we need to investigate the situation in which it happens and fix it + Debug.LogError($"ApplySinglePropertyAndRemoveOverride error: Unhandled situation for {instanceProperty.propertyPath}. Please report a bug."); + skipRestOfProperties = true; + return; } // Copy overridden property value to asset - prefabSourceSerializedObject.CopyFromSerializedProperty(instanceProperty); + if (instanceProperty.propertyType == SerializedPropertyType.ManagedReference) + { + // For a managed reference root property we assign the entire object reference value so we can handle if the Asset value is null from start, since in + // this case we cannot use CopyFromSerializedProperty as we do for normal properties. + sourceProperty.managedReferenceValue = instanceProperty.managedReferenceValue; + } + else + { + prefabSourceSerializedObject.CopyFromSerializedProperty(instanceProperty); + } + changedObjects.Add(prefabSourceSerializedObject); // Abort if property has reference to object in scene. @@ -739,6 +867,10 @@ internal static void RevertPropertyOverrides(SerializedProperty[] instanceProper if (WarnIfInAnimationMode(OverrideOperation.Revert, action)) return; + foreach (var property in instanceProperties) + if (WarnIfRevertingManagedReferenceIsNotPossible(property, action)) + return; + foreach (var property in instanceProperties) RevertPropertyOverride(property, action); } @@ -750,12 +882,147 @@ public static void RevertPropertyOverride(SerializedProperty instanceProperty, I ThrowExceptionIfAllPrefabInstanceObjectsAreInvalid(instanceProperty.serializedObject.targetObjects, false); + if (WarnIfRevertingManagedReferenceIsNotPossible(instanceProperty, action)) + return; + + foreach (Object targetObject in instanceProperty.serializedObject.targetObjects) + { + GameObject outermostRoot = GetOutermostPrefabInstanceRoot(targetObject); + if (outermostRoot != null) + Internal_CallPrefabInstanceReverting(outermostRoot); + } + instanceProperty.prefabOverride = false; + // Because prefabOverride changed ApplyModifiedProperties will do a prefab merge causing the revert. if (action == InteractionMode.UserAction) instanceProperty.serializedObject.ApplyModifiedProperties(); else instanceProperty.serializedObject.ApplyModifiedPropertiesWithoutUndo(); + + foreach (Object targetObject in instanceProperty.serializedObject.targetObjects) + { + GameObject outermostRoot = GetOutermostPrefabInstanceRoot(targetObject); + if (outermostRoot != null) + Internal_CallPrefabInstanceReverted(outermostRoot); + } + } + + internal static bool IsPropertyBeingDrivenByPrefabStage(SerializedProperty property) + { + Object target = property.serializedObject.targetObject; + GameObject go = GetGameObject(target); + if (go != null && go.scene.IsValid() && EditorSceneManager.IsPreviewScene(go.scene)) + { + var prefabStage = SceneManagement.PrefabStageUtility.GetCurrentPrefabStage(); + if (prefabStage != null && prefabStage.mode == SceneManagement.PrefabStage.Mode.InContext) + { + var propertyPath = (property.isReferencingAManagedReferenceField ? property.managedReferencePropertyPath : property.propertyPath); + ScriptableObject driver = prefabStage; + if ( + (DrivenPropertyManagerInternal.IsDriving(driver, target, propertyPath)) + || + ((target is Transform || target is ParticleSystem || property.propertyType == SerializedPropertyType.Color) && DrivenPropertyManagerInternal.IsDrivingPartial(driver, target, propertyPath))) + { + return true; + } + } + } + return false; + } + + static bool WarnIfApplyingManagedReferenceFieldIsNotPossible(SerializedProperty instanceProperty, SerializedProperty sourceProperty, InteractionMode action) + { + bool isReferencingAManagedReferenceField = instanceProperty.isReferencingAManagedReferenceField; + if (!isReferencingAManagedReferenceField) + return false; + + if (sourceProperty != null) + { + // Even if we find the propertyPath on the source object we also need to validate if this is a managed reference to the correct source object, except when + // applying an entire object because then entire state is transfered to the asset and can merged back to the instance so here we don't show the dialog. + bool applyingNotPossible = instanceProperty.managedReferencePropertyPath != sourceProperty.managedReferencePropertyPath; + if (applyingNotPossible) + { + string title = L10n.Tr("Mismatching Objects"); + string errorMsg = L10n.Tr("Cannot apply SerializeReference field since the Prefab instance is referencing a new object compared to the Prefab Asset.\n\nThis means that the changes from the Prefab Asset cannot be merged back to the Prefab instance.\n\nYou can apply the root of the field or the entire component"); + if (action == InteractionMode.AutomatedAction) + throw new InvalidOperationException(title + ": " + errorMsg + $"(instance property { instanceProperty.managedReferencePropertyPath}, asset property { sourceProperty.managedReferencePropertyPath})"); + else + EditorUtility.DisplayDialog( + title, + errorMsg, + L10n.Tr("OK")); + return true; + } + } + else + { + // Special handling for managed references. + // If we have a valid managedReferencePropertyPath then the types must be different since we could not find the + // propertyPath on the Prefab Asset, tell the user that this is not supported. + if (!string.IsNullOrEmpty(instanceProperty.managedReferencePropertyPath)) + { + string title = L10n.Tr("Mismatching Types"); + string errorMsg = L10n.Tr("Cannot apply a SerializeReference sub field when the type from the Prefab instance is different from the Prefab Asset.\n\nYou can apply the root of the field or the entire component"); + if (action == InteractionMode.AutomatedAction) + throw new InvalidOperationException(title + ": " + errorMsg); + else + EditorUtility.DisplayDialog( + title, + errorMsg, + L10n.Tr("OK")); + + return true; + } + } + + return false; + } + + static bool WarnIfRevertingManagedReferenceIsNotPossible(SerializedProperty instanceProperty, InteractionMode action) + { + if (!instanceProperty.isReferencingAManagedReferenceField) + return false; + + var instanceObject = instanceProperty.serializedObject.targetObject; + var prefabSourceObject = GetCorrespondingObjectFromSource(instanceObject); + SerializedObject prefabSourceSerializedObject = new SerializedObject(prefabSourceObject); + SerializedProperty sourceProperty = prefabSourceSerializedObject.FindProperty(instanceProperty.propertyPath); + + string errorMsg = ""; + string title = ""; + if (sourceProperty != null) + { + // Object mismatch: + bool revertingNotPossible = instanceProperty.managedReferencePropertyPath != sourceProperty.managedReferencePropertyPath; + if (revertingNotPossible) + { + title = L10n.Tr("Mismatching Objects"); + errorMsg = L10n.Tr("Cannot revert a single SerializeReference field since the Prefab instance is referencing a new object compared to the Prefab Asset.\n\nThis means that the entire object is considered an override, cannot revert parts of it.\n\nYou can revert the root of the serialized reference or the entire component."); + } + } + else + { + // Type mismatch: + title = L10n.Tr("Mismatching Types"); + errorMsg = L10n.Tr("Cannot revert a parts of a SerializeReference property since the Prefab instance is referencing a different type compared to the Prefab Asset.\n\nYou can revert the root of the serialized reference or the entire component."); + } + + bool warnUser = !string.IsNullOrEmpty(errorMsg); + if (warnUser) + { + if (action == InteractionMode.AutomatedAction) + { + throw new InvalidOperationException(title + ": " + errorMsg); + } + else if (action == InteractionMode.UserAction) + { + EditorUtility.DisplayDialog(title, errorMsg, L10n.Tr("OK")); + } + } + + return warnUser; } public static void ApplyObjectOverride(Object instanceComponentOrGameObject, string assetPath, InteractionMode action) @@ -927,6 +1194,8 @@ public static void RevertAddedComponent(Component component, InteractionMode act var prefabInstanceGameObject = component.gameObject; + PrefabUtility.Internal_CallPrefabInstanceReverting(prefabInstanceGameObject); + if (action == InteractionMode.UserAction) { string dependentComponents = string.Join( @@ -957,6 +1226,8 @@ public static void RevertAddedComponent(Component component, InteractionMode act { PrefabUtility.MergePrefabInstance_internal(prefabInstanceGameObject); } + + PrefabUtility.Internal_CallPrefabInstanceReverted(prefabInstanceGameObject); } private static bool IsPrefabInstanceObjectOf(Object instance, Object source) @@ -1047,11 +1318,20 @@ public static void ApplyRemovedComponent(GameObject instanceGameObject, Componen Debug.LogError($"No undo was registered when removing {assetComponent.name} from {assetRoot.name}. \nError: {errorMessage}", assetRoot); } + var prefabInstanceObject = PrefabUtility.GetPrefabInstanceHandle(instanceGameObject); + + if (action == InteractionMode.UserAction) + Undo.RegisterCompleteObjectUndo(prefabInstanceObject, actionName); + + // Having registered undo on the Prefab instance we can now apply the removed component override using (var scope = new EditPrefabContentsScope(assetPath)) { //Search components in file that matches the FileIds of componentInAsset - void DeleteCorrespondingComponent(Component componentInAsset) + bool DeleteCorrespondingComponent(Component componentInAsset) { + if (componentInAsset == null) + return false; + var assetComponentId = Unsupported.GetFileIDHint(componentInAsset); var componentsOfTypeInAsset = scope.prefabContentsRoot.GetComponentsInChildren(componentInAsset.GetType(), true); @@ -1060,17 +1340,23 @@ void DeleteCorrespondingComponent(Component componentInAsset) if (Unsupported.GetOrGenerateFileIDHint(component) == assetComponentId) { Object.DestroyImmediate(component); - return; + return true; } } Debug.LogError($"Component {componentInAsset} could not be found and deleted from corresponding asset."); + return false; } - DeleteCorrespondingComponent(assetComponent); - if (coupledAssetComponent != null) - DeleteCorrespondingComponent(coupledAssetComponent); + if (DeleteCorrespondingComponent(assetComponent)) + { + RemoveRemovedComponentOverride(instanceGameObject, assetComponent); + + if (DeleteCorrespondingComponent(coupledAssetComponent)) + RemoveRemovedComponentOverride(instanceGameObject, coupledAssetComponent); + } } + // Register undo for the changed asset if (action == InteractionMode.UserAction && originalFileContent != null) { var guid = AssetDatabase.GUIDFromAssetPath(assetPath); @@ -1080,10 +1366,8 @@ void DeleteCorrespondingComponent(Component componentInAsset) Debug.LogError($"No undo was registered when removing {assetComponent.name} from {assetRoot.name}. \nError: {errorMessage}", assetRoot); } - var prefabInstanceObject = PrefabUtility.GetPrefabInstanceHandle(instanceGameObject); - - if (action == InteractionMode.UserAction) - Undo.RegisterCompleteObjectUndo(prefabInstanceObject, actionName); + // Ensure correct action name after asset undo + Undo.SetCurrentGroupName(actionName); RemoveRemovedComponentOverridesWhichAreInvalid(prefabInstanceObject); @@ -1141,6 +1425,8 @@ public static void RevertRemovedComponent(GameObject instanceGameObject, Compone return; } + PrefabUtility.Internal_CallPrefabInstanceReverting(instanceGameObject); + var actionName = "Revert Prefab removed component"; var prefabInstanceObject = PrefabUtility.GetPrefabInstanceHandle(instanceGameObject); @@ -1176,6 +1462,8 @@ public static void RevertRemovedComponent(GameObject instanceGameObject, Compone } } } + + PrefabUtility.Internal_CallPrefabInstanceReverted(instanceGameObject); } private static void RemoveRemovedGameObjectOverridesWhichAreNull(Object prefabInstanceObject) @@ -1420,14 +1708,19 @@ public static void RevertAddedGameObject(GameObject gameObject, InteractionMode throw new ArgumentNullException(nameof(gameObject), "Cannot revert added GameObject. GameObject is null."); if (!IsAddedGameObjectOverride(gameObject)) - throw new ArgumentException("Cannot apply added GameObject. GameObject is not an added GameObject override on a Prefab instance.", nameof(gameObject)); + throw new ArgumentException("Cannot revert added GameObject. GameObject is not an added GameObject override on a Prefab instance.", nameof(gameObject)); ThrowExceptionIfInstanceIsPersistent(gameObject); + GameObject instance = PrefabUtility.GetOutermostPrefabInstanceRoot(gameObject.transform.parent); + PrefabUtility.Internal_CallPrefabInstanceReverting(instance); + if (action == InteractionMode.UserAction) Undo.DestroyObjectImmediate(gameObject); else Object.DestroyImmediate(gameObject); + + PrefabUtility.Internal_CallPrefabInstanceReverted(instance); } public static List GetObjectOverrides(GameObject prefabInstance, bool includeDefaultOverrides = false) @@ -1684,13 +1977,13 @@ internal static void ValidatePath(GameObject instanceRoot, string path) } } - private static void SaveAsPrefabAssetArgumentCheck(GameObject instanceRoot, string path) + private static void SaveAsPrefabAssetArgumentCheck(GameObject instanceRoot, string path, bool connectToInstance) { if (instanceRoot == null) throw new ArgumentNullException("Parameter root is null"); - if (EditorUtility.IsPersistent(instanceRoot)) - throw new ArgumentException("Can't save persistent object as a Prefab asset"); + if (EditorUtility.IsPersistent(instanceRoot) && connectToInstance) + throw new ArgumentException("Can't save persistent Objects and connect them to the saved Prefab"); if (IsPartOfNonAssetPrefabInstance(instanceRoot)) { @@ -1722,7 +2015,7 @@ private static void ReplacePrefabArgumentCheck(GameObject root, string path) public static GameObject SaveAsPrefabAsset(GameObject instanceRoot, string assetPath, out bool success) { - SaveAsPrefabAssetArgumentCheck(instanceRoot, assetPath); + SaveAsPrefabAssetArgumentCheck(instanceRoot, assetPath, false); return SaveAsPrefabAsset_Internal(instanceRoot, assetPath, out success); } @@ -1741,7 +2034,7 @@ public static GameObject SaveAsPrefabAssetAndConnect(GameObject instanceRoot, st public static GameObject SaveAsPrefabAssetAndConnect(GameObject instanceRoot, string assetPath, InteractionMode action, out bool success) { - SaveAsPrefabAssetArgumentCheck(instanceRoot, assetPath); + SaveAsPrefabAssetArgumentCheck(instanceRoot, assetPath, true); if (IsPathInStreamingAssets(assetPath)) throw new ArgumentException("Can't connect a Prefab in the StreamingAssets folder to GameObjects in the scene. To save a Prefab to the StreamingAssets folder use SaveAsPrefabAsset instead."); @@ -1790,7 +2083,7 @@ internal static void ApplyPrefabInstance(GameObject instance) var assetObject = GetCorrespondingObjectFromSource(instance); string path = AssetDatabase.GetAssetPath(assetObject); - SaveAsPrefabAssetArgumentCheck(instance, path); + SaveAsPrefabAssetArgumentCheck(instance, path, true); ApplyPrefabInstance_Internal(instance); } @@ -1857,6 +2150,9 @@ internal static void ThrowIfInvalidArgumentsForReplacePrefabInstance(GameObject if (checkValidAsset) ThrowIfInvalidAssetForReplacePrefabInstance(prefabAssetRoot, mode); + if (!IsPartOfNonAssetPrefabInstance(prefabInstanceRoot)) + throw new InvalidOperationException(string.Format("Input '{0}' is not a Prefab instance, for plain GameObjects use ConvertToPrefabInstance() instead", prefabInstanceRoot.name)); + if (!IsOutermostPrefabInstanceRoot(prefabInstanceRoot)) throw new ArgumentException("Input instance is not an outermost Prefab instance root. Input instance: " + prefabInstanceRoot.name, nameof(prefabInstanceRoot)); if (EditorUtility.IsPersistent(prefabInstanceRoot)) @@ -1869,10 +2165,16 @@ internal static void ThrowIfInvalidArgumentsForReplacePrefabInstance(GameObject if (prefabInstanceRoot.transform.GetType() != prefabAssetRoot.transform.GetType()) throw new InvalidOperationException(string.Format("Cannot replace the Prefab instance '{0}' with root transform of type {1} with a Prefab asset with root transform of type {2}. Transform types must match.", prefabInstanceRoot.name, prefabInstanceRoot.transform.GetType().Name, prefabAssetRoot.transform.GetType().Name)); - // Recording undo does not handle missing scripts - var gameObjectsWithInvalidScript = FindGameObjectsWithInvalidComponent(prefabInstanceRoot); - if (mode == InteractionMode.UserAction && gameObjectsWithInvalidScript.Count > 0) - throw new InvalidOperationException(string.Format($"Cannot replace the Prefab instance when it has a missing script. GameObject '{gameObjectsWithInvalidScript[0].name}' has a missing script.")); + if (prefabInstanceRoot.hideFlags.HasFlag(HideFlags.DontSaveInEditor) || prefabInstanceRoot.transform.hideFlags.HasFlag(HideFlags.DontSaveInEditor)) + throw new ArgumentException("Input instance root is using the HideFlags.DontSaveInEditor flag which is not supported when replacing: Input instance: " + prefabInstanceRoot.name, nameof(prefabInstanceRoot)); + + if (mode == InteractionMode.UserAction) + { + // Recording undo does not handle missing scripts + var gameObjectsWithInvalidScript = FindGameObjectsWithInvalidComponent(prefabInstanceRoot); + if (gameObjectsWithInvalidScript.Count > 0) + throw new InvalidOperationException(string.Format($"Cannot replace the Prefab instance when it has a missing script. GameObject '{gameObjectsWithInvalidScript[0].name}' has a missing script. Use InteractionMode.AutomatedAction to force the replace.")); + } } public static void ReplacePrefabAssetOfPrefabInstances(GameObject[] prefabInstanceRoots, GameObject prefabAssetRoot, InteractionMode mode) @@ -1885,6 +2187,9 @@ public static void ReplacePrefabAssetOfPrefabInstances(GameObject[] prefabInstan if (prefabInstanceRoots == null) throw new ArgumentNullException(nameof(prefabInstanceRoots)); + if (prefabInstanceRoots.Length == 0) + throw new ArgumentException(nameof(prefabInstanceRoots) + " has no objects"); + if (settings == null) throw new ArgumentNullException(nameof(settings)); @@ -1916,6 +2221,12 @@ public static void ReplacePrefabAssetOfPrefabInstance(GameObject prefabInstanceR private static void ReplacePrefabAssetOfPrefabInstance_NoInputValidation(GameObject prefabInstanceRoot, GameObject prefabAssetRoot, PrefabReplacingSettings settings, InteractionMode mode) { + if (GetCorrespondingConnectedObjectFromSource(prefabInstanceRoot) == prefabAssetRoot) + { + // No need to replace, already using the input Prefab Asset + return; + } + var undoActionName = "Replace Prefab Instance"; if (mode == InteractionMode.UserAction) { @@ -1934,6 +2245,144 @@ private static void ReplacePrefabAssetOfPrefabInstance_NoInputValidation(GameObj Undo.FlushTrackedObjects(); } + internal static void ThrowIfInvalidArgumentsForConvertToPrefabInstance(GameObject plainGameObject, GameObject prefabAssetRoot, bool checkValidAsset, InteractionMode mode) + { + if (plainGameObject == null) + throw new ArgumentNullException(nameof(plainGameObject)); + + if (prefabAssetRoot == null) + throw new ArgumentNullException(nameof(prefabAssetRoot)); + + if (IsPartOfNonAssetPrefabInstance(plainGameObject)) + throw new InvalidOperationException(string.Format("Input '{0}' is not a plain GameObject, it is already a Prefab instance. Use ReplacePrefabAssetOfPrefabInstance() instead.", plainGameObject.name)); + + if (EditorUtility.IsPersistent(plainGameObject)) + throw new ArgumentException("Input is from a Prefab asset, this is not supported. Input GameObject: " + plainGameObject.name, nameof(plainGameObject)); + + if (checkValidAsset) + ThrowIfInvalidAssetForReplacePrefabInstance(prefabAssetRoot, mode); + + if (plainGameObject.transform.GetType() != prefabAssetRoot.transform.GetType()) + throw new InvalidOperationException(string.Format("Cannot convert the GameObject '{0}' with root transform of type {1} with a Prefab asset with root transform of type {2}. Transform types must match.", plainGameObject.name, plainGameObject.transform.GetType().Name, prefabAssetRoot.transform.GetType().Name)); + + // Prefab Mode and EditPrefabContents scope handling + if (PrefabStageUtility.IsGameObjectThePrefabRootInAnyPrefabStage(plainGameObject) || (EditorSceneManager.IsPreviewSceneObject(plainGameObject) && plainGameObject.transform.parent == null)) + throw new InvalidOperationException("Replacing the root GameObject in a Prefab with a Prefab instance is not supported since it will break all overrides for existing instances of this Prefab, including their positions and rotations." + plainGameObject.name); + + if (plainGameObject.hideFlags.HasFlag(HideFlags.DontSaveInEditor) || plainGameObject.transform.hideFlags.HasFlag(HideFlags.DontSaveInEditor)) + throw new ArgumentException("Input GameObject is using the HideFlags.DontSaveInEditor flag which is not supported when converting to Prefab instance: GameObject: " + plainGameObject.name, nameof(plainGameObject)); + + if (mode == InteractionMode.UserAction) + { + // Recording undo does not handle missing scripts + var gameObjectsWithInvalidScript = FindGameObjectsWithInvalidComponent(plainGameObject); + if (gameObjectsWithInvalidScript.Count > 0) + throw new InvalidOperationException(string.Format($"Cannot convert the GameObject when it has a missing script. GameObject '{gameObjectsWithInvalidScript[0].name}' has a missing script. This is not supported by the Undo system. Use InteractionMode.AutomatedAction instead.")); + } + } + + public static void ConvertToPrefabInstance(GameObject plainGameObject, GameObject prefabAssetRoot, ConvertToPrefabInstanceSettings settings, InteractionMode mode) + { + ThrowIfInvalidArgumentsForConvertToPrefabInstance(plainGameObject, prefabAssetRoot, true, mode); + + ConvertToPrefabInstance_NoInputValidation(plainGameObject, prefabAssetRoot, settings, mode); + + EditorUtility.ForceRebuildInspectors(); + } + + public static void ConvertToPrefabInstances(GameObject[] plainGameObjects, GameObject prefabAssetRoot, ConvertToPrefabInstanceSettings settings, InteractionMode mode) + { + if (plainGameObjects == null) + throw new ArgumentNullException(nameof(plainGameObjects)); + + if (plainGameObjects.Length == 0) + throw new ArgumentException(nameof(plainGameObjects) + " has no objects"); + + foreach(var go in plainGameObjects) + if (go == null) + throw new ArgumentException(nameof(plainGameObjects) + " has a null GameObject"); + + if (settings == null) + throw new ArgumentNullException(nameof(settings)); + + ThrowIfInvalidAssetForReplacePrefabInstance(prefabAssetRoot, mode); + + var topLevelGameObjects = GetTopLevelGameObjects(plainGameObjects); + foreach (var go in topLevelGameObjects) + ThrowIfInvalidArgumentsForConvertToPrefabInstance(go, prefabAssetRoot, false, mode); + + foreach (var go in topLevelGameObjects) + ConvertToPrefabInstance_NoInputValidation(go, prefabAssetRoot, settings, mode); + + EditorUtility.ForceRebuildInspectors(); + } + + private static void ConvertToPrefabInstance_NoInputValidation(GameObject plainGameObject, GameObject prefabAssetRoot, ConvertToPrefabInstanceSettings settings, InteractionMode mode) + { + var undoActionName = "Convert to Prefab Instance"; + if (mode == InteractionMode.UserAction) + { + Undo.FlushTrackedObjects(); + + // Make sure all instance are unpacked so the merge code can delete GameObjects that are not matched + TransformVisitor visitor = new TransformVisitor(); + visitor.VisitAll(plainGameObject.transform, (transform, userdata) => { + GameObject go = transform.gameObject; + if (IsOutermostPrefabInstanceRoot(go)) + { + UnpackPrefabInstance(go, PrefabUnpackMode.Completely, InteractionMode.UserAction); + } + }, null); + + Undo.SetCurrentGroupName(undoActionName); + Undo.RegisterFullObjectHierarchyUndo(plainGameObject, undoActionName); + } + + bool success = ConvertToPrefabInstance_Internal(plainGameObject, prefabAssetRoot, settings); + if (!success) + { + Debug.LogError(string.Format("Converting plain GameObject to Prefab instance failed for GameObject '{0}' using asset '{1}' at '{2}'", plainGameObject.name, prefabAssetRoot.name, AssetDatabase.GetAssetPath(prefabAssetRoot)), plainGameObject); + } + + if (mode == InteractionMode.UserAction) + Undo.FlushTrackedObjects(); + } + + static bool ContainsParent(GameObject go, HashSet gameObjectSet) + { + Transform parent = go.transform.parent; + while (parent) + { + if (gameObjectSet.Contains(parent.gameObject)) + break; + parent = parent.parent; + } + + if (parent == null) + return false; + else + return true; + } + + static List GetTopLevelGameObjects(GameObject[] gameObjects) + { + List result = new List(); + HashSet gameObjectHashSet = new HashSet(gameObjects); + + foreach (var go in gameObjects) + { + if (!ContainsParent(go, gameObjectHashSet)) + result.Add(go); + } + + return result; + } + + internal static void InstantiateDraggedPrefabUpon(GameObject draggedUponGameObject, GameObject prefabAssetRoot) + { + InstantiateDraggedPrefabUpon_Internal(draggedUponGameObject, prefabAssetRoot); + } + [Obsolete("Use SaveAsPrefabAsset with a path instead.")] public static GameObject ReplacePrefab(GameObject go, Object targetPrefab) { @@ -2181,6 +2630,25 @@ internal static bool PromptAndCheckoutPrefabIfNeeded(string[] assetPaths, SaveVe return result; } + public static event Action prefabInstanceReverting; + public static event Action prefabInstanceReverted; + + [RequiredByNativeCode] + internal static void Internal_CallPrefabInstanceReverting(GameObject instanceRoot) + { + Assert.IsNotNull(instanceRoot); + + prefabInstanceReverting?.Invoke(instanceRoot); + } + + [RequiredByNativeCode] + internal static void Internal_CallPrefabInstanceReverted(GameObject instanceRoot) + { + Assert.IsNotNull(instanceRoot); + + prefabInstanceReverted?.Invoke(instanceRoot); + } + public static event Action prefabInstanceUnpacking; public static event Action prefabInstanceUnpacked; @@ -2213,6 +2681,9 @@ public static void UnpackPrefabInstance(GameObject instanceRoot, PrefabUnpackMod { UnpackPrefabInstanceAndReturnNewOutermostRoots(instanceRoot, unpackMode); } + + if (action == InteractionMode.UserAction) + Undo.FlushTrackedObjects(); } public static GameObject[] UnpackPrefabInstanceAndReturnNewOutermostRoots(GameObject instanceRoot, PrefabUnpackMode unpackMode) @@ -2241,7 +2712,6 @@ public static void UnpackAllInstancesOfPrefab(GameObject prefabRoot, PrefabUnpac } } - internal static List FindGameObjectsWithInvalidComponent(GameObject rootOfSearch) { TransformVisitor transformVisitor = new TransformVisitor(); @@ -2255,38 +2725,38 @@ internal static bool HasInvalidComponent(GameObject rootOfSearch) return FindGameObjectsWithInvalidComponent(rootOfSearch).Count > 0; } - internal static bool HasInvalidComponent(Object gameObjectOrComponent) + internal static bool HasInvalidComponent(Object componentOrGameObject) { - if (gameObjectOrComponent == null) + if (componentOrGameObject == null) return true; - if (gameObjectOrComponent is Component) + if (componentOrGameObject is Component) { - Component comp = (Component)gameObjectOrComponent; - gameObjectOrComponent = (GameObject)comp.gameObject; + Component comp = (Component)componentOrGameObject; + componentOrGameObject = (GameObject)comp.gameObject; } - if (!(gameObjectOrComponent is GameObject)) + if (!(componentOrGameObject is GameObject)) return false; - return HasInvalidComponent((GameObject)gameObjectOrComponent); + return HasInvalidComponent((GameObject)componentOrGameObject); } - public static bool IsPartOfPrefabThatCanBeAppliedTo(Object gameObjectOrComponent) + public static bool IsPartOfPrefabThatCanBeAppliedTo(Object componentOrGameObject) { - if (gameObjectOrComponent == null) + if (componentOrGameObject == null) return false; - if (IsPartOfImmutablePrefab(gameObjectOrComponent)) + if (IsPartOfImmutablePrefab(componentOrGameObject)) return false; - if (!EditorUtility.IsPersistent(gameObjectOrComponent)) - gameObjectOrComponent = GetCorrespondingObjectFromSource(gameObjectOrComponent); + if (!EditorUtility.IsPersistent(componentOrGameObject)) + componentOrGameObject = GetCorrespondingObjectFromSource(componentOrGameObject); - if (HasInvalidComponent(gameObjectOrComponent)) + if (HasInvalidComponent(componentOrGameObject)) return false; - if (PrefabUtility.HasManagedReferencesWithMissingTypes(gameObjectOrComponent)) + if (PrefabUtility.HasManagedReferencesWithMissingTypes(componentOrGameObject)) return false; return true; @@ -3019,7 +3489,7 @@ internal static bool DoRemovePrefabInstanceUnusedOverridesDialog(InstanceOverrid { title = titleRemoveUnusedOverrides; message += "\n\n" + msgDetailsWrittenToTheLog; - if (EditorUtility.DisplayDialog(title, message, L10n.Tr("Yes"), L10n.Tr("No"))) + if (EditorUtility.DisplayDialog(title, message, L10n.Tr("Remove"), L10n.Tr("Cancel"))) return true; } else @@ -3137,16 +3607,35 @@ internal static bool LogRemovedUnusedRemovedComponents(GameObject instance, int return true; } - internal static event Func allowRecordingPrefabPropertyOverridesFor; + private static event Func m_AllowRecordingPrefabPropertyOverridesFor; + + internal static event Func allowRecordingPrefabPropertyOverridesFor + { + add + { + m_AllowRecordingPrefabPropertyOverridesFor += value; + SetHasSubscribersToAllowRecordingPrefabPropertyOverrides(true); + } + remove + { + m_AllowRecordingPrefabPropertyOverridesFor -= value; + if (m_AllowRecordingPrefabPropertyOverridesFor == null) + SetHasSubscribersToAllowRecordingPrefabPropertyOverrides(false); + } + } [RequiredByNativeCode] static bool AllowRecordingPrefabPropertyOverridesFor(UnityEngine.Object componentOrGameObject) { - if (allowRecordingPrefabPropertyOverridesFor == null) + if (m_AllowRecordingPrefabPropertyOverridesFor == null) + { + Debug.LogError("We should not be calling into managed from native if we have no subscribers."); return true; + } + - foreach (Func deleg in allowRecordingPrefabPropertyOverridesFor.GetInvocationList()) + foreach (Func deleg in m_AllowRecordingPrefabPropertyOverridesFor.GetInvocationList()) { if (deleg(componentOrGameObject) == false) return false; diff --git a/Editor/Mono/PreferencesWindow/AssetPipelinePreferences.cs b/Editor/Mono/PreferencesWindow/AssetPipelinePreferences.cs index 6f28c8f21a..0d39539b3b 100644 --- a/Editor/Mono/PreferencesWindow/AssetPipelinePreferences.cs +++ b/Editor/Mono/PreferencesWindow/AssetPipelinePreferences.cs @@ -49,7 +49,6 @@ class Properties const string kCacheServerIPAddressKey = "CacheServer2IPAddress"; const string kCacheServerModeKey = "CacheServer2Mode"; - const string kIpAddressKeyArgs = "-CacheServerIPAddress"; const string kModeKey = "CacheServerMode"; const string kDeprecatedEnabledKey = "CacheServerEnabled"; @@ -154,10 +153,6 @@ static void ReadCacheServerPreferences() static void WriteCacheServerPreferences() { - // Don't change anything if there's a command line override - if (GetCommandLineRemoteAddressOverride() != null) - return; - CacheServerMode oldMode = (CacheServerMode)EditorPrefs.GetInt(kModeKey); var oldPath = EditorPrefs.GetString(LocalCacheServer.PathKey); var oldCustomPath = EditorPrefs.GetBool(LocalCacheServer.CustomPathKey); @@ -196,17 +191,6 @@ static void WriteCacheServerPreferences() } } - public static string GetCommandLineRemoteAddressOverride() - { - string address = null; - var argv = Environment.GetCommandLineArgs(); - var index = Array.IndexOf(argv, kIpAddressKeyArgs); - if (index >= 0 && argv.Length > index + 1) - address = argv[index + 1]; - - return address; - } - public override void OnActivate(string searchContext, VisualElement rootElement) { base.OnActivate(searchContext, rootElement); @@ -249,7 +233,7 @@ void ShowGUI() { // Known issue with Docs redirect - versioned pages might not open offline docs var help = Help.FindHelpNamed("UnityAccelerator"); - Application.OpenURL(help); + Help.BrowseURL(help); } GUILayout.EndHorizontal(); EditorGUI.BeginChangeCheck(); @@ -257,10 +241,6 @@ void ShowGUI() EditorGUILayout.Space(); CacheServerGUI(); - var overrideAddress = GetCommandLineRemoteAddressOverride(); - if (overrideAddress != null) - EditorGUILayout.HelpBox("Cache Server preferences currently forced via command line argument to " + overrideAddress + " and any changes here will not take effect until starting Unity without that command line argument.", MessageType.Info, true); - if (EditorGUI.EndChangeCheck()) s_HasPendingChanges = true; @@ -299,22 +279,7 @@ void AssetImportGUI() void DoAutoRefreshMode() { - bool collabEnabled = Collab.instance.IsCollabEnabledForCurrentProject(); - using (new EditorGUI.DisabledScope(collabEnabled)) - { - if (collabEnabled) - { - // Don't keep toggle value in m_AutoRefresh since we don't want to save the overwritten value - EditorGUILayout.EnumPopup(Properties.autoRefresh, AssetPipelineAutoRefreshMode.Enabled); - - EditorGUILayout.Toggle(Properties.autoRefresh, true); - EditorGUILayout.HelpBox(Properties.autoRefreshHelpBox); - } - else - { - m_AutoRefresh = (AssetPipelineAutoRefreshMode)EditorGUILayout.EnumPopup(Properties.autoRefresh, m_AutoRefresh); - } - } + m_AutoRefresh = (AssetPipelineAutoRefreshMode)EditorGUILayout.EnumPopup(Properties.autoRefresh, m_AutoRefresh); } void DoImportWorkerCount() @@ -328,7 +293,7 @@ void DoImportWorkerCount() { // Known issue with Docs redirect - versioned pages might not open offline docs var help = Help.FindHelpNamed("ParallelImport"); - Application.OpenURL(help); + Help.BrowseURL(help); } GUILayout.EndHorizontal(); diff --git a/Editor/Mono/PreferencesWindow/CollectionsPreferences.cs b/Editor/Mono/PreferencesWindow/CollectionsPreferences.cs new file mode 100644 index 0000000000..ee7bb326d7 --- /dev/null +++ b/Editor/Mono/PreferencesWindow/CollectionsPreferences.cs @@ -0,0 +1,160 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + + +using System; +using UnityEditor; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs.LowLevel.Unsafe; +using UnityEngine; + +class JobsMenu +{ + private static int savedJobWorkerCount = JobsUtility.JobWorkerCount; + + [SettingsProvider] + private static SettingsProvider JobsPreferencesItem() + { + var provider = new SettingsProvider("Preferences/Jobs", SettingsScope.User) + { + label = "Jobs", + keywords = new[] { "Jobs" }, + guiHandler = (searchContext) => + { + var originalWidth = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth = 200f; + EditorGUILayout.BeginVertical(); + + GUILayout.BeginVertical(); + EditorGUILayout.LabelField("For safety, these values are reset on editor restart."); + + bool madeChange = false; + + bool oldWorkerCount = (JobsUtility.JobWorkerCount > 0); + bool newWorkerCount = EditorGUILayout.Toggle(new GUIContent("Use Job Threads:"), oldWorkerCount); + if (newWorkerCount != oldWorkerCount) + { + madeChange = true; + SwitchUseJobThreads(); + } + + bool oldUseJobsDebugger = JobsUtility.JobDebuggerEnabled; + var newUseJobsDebugger = EditorGUILayout.Toggle(new GUIContent("Enable Jobs Debugger"), JobsUtility.JobDebuggerEnabled); + if (newUseJobsDebugger != oldUseJobsDebugger) + { + madeChange = true; + SetUseJobsDebugger(newUseJobsDebugger); + } + + var oldLeakDetectionMode = NativeLeakDetection.Mode; + var newLeakDetectionMode = (NativeLeakDetectionMode)EditorGUILayout.EnumPopup(new GUIContent("Leak Detection Level"), oldLeakDetectionMode); + if (newLeakDetectionMode != oldLeakDetectionMode) + { + madeChange = true; + SetLeakDetection(newLeakDetectionMode); + } + + if (madeChange) + Telemetry.LogMenuPreferences(new Telemetry.MenuPreferencesEvent { useJobsThreads = newUseJobsDebugger, enableJobsDebugger = newUseJobsDebugger, nativeLeakDetectionMode = newLeakDetectionMode }); + + GUILayout.EndVertical(); + EditorGUILayout.EndVertical(); + + EditorGUIUtility.labelWidth = originalWidth; + } + + }; + + return provider; + } + + static void SwitchUseJobThreads() + { + if (JobsUtility.JobWorkerCount > 0) + { + savedJobWorkerCount = JobsUtility.JobWorkerCount; + JobsUtility.JobWorkerCount = 0; + } + else + { + JobsUtility.JobWorkerCount = savedJobWorkerCount; + if (savedJobWorkerCount == 0) + { + JobsUtility.ResetJobWorkerCount(); + } + } + } + + static void SetUseJobsDebugger(bool value) + { + JobsUtility.JobDebuggerEnabled = value; + } + + static void SetLeakDetection(NativeLeakDetectionMode value) + { + switch (value) + { + case NativeLeakDetectionMode.Disabled: + { + Debug.LogWarning("Leak detection has been disabled. Leak warnings will not be generated, and all leaks up to now are forgotten."); + break; + } + case NativeLeakDetectionMode.Enabled: + { + Debug.Log("Leak detection has been enabled. Leak warnings will be generated upon domain reload."); + break; + } + case NativeLeakDetectionMode.EnabledWithStackTrace: + { + Debug.Log("Leak detection with stack traces has been enabled. Leak warnings will be generated upon domain reload."); + break; + } + default: + { + throw new Exception($"Unhandled {nameof(NativeLeakDetectionMode)}"); + } + } + + NativeLeakDetection.Mode = value; + } + + internal struct Telemetry + { + const string k_VendorKey = "unity.collections"; + const int k_MaxEventsPerHour = 1000; + const int k_MaxNumberOfElements = 1000; + const int k_Version = 1; + static bool s_EventsRegistered = false; + + static void RegisterTelemetryEvents() + { + EditorAnalytics.RegisterEventWithLimit(k_Event_MenuPreferences, k_MaxEventsPerHour, k_MaxNumberOfElements, k_VendorKey, k_Version); + + s_EventsRegistered = true; + } + + internal struct MenuPreferencesEvent + { + public bool enableJobsDebugger; + public bool useJobsThreads; + public NativeLeakDetectionMode nativeLeakDetectionMode; + } + + const string k_Event_MenuPreferences = "collectionsMenuPreferences"; + + internal static void LogMenuPreferences(MenuPreferencesEvent value) + { + SendEditorEvent(k_Event_MenuPreferences, value); + } + + private static void SendEditorEvent(string eventName, T eventData) where T : unmanaged + { + if (!s_EventsRegistered) + RegisterTelemetryEvents(); + + EditorAnalytics.SendEventWithLimit(eventName, eventData, k_Version); + } + } +} diff --git a/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs b/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs index f73431972d..fc4cb754dd 100644 --- a/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs +++ b/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs @@ -84,7 +84,8 @@ class GeneralProperties public static readonly GUIContent performBumpMapCheck = EditorGUIUtility.TrTextContent("Perform Bump Map Check", "Enables Bump Map Checks upon import of Materials. This checks that textures used in a normal map material slot are actually defined as normal maps."); public static readonly GUIContent enableExtendedLogging = EditorGUIUtility.TrTextContent("Timestamp Editor log entries", "Adds timestamp and thread Id to Editor.log messages."); - + public static readonly GUIContent enablePlayModeTooltips = EditorGUIUtility.TrTextContent("Enable PlayMode Tooltips", "Enables tooltips in the editor while in play mode."); + public static readonly GUIContent useProjectPathInTitle = EditorGUIUtility.TrTextContent("Use Project Path in Window Title", "If enabled the Project's name is replaced in the main window title with the Project's path on disk."); } class ExternalProperties @@ -130,6 +131,7 @@ class SceneViewProperties public static readonly GUIContent createObjectsAtWorldOrigin = EditorGUIUtility.TrTextContent("Create Objects at Origin", "Enable this preference to instantiate new 3D objects at World coordinates 0,0,0. Disable it to instantiate them at the Scene pivot (in front of the Scene view Camera)."); public static readonly GUIContent enableConstrainProportionsScalingForNewObjects = EditorGUIUtility.TrTextContent("Create Objects with Constrained Proportions scale on", "If enabled, scale in the transform component will be set to constrain proportions for new GameObjects by default"); public static readonly GUIContent useInspectorExpandedStateContent = EditorGUIUtility.TrTextContent("Auto-hide gizmos", "Automatically hide gizmos of Components collapsed in the Inspector"); + public static readonly GUIContent ignoreAlwaysRefreshWhenNotFocused = EditorGUIUtility.TrTextContent("Refresh the Scene view only when the Editor is in focus.", "If enabled, ignore the \"Always Refresh\" flag on the Scene view when the Editor is not the foregrounded application."); } class LanguageProperties @@ -253,6 +255,22 @@ public override void OnActivate(string searchContext, VisualElement rootElement) ReadPreferences(); } + internal static bool useProjectPathInTitle + { + get + { + return EditorPrefs.GetBool("UseProjectPathInTitle", false); + } + set + { + if(value != EditorPrefs.GetBool("UseProjectPathInTitle", false)) + { + EditorPrefs.SetBool("UseProjectPathInTitle", value); + EditorApplication.UpdateMainWindowTitle(); + } + } + } + [SettingsProvider] internal static SettingsProvider CreateGeneralProvider() { @@ -555,7 +573,7 @@ private void ShowGeneral(string searchContext) if (Application.platform == RuntimePlatform.WindowsEditor) { - var progressDialogDelay = EditorGUILayout.DelayedFloatField(GeneralProperties.progressDialogDelay, m_ProgressDialogDelay); + var progressDialogDelay = EditorGUILayout.FloatField(GeneralProperties.progressDialogDelay, m_ProgressDialogDelay); progressDialogDelay = Mathf.Clamp(progressDialogDelay, 0.1f, 1000.0f); if (progressDialogDelay != m_ProgressDialogDelay) { @@ -567,6 +585,8 @@ private void ShowGeneral(string searchContext) GameView.openWindowOnEnteringPlayMode = EditorGUILayout.Toggle(GeneralProperties.enterPlayModeSettingsFocusGameView, GameView.openWindowOnEnteringPlayMode); + useProjectPathInTitle = EditorGUILayout.Toggle(GeneralProperties.useProjectPathInTitle, useProjectPathInTitle); + DrawInteractionModeOptions(); DrawPackageManagerOptions(); @@ -575,7 +595,9 @@ private void ShowGeneral(string searchContext) m_EnableExtendedLogging = EditorGUILayout.Toggle(GeneralProperties.enableExtendedLogging, m_EnableExtendedLogging); + DrawEnableTooltipsInPlayMode(); EditorGUILayout.Space(); + GUILayout.Label(GeneralProperties.hierarchyHeader, EditorStyles.boldLabel); EditorGUI.indentLevel++; @@ -693,6 +715,22 @@ void DrawPerformBumpMapCheck() } } + void DrawEnableTooltipsInPlayMode() + { + const string tooltipsKeyName = "EnableTooltipsInPlayMode"; + var enableTooltips = EditorPrefs.GetBool(tooltipsKeyName, false); + + EditorGUI.BeginChangeCheck(); + enableTooltips = EditorGUILayout.Toggle(GeneralProperties.enablePlayModeTooltips, enableTooltips); + if (EditorGUI.EndChangeCheck()) + { + EditorPrefs.SetBool(tooltipsKeyName, enableTooltips); + + // Transfer native + EditorApplication.UpdateTooltipsInPlayModeSettings(); + } + } + public void ApplyChangesToPrefs(bool force = false) { if (GUI.changed || force) @@ -738,11 +776,7 @@ internal static SortedDictionary>> OrderPre private void RevertColors() { - foreach (KeyValuePair kvp in PrefSettings.Prefs()) - { - kvp.Value.ResetToDefault(); - EditorPrefs.SetString(kvp.Value.Name, kvp.Value.ToUniqueString()); - } + PrefSettings.RevertAll(); } private void ShowColors(string searchContext) @@ -791,6 +825,7 @@ private void ShowSceneView(string searchContext) m_Create3DObjectsAtOrigin = EditorGUILayout.Toggle(SceneViewProperties.createObjectsAtWorldOrigin, m_Create3DObjectsAtOrigin); m_EnableConstrainProportionsScalingForNewObjects = EditorGUILayout.Toggle(SceneViewProperties.enableConstrainProportionsScalingForNewObjects, m_EnableConstrainProportionsScalingForNewObjects); AnnotationUtility.useInspectorExpandedState = EditorGUILayout.Toggle(SceneViewProperties.useInspectorExpandedStateContent, AnnotationUtility.useInspectorExpandedState); + SceneView.s_PreferenceIgnoreAlwaysRefreshWhenNotFocused.value = EditorGUILayout.Toggle(SceneViewProperties.ignoreAlwaysRefreshWhenNotFocused, SceneView.s_PreferenceIgnoreAlwaysRefreshWhenNotFocused); GUILayout.Label("Handles", EditorStyles.boldLabel); Handles.s_LineThickness.value = EditorGUILayout.IntSlider(SceneViewProperties.handlesLineThickness, (int)Handles.s_LineThickness.value, 1, 5); @@ -1011,6 +1046,7 @@ private void ShowDeveloperMode(string searchContext) if (m_DeveloperModeDirty) { ApplyChangesToPrefs(); + EditorApplication.UpdateMainWindowTitle(); } } @@ -1057,8 +1093,7 @@ private void WritePreferences() EditorPrefs.SetBool("ReopenLastUsedProjectOnStartup", m_ReopenLastUsedProjectOnStartup); EditorPrefs.SetBool("EnableEditorAnalytics", m_EnableEditorAnalytics); EditorPrefs.SetBool("SaveScenesBeforeBuilding", m_AutoSaveScenesBeforeBuilding); - - ScriptCompilationDuringPlayEditorPref = m_ScriptCompilationDuringPlay; + EditorPrefs.SetInt("ScriptCompilationDuringPlay", (int)m_ScriptCompilationDuringPlay); // The Preferences window always writes all preferences, we don't want this behavior since we // want the default value to just match "IsSourceBuild" until the developer has explicitly changed it. @@ -1098,29 +1133,13 @@ private void WritePreferences() EditorPrefs.SetBool("GraphSnapping", m_GraphSnapping); EditorPrefs.SetBool("EnableConstrainProportionsTransformScale", m_EnableConstrainProportionsScalingForNewObjects); + EditorPrefs.SetBool("UseInspectorExpandedState", AnnotationUtility.useInspectorExpandedState); EditorPrefs.SetBool("EnableExtendedLogging", m_EnableExtendedLogging); } private int CurrentEditorScalingValue { - get { return Mathf.RoundToInt(GUIUtility.pixelsPerPoint * 100); } - } - - private ScriptChangesDuringPlayOptions ScriptCompilationDuringPlayEditorPref - { - get - { - var scriptCompilationDuringPlay = EditorPrefs.GetInt("ScriptCompilationDuringPlay", 0); - - // Upgrade old RecompileAfterFinishedPlaying setting (1) to RecompileAndContinuePlaying - if (scriptCompilationDuringPlay == 1) - return ScriptChangesDuringPlayOptions.RecompileAndContinuePlaying; - return (ScriptChangesDuringPlayOptions)scriptCompilationDuringPlay; - } - set - { - EditorPrefs.SetInt("ScriptCompilationDuringPlay", (int)value); - } + get {return Mathf.RoundToInt(GUIUtility.pixelsPerPoint * 100); } } private void ReadPreferences() @@ -1166,7 +1185,8 @@ private void ReadPreferences() m_EnableEditorAnalytics = EditorPrefs.GetBool("EnableEditorAnalyticsV2", EditorPrefs.GetBool("EnableEditorAnalytics", true)); - m_ScriptCompilationDuringPlay = ScriptCompilationDuringPlayEditorPref; + m_AutoSaveScenesBeforeBuilding = EditorPrefs.GetBool("SaveScenesBeforeBuilding"); + m_ScriptCompilationDuringPlay = (ScriptChangesDuringPlayOptions)EditorPrefs.GetInt("ScriptCompilationDuringPlay", 0); m_DeveloperMode = Unsupported.IsDeveloperMode(); m_ShowRepaintDots = EditorGUI.s_ShowRepaintDots.value; @@ -1188,6 +1208,7 @@ private void ReadPreferences() m_GpuDevice = EditorPrefs.GetString("GpuDeviceName"); m_EnableConstrainProportionsScalingForNewObjects = EditorPrefs.GetBool("EnableConstrainProportionsTransformScale", false); + AnnotationUtility.useInspectorExpandedState = EditorPrefs.GetBool("UseInspectorExpandedState", false); if (EditorPrefs.HasKey(kContentScalePrefKey)) { diff --git a/Editor/Mono/Progress/Progress.bindings.cs b/Editor/Mono/Progress/Progress.bindings.cs index 37d25ed507..05c33e8d0a 100644 --- a/Editor/Mono/Progress/Progress.bindings.cs +++ b/Editor/Mono/Progress/Progress.bindings.cs @@ -13,11 +13,13 @@ namespace UnityEditor { [StaticAccessor("Editor::Progress", StaticAccessorType.DoubleColon)] - [NativeHeader("Editor/Src/Progress.h")] + [NativeHeader(k_NativeHeader)] public static partial class Progress { + const string k_NativeHeader = "Editor/Src/Progress.h"; + // Keep in sync with Editor\src\Progress.h - [NativeType(Header = "Editor/Src/Progress.h")] + [NativeType(Header = k_NativeHeader)] public enum Status { Running, @@ -27,7 +29,7 @@ public enum Status Paused } - [Flags, NativeType(Header = "Editor/Src/Progress.h")] + [Flags, NativeType(Header = k_NativeHeader)] public enum Options { None = 0 << 0, @@ -38,7 +40,7 @@ public enum Options Unmanaged = 1 << 4 } - [NativeType(Header = "Editor/Src/Progress.h")] + [NativeType(Header = k_NativeHeader)] public enum TimeDisplayMode { NoTimeShown, @@ -46,7 +48,7 @@ public enum TimeDisplayMode ShowRemainingTime } - [NativeType(Header = "Editor/Src/Progress.h")] + [NativeType(Header = k_NativeHeader)] public enum Priority { Unresponsive = 0, @@ -56,7 +58,7 @@ public enum Priority High = 10 } - [Flags, NativeType(Header = "Editor/Src/Progress.h")] + [Flags, NativeType(Header = k_NativeHeader)] internal enum Updates : uint { NothingChanged = 0, @@ -77,8 +79,16 @@ internal enum Updates : uint EverythingChanged = 0xffffffff } + [NativeType(Header = k_NativeHeader)] + internal enum ExplicitLoggingState + { + NotSet, + Enabled, + Disabled + } + [StructLayout(LayoutKind.Sequential)] - [NativeHeader("Editor/Src/Progress.h")] + [NativeHeader(k_NativeHeader)] [RequiredByNativeCode(GenerateProxy = true)] internal struct ProgressIdAndUpdates { @@ -469,7 +479,7 @@ internal static float GetMaxElapsedTime() return maxElapsedTime; } - // For testing purposes only. + // Anything below this line is for testing purposes only. internal static void ClearProgressItems() { s_ProgressItems.Clear(); @@ -480,6 +490,44 @@ internal static void ClearProgressItems() s_RemainingTime = TimeSpan.Zero; s_LastRemainingTimeUpdate = DateTime.Now; } + + internal static ExplicitLoggingState errorLoggingState + { + get => GetExplicitErrorLoggingState(); + set => SetExplicitErrorLoggingState(value); + } + internal static ExplicitLoggingState warningLoggingState + { + get => GetExplicitWarningLoggingState(); + set => SetExplicitWarningLoggingState(value); + } + + [NativeMethod(IsFreeFunction = true, IsThreadSafe = false, Name = "Editor::Progress::Internal_GetExplicitErrorLoggingState")] + static extern ExplicitLoggingState GetExplicitErrorLoggingState(); + + [NativeMethod(IsFreeFunction = true, IsThreadSafe = false, Name = "Editor::Progress::Internal_GetExplicitWarningLoggingState")] + static extern ExplicitLoggingState GetExplicitWarningLoggingState(); + + [NativeMethod(IsFreeFunction = true, IsThreadSafe = false, Name = "Editor::Progress::Internal_SetExplicitErrorLoggingState")] + static extern void SetExplicitErrorLoggingState(ExplicitLoggingState state); + + [NativeMethod(IsFreeFunction = true, IsThreadSafe = false, Name = "Editor::Progress::Internal_SetExplicitWarningLoggingState")] + static extern void SetExplicitWarningLoggingState(ExplicitLoggingState state); + + internal static bool manualUpdate + { + get => IsManualUpdate(); + set => SetManualUpdate(value); + } + + [NativeMethod(IsFreeFunction = true, IsThreadSafe = false, Name = "Editor::Progress::Internal_IsManualUpdate")] + static extern bool IsManualUpdate(); + + [NativeMethod(IsFreeFunction = true, IsThreadSafe = false, Name = "Editor::Progress::Internal_SetManualUpdate")] + static extern void SetManualUpdate(bool manualUpdate); + + [NativeMethod(IsFreeFunction = true, IsThreadSafe = false, Name = "Editor::Progress::ForceUpdateProgress")] + internal static extern void ForceUpdate(); } static class ProgressEnumExtensions diff --git a/Editor/Mono/ProjectBrowser.cs b/Editor/Mono/ProjectBrowser.cs index 3e9e4521d0..cfa0d824e1 100644 --- a/Editor/Mono/ProjectBrowser.cs +++ b/Editor/Mono/ProjectBrowser.cs @@ -21,7 +21,7 @@ namespace UnityEditor { // The title is also used for fetching the project window tab icon: Project.png [EditorWindowTitle(title = "Project", icon = "Project")] - internal class ProjectBrowser : EditorWindow, IHasCustomMenu + internal class ProjectBrowser : EditorWindow, IHasCustomMenu, ISearchableContainer { public const int kPackagesFolderInstanceId = int.MaxValue; public const int kAssetCreationInstanceID_ForNonExistingAssets = Int32.MaxValue - 1; @@ -89,7 +89,9 @@ class Styles public GUIContent m_FilterByImportLog = EditorGUIUtility.TrIconContent("d_console.erroricon.inactive.sml", "Search by Import Log Type"); public GUIContent m_CreateDropdownContent = EditorGUIUtility.IconContent("CreateAddNew"); public GUIContent m_SaveFilterContent = EditorGUIUtility.TrIconContent("Favorite", "Save search"); - public GUIContent m_PackagesVisibilityContent = EditorGUIUtility.TrIconContent("SceneViewVisibility", "Number of hidden packages, click to toggle hidden packages visibility"); + public GUIContent m_PackageContentDefault = new GUIContent("", ""); + public GUIContent m_PackagesContentNotVisible = EditorGUIUtility.TrIconContent("PBrowserPackagesNotVisible", "Number of hidden packages, click to display packages."); + public GUIContent m_PackagesContentVisible = EditorGUIUtility.TrIconContent("PBrowserPackagesVisible", "Number of displayed packages, click to hide packages."); public GUIContent m_EmptyFolderText = EditorGUIUtility.TrTextContent("This folder is empty"); public GUIContent m_SearchIn = EditorGUIUtility.TrTextContent("Search:"); @@ -103,6 +105,7 @@ static GUIStyle GetStyle(string styleName) return styleName; // Implicit construction of GUIStyle } } + static Styles s_Styles; static int s_HashForSearchField = "ProjectBrowserSearchField".GetHashCode(); @@ -135,6 +138,8 @@ internal bool isLocked set { m_LockTracker.isLocked = value; } } + public string searchText => m_SearchFieldText; + bool m_EnableOldAssetTree = true; bool m_FocusSearchField; string m_SelectedPath; @@ -149,8 +154,8 @@ internal bool isLocked bool m_UseTreeViewSelectionInsteadOfMainSelection; bool useTreeViewSelectionInsteadOfMainSelection { - get {return m_UseTreeViewSelectionInsteadOfMainSelection; } - set {m_UseTreeViewSelectionInsteadOfMainSelection = value; } + get { return m_UseTreeViewSelectionInsteadOfMainSelection; } + set { m_UseTreeViewSelectionInsteadOfMainSelection = value; } } @@ -168,7 +173,7 @@ bool useTreeViewSelectionInsteadOfMainSelection // Icon/List area [SerializeField] ObjectListAreaState m_ListAreaState; // state that survives assembly reloads - ObjectListArea m_ListArea; + ObjectListArea m_ListArea; internal ObjectListArea ListArea // Exposed for usage in tests { get { return m_ListArea; } @@ -189,15 +194,15 @@ bool useTreeViewSelectionInsteadOfMainSelection bool m_SkipHiddenPackages; // Layout - const float k_MinHeight = 250; - const float k_MinWidthOneColumn = 230f;// could be 205 with special handling - const float k_MinWidthTwoColumns = 230f; - static float k_ToolbarHeight => EditorGUI.kWindowToolbarHeight; - static float k_BottomBarHeight => EditorGUI.kWindowToolbarHeight; + const float k_MinHeight = 250; + const float k_MinWidthOneColumn = 230f;// could be 205 with special handling + const float k_MinWidthTwoColumns = 230f; + static float k_ToolbarHeight => EditorGUI.kWindowToolbarHeight; + static float k_BottomBarHeight => EditorGUI.kWindowToolbarHeight; [SerializeField] - float m_DirectoriesAreaWidth = k_MinWidthTwoColumns / 2; - const float k_ResizerWidth = 5f; - const float k_SliderWidth = 55f; + float m_DirectoriesAreaWidth = k_MinWidthTwoColumns / 2; + const float k_ResizerWidth = 5f; + const float k_SliderWidth = 55f; [NonSerialized] float m_SearchAreaMenuOffset = -1f; [NonSerialized] @@ -588,10 +593,10 @@ SearchViewState GetSearchViewState() { switch (m_SearchFilter.GetState()) { - case SearchFilter.State.SearchingInAllAssets: return SearchViewState.AllAssets; - case SearchFilter.State.SearchingInAssetsOnly: return SearchViewState.InAssetsOnly; + case SearchFilter.State.SearchingInAllAssets: return SearchViewState.AllAssets; + case SearchFilter.State.SearchingInAssetsOnly: return SearchViewState.InAssetsOnly; case SearchFilter.State.SearchingInPackagesOnly: return SearchViewState.InPackagesOnly; - case SearchFilter.State.SearchingInFolders: return SearchViewState.SubFolders; + case SearchFilter.State.SearchingInFolders: return SearchViewState.SubFolders; } return SearchViewState.NotSearching; } @@ -923,7 +928,7 @@ public void LogTypeListCallback(PopupList.ListElement element) // Toggle clicked element element.selected = !element.selected; - m_SearchFilter.importLogFlags = element.selected ? (UnityEditor.AssetImporters.ImportLogFlags) Enum.Parse(typeof(UnityEditor.AssetImporters.ImportLogFlags), element.types.First()) : ImportLogFlags.None; + m_SearchFilter.importLogFlags = element.selected ? (UnityEditor.AssetImporters.ImportLogFlags)Enum.Parse(typeof(UnityEditor.AssetImporters.ImportLogFlags), element.types.First()) : ImportLogFlags.None; m_SearchFieldText = m_SearchFilter.FilterToSearchFieldString(); TopBarSearchSettingsChanged(); @@ -980,8 +985,8 @@ void SetupLogTypeList() m_LogTypes.m_MaxCount = 2; m_LogTypes.m_SortAlphabetically = true; - m_LogTypes.AddElement("Warnings", new string[]{"Warning"}); - m_LogTypes.AddElement("Errors", new string[]{"Error"}); + m_LogTypes.AddElement("Warnings", new string[] { "Warning" }); + m_LogTypes.AddElement("Errors", new string[] { "Error" }); } void SyncFilterGUI() @@ -1012,21 +1017,12 @@ void ShowFolderContents(int folderInstanceID, bool revealAndFrameInFolderTree) if (folderInstanceID == kPackagesFolderInstanceId) folderPath = PackageManager.Folders.GetPackagesPath(); - - if (m_SearchFilter.GetState() == SearchFilter.State.FolderBrowsing) - { - if (m_SearchFilter.folders.Length == 1 && m_SearchFilter.folders[0] == folderPath) - { - return; // Already showing folder - } - } - if (!m_SkipHiddenPackages || PackageManagerUtilityInternal.IsPathInVisiblePackage(folderPath)) { m_SearchFilter.ClearSearch(); - m_SearchFilter.folders = new[] {folderPath}; + m_SearchFilter.folders = new[] { folderPath }; m_SearchFilter.skipHidden = m_SkipHiddenPackages; - m_FolderTree.SetSelection(new[] {folderInstanceID}, revealAndFrameInFolderTree); + m_FolderTree.SetSelection(new[] { folderInstanceID }, revealAndFrameInFolderTree); FolderTreeSelectionChanged(true); } } @@ -1102,7 +1098,7 @@ void ShowParentFolderOfCurrentlySelected() TreeViewItem item = m_FolderTree.FindItem(selectedFolderInstanceIDs[0]); if (item != null && item.parent != null && item.id != AssetDatabase.GetMainAssetOrInProgressProxyInstanceID("Assets")) { - SetFolderSelection(new[] {item.parent.id}, true); + SetFolderSelection(new[] { item.parent.id }, true); m_ListArea.Frame(item.id, true, false); Selection.activeInstanceID = item.id; } @@ -1398,7 +1394,7 @@ void AssetTreeSelectionCallback(int[] selectedTreeViewInstanceIDs) // The selection could be cancelled if an Inspector with hasUnsavedChanges is opened. // In that case, let's update the tree so the highlight is set back to the actual selection. - if(Selection.instanceIDs != selectedTreeViewInstanceIDs) + if (Selection.instanceIDs != selectedTreeViewInstanceIDs) m_AssetTree.SetSelection(Selection.instanceIDs, true); RefreshSelectedPath(); @@ -1570,11 +1566,11 @@ void ShowAndHideFolderTreeSelectionAsNeeded() case SearchViewState.InPackagesOnly: case SearchViewState.SubFolders: case SearchViewState.NotSearching: - { - if (!isSavedFilterSelected) - m_FolderTree.SetSelection(GetFolderInstanceIDs(m_SearchFilter.folders), true); - } - break; + { + if (!isSavedFilterSelected) + m_FolderTree.SetSelection(GetFolderInstanceIDs(m_SearchFilter.folders), true); + } + break; } } } @@ -1591,10 +1587,10 @@ void InitListArea() m_SearchFilter.skipHidden = m_SkipHiddenPackages; - m_ListArea.InitForSearch(m_ListAreaRect, HierarchyType.Assets, + m_ListArea?.InitForSearch(m_ListAreaRect, HierarchyType.Assets, m_SearchFilter, false, s => AssetDatabase.GetMainAssetInstanceID(s)); - m_ListArea.InitSelection(Selection.instanceIDs); + m_ListArea?.InitSelection(Selection.instanceIDs); } void OnInspectorUpdate() @@ -1788,7 +1784,7 @@ void HandleCommandEventsForTreeView() evt.Use(); if (execute && itemType == ItemType.Asset && AssetClipboardUtility.CanPaste()) { - int[] copiedFolders = AssetClipboardUtility.PasteFolders(); + int[] copiedFolders = AssetClipboardUtility.PasteFolders(); SetFolderSelection(copiedFolders, true); GUIUtility.ExitGUI(); } @@ -2067,7 +2063,7 @@ void OnGUI() // Vertical splitter line between folders and content (drawn before listarea so listarea ping is drawn on top of line) EditorGUIUtility.DrawHorizontalSplitter(new Rect(m_ListAreaRect.x + 1f, EditorGUI.kWindowToolbarHeight, 1, m_TreeViewRect.height)); - if (m_SearchFilter.GetState() == SearchFilter.State.FolderBrowsing && m_ListArea.numItemsDisplayed == 0) + if (m_SearchFilter.GetState() == SearchFilter.State.FolderBrowsing && m_ListArea.numItemsDisplayed == 0) { Vector2 size = EditorStyles.label.CalcSize(s_Styles.m_EmptyFolderText); Rect textRect = new Rect(m_ListAreaRect.x + 2f + Mathf.Max(0, (m_ListAreaRect.width - size.x) * 0.5f), m_ListAreaRect.y + 10f, size.x, 20f); @@ -2110,7 +2106,7 @@ void HandleContextClickInListArea(Rect listRect) case EventType.MouseDown: // This section handles selecting the folders showing their content, if right clicked outside items. // We do this to ensure our assets context menu is operating on the active folder(s) - if (m_ViewMode == ViewMode.TwoColumns && m_SearchFilter.GetState() == SearchFilter.State.FolderBrowsing && evt.button == 1 && !m_ItemSelectedByRightClickThisEvent) + if (m_ViewMode == ViewMode.TwoColumns && m_SearchFilter.GetState() == SearchFilter.State.FolderBrowsing && evt.button == 1 && !m_ItemSelectedByRightClickThisEvent) { if (m_SearchFilter.folders.Length > 0 && listRect.Contains(evt.mousePosition)) { @@ -2258,7 +2254,6 @@ void TopToolbar() { ButtonSaveFilter(); } - ToggleHiddenPackagesVisibility(); } GUILayout.EndHorizontal(); @@ -2297,8 +2292,8 @@ public virtual void AddItemsToMenu(GenericMenu menu) { if (m_EnableOldAssetTree) { - GUIContent assetTreeText = EditorGUIUtility.TrTextContent("One Column Layout"); - GUIContent assetBrowserText = EditorGUIUtility.TrTextContent("Two Column Layout"); + GUIContent assetTreeText = EditorGUIUtility.TrTextContent("One Column Layout"); + GUIContent assetBrowserText = EditorGUIUtility.TrTextContent("Two Column Layout"); menu.AddItem(assetTreeText, m_ViewMode == ViewMode.OneColumn, SetOneColumn); if (position.width >= k_MinWidthTwoColumns) @@ -2350,7 +2345,7 @@ void ButtonSaveFilter() if (treeViewSelection.Length == 1) { int instanceID = treeViewSelection[0]; - bool isRootFilter = SavedSearchFilters.GetRootInstanceID() == instanceID; + bool isRootFilter = SavedSearchFilters.GetRootInstanceID() == instanceID; // Ask if filter should be overwritten if (SavedSearchFilters.IsSavedFilter(instanceID) && !isRootFilter) @@ -2424,8 +2419,10 @@ void LogTypeDropDown() private void ToggleHiddenPackagesVisibility() { - s_Styles.m_PackagesVisibilityContent.text = PackageManagerUtilityInternal.HiddenPackagesCount.ToString(); - var skipHiddenPackage = GUILayout.Toggle(m_SkipHiddenPackages, s_Styles.m_PackagesVisibilityContent, EditorStyles.toolbarButtonRight); + s_Styles.m_PackageContentDefault = m_SkipHiddenPackages ? s_Styles.m_PackagesContentNotVisible : s_Styles.m_PackagesContentVisible; + s_Styles.m_PackageContentDefault.text = PackageManagerUtilityInternal.HiddenPackagesCount.ToString(); + var skipHiddenPackage = GUILayout.Toggle(m_SkipHiddenPackages, s_Styles.m_PackageContentDefault, EditorStyles.toolbarButtonRight); + if (skipHiddenPackage != m_SkipHiddenPackages) { m_SkipHiddenPackages = skipHiddenPackage; @@ -2734,7 +2731,7 @@ void BreadCrumbBar() bool lastElement = i == m_BreadCrumbs.Count - 1; GUIStyle style = lastElement ? EditorStyles.boldLabel : EditorStyles.label; //EditorStyles.miniBoldLabel : EditorStyles.miniLabel;// GUIContent folderContent = m_BreadCrumbs[i].Key; - string folderPath = m_BreadCrumbs[i].Value; + string folderPath = m_BreadCrumbs[i].Value; Vector2 size = style.CalcSize(folderContent); rect.width = size.x; if (GUI.Button(rect, folderContent, style)) @@ -2896,7 +2893,9 @@ public void FrameObject(int instanceID, bool ping) frame = false; // If the item is visible then we can ping it however if it requires revealing then we can not and should indicate why(locked project view). - if ((m_ViewMode == ViewMode.TwoColumns && !m_ListArea.IsShowing(instanceID)) || (m_ViewMode == ViewMode.OneColumn && m_AssetTree.data.GetRow(instanceID) == -1)) + if (canFrame && + ((m_ViewMode == ViewMode.TwoColumns && m_ListArea != null && !m_ListArea.IsShowing(instanceID)) + || (m_ViewMode == ViewMode.OneColumn && m_AssetTree != null && m_AssetTree.data.GetRow(instanceID) == -1))) { Repaint(); m_LockTracker.PingIcon(); @@ -3185,7 +3184,7 @@ static internal void Show(string folder, string currentSubFolder, Rect activator subFolderDisplayNames.Add(Path.GetFileName(subFolderPath)); } - var menu = new GenericMenu {allowDuplicateNames = true}; + var menu = new GenericMenu { allowDuplicateNames = true }; if (subFolders.Count > 0) { var i = 0; diff --git a/Editor/Mono/ProjectBrowser/SavedSearchFilter.cs b/Editor/Mono/ProjectBrowser/SavedSearchFilter.cs index 9b6a27d0dc..0d757d0381 100644 --- a/Editor/Mono/ProjectBrowser/SavedSearchFilter.cs +++ b/Editor/Mono/ProjectBrowser/SavedSearchFilter.cs @@ -10,7 +10,6 @@ using UnityEditor.IMGUI.Controls; using UnityEngine; using UnityEditorInternal; -using UnityEditor.Collaboration; using Object = UnityEngine.Object; @@ -465,22 +464,6 @@ TreeViewItem BuildTreeView() root = item; else { - if (Collab.instance.collabFilters.ContainsSearchFilter(savedFilter.m_Name, savedFilter.m_Filter.FilterToSearchFieldString())) - { - if (!Collab.instance.IsCollabEnabledForCurrentProject()) - continue; - } - - if (SoftlockViewController.Instance.softLockFilters.ContainsSearchFilter(savedFilter.m_Name, savedFilter.m_Filter.FilterToSearchFieldString())) - { - if (CollabSettingsManager.IsAvailable(CollabSettingType.InProgressEnabled)) - { - if (!Collab.instance.IsCollabEnabledForCurrentProject() || !CollabSettingsManager.inProgressEnabled) - continue; - } - else - continue; - } items.Add(item); } } diff --git a/Editor/Mono/ProjectWindow/GlobSearchUtilities.cs b/Editor/Mono/ProjectWindow/GlobSearchUtilities.cs index 749c23bf0e..e6149e3e7b 100644 --- a/Editor/Mono/ProjectWindow/GlobSearchUtilities.cs +++ b/Editor/Mono/ProjectWindow/GlobSearchUtilities.cs @@ -11,8 +11,8 @@ namespace UnityEditor { static class GlobSearchUtilities { - static readonly Regex k_BasicSymbolsRegex = new Regex(@"(?\\\[..+\])|(?\\\*\\\*/)|(?\\\*\\\*)|(?\\\*)|(?\\\?)"); - static readonly Regex k_ComplexSymbolsRegex = new Regex(@"(?\\\(.+(?:\\\|.+)+\\\))"); + static readonly Regex k_BasicSymbolsRegex = new Regex(@"(?\\\[..+?\])|(?\\\*\\\*/)|(?\\\*\\\*)|(?\\\*)|(?\\\?)"); + static readonly Regex k_ComplexSymbolsRegex = new Regex(@"(?\\\(.+?(?:\\\|.+?)+\\\))"); static Dictionary> s_GlobToRegexMatch; static GlobSearchUtilities() diff --git a/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs b/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs index 468e653138..0b9cafa921 100644 --- a/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs +++ b/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs @@ -67,7 +67,7 @@ public override void Action(int instanceId, string pathName, string resourceFile cleanPath); var obj = AssetDatabase.LoadMainAssetAtPath(cleanPath); var name = obj.name; - ObjectFactory.SmartResetObjectToDefault(obj); + ObjectFactory.FinalizeObjectAndAwake(obj); obj.name = name; AssetDatabase.SaveAssetIfDirty(obj); ProjectWindowUtil.FrameObjectInProjectWindow(instanceId); @@ -364,7 +364,7 @@ static void CreatePrefabVariant() string sourcePath = AssetDatabase.GetAssetPath(go); string sourceDir = Path.GetDirectoryName(sourcePath).ConvertSeparatorsToUnity(); - string variantPath = GetPrefabVariantPath(sourceDir, go.name); + string variantPath = GetPrefabVariantPath(sourceDir, go); StartNameEditingIfProjectWindowExists( 0, @@ -395,7 +395,7 @@ static GameObject[] CreatePrefabVariants(GameObject[] gameObjects) { string sourcePath = AssetDatabase.GetAssetPath(go); string sourceDir = Path.GetDirectoryName(sourcePath).ConvertSeparatorsToUnity(); - string variantPath = GetPrefabVariantPath(sourceDir, go.name); + string variantPath = GetPrefabVariantPath(sourceDir, go); variantPath = AssetDatabase.GenerateUniqueAssetPath(variantPath); var variant = PrefabUtility.CreateVariant(go, variantPath); @@ -412,9 +412,12 @@ static GameObject[] CreatePrefabVariants(GameObject[] gameObjects) return createdVariants.ToArray(); } - static string GetPrefabVariantPath(string folder, string gameObjectName) + static string GetPrefabVariantPath(string folder, GameObject gameObject) { - return string.Format("{0}/{1} Variant.prefab", folder, gameObjectName); + if (PrefabUtility.IsPartOfModelPrefab(gameObject)) + return string.Format("{0}/{1}.prefab", folder, gameObject.name); + else + return string.Format("{0}/{1} Variant.prefab", folder, gameObject.name); } public static void CreateAssetWithContent(string filename, string content, Texture2D icon = null) @@ -533,8 +536,8 @@ internal static string RemoveOrInsertNamespace(string content, string rootNamesp if (string.IsNullOrEmpty(rootNamespace)) { - content = Regex.Replace(content, $"((\\r\\n)|\\n)[ \\t]*{rootNamespaceBeginTag}[ \\t]*", string.Empty); - content = Regex.Replace(content, $"((\\r\\n)|\\n)[ \\t]*{rootNamespaceEndTag}[ \\t]*", string.Empty); + content = Regex.Replace(content, $"((\\r\\n)|\\n)?[ \\t]*{rootNamespaceBeginTag}[ \\t]*", string.Empty); + content = Regex.Replace(content, $"((\\r\\n)|\\n)?[ \\t]*{rootNamespaceEndTag}[ \\t]*", string.Empty); return content; } @@ -782,13 +785,13 @@ internal static void GetAncestors(int instanceID, HashSet ancestors) if (instanceID == ProjectBrowser.kPackagesFolderInstanceId) return; - // Ensure we add the main asset as ancestor if input is a subasset + // Ensure we add the main asset as ancestor if input is a sub-asset int mainAssetInstanceID = AssetDatabase.GetMainAssetOrInProgressProxyInstanceID(AssetDatabase.GetAssetPath(instanceID)); bool isSubAsset = mainAssetInstanceID != instanceID; if (isSubAsset) ancestors.Add(mainAssetInstanceID); - // Find ancestors of main aset + // Find ancestors of main asset string currentFolderPath = GetContainingFolder(AssetDatabase.GetAssetPath(mainAssetInstanceID)); while (!string.IsNullOrEmpty(currentFolderPath)) { @@ -1012,16 +1015,24 @@ internal static bool DeleteAssets(List instanceIDs, bool askIfSure) infotext.AppendLine(""); infotext.AppendLine(L10n.Tr("You cannot undo the delete assets action.")); - containsMaterial &= AnyTargetMaterialHasChildren(paths); + if (containsMaterial) + { + // If the assets to be deleted contain a material, check for its children. + // Warning: AnyTargetMaterialHasChildren will load assets so it can be costly. + containsMaterial = AnyTargetMaterialHasChildren(paths); + } + if (containsMaterial) { infotext.AppendLine(); string name = (paths.Length == 1) ? "This Material" : "One or more of these Material(s)"; - infotext.AppendLine(name + " has one or more children. Would you like to reparent all of these children to their closest remaining ancestor?"); - int dialogOptionIndex = EditorUtility.DisplayDialogComplex(title, infotext.ToString(), L10n.Tr("Delete and reparent children"), L10n.Tr("Delete only"), L10n.Tr("Cancel")); - if (dialogOptionIndex == 0) + infotext.AppendLine(name + " is inherited by one or more children. Deleting will result in the children re-mapping to their closest remaining ancestor. Would you like to proceed with re-parenting?"); + + bool dialogOptionIndex = EditorUtility.DisplayDialog(title, infotext.ToString(), L10n.Tr("Delete and re-parent children"), L10n.Tr("Cancel")); + + if (dialogOptionIndex) reparentMaterials = true; - else if (dialogOptionIndex == 2) + else return false; } else if (!EditorUtility.DisplayDialog(title, infotext.ToString(), L10n.Tr("Delete"), L10n.Tr("Cancel"))) diff --git a/Editor/Mono/ProjectWindow/SearchFilter.cs b/Editor/Mono/ProjectWindow/SearchFilter.cs index f6e8a2865f..3eb5e016f6 100644 --- a/Editor/Mono/ProjectWindow/SearchFilter.cs +++ b/Editor/Mono/ProjectWindow/SearchFilter.cs @@ -8,8 +8,6 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; -using UnityEditor.Collaboration; -using UnityEditor.Connect; namespace UnityEditor { @@ -44,10 +42,6 @@ public enum State [SerializeField] private string[] m_AssetBundleNames = new string[0]; [SerializeField] - private string[] m_VersionControlStates = new string[0]; - [SerializeField] - private string[] m_SoftLockControlStates = new string[0]; - [SerializeField] private int[] m_ReferencingInstanceIDs = new int[0]; [SerializeField] private int[] m_SceneHandles; @@ -67,12 +61,13 @@ public enum State [SerializeField] private ImportLogFlags m_ImportLogFlags; + [SerializeField] + private bool m_FilterByTypeIntersection; + // Interface public string nameFilter { get { return m_NameFilter; } set { m_NameFilter = value; }} public string[] classNames { get { return m_ClassNames; } set { m_ClassNames = value; }} public string[] assetLabels { get { return m_AssetLabels; } set { m_AssetLabels = value; }} - public string[] versionControlStates { get { return m_VersionControlStates; } set { m_VersionControlStates = value; }} - public string[] softLockControlStates { get { return m_SoftLockControlStates; } set { m_SoftLockControlStates = value; }} public string[] assetBundleNames { get { return m_AssetBundleNames; } set { m_AssetBundleNames = value; }} public int[] referencingInstanceIDs { get { return m_ReferencingInstanceIDs; } set { m_ReferencingInstanceIDs = value; }} public int[] sceneHandles { get { return m_SceneHandles; } set { m_SceneHandles = value; }} @@ -83,6 +78,7 @@ public enum State public string[] globs { get { return m_Globs; } set { m_Globs = value; }} public string originalText { get => m_OriginalText; set => m_OriginalText = value; } public ImportLogFlags importLogFlags { get => m_ImportLogFlags; set => m_ImportLogFlags = value; } + internal bool filterByTypeIntersection { get => m_FilterByTypeIntersection; set => m_FilterByTypeIntersection = value; } public void ClearSearch() { @@ -94,11 +90,10 @@ public void ClearSearch() m_ReferencingInstanceIDs = new int[0]; m_SceneHandles = new int[0]; m_Globs = new string[0]; - m_VersionControlStates = new string[0]; - m_SoftLockControlStates = new string[0]; m_ShowAllHits = false; m_SkipHidden = false; m_ImportLogFlags = ImportLogFlags.None; + m_FilterByTypeIntersection = false; } bool IsNullOrEmpty(T[] list) @@ -116,8 +111,6 @@ public State GetState() !IsNullOrEmpty(m_ReferencingInstanceIDs) || m_ImportLogFlags != ImportLogFlags.None; - isSearchActive = isSearchActive || !IsNullOrEmpty(m_VersionControlStates); - isSearchActive = isSearchActive || !IsNullOrEmpty(m_SoftLockControlStates); bool foldersActive = !IsNullOrEmpty(m_Folders); @@ -179,16 +172,6 @@ public bool SetNewFilter(SearchFilter newFilter) m_Folders = newFilter.m_Folders; changed = true; } - if (newFilter.m_VersionControlStates != m_VersionControlStates) - { - m_VersionControlStates = newFilter.m_VersionControlStates; - changed = true; - } - if (newFilter.m_SoftLockControlStates != m_SoftLockControlStates) - { - m_SoftLockControlStates = newFilter.m_SoftLockControlStates; - changed = true; - } if (newFilter.m_AssetLabels != m_AssetLabels) { m_AssetLabels = newFilter.m_AssetLabels; @@ -238,6 +221,13 @@ public bool SetNewFilter(SearchFilter newFilter) m_Globs = newFilter.m_Globs; changed = true; } + + if (newFilter.m_FilterByTypeIntersection != m_FilterByTypeIntersection) + { + m_FilterByTypeIntersection = newFilter.m_FilterByTypeIntersection; + changed = true; + } + return changed; } @@ -255,11 +245,6 @@ public override string ToString() if (m_AssetLabels != null && m_AssetLabels.Length > 0) result += "[Labels: " + m_AssetLabels[0] + "]"; - if (m_VersionControlStates != null && m_VersionControlStates.Length > 0) - result += "[VersionStates: " + m_VersionControlStates[0] + "]"; - - if (m_SoftLockControlStates != null && m_SoftLockControlStates.Length > 0) - result += "[SoftLockStates: " + m_SoftLockControlStates[0] + "]"; if (m_AssetBundleNames != null && m_AssetBundleNames.Length > 0) result += "[AssetBundleNames: " + m_AssetBundleNames[0] + "]"; @@ -292,6 +277,11 @@ public override string ToString() result += $"[ImportLog: {ImportLog.Filters.WarningsStr}]"; } + if (m_FilterByTypeIntersection) + { + result += $"[FilterByTypeIntersection]"; + } + return result; } @@ -311,8 +301,6 @@ internal string FilterToSearchFieldString() // See SearchUtility.cs for search tokens AddToString(FormatFilterTokenForSearchEngine("t"), m_ClassNames, ref result); AddToString(FormatFilterTokenForSearchEngine("l"), m_AssetLabels, ref result); - AddToString("v:", m_VersionControlStates, ref result); - AddToString("s:", m_SoftLockControlStates, ref result); AddToString("b:", m_AssetBundleNames, ref result); AddToString("glob:", m_Globs.Select(a => $"\"{a}\"").ToArray(), ref result); diff --git a/Editor/Mono/ProjectWindow/SearchableEditorWindow.cs b/Editor/Mono/ProjectWindow/SearchableEditorWindow.cs index 31763add7f..5baa47c856 100644 --- a/Editor/Mono/ProjectWindow/SearchableEditorWindow.cs +++ b/Editor/Mono/ProjectWindow/SearchableEditorWindow.cs @@ -67,26 +67,15 @@ internal static SearchFilter CreateFilter(string searchString, SearchMode search string m_SearchStringDebounced; Action m_DeregisterDebounceCall; - [MenuItem("CONTEXT/Component/Find References In Scene")] + [MenuItem("CONTEXT/Component/Find References In Scene", secondaryPriority = 15)] private static void OnSearchForReferencesToComponent(MenuCommand command) { var component = command.context as Component; if (component) - { - var searchFilter = "ref:" + component.GetInstanceID() + ":"; - foreach (SearchableEditorWindow sw in searchableWindows) - { - if (sw.m_HierarchyType == HierarchyType.GameObjects) - { - sw.SetSearchFilter(searchFilter, SearchMode.All, false, false); - sw.m_HasSearchFilterFocus = true; - sw.Repaint(); - } - } - } + SearchForReferencesToInstanceID(component.GetInstanceID()); } - [MenuItem("CONTEXT/Component/Properties...", false, 99999)] + [MenuItem("CONTEXT/Component/Properties...", priority = 99999)] private static void OnOpenPropertiesToComponent(MenuCommand command) { var component = command.context as Component; @@ -118,6 +107,18 @@ private static bool OnSearchForReferencesValidate() return false; } + [MenuItem("Assets/Find References In Project", false, 26)] + private static void OnSearchForReferencesInProject() + { + SearchForReferencesInProject(Selection.activeObject); + } + + [MenuItem("Assets/Find References In Project", true)] + private static bool OnSearchForReferencesInProjectValidate() + { + return Selection.activeObject && Selection.activeObject != null; + } + virtual public void OnEnable() { SearchService.SearchService.syncSearchChanged += OnSyncSearchChanged; @@ -195,6 +196,11 @@ internal SearchMode searchMode set { m_SearchMode = value; } } + internal static void SearchForReferencesInProject(UnityEngine.Object obj) + { + CommandService.Execute("OpenToFindReferenceOnObject", CommandHint.Menu, obj); + } + internal static void SearchForReferencesToInstanceID(int instanceID) { string searchFilter; diff --git a/Editor/Mono/RenderDoc/RenderDoc.bindings.cs b/Editor/Mono/RenderDoc/RenderDoc.bindings.cs index caf581e060..bb4acf7f55 100644 --- a/Editor/Mono/RenderDoc/RenderDoc.bindings.cs +++ b/Editor/Mono/RenderDoc/RenderDoc.bindings.cs @@ -19,7 +19,7 @@ public static partial class RenderDoc static WindowAction RenderDocGlobalAction() { // Developer-mode render doc button to enable capturing any HostView content/panels - var action = WindowAction.CreateWindowActionButton("RenderDoc", CaptureRenderDocFullContent, null, ContainerWindow.kButtonWidth + 1, RenderDocCaptureButton); + var action = WindowAction.CreateWindowActionButton("RenderDoc", CaptureRenderDoc, null, ContainerWindow.kButtonWidth + 1, RenderDocCaptureButton); action.validateHandler = ShowRenderDocButton; return action; } @@ -45,9 +45,12 @@ internal static bool RenderDocCaptureButton(EditorWindow view, WindowAction self return GUI.Button(r2, s_RenderDocContent, EditorStyles.iconButton); } - private static void CaptureRenderDocFullContent(EditorWindow view, WindowAction self) + private static void CaptureRenderDoc(EditorWindow view, WindowAction self) { - view.m_Parent.CaptureRenderDocFullContent(); + if (view is GameView) + view.m_Parent.CaptureRenderDocScene(); + else + view.m_Parent.CaptureRenderDocFullContent(); } private static bool ShowRenderDocButton(EditorWindow view, WindowAction self) @@ -73,7 +76,7 @@ internal static void CaptureRenderDoc() } else if (EditorWindow.focusedWindow != null) { - CaptureRenderDocFullContent(EditorWindow.focusedWindow, null); + CaptureRenderDoc(EditorWindow.focusedWindow, null); } } diff --git a/Editor/Mono/SceneHierarchy.cs b/Editor/Mono/SceneHierarchy.cs index bdb1d5665b..cf874cb636 100644 --- a/Editor/Mono/SceneHierarchy.cs +++ b/Editor/Mono/SceneHierarchy.cs @@ -463,14 +463,27 @@ void PlayModeStateChanged(PlayModeStateChange state) void OnSceneCreated(Scene scene, NewSceneSetup setup, NewSceneMode mode) { + ClearSearchSessionIfAny(); ExpandTreeViewItem(scene.handle, true); } void OnSceneOpened(Scene scene, OpenSceneMode mode) { + ClearSearchSessionIfAny(); ExpandTreeViewItem(scene.handle, true); } + void ClearSearchSessionIfAny() + { + var dataSource = treeView.data as GameObjectTreeViewDataSource; + if (dataSource != null && !string.IsNullOrEmpty(dataSource.searchString)) + { + var oldSearch = dataSource.searchString; + dataSource.searchString = ""; + dataSource.searchString = oldSearch; + } + } + internal void ExpandTreeViewItem(int id, bool expand) { var dataSource = treeView.data as TreeViewDataSource; @@ -994,7 +1007,7 @@ bool GetIsNotEditable() return false; } - static bool IsSelectedOrChildOfSelection(Transform transform) + static bool IsChildOfSelectionOrSelected(Transform transform) { if (transform == null) return false; @@ -1024,17 +1037,21 @@ void ExecuteCommands() } bool execute = evt.type == EventType.ExecuteCommand; + // If the custom parent for new objects is set we don't allow cut, copy or duplicate any ancestors of this object + // as it might be context objects (as in Prefab Mode in Context). + bool allowCutCopyAndDuplicate = m_CustomParentForNewGameObjects == null + || !IsChildOfSelectionOrSelected(m_CustomParentForNewGameObjects.parent); if (evt.commandName == EventCommandNames.Delete || evt.commandName == EventCommandNames.SoftDelete) { - if (execute && !IsSelectedOrChildOfSelection(m_CustomParentForNewGameObjects)) + if (execute && !IsChildOfSelectionOrSelected(m_CustomParentForNewGameObjects)) DeleteGO(); evt.Use(); GUIUtility.ExitGUI(); } else if (evt.commandName == EventCommandNames.Duplicate) { - if (execute) + if (execute && allowCutCopyAndDuplicate) DuplicateGO(); evt.Use(); GUIUtility.ExitGUI(); @@ -1048,12 +1065,13 @@ void ExecuteCommands() } else if (evt.commandName == EventCommandNames.Cut) { - ClipboardUtility.CutGO(); + if (allowCutCopyAndDuplicate) + ClipboardUtility.CutGO(); GUIUtility.ExitGUI(); } else if (evt.commandName == EventCommandNames.Copy) { - if (execute) + if (execute && allowCutCopyAndDuplicate) ClipboardUtility.CopyGO(); evt.Use(); GUIUtility.ExitGUI(); @@ -1136,7 +1154,7 @@ void CreateSubSceneGameObjectContextClick(GenericMenu menu, int contextClickedIt menu.AddSeparator(""); - if (IsSelectedOrChildOfSelection(m_CustomParentForNewGameObjects)) + if (IsChildOfSelectionOrSelected(m_CustomParentForNewGameObjects)) menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Delete GameObject")); else menu.AddItem(EditorGUIUtility.TrTextContent("Delete GameObject"), false, DeleteGO); @@ -1144,8 +1162,20 @@ void CreateSubSceneGameObjectContextClick(GenericMenu menu, int contextClickedIt void CreateGameObjectContextClick(GenericMenu menu, int contextClickedItemID) { - menu.AddItem(EditorGUIUtility.TrTextContent("Cut"), false, ClipboardUtility.CutGO); - menu.AddItem(EditorGUIUtility.TrTextContent("Copy"), false, ClipboardUtility.CopyGO); + bool itemIsSelected = Selection.gameObjects.Length > 0; + // If the custom parent for new objects is set we don't allow cut, copy or duplicate any ancestors of this object + // as it might be context objects (as in Prefab Mode in Context). + bool allowCutCopyAndDuplicate = m_CustomParentForNewGameObjects == null + || !IsChildOfSelectionOrSelected(m_CustomParentForNewGameObjects.parent); + + if (itemIsSelected && allowCutCopyAndDuplicate) + menu.AddItem(EditorGUIUtility.TrTextContent("Cut"), false, ClipboardUtility.CutGO); + else + menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Cut")); + if (itemIsSelected && allowCutCopyAndDuplicate) + menu.AddItem(EditorGUIUtility.TrTextContent("Copy"), false, ClipboardUtility.CopyGO); + else + menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Copy")); if (CutBoard.CanGameObjectsBePasted() || Unsupported.CanPasteGameObjectsFromPasteboard()) menu.AddItem(EditorGUIUtility.TrTextContent("Paste"), false, PasteGO); else @@ -1156,17 +1186,22 @@ void CreateGameObjectContextClick(GenericMenu menu, int contextClickedItemID) menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Paste As Child")); menu.AddSeparator(""); - // TODO: Add this back in. - if (!hasSearchFilter && m_TreeViewState.selectedIDs.Count == 1 && !GetIsNotEditable()) + + if (itemIsSelected && !hasSearchFilter && m_TreeViewState.selectedIDs.Count == 1 && !GetIsNotEditable()) menu.AddItem(EditorGUIUtility.TrTextContent("Rename"), false, RenameGO); else menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Rename")); - menu.AddItem(EditorGUIUtility.TrTextContent("Duplicate"), false, DuplicateGO); - if (IsSelectedOrChildOfSelection(m_CustomParentForNewGameObjects)) + if (itemIsSelected && allowCutCopyAndDuplicate) + menu.AddItem(EditorGUIUtility.TrTextContent("Duplicate"), false, DuplicateGO); + else + menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Duplicate")); + + if (m_CustomParentForNewGameObjects != null && IsChildOfSelectionOrSelected(m_CustomParentForNewGameObjects) || !itemIsSelected) menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Delete")); else menu.AddItem(EditorGUIUtility.TrTextContent("Delete"), false, DeleteGO); + menu.AddSeparator(""); @@ -1175,6 +1210,15 @@ void CreateGameObjectContextClick(GenericMenu menu, int contextClickedItemID) else menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Select Children")); + menu.AddSeparator(""); + if (Selection.activeGameObject) + { + menu.AddItem(EditorGUIUtility.TrTextContent("Find References in Scene"), false, FindReferenceInScene); + } + else + { + menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Find References in Scene")); + } menu.AddSeparator(""); @@ -1213,10 +1257,14 @@ void CreateGameObjectContextClick(GenericMenu menu, int contextClickedItemID) } } - if (!string.IsNullOrEmpty(assetPath)) - { + var contextClickedGameObject = contextClickedItemID == 0 ? null : EditorUtility.InstanceIDToObject(contextClickedItemID) as GameObject; + var isSelectPrefabRootAvailable = IsSelectPrefabRootAvailable(); + + if (contextClickedGameObject != null || !string.IsNullOrEmpty(assetPath)) menu.AddSeparator(""); + if (!string.IsNullOrEmpty(assetPath)) + { if (PrefabUtility.IsPartOfModelPrefab(prefabAsset)) { menu.AddItem(EditorGUIUtility.TrTextContent("Prefab/Open Model"), false, () => @@ -1239,6 +1287,7 @@ void CreateGameObjectContextClick(GenericMenu menu, int contextClickedItemID) if (!string.IsNullOrEmpty(assetPath)) { + menu.AddSeparator("Prefab/"); menu.AddItem(EditorGUIUtility.TrTextContent("Prefab/Select Asset"), false, () => { Selection.activeObject = prefabAsset; @@ -1246,7 +1295,7 @@ void CreateGameObjectContextClick(GenericMenu menu, int contextClickedItemID) }); } - if (IsSelectPrefabRootAvailable()) + if (isSelectPrefabRootAvailable) { menu.AddItem(EditorGUIUtility.TrTextContent("Prefab/Select Root"), false, SelectPrefabRoot); } @@ -1289,10 +1338,24 @@ void CreateGameObjectContextClick(GenericMenu menu, int contextClickedItemID) ); } + if (contextClickedGameObject != null) + { + menu.AddSeparator("Prefab/"); + List listOfInstanceRoots; + List listOfPlainGameObjects; + PrefabReplaceUtility.FindGameObjectsToReplace(contextClickedGameObject, out listOfPlainGameObjects, out listOfInstanceRoots); + + var multiselection = listOfInstanceRoots.Count > 1 || listOfPlainGameObjects.Count > 1; + + PrefabReplaceUtility.AddReplaceMenuItemsToMenuBasedOnCurrentSelection(menu, "Prefab/", contextClickedGameObject, listOfInstanceRoots, listOfPlainGameObjects, null); + } + if (AnyOutermostPrefabRoots()) { + menu.AddSeparator("Prefab/"); menu.AddItem(EditorGUIUtility.TrTextContent("Prefab/Unpack"), false, UnpackPrefab); menu.AddItem(EditorGUIUtility.TrTextContent("Prefab/Unpack Completely"), false, UnpackPrefabCompletely); + menu.AddSeparator("Prefab/"); menu.AddItem(EditorGUIUtility.TrTextContent("Prefab/Check for Unused Overrides"), false, RemoveSelectedPrefabInstanceUnusedOverrides); } @@ -1330,6 +1393,14 @@ void CreateGameObjectContextClick(GenericMenu menu, int contextClickedItemID) menu.ShowAsContext(); } + private void FindReferenceInScene() + { + var selectedObject = Selection.activeObject; + if (!selectedObject) + return; + SearchableEditorWindow.SearchForReferencesToInstanceID(selectedObject.GetInstanceID()); + } + protected void AddCreateGameObjectItemsToSceneMenu(GenericMenu menu, Scene scene) { AddCreateGameObjectItemsToMenu(menu, Selection.transforms.Select(t => t.gameObject).ToArray(), false, false, true, scene.handle, MenuUtils.ContextMenuOrigin.Scene); diff --git a/Editor/Mono/SceneHierarchyWindow.cs b/Editor/Mono/SceneHierarchyWindow.cs index 6ffe17a549..70c2437c87 100644 --- a/Editor/Mono/SceneHierarchyWindow.cs +++ b/Editor/Mono/SceneHierarchyWindow.cs @@ -12,7 +12,7 @@ namespace UnityEditor { [EditorWindowTitle(title = "Hierarchy", useTypeNameAsIconName = true)] - internal class SceneHierarchyWindow : SearchableEditorWindow, IHasCustomMenu, IPropertySourceOpener + internal class SceneHierarchyWindow : SearchableEditorWindow, IHasCustomMenu, IPropertySourceOpener, ISearchableContainer { public static SceneHierarchyWindow lastInteractedHierarchyWindow { get { return s_LastInteractedHierarchy; } } static SceneHierarchyWindow s_LastInteractedHierarchy; @@ -38,6 +38,8 @@ static class Styles bool showingStageHeader { get { return !(StageNavigationManager.instance.currentStage is MainStage); } } + public string searchText => m_SearchFilter; + void Awake() { m_HierarchyType = HierarchyType.GameObjects; diff --git a/Editor/Mono/SceneManagement/EditorSceneManager.cs b/Editor/Mono/SceneManagement/EditorSceneManager.cs index 8ca4e14cb7..0d5a95feb9 100644 --- a/Editor/Mono/SceneManagement/EditorSceneManager.cs +++ b/Editor/Mono/SceneManagement/EditorSceneManager.cs @@ -10,6 +10,7 @@ namespace UnityEditor.SceneManagement { public sealed partial class EditorSceneManager { + public delegate void SceneManagerSetupRestoredCallback(Scene[] scenes); public delegate void NewSceneCreatedCallback(Scene scene, NewSceneSetup setup, NewSceneMode mode); public delegate void SceneOpeningCallback(string path, OpenSceneMode mode); public delegate void SceneOpenedCallback(Scene scene, OpenSceneMode mode); @@ -19,30 +20,41 @@ public sealed partial class EditorSceneManager public delegate void SceneSavedCallback(Scene scene); public delegate void SceneDirtiedCallback(Scene scene); + public static event SceneManagerSetupRestoredCallback sceneManagerSetupRestored + { + add => m_SceneManagerSetupRestoredEvent.Add(value); + remove => m_SceneManagerSetupRestoredEvent.Remove(value); + } + private static EventWithPerformanceTracker m_SceneManagerSetupRestoredEvent = new EventWithPerformanceTracker($"{nameof(EditorSceneManager)}.{nameof(sceneManagerSetupRestored)}"); + public static event NewSceneCreatedCallback newSceneCreated { add => m_NewSceneCreatedEvent.Add(value); remove => m_NewSceneCreatedEvent.Remove(value); } private static EventWithPerformanceTracker m_NewSceneCreatedEvent = new EventWithPerformanceTracker($"{nameof(EditorSceneManager)}.{nameof(newSceneCreated)}"); + public static event SceneOpeningCallback sceneOpening { add => m_SceneOpeningEvent.Add(value); remove => m_SceneOpeningEvent.Remove(value); } private static EventWithPerformanceTracker m_SceneOpeningEvent = new EventWithPerformanceTracker($"{nameof(EditorSceneManager)}.{nameof(sceneOpening)}"); + public static event SceneOpenedCallback sceneOpened { add => m_SceneOpenedEvent.Add(value); remove => m_SceneOpenedEvent.Remove(value); } private static EventWithPerformanceTracker m_SceneOpenedEvent = new EventWithPerformanceTracker($"{nameof(EditorSceneManager)}.{nameof(sceneOpened)}"); + public static event SceneClosingCallback sceneClosing { add => m_SceneClosingEvent.Add(value); remove => m_SceneClosingEvent.Remove(value); } private static EventWithPerformanceTracker m_SceneClosingEvent = new EventWithPerformanceTracker($"{nameof(EditorSceneManager)}.{nameof(sceneClosing)}"); + public static event SceneClosedCallback sceneClosed { add => m_SceneClosedEvent.Add(value); @@ -68,6 +80,13 @@ public static event SceneDirtiedCallback sceneDirtied } private static EventWithPerformanceTracker m_SceneDirtiedEvent = new EventWithPerformanceTracker($"{nameof(EditorSceneManager)}.{nameof(sceneDirtied)}"); + [RequiredByNativeCode] + private static void Internal_SceneManagerSetupRestored(Scene[] scenes) + { + foreach (var evt in m_SceneManagerSetupRestoredEvent) + evt(scenes); + } + [RequiredByNativeCode] private static void Internal_NewSceneCreated(Scene scene, NewSceneSetup setup, NewSceneMode mode) { diff --git a/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStage.cs b/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStage.cs index ee0be221ae..4aab4273f3 100644 --- a/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStage.cs +++ b/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStage.cs @@ -35,7 +35,7 @@ static class Styles public static GUIContent contextLabel = EditorGUIUtility.TrTextContent("Context:"); public static GUIContent[] contextRenderModeTexts = new[] { EditorGUIUtility.TrTextContent("Normal"), EditorGUIUtility.TrTextContent("Gray"), EditorGUIUtility.TrTextContent("Hidden") }; public static StageUtility.ContextRenderMode[] contextRenderModeOptions = new[] { StageUtility.ContextRenderMode.Normal, StageUtility.ContextRenderMode.GreyedOut, StageUtility.ContextRenderMode.Hidden }; - public static GUIContent showOverridesLabel = EditorGUIUtility.TrTextContent("Show Overrides", "Visualize overrides from the Prefab instance on the Prefab Asset. Overrides on the root Transform are always visualized."); + public static GUIContent showOverridesLabel = EditorGUIUtility.TrTextContent("Show Overrides", "Visualize property overrides from the Prefab instance on the Prefab Asset. Overrides on the root Transform are always visualized."); public static GUIContent showOverridesLabelWithTooManyOverridesTooltip = EditorGUIUtility.TrTextContent("Show Overrides", "Show Overrides are disabled because there are too many overrides to visualize. Overrides on the root Transform are always visualized though."); static Styles() @@ -47,6 +47,8 @@ static Styles() } } + internal static string s_PrefabInContextPreviewValuesTooltip = L10n.Tr("This property is previewing the overridden value on the Prefab instance.\n\nTo edit this property, open this Prefab Asset in isolation by pressing the modifier key [Alt] while you open it."); + public enum Mode { InIsolation, @@ -451,7 +453,23 @@ void SetPrefabInstanceHiddenForInContextEditing(bool hide) StageUtility.SetPrefabInstanceHiddenForInContextEditing(m_OpenedFromInstanceRoot, hide); } - static List s_ReusableCanvasList = new List(); + void UpdateSortableComponentsWithStagePriority(int stagePriority) + { + List rendererList = new List(); + List canvasList = new List(); + + // Renderer components requires to know there is stage priority when entering context and isolation + // mode. If this is not shared, their sorting order will not be evaluated accordingly since they are not + // in the same layer. + m_PrefabContentsRoot.GetComponentsInChildren(true, rendererList); + m_PrefabContentsRoot.GetComponentsInChildren(true, canvasList); + + foreach (Renderer renderer in rendererList) + renderer.stagePriority = (byte)stagePriority; + + foreach (Canvas canvas in canvasList) + canvas.stagePriority = (byte)stagePriority; + } bool LoadStage() { @@ -497,13 +515,11 @@ bool LoadStage() if (m_PrefabContentsRoot != null) { // Corresponds to which breadcrumb this is. - int stagePriority = StageNavigationManager.instance.stageHistory.IndexOf(this); + var stagePriority = StageNavigationManager.instance.stageHistory.IndexOf(this); if (isUIPrefab) { - m_PrefabContentsRoot.GetComponentsInChildren(true, s_ReusableCanvasList); - foreach (Canvas canvas in s_ReusableCanvasList) - canvas.stagePriority = (byte)stagePriority; + UpdateSortableComponentsWithStagePriority(stagePriority); if (m_Mode == Mode.InIsolation && m_PrefabContentsRoot.transform.parent == null) PrefabStageUtility.HandleUIReparentingIfNeeded(m_PrefabContentsRoot, stagePriority); @@ -546,6 +562,7 @@ bool LoadStage() dummyCanvas.sortingOrder = instanceCanvas.sortingOrder; dummyCanvas.referencePixelsPerUnit = instanceCanvas.referencePixelsPerUnit; dummyCanvas.stagePriority = (byte)stagePriority; + dummyCanvas.sortingLayerID = instanceCanvas.sortingLayerID; } } @@ -657,7 +674,7 @@ protected internal override void OnReturnToStage() } } - bool HasPatchedPropertyModificationsFor(UnityEngine.Object obj, string partialPropertyName) + internal bool HasPatchedPropertyModificationsFor(UnityEngine.Object obj, string partialPropertyName) { if (m_PatchedProperties == null) return false; @@ -691,8 +708,9 @@ void RecordPatchedPropertiesForContent() if (PrefabUtility.GetPrefabInstanceStatus(openedFromInstanceRoot) != PrefabInstanceStatus.Connected) return; - Dictionary contentObjectsFromFileID = new Dictionary(); - Dictionary instanceTransformsFromFileID = new Dictionary(); + var contentObjectsFromFileID = new Dictionary(); + var instanceTransformsFromFileID = new Dictionary(); + var targetsToSerializedTargets = new Dictionary(); TransformVisitor visitor = new TransformVisitor(); @@ -806,6 +824,35 @@ void RecordPatchedPropertiesForContent() mod.value = modFromValue.value; } + if (!targetsToSerializedTargets.ContainsKey(targetInContent)) + { + targetsToSerializedTargets[targetInContent] = new SerializedObject(targetInContent); + } + var serializedTargetInContent = targetsToSerializedTargets[targetInContent]; + var serializedPropertyInContent = serializedTargetInContent.FindProperty(mod.propertyPath); + if (serializedPropertyInContent != null) + { + // Ignore [SerializedReference] modifications + if (serializedPropertyInContent.propertyType == SerializedPropertyType.ManagedReference) + { + continue; + } + + // Ignore non persistent ObjectReference modifications + if (serializedPropertyInContent.propertyType == SerializedPropertyType.ObjectReference && mod.objectReference != null && !IsPersistent(mod.objectReference)) + { + continue; + } + } + + // Info for ManagedReference fields can't be retrieved using the property path + // All overrides for ManagedReference fields start with "managedReferences[" + if (mod.propertyPath.StartsWith("managedReferences[")) + { + // [SerializedReference] - it can't be applied in all scenarios, ignore it + continue; + } + DrivenPropertyManager.TryRegisterProperty(this, targetInContent, mod.propertyPath); m_PatchedProperties.Add(new PatchedProperty() { modification = mod, targetInContent = targetInContent }); } @@ -1243,7 +1290,15 @@ void DetectPrefabFileIconChange() void DetectSceneDirtinessChange() { if (scene.dirtyID != m_LastSceneDirtyID) + { + // We want to make sure that all the new sortable components (e.g, canvas, renderer) being added have + // the correct StagePriority assigned to them. Otherwise they won't be sorted accordingly when in + // context/isolation mode. + if (PrefabStageUtility.IsUIPrefab(m_PrefabAssetPath)) + UpdateSortableComponentsWithStagePriority(StageNavigationManager.instance.stageHistory.IndexOf(this)); + SceneView.RepaintAll(); + } m_LastSceneDirtyID = scene.dirtyID; } @@ -1920,7 +1975,8 @@ internal bool showOverrides if (prefabStage != null) prefabStage.RefreshPatchedProperties(); } - EditorApplication.RequestRepaintAllViews(); + + PropertyEditor.ClearAndRebuildAll(); } } diff --git a/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStageUtility.cs b/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStageUtility.cs index 0c76a3cf06..d743be4849 100644 --- a/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStageUtility.cs +++ b/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStageUtility.cs @@ -235,7 +235,9 @@ internal static bool CheckIfAnyComponentShouldBlockPrefabModeInPlayMode(string p if (warnList.Count > 0) { - string blockingNames = string.Join(", ", warnList.Select(e => MonoScript.FromMonoBehaviour(e).name).ToArray()); + string blockingNames = string.Join(", ", warnList.Select(e => MonoScript.FromMonoBehaviour(e).name).Distinct().ToArray()); + if (blockingNames.Length > 1000) + blockingNames = blockingNames.Substring(0, 1000) + "\n..."; return EditorUtility.DisplayDialog( L10n.Tr("Risk of unwanted modifications"), string.Format( @@ -294,13 +296,6 @@ internal static bool IsGameObjectThePrefabRootInAnyPrefabStage(GameObject gameOb return false; } - [UsedByNativeCode] - internal static bool IsGameObjectInInvalidPrefabStage(GameObject gameObject) - { - PrefabStage prefabStage = GetCurrentPrefabStage(); - return (prefabStage != null && prefabStage.scene == gameObject.scene && !prefabStage.isValid); - } - [UsedByNativeCode] internal static bool IsPrefabStageScene(Scene scene) { @@ -665,13 +660,14 @@ internal static GUIContent GetPrefabButtonContent(int instanceID) { GUIContent result; var defaultPrefabMode = PreferencesProvider.GetDefaultPrefabModeForHierarchy(); + var modifierKey = Application.platform == RuntimePlatform.OSXEditor ? "Option" : "Alt"; switch (defaultPrefabMode) { case PrefabStage.Mode.InContext: - result = new GUIContent("", null, $"Open Prefab Asset in context.\nPress modifier key [Alt] to open in isolation."); + result = new GUIContent("", null, $"Open Prefab Asset in context.\nPress the {modifierKey} modifier key to open in isolation."); break; case PrefabStage.Mode.InIsolation: - result = new GUIContent("", null, "Open Prefab Asset in isolation.\nPress modifier key [Alt] to open in context."); + result = new GUIContent("", null, $"Open Prefab Asset in isolation.\nPress the {modifierKey} modifier key to open in context."); break; default: result = new GUIContent(""); diff --git a/Editor/Mono/SceneManagement/StageManager/StageUtility.bindings.cs b/Editor/Mono/SceneManagement/StageManager/StageUtility.bindings.cs index 3afb05e064..4d50daea01 100644 --- a/Editor/Mono/SceneManagement/StageManager/StageUtility.bindings.cs +++ b/Editor/Mono/SceneManagement/StageManager/StageUtility.bindings.cs @@ -61,5 +61,8 @@ public static partial class StageUtility [StaticAccessor("StageUtilityBindings", StaticAccessorType.DoubleColon)] extern private static void CallAwakeFromLoadOnSubHierarchyInternal([NotNull("NullExceptionObject")] GameObject prefabInstanceRoot); + + [StaticAccessor("StageUtility", StaticAccessorType.DoubleColon)] + extern internal static bool IsGizmoCulledBySceneCullingMasksOrFocusedScene([NotNull] GameObject gameObject, [NotNull] Camera camera); } } diff --git a/Editor/Mono/SceneModeWindows/DefaultLightingExplorerExtension.cs b/Editor/Mono/SceneModeWindows/DefaultLightingExplorerExtension.cs index ca1dd05b43..47b9f227dc 100644 --- a/Editor/Mono/SceneModeWindows/DefaultLightingExplorerExtension.cs +++ b/Editor/Mono/SceneModeWindows/DefaultLightingExplorerExtension.cs @@ -23,6 +23,7 @@ private static class Styles public static readonly GUIContent Shape = EditorGUIUtility.TrTextContent("Shape"); public static readonly GUIContent Mode = EditorGUIUtility.TrTextContent("Mode"); public static readonly GUIContent Color = EditorGUIUtility.TrTextContent("Color"); + public static readonly GUIContent Range = EditorGUIUtility.TrTextContent("Range"); public static readonly GUIContent Intensity = EditorGUIUtility.TrTextContent("Intensity"); public static readonly GUIContent IndirectMultiplier = EditorGUIUtility.TrTextContent("Indirect Multiplier"); public static readonly GUIContent ShadowType = EditorGUIUtility.TrTextContent("Shadows"); @@ -340,7 +341,19 @@ protected virtual LightingExplorerTableColumn[] GetLightColumns() } }, null, null, new int[] { 2 }), // 4: Mode new LightingExplorerTableColumn(LightingExplorerTableColumn.DataType.Color, Styles.Color, "m_Color", 70), // 5: Color - new LightingExplorerTableColumn(LightingExplorerTableColumn.DataType.Float, Styles.Intensity, "m_Intensity", 60), // 6: Intensity + new LightingExplorerTableColumn(LightingExplorerTableColumn.DataType.Float, Styles.Range, "m_Range", 60, (r, prop, dep) => + { + var lightType = prop.serializedObject.FindProperty("m_Type"); + if (lightType != null) + { + bool directionalLight = lightType.enumValueIndex == (int)LightType.Directional; + if (!directionalLight) + { + EditorGUI.PropertyField(r, prop, GUIContent.none); + } + } + }), // 6: Range + new LightingExplorerTableColumn(LightingExplorerTableColumn.DataType.Float, Styles.Intensity, "m_Intensity", 60), // 7: Intensity new LightingExplorerTableColumn(LightingExplorerTableColumn.DataType.Float, Styles.IndirectMultiplier, "m_BounceIntensity", 110, (r, prop, dep) => { bool realtimeLight = dep.Length > 1 && dep[0].intValue == (int)LightmapBakeType.Realtime; @@ -349,7 +362,7 @@ protected virtual LightingExplorerTableColumn[] GetLightColumns() { EditorGUI.PropertyField(r, prop, GUIContent.none); } - }, null, null, new int[] { 4 }), // 7: Indirect Multiplier + }, null, null, new int[] { 4 }), // 8: Indirect Multiplier new LightingExplorerTableColumn(LightingExplorerTableColumn.DataType.Enum, Styles.ShadowType, "m_Shadows.m_Type", 100, (r, prop, dep) => { bool areaLight = dep.Length > 1 && (dep[0].enumValueIndex == (int)LightType.Rectangle || dep[0].enumValueIndex == (int)LightType.Disc); @@ -370,7 +383,7 @@ protected virtual LightingExplorerTableColumn[] GetLightColumns() { EditorGUI.PropertyField(r, prop, GUIContent.none); } - }, null, null, new int[] { 2 }), // 8: Shadow Type + }, null, null, new int[] { 2 }), // 9: Shadow Type }; } @@ -458,7 +471,9 @@ protected virtual LightingExplorerTableColumn[] GetEmissivesColumns() if (EditorGUI.EndChangeCheck()) { Material material = (Material)prop.serializedObject.targetObject; + Undo.RecordObject(material, $"Modify Emission Flags of {material.name}"); material.globalIlluminationFlags = giFlags; + EditorUtility.SetDirty(material); prop.serializedObject.Update(); } @@ -482,7 +497,9 @@ protected virtual LightingExplorerTableColumn[] GetEmissivesColumns() if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(material, $"Modify Emission Flags of {material.name}"); material.SetColor("_EmissionColor", newValue); + EditorUtility.SetDirty(material); } EditorGUI.EndProperty(); } @@ -498,7 +515,9 @@ protected virtual LightingExplorerTableColumn[] GetEmissivesColumns() Color color = sourceMaterial.GetColor("_EmissionColor"); Material targetMaterial = (Material)target.serializedObject.targetObject; + Undo.RecordObject(targetMaterial, $"Modify Emission Flags of {targetMaterial.name}"); targetMaterial.SetColor("_EmissionColor", color); + EditorUtility.SetDirty(targetMaterial); }) // 3: Color }; } diff --git a/Editor/Mono/SceneModeWindows/LightingExplorerWindow.cs b/Editor/Mono/SceneModeWindows/LightingExplorerWindow.cs index 8dbd8ca957..f53f184f70 100644 --- a/Editor/Mono/SceneModeWindows/LightingExplorerWindow.cs +++ b/Editor/Mono/SceneModeWindows/LightingExplorerWindow.cs @@ -40,7 +40,7 @@ internal class LightingExplorerWindow : EditorWindow ILightingExplorerExtension m_CurrentLightingExplorerExtension = null; static ILightingExplorerExtension s_DefaultLightingExplorerExtension = null; - [MenuItem("Window/Rendering/Light Explorer", false, 2)] + [MenuItem("Window/Rendering/Light Explorer", priority = 2, secondaryPriority = 1)] static void CreateLightingExplorerWindow() { LightingExplorerWindow window = EditorWindow.GetWindow(); diff --git a/Editor/Mono/SceneModeWindows/LightingWindow.cs b/Editor/Mono/SceneModeWindows/LightingWindow.cs index ef49c49293..9400ec9cc6 100644 --- a/Editor/Mono/SceneModeWindows/LightingWindow.cs +++ b/Editor/Mono/SceneModeWindows/LightingWindow.cs @@ -62,7 +62,7 @@ enum Mode BakedLightmaps } - const string kGlobalIlluminationUnityManualPage = "file:///unity/Manual/GlobalIllumination.html"; + string kGlobalIlluminationUnityManualPage = $"https://docs.unity3d.com/{Application.unityVersionVer}.{Application.unityVersionMaj}/Documentation/Manual/lighting-window.html"; int m_SelectedModeIndex = 0; List m_Modes = null; @@ -117,6 +117,8 @@ internal void SetSelectedTabIndex(int index) void OnEnable() { + s_Window = this; + titleContent = GetLocalizedTitleContent(); foreach (var pair in m_Tabs) @@ -332,19 +334,6 @@ void BakeDropDownCallback(object data) } } - private bool IsPackageUsed(string packageName) - { - PackageManager.PackageInfo[] allInfo = PackageManager.PackageInfo.GetAllRegisteredPackages(); - foreach (PackageManager.PackageInfo info in allInfo) - { - if (info.name == packageName) - { - return true; - } - } - return false; - } - void Buttons() { using (new EditorGUI.DisabledScope(EditorApplication.isPlayingOrWillChangePlaymode)) @@ -354,39 +343,39 @@ void Buttons() EditorGUILayout.HelpBox(Lightmapping.lightingDataAsset.validityErrorMessage, MessageType.Warning); } - bool entitiesPackage = IsPackageUsed("com.unity.entities"); + bool isEntitiesPackageUsed = PackageManager.PackageInfo.IsPackageRegistered("com.unity.entities"); + bool iterativeInLightingSettings = (m_WorkflowMode.intValue == (int)Lightmapping.GIWorkflowMode.Iterative); - if (entitiesPackage) - EditorGUILayout.HelpBox("Auto Generate mode is unavailable when the Entities package is installed. When you generate lighting in a project where you have installed the Entities package, the Unity Editor opens all loaded subscenes. This may slow down Editor performance.", MessageType.Warning); + if (isEntitiesPackageUsed && iterativeInLightingSettings) + EditorGUILayout.HelpBox("Unity ignores Auto Generate mode when the Entities package is installed. When you generate lighting in a project where you have installed the Entities package, the Unity Editor opens all loaded subscenes. This may slow down Editor performance.", MessageType.Warning); EditorGUILayout.Space(); GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); - bool iterative = (m_WorkflowMode.intValue == (int)Lightmapping.GIWorkflowMode.Iterative) && entitiesPackage; - Rect rect = GUILayoutUtility.GetRect(Styles.continuousBakeLabel, GUIStyle.none); EditorGUI.BeginProperty(rect, Styles.continuousBakeLabel, m_WorkflowMode); // Auto Generate checkbox. EditorGUI.BeginChangeCheck(); - using (new EditorGUI.DisabledScope(m_LightingSettingsReadOnlyMode || entitiesPackage)) + using (new EditorGUI.DisabledScope(m_LightingSettingsReadOnlyMode)) { - iterative = GUILayout.Toggle(iterative, Styles.continuousBakeLabel); + iterativeInLightingSettings = GUILayout.Toggle(iterativeInLightingSettings, Styles.continuousBakeLabel); } if (EditorGUI.EndChangeCheck()) { - m_WorkflowMode.intValue = (int)(iterative ? Lightmapping.GIWorkflowMode.Iterative : Lightmapping.GIWorkflowMode.OnDemand); + m_WorkflowMode.intValue = (int)(iterativeInLightingSettings ? Lightmapping.GIWorkflowMode.Iterative : Lightmapping.GIWorkflowMode.OnDemand); } EditorGUI.EndProperty(); - using (new EditorGUI.DisabledScope(iterative)) + bool resultingIterative = iterativeInLightingSettings && !isEntitiesPackageUsed; + using (new EditorGUI.DisabledScope(resultingIterative)) { // Bake button if we are not currently baking - bool showBakeButton = iterative || !Lightmapping.isRunning; + bool showBakeButton = resultingIterative || !Lightmapping.isRunning; if (showBakeButton) { if (EditorGUI.ButtonWithDropdownList(Styles.buildLabel, Styles.BakeModeStrings, BakeDropDownCallback, GUILayout.Width(170))) @@ -621,12 +610,22 @@ void Summary() GUILayout.EndVertical(); } - [MenuItem("Window/Rendering/Lighting", false, 1)] + internal static LightingWindow s_Window; + internal static bool isShown => s_Window && !s_Window.docked; + + [MenuItem("Window/Rendering/Lighting %9", false, 1)] internal static void CreateLightingWindow() { LightingWindow window = EditorWindow.GetWindow(); window.minSize = new Vector2(390, 390); window.Show(); + s_Window = window; + } + + internal static void DestroyLightingWindow() + { + s_Window.Close(); + s_Window = null; } } } // namespace diff --git a/Editor/Mono/SceneModeWindows/LightingWindowLightingTab.cs b/Editor/Mono/SceneModeWindows/LightingWindowLightingTab.cs index 0c6be7a1d6..5f4ae228cc 100644 --- a/Editor/Mono/SceneModeWindows/LightingWindowLightingTab.cs +++ b/Editor/Mono/SceneModeWindows/LightingWindowLightingTab.cs @@ -22,7 +22,8 @@ class Styles { public static readonly float buttonWidth = 200; - public static readonly GUIContent newLightingSettings = EditorGUIUtility.TrTextContent("New Lighting Settings"); + public static readonly GUIContent newLightingSettings = EditorGUIUtility.TrTextContent("New", "Create a new Lighting Settings Asset with default settings."); + public static readonly GUIContent cloneLightingSettings = EditorGUIUtility.TrTextContent("Clone", "Create a new Lighting Settings Asset based on the current settings."); public static readonly GUIContent lightingSettings = EditorGUIUtility.TrTextContent("Lighting Settings"); public static readonly GUIContent workflowSettings = EditorGUIUtility.TrTextContent("Workflow Settings"); @@ -34,6 +35,7 @@ class Styles public static readonly GUIContent progressiveGPUChangeWarning = EditorGUIUtility.TrTextContent("Changing the compute device used by the Progressive GPU Lightmapper requires the editor to be relaunched. Do you want to change device and restart?"); public static readonly GUIContent concurrentJobs = EditorGUIUtility.TrTextContent("Concurrent Jobs", "The amount of simultaneously scheduled jobs."); public static readonly GUIContent progressiveGPUUnknownDeviceInfo = EditorGUIUtility.TrTextContent("No devices found. Please start an initial bake to make this information available."); + public static readonly GUIContent recalculateEnvironmentLighting = EditorGUIUtility.TrTextContent("Recalculate Environment Lighting", "Whether to automatically generate environment lighting in cases where the Active Scene has not previously been baked. This affects the ambient Light Probe and default cubemap which are both generated from the sky."); public static readonly int[] progressiveGPUUnknownDeviceValues = { 0 }; public static readonly GUIContent[] progressiveGPUUnknownDeviceStrings = @@ -113,6 +115,24 @@ public void OnSelectionChange() { } + private void CreateLightingSettings(LightingSettings from = null) + { + LightingSettings ls; + if (from == null) + { + ls = new LightingSettings(); + ls.name = "New Lighting Settings"; + } + else + { + ls = Object.Instantiate(from); + ls.name = from.name; + } + Undo.RecordObject(m_LightmapSettings.targetObject, "New Lighting Settings"); + Lightmapping.lightingSettingsInternal = ls; + ProjectWindowUtil.CreateAsset(ls, (ls.name + ".lighting")); + } + void LightingSettingsGUI() { m_ShowLightingSettings.value = EditorGUILayout.FoldoutTitlebar(m_ShowLightingSettings.value, Styles.lightingSettings, true); @@ -121,20 +141,13 @@ void LightingSettingsGUI() { ++EditorGUI.indentLevel; - EditorGUILayout.PropertyField(m_LightingSettingsAsset, GUIContent.Temp("Lighting Settings Asset")); - - EditorGUILayout.Space(); GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); + EditorGUILayout.PropertyField(m_LightingSettingsAsset, GUIContent.Temp("Lighting Settings Asset")); - if (GUILayout.Button(Styles.newLightingSettings, GUILayout.Width(170))) - { - var ls = new LightingSettings(); - ls.name = "New Lighting Settings"; - Undo.RecordObject(m_LightmapSettings.targetObject, "New Lighting Settings"); - Lightmapping.lightingSettingsInternal = ls; - ProjectWindowUtil.CreateAsset(ls, (ls.name + ".lighting")); - } + if (GUILayout.Button(Styles.newLightingSettings, EditorStyles.miniButtonLeft, GUILayout.Width(50))) + CreateLightingSettings(); + else if (GUILayout.Button(Styles.cloneLightingSettings, EditorStyles.miniButtonRight, GUILayout.Width(50))) + CreateLightingSettings(Lightmapping.GetLightingSettingsOrDefaultsFallback()); GUILayout.EndHorizontal(); EditorGUILayout.Space(); @@ -216,6 +229,12 @@ void WorkflowSettingsGUI() EditorApplication.SetSceneRepaintDirty(); } + // If either auto ambient or auto reflection baking is supported, show the SkyManager toggle. + if (SupportedRenderingFeatures.active.autoAmbientProbeBaking || SupportedRenderingFeatures.active.autoDefaultReflectionProbeBaking) + { + BuiltinSkyManager.enabled = EditorGUILayout.Toggle(Styles.recalculateEnvironmentLighting, BuiltinSkyManager.enabled); + } + if (Unsupported.IsDeveloperMode()) { Lightmapping.concurrentJobsType = (Lightmapping.ConcurrentJobsType)EditorGUILayout.IntPopup(Styles.concurrentJobs, (int)Lightmapping.concurrentJobsType, Styles.concurrentJobsTypeStrings, Styles.concurrentJobsTypeValues); diff --git a/Editor/Mono/SceneModeWindows/OcclusionCullingWindow.cs b/Editor/Mono/SceneModeWindows/OcclusionCullingWindow.cs index 469be94249..a4a720fcdd 100644 --- a/Editor/Mono/SceneModeWindows/OcclusionCullingWindow.cs +++ b/Editor/Mono/SceneModeWindows/OcclusionCullingWindow.cs @@ -239,6 +239,7 @@ void BakeSettings() // Edit Smallest Occluder EditorGUI.BeginChangeCheck(); float smallestOccluder = EditorGUILayout.FloatField(s_Styles.smallestOccluder, StaticOcclusionCulling.smallestOccluder); + smallestOccluder = Math.Clamp(smallestOccluder, 0.0f, float.MaxValue); if (EditorGUI.EndChangeCheck()) { Undo.RegisterCompleteObjectUndo(StaticOcclusionCulling.occlusionCullingSettings, "Change Smallest Occluder"); @@ -248,6 +249,7 @@ void BakeSettings() // Edit smallest hole EditorGUI.BeginChangeCheck(); float smallestHole = EditorGUILayout.FloatField(s_Styles.smallestHole, StaticOcclusionCulling.smallestHole); + smallestHole = Math.Clamp(smallestHole, 0.0f, float.MaxValue); if (EditorGUI.EndChangeCheck()) { Undo.RegisterCompleteObjectUndo(StaticOcclusionCulling.occlusionCullingSettings, "Change Smallest Hole"); diff --git a/Editor/Mono/SceneModeWindows/SceneModeUtility.cs b/Editor/Mono/SceneModeWindows/SceneModeUtility.cs index a7b132b262..d56ef25e7d 100644 --- a/Editor/Mono/SceneModeWindows/SceneModeUtility.cs +++ b/Editor/Mono/SceneModeWindows/SceneModeUtility.cs @@ -12,8 +12,42 @@ namespace UnityEditor { public static class SceneModeUtility { + class SceneModeData : ScriptableSingleton + { + public string focusTypeName = null; + public SceneHierarchyWindow hierarchyWindow = null; + } + private static Type s_FocusType = null; - private static SceneHierarchyWindow s_HierarchyWindow = null; + private static Type focusType + { + get + { + if(s_FocusType == null && focusTypeName != null) + s_FocusType = Type.GetType(focusTypeName); + + return s_FocusType; + } + set + { + s_FocusType = value; + focusTypeName = s_FocusType?.AssemblyQualifiedName; + } + } + + private static string focusTypeName + { + get => SceneModeData.instance.focusTypeName; + set => SceneModeData.instance.focusTypeName = value; + } + + private static SceneHierarchyWindow hierarchyWindow + { + get => SceneModeData.instance.hierarchyWindow; + set => SceneModeData.instance.hierarchyWindow = value; + } + + private static GUIContent s_NoneButtonContent = null; private class Styles @@ -56,15 +90,15 @@ public static void SearchForType(Type type) if (win) { - s_HierarchyWindow = win; + hierarchyWindow = win; if (type == null || type == typeof(GameObject)) { - s_FocusType = null; + focusType = null; win.ClearSearchFilter(); } else { - s_FocusType = type; + focusType = type; if (win.searchMode == SearchableEditorWindow.SearchMode.Name) win.searchMode = SearchableEditorWindow.SearchMode.All; win.SetSearchFilter("t:" + type.Name, win.searchMode, false); @@ -72,7 +106,7 @@ public static void SearchForType(Type type) } } else - s_FocusType = null; + focusType = null; } public static Type SearchBar(params Type[] types) @@ -83,8 +117,11 @@ public static Type SearchBar(params Type[] types) s_NoneButtonContent.text = "None"; } - if (s_FocusType != null && (s_HierarchyWindow == null || s_HierarchyWindow.m_SearchFilter != "t:" + s_FocusType.Name)) - s_FocusType = null; + if (s_FocusType != null && + (hierarchyWindow == null || hierarchyWindow.m_SearchFilter != "t:" + s_FocusType.Name)) + { + focusType = null; + } GUILayout.Label("Scene Filter:"); @@ -94,7 +131,7 @@ public static Type SearchBar(params Type[] types) GUIContent label = EditorGUIUtility.TempContent( "All", AssetPreview.GetMiniTypeThumbnail(typeof(GameObject))); - if (TypeButton(label, s_FocusType == null, styles.typeButton)) + if (TypeButton(label, focusType == null, styles.typeButton)) SceneModeUtility.SearchForType(null); } @@ -110,13 +147,13 @@ public static Type SearchBar(params Type[] types) icon = AssetPreview.GetMiniTypeThumbnail(type); string name = ObjectNames.NicifyVariableName(type.Name) + "s"; GUIContent label = EditorGUIUtility.TempContent(name, icon); - if (TypeButton(label, type == s_FocusType, styles.typeButton)) + if (TypeButton(label, type == focusType, styles.typeButton)) SceneModeUtility.SearchForType(type); } GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); - return s_FocusType; + return focusType; } private static bool TypeButton(GUIContent label, bool selected, GUIStyle style) diff --git a/Editor/Mono/SceneView/RectSelection.cs b/Editor/Mono/SceneView/RectSelection.cs index 0c81f7428c..2395e010b3 100644 --- a/Editor/Mono/SceneView/RectSelection.cs +++ b/Editor/Mono/SceneView/RectSelection.cs @@ -3,208 +3,305 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; -using UnityEngine; using System.Collections.Generic; -using System.Linq; +using UnityEditor.ShortcutManagement; +using UnityEngine; using Object = UnityEngine.Object; namespace UnityEditor { // Handles picking/selection in the scene view (both "click" type and "drag-rect" type) - internal class RectSelection + class RectSelection { - Vector2 m_SelectStartPoint; + public enum SelectionType { Normal, Additive, Subtractive } + Vector2 m_SelectMousePoint; - Object[] m_SelectionStart; - bool m_RectSelecting; - Dictionary m_LastSelection; - enum SelectionType { Normal, Additive, Subtractive } + Vector2 m_StartPoint; + + SelectionType m_CurrentSelectionType; + + Object[] m_SelectionStart = null; Object[] m_CurrentSelection = null; - readonly EditorWindow m_Window; - internal static event Action rectSelectionStarting = delegate { }; - internal static event Action rectSelectionFinished = delegate { }; + Dictionary m_LastSelection; + + readonly SceneViewRectSelection m_RectSelectionShortcutContext = new SceneViewRectSelection(); + + public static event Action rectSelectionStarting = delegate { }; + public static event Action rectSelectionFinished = delegate { }; + + bool m_IsNearestControl = false; - static readonly int s_RectSelectionID = GUIUtility.GetPermanentControlID(); + const string k_PickingEventCommandName = "SceneViewPickingEventCommand"; + const string k_SetRectSelectionHotControlEventCommandName = "SetRectSelectionHotControlEventCommand"; - public RectSelection(EditorWindow window) + const string k_RectSelectionNormal = "Scene View/Rect Selection Normal"; + const string k_RectSelectionAdditive = "Scene View/Rect Selection Additive"; + const string k_RectSelectionSubtractive = "Scene View/Rect Selection Subtractive"; + const string k_PickingNormal = "Scene View/Picking Normal"; + const string k_PickingAdditive = "Scene View/Picking Additive"; + const string k_PickingSubtractive = "Scene View/Picking Subtractive"; + + readonly int k_RectSelectionID = GUIUtility.GetPermanentControlID(); + + public void RegisterShortcutContext() { - m_Window = window; + ShortcutIntegration.instance.contextManager.RegisterToolContext(m_RectSelectionShortcutContext); } - public void OnGUI() + public void UnregisterShortcutContext() { - Event evt = Event.current; + ShortcutIntegration.instance.contextManager.DeregisterToolContext(m_RectSelectionShortcutContext); + } - Handles.BeginGUI(); + class SceneViewRectSelection : IShortcutToolContext + { + public SceneView window => EditorWindow.focusedWindow as SceneView; - Vector2 mousePos = evt.mousePosition; - int id = s_RectSelectionID; + public bool active => IsActive; - switch (evt.GetTypeForControl(id)) + public static bool IsActive { - case EventType.Layout: - case EventType.MouseMove: - if (!Tools.viewToolActive) - HandleUtility.AddDefaultControl(id); + get + { + if (!(EditorWindow.focusedWindow is SceneView view) || view.sceneViewMotion == null) + return false; + + return view.sceneViewMotion.viewportsUnderMouse && Tools.current != Tool.View; + } + } + } + + [ClutchShortcut(k_RectSelectionNormal, typeof(SceneViewRectSelection), KeyCode.Mouse0)] + static void OnNormalRectSelection(ShortcutArguments args) + { + if (args.context is SceneViewRectSelection ctx && ctx.window != null && ctx.window.rectSelection != null) + ctx.window.rectSelection.OnRectSelection(args, SelectionType.Normal, ctx.window); + } - //Handle the case of the drag being canceled - if (m_RectSelecting && GUIUtility.hotControl != id) + [ClutchShortcut(k_RectSelectionAdditive, typeof(SceneViewRectSelection), KeyCode.Mouse0, ShortcutModifiers.Shift)] + static void OnAdditiveRectSelection(ShortcutArguments args) + { + if (args.context is SceneViewRectSelection ctx && ctx.window != null && ctx.window.rectSelection != null) + ctx.window.rectSelection.OnRectSelection(args, SelectionType.Additive, ctx.window); + } + + [ClutchShortcut(k_RectSelectionSubtractive, typeof(SceneViewRectSelection), KeyCode.Mouse0, ShortcutModifiers.Action)] + static void OnSubtractiveRectSelection(ShortcutArguments args) + { + if (args.context is SceneViewRectSelection ctx && ctx.window != null && ctx.window.rectSelection != null) + ctx.window.rectSelection.OnRectSelection(args, SelectionType.Subtractive, ctx.window); + } + + void OnRectSelection(ShortcutArguments args, SelectionType selectionType, SceneView view) + { + // Validating that the hotControl is either equal to 0 or k_RectSelectionID allows to only start the rect selection + // when no other tool overrides the shortcut key and to change modifiers while rect selecting. + if (args.stage == ShortcutStage.Begin && (GUIUtility.hotControl == 0 || GUIUtility.hotControl == k_RectSelectionID)) + { + m_CurrentSelectionType = selectionType; + StartRectSelection(view); + } + else if (args.stage == ShortcutStage.End) + { + CompleteRectSelection(); + } + } + + [Shortcut(k_PickingNormal, typeof(SceneViewRectSelection), KeyCode.Mouse0)] + static void OnNormalPicking(ShortcutArguments args) + { + if (args.context is SceneViewRectSelection ctx && ctx.window != null && ctx.window.rectSelection != null) + ctx.window.rectSelection.DelayPicking(ctx.window, SelectionType.Normal); + } + + [Shortcut(k_PickingAdditive, typeof(SceneViewRectSelection), KeyCode.Mouse0, ShortcutModifiers.Shift)] + static void OnAdditivePicking(ShortcutArguments args) + { + if (args.context is SceneViewRectSelection ctx && ctx.window != null && ctx.window.rectSelection != null) + ctx.window.rectSelection.DelayPicking(ctx.window, SelectionType.Additive); + } + + [Shortcut(k_PickingSubtractive, typeof(SceneViewRectSelection), KeyCode.Mouse0, ShortcutModifiers.Action)] + static void OnSubtractivePicking(ShortcutArguments args) + { + if (args.context is SceneViewRectSelection ctx && ctx.window != null && ctx.window.rectSelection != null) + ctx.window.rectSelection.DelayPicking(ctx.window, SelectionType.Subtractive); + } + + // Delaying the picking to a command event is necessary because some HandleUtility methods + // need to be called in an OnGUI. + void DelayPicking(SceneView sceneview, SelectionType selectionType) + { + if (sceneview == null) + return; + + m_CurrentSelectionType = selectionType; + sceneview.SendEvent(EditorGUIUtility.CommandEvent(k_PickingEventCommandName)); + } + + void Pick(SelectionType selectionType, Vector2 mousePos, Event evt) + { + if (selectionType == SelectionType.Subtractive || selectionType == SelectionType.Additive) + { + // For shift, we check if EXACTLY the active GO is hovered by mouse and then subtract. Otherwise additive. + // For control/cmd, we check if ANY of the selected GO is hovered by mouse and then subtract. Otherwise additive. + // Control/cmd takes priority over shift. + var hovered = HandleUtility.PickObject(mousePos, false); + + var handledIt = false; + // shift-click deselects only if the active GO is exactly what we clicked on + if (selectionType != SelectionType.Subtractive && Selection.activeObject == hovered.target) + { + UpdateSelection(m_SelectionStart, hovered.target, SelectionType.Subtractive, false); + handledIt = true; + } + + // ctrl-click deselects everything up to prefab root, that is already selected + if (!handledIt && selectionType == SelectionType.Subtractive) + { + var selectedObjects = Selection.objects; + hovered.TryGetComponent(out var hoveredTransform); + var hoveredRoot = HandleUtility.FindSelectionBaseForPicking(hoveredTransform); + var deselectList = new List(); + + while (hovered.target != null) { - CompleteRectSelection(); + foreach (var obj in selectedObjects) + { + if (obj.Equals(hovered.target)) + { + deselectList.Add(hovered.target); + break; + } + } + + if (hovered.target == hoveredRoot) + break; + + if (!hovered.TryGetParent(out var parent)) + break; + + hovered = new PickingObject(parent.gameObject); } - break; - case EventType.MouseDown: - if (HandleUtility.nearestControl == id && evt.button == 0) + if (deselectList.Count > 0) { - GUIUtility.hotControl = id; - m_SelectStartPoint = mousePos; - m_SelectionStart = Selection.objects; - m_RectSelecting = false; + UpdateSelection(m_SelectionStart, deselectList.ToArray(), SelectionType.Subtractive, false); + handledIt = true; } + } + + // we did not deselect anything, so add the new thing into selection instead + if (!handledIt) + { + var picked = HandleUtility.PickObject(mousePos, true); + UpdateSelection(m_SelectionStart, picked.target, SelectionType.Additive, false); + } + } + else // With no modifier keys, we do the "cycle through overlapped" picking logic in SceneViewPicking.cs + { + var picked = SceneViewPicking.PickGameObject(mousePos); + UpdateSelection(m_SelectionStart, picked.target, selectionType, false); + } + + evt.Use(); + } + + public void OnGUI(bool isLastActiveSceneView) + { + Event evt = Event.current; + + Handles.BeginGUI(); + + switch (evt.GetTypeForControl(k_RectSelectionID)) + { + case EventType.Layout: + case EventType.MouseMove: + // Only add default control if the Scene view is focused. Otherwise, can + // cause issues when multiple Scene views open. + if (!Tools.viewToolActive && isLastActiveSceneView) + HandleUtility.AddDefaultControl(k_RectSelectionID); + break; + case EventType.MouseDown: + HandleOnMouseDown(evt); + break; + case EventType.MouseUp: + HandleOnMouseUp(); break; case EventType.MouseDrag: - if (GUIUtility.hotControl == id) + if (GUIUtility.hotControl == k_RectSelectionID && m_IsNearestControl) { - if (!m_RectSelecting && (mousePos - m_SelectStartPoint).magnitude > 6f) + m_SelectMousePoint = evt.mousePosition; + GameObject[] rectObjs = HandleUtility.PickRectObjects(EditorGUIExt.FromToRect(m_StartPoint, m_SelectMousePoint)); + m_CurrentSelection = rectObjs; + bool setIt = false; + + if (m_LastSelection == null) { - EditorApplication.modifierKeysChanged += SendCommandsOnModifierKeys; - m_RectSelecting = true; - ActiveEditorTracker.delayFlushDirtyRebuild = true; - m_LastSelection = null; - m_CurrentSelection = null; - rectSelectionStarting(); + m_LastSelection = new Dictionary(); + setIt = true; } - if (m_RectSelecting) + + setIt |= m_LastSelection.Count != rectObjs.Length; + + if (!setIt) { - m_SelectMousePoint = new Vector2(Mathf.Max(mousePos.x, 0), Mathf.Max(mousePos.y, 0)); - GameObject[] rectObjs = HandleUtility.PickRectObjects(EditorGUIExt.FromToRect(m_SelectStartPoint, m_SelectMousePoint)); - m_CurrentSelection = rectObjs; - bool setIt = false; - if (m_LastSelection == null) - { - m_LastSelection = new Dictionary(); - setIt = true; - } - setIt |= m_LastSelection.Count != rectObjs.Length; - if (!setIt) + Dictionary set = new Dictionary(rectObjs.Length); + foreach (GameObject g in rectObjs) + set.Add(g, false); + + foreach (GameObject g in m_LastSelection.Keys) { - Dictionary set = new Dictionary(rectObjs.Length); - foreach (GameObject g in rectObjs) - set.Add(g, false); - foreach (GameObject g in m_LastSelection.Keys) + if (!set.ContainsKey(g)) { - if (!set.ContainsKey(g)) - { - setIt = true; - break; - } + setIt = true; + break; } } - if (setIt) - { - m_LastSelection = new Dictionary(rectObjs.Length); - foreach (GameObject g in rectObjs) - m_LastSelection.Add(g, false); - if (evt.shift) - UpdateSelection(m_SelectionStart, rectObjs, SelectionType.Additive, m_RectSelecting); - else if (EditorGUI.actionKey) - UpdateSelection(m_SelectionStart, rectObjs, SelectionType.Subtractive, m_RectSelecting); - else - UpdateSelection(m_SelectionStart, rectObjs, SelectionType.Normal, m_RectSelecting); - } } + + if (setIt) + { + m_LastSelection = new Dictionary(rectObjs.Length); + + foreach (GameObject g in rectObjs) + m_LastSelection.Add(g, false); + + UpdateSelection(m_SelectionStart, rectObjs, m_CurrentSelectionType, true); + } + evt.Use(); } break; - - case EventType.Repaint: - if (GUIUtility.hotControl == id && m_RectSelecting) - EditorStyles.selectionRect.Draw(EditorGUIExt.FromToRect(m_SelectStartPoint, m_SelectMousePoint), GUIContent.none, false, false, false, false); - break; - - case EventType.MouseUp: - if (GUIUtility.hotControl == id && evt.button == 0) + case EventType.KeyDown: // Escape + if (evt.keyCode == KeyCode.Escape && GUIUtility.hotControl == k_RectSelectionID) { - GUIUtility.hotControl = 0; - if (m_RectSelecting) - { - CompleteRectSelection(); - evt.Use(); - } - else - { - if (evt.shift || EditorGUI.actionKey) - { - // For shift, we check if EXACTLY the active GO is hovered by mouse and then subtract. Otherwise additive. - // For control/cmd, we check if ANY of the selected GO is hovered by mouse and then subtract. Otherwise additive. - // Control/cmd takes priority over shift. - var hovered = HandleUtility.PickObject(evt.mousePosition, false); - - var handledIt = false; - // shift-click deselects only if the active GO is exactly what we clicked on - if (!EditorGUI.actionKey && Selection.activeObject == hovered.target) - { - UpdateSelection(m_SelectionStart, hovered.target, SelectionType.Subtractive, m_RectSelecting); - handledIt = true; - } + CompleteRectSelection(); - // ctrl-click deselects everything up to prefab root, that is already selected - if (!handledIt && EditorGUI.actionKey) - { - var selectedObjects = Selection.objects; - hovered.TryGetComponent(out var hoveredTransform); - var hoveredRoot = HandleUtility.FindSelectionBaseForPicking(hoveredTransform); - var deselectList = new List(); - - while (hovered.target != null) - { - if (selectedObjects.Contains(hovered.target)) - deselectList.Add(hovered.target); - - if (hovered.target == hoveredRoot) - break; - - if (!hovered.TryGetParent(out var parent)) - break; - - hovered = new PickingObject(parent.gameObject); - } - - if (deselectList.Any()) - { - UpdateSelection(m_SelectionStart, deselectList.ToArray(), SelectionType.Subtractive, m_RectSelecting); - handledIt = true; - } - } + GUIUtility.hotControl = 0; - // we did not deselect anything, so add the new thing into selection instead - if (!handledIt) - { - var picked = HandleUtility.PickObject(evt.mousePosition, true); - UpdateSelection(m_SelectionStart, picked.target, SelectionType.Additive, m_RectSelecting); - } - } - else // With no modifier keys, we do the "cycle through overlapped" picking logic in SceneViewPicking.cs - { - var picked = SceneViewPicking.PickGameObject(evt.mousePosition); - UpdateSelection(m_SelectionStart, picked.target, SelectionType.Normal, m_RectSelecting); - } + // Set the current selection to the previous selection. + Selection.objects = m_SelectionStart; - evt.Use(); - } + HandleOnMouseUp(); + } + break; + case EventType.Repaint: + if (GUIUtility.hotControl == k_RectSelectionID && m_IsNearestControl && m_StartPoint != m_SelectMousePoint) + { + EditorStyles.selectionRect.Draw(EditorGUIExt.FromToRect(m_StartPoint, m_SelectMousePoint), + GUIContent.none, false, false, false, false); } break; case EventType.ExecuteCommand: - if (id == GUIUtility.hotControl && evt.commandName == EventCommandNames.ModifierKeysChanged) + if (evt.commandName == k_PickingEventCommandName && m_IsNearestControl) + { + Pick(m_CurrentSelectionType, m_StartPoint, evt); + } + else if (evt.commandName == k_SetRectSelectionHotControlEventCommandName) { - if (evt.shift) - UpdateSelection(m_SelectionStart, m_CurrentSelection, SelectionType.Additive, m_RectSelecting); - else if (EditorGUI.actionKey) - UpdateSelection(m_SelectionStart, m_CurrentSelection, SelectionType.Subtractive, m_RectSelecting); - else - UpdateSelection(m_SelectionStart, m_CurrentSelection, SelectionType.Normal, m_RectSelecting); + GUIUtility.hotControl = k_RectSelectionID; evt.Use(); } break; @@ -213,17 +310,53 @@ public void OnGUI() Handles.EndGUI(); } + void HandleOnMouseDown(Event evt) + { + if (m_IsNearestControl) + m_IsNearestControl = false; + + if (GUIUtility.hotControl == 0 && HandleUtility.nearestControl == k_RectSelectionID) + { + m_StartPoint = evt.mousePosition; + m_SelectMousePoint = m_StartPoint; + m_IsNearestControl = true; + } + + m_SelectionStart = Selection.objects; + m_CurrentSelection = null; + m_LastSelection = null; + } + + void HandleOnMouseUp() + { + if (GUIUtility.hotControl == k_RectSelectionID) + { + m_IsNearestControl = false; + GUIUtility.hotControl = 0; + } + } + void CompleteRectSelection() { - EditorApplication.modifierKeysChanged -= SendCommandsOnModifierKeys; - m_RectSelecting = false; ActiveEditorTracker.delayFlushDirtyRebuild = false; ActiveEditorTracker.RebuildAllIfNecessary(); - m_SelectionStart = new Object[0]; rectSelectionFinished(); } - static void UpdateSelection(Object[] existingSelection, Object newObject, SelectionType type, bool isRectSelection) + void StartRectSelection(SceneView view) + { + ActiveEditorTracker.delayFlushDirtyRebuild = true; + + // The hot control needs to be set in an OnGUI call. + view.SendEvent(EditorGUIUtility.CommandEvent(k_SetRectSelectionHotControlEventCommandName)); + + rectSelectionStarting(); + + // This is needed to update the selection in case the modifier keys changed. + UpdateSelection(m_SelectionStart, m_CurrentSelection, m_CurrentSelectionType, true); + } + + void UpdateSelection(Object[] existingSelection, Object newObject, SelectionType type, bool isRectSelection) { Object[] objs; if (newObject == null) @@ -239,8 +372,11 @@ static void UpdateSelection(Object[] existingSelection, Object newObject, Select UpdateSelection(existingSelection, objs, type, isRectSelection); } - static void UpdateSelection(Object[] existingSelection, Object[] newObjects, SelectionType type, bool isRectSelection) + void UpdateSelection(Object[] existingSelection, Object[] newObjects, SelectionType type, bool isRectSelection) { + if (existingSelection == null || newObjects == null) + return; + Object[] newSelection; switch (type) { @@ -248,9 +384,11 @@ static void UpdateSelection(Object[] existingSelection, Object[] newObjects, Sel if (newObjects.Length > 0) { newSelection = new Object[existingSelection.Length + newObjects.Length]; - System.Array.Copy(existingSelection, newSelection, existingSelection.Length); + Array.Copy(existingSelection, newSelection, existingSelection.Length); + for (int i = 0; i < newObjects.Length; i++) newSelection[existingSelection.Length + i] = newObjects[i]; + Object active = isRectSelection ? newSelection[0] : newObjects[0]; Selection.SetSelectionWithActiveObject(newSelection, active); } @@ -277,14 +415,6 @@ static void UpdateSelection(Object[] existingSelection, Object[] newObjects, Sel Selection.objects = newObjects; break; } - GUIUtility.ExitGUI(); - } - - // When rect selecting, we update the selected objects based on which modifier keys are currently held down, - // so the window needs to repaint. - void SendCommandsOnModifierKeys() - { - m_Window.SendEvent(EditorGUIUtility.CommandEvent(EventCommandNames.ModifierKeysChanged)); } } } // namespace diff --git a/Editor/Mono/SceneView/SceneView.cs b/Editor/Mono/SceneView/SceneView.cs index 23c52dd77d..98c1a7b09a 100644 --- a/Editor/Mono/SceneView/SceneView.cs +++ b/Editor/Mono/SceneView/SceneView.cs @@ -151,6 +151,7 @@ private set public static Color selectedOutlineColor => kSceneViewSelectedOutline.Color; public bool isUsingSceneFiltering => UseSceneFiltering(); + internal static SavedBool s_PreferenceIgnoreAlwaysRefreshWhenNotFocused = new SavedBool("SceneView.ignoreAlwaysRefreshWhenNotFocused", false); internal static SavedBool s_PreferenceEnableFilteringWhileSearching = new SavedBool("SceneView.enableFilteringWhileSearching", true); internal static SavedBool s_PreferenceEnableFilteringWhileLodGroupEditing = new SavedBool("SceneView.enableFilteringWhileLodGroupEditing", true); @@ -187,6 +188,15 @@ static void OnSelectedObjectWasDestroyed(int unused) s_SelectionCacheDirty = true; } + static void OnNonSelectedObjectWasDestroyed(int instanceID) + { + if (s_CachedChildRenderersFromSelectionHashSet != null && s_CachedChildRenderersFromSelectionHashSet.Contains(instanceID)) + { + s_ActiveEditorsDirty = true; + s_SelectionCacheDirty = true; + } + } + static void OnEditorTrackerRebuilt() { s_ActiveEditorsDirty = true; @@ -376,6 +386,7 @@ public bool sceneLighting private bool m_WasFocused = false; private static int[] s_CachedParentRenderersFromSelection, s_CachedChildRenderersFromSelection; + private static readonly HashSet s_CachedChildRenderersFromSelectionHashSet = new HashSet(); [Serializable] public class SceneViewState @@ -929,7 +940,6 @@ public CursorRect(Rect rect, MouseCursor cursor) } private static MouseCursor s_LastCursor = MouseCursor.Arrow; private static readonly List s_MouseRects = new List(); - private bool s_DraggingCursorIsCached; internal static void AddCursorRect(Rect rect, MouseCursor cursor) { @@ -980,6 +990,11 @@ public float cameraDistance Light[] m_Light = new Light[3]; RectSelection m_RectSelection; + internal RectSelection rectSelection => m_RectSelection; + + SceneViewMotion m_SceneViewMotion; + + internal SceneViewMotion sceneViewMotion => m_SceneViewMotion; const float kDefaultPerspectiveFov = 60; @@ -1096,6 +1111,33 @@ internal bool viewIsLockedToObject } } + [RequiredByNativeCode] + static void FrameSelectedMenuItem(bool locked) + { + var command = locked ? EventCommandNames.FrameSelectedWithLock : EventCommandNames.FrameSelected; + + var win = mouseOverWindow; + var ret = win != null && win.SendEvent(EditorGUIUtility.CommandEvent(command)); + + if (ret) + { + // In case the window under the mouse handle the Focus event, it should be focused on. + win.Focus(); + } + else + { + // Otherwise get the current focused window to handle that event. + win = focusedWindow; + ret = win != null && win.SendEvent(EditorGUIUtility.CommandEvent(command)); + } + + // if no hovered or focused window used the frame command, send the command to the last active scene view. + // as a special case, if the window that used the frame command was hierarchy, also send a frame event to + // the last active scene view. + if ((!ret || win is SceneHierarchyWindow) && lastActiveSceneView != null) + lastActiveSceneView.SendEvent(EditorGUIUtility.CommandEvent(command)); + } + [RequiredByNativeCode] public static bool FrameLastActiveSceneView() { @@ -1104,7 +1146,6 @@ public static bool FrameLastActiveSceneView() return lastActiveSceneView.SendEvent(EditorGUIUtility.CommandEvent(EventCommandNames.FrameSelected)); } - [RequiredByNativeCode] public static bool FrameLastActiveSceneViewWithLock() { if (lastActiveSceneView == null) @@ -1166,7 +1207,10 @@ internal override void SetSearchFilter(string searchFilter, SearchMode mode, boo internal void OnLostFocus() { if (lastActiveSceneView == this) - SceneViewMotion.ResetMotion(); + { + m_SceneViewMotion.ResetMotion(); + m_SceneViewMotion.CompleteSceneViewMotionTool(); + } } private void OnBeforeRemovedAsTab() @@ -1190,11 +1234,24 @@ public override void OnEnable() baseRootVisualElement.Insert(0, prefabToolbar); rootVisualElement.Add(cameraViewVisualElement); + m_SceneViewMotion = new SceneViewMotion(); + + rootVisualElement.RegisterCallback(e => m_SceneViewMotion.viewportsUnderMouse = true); + rootVisualElement.RegisterCallback(e => m_SceneViewMotion.viewportsUnderMouse = false); + m_OrientationGizmo = overlayCanvas.overlays.FirstOrDefault(x => x is SceneOrientationGizmo) as SceneOrientationGizmo; titleContent = GetLocalizedTitleContent(); - m_RectSelection = new RectSelection(this); - SceneViewMotion.ResetDragState(); + + m_RectSelection = new RectSelection(); + + if (s_SceneViews.Count == 0) + { + m_SceneViewMotion.RegisterShortcutContexts(); + m_RectSelection.RegisterShortcutContext(); + } + + m_SceneViewMotion.CompleteSceneViewMotionTool(); if (m_Grid == null) m_Grid = new SceneViewGrid(); @@ -1226,6 +1283,7 @@ public override void OnEnable() SceneVisibilityManager.currentStageIsIsolated += CurrentStageIsolated; ActiveEditorTracker.editorTrackerRebuilt += OnEditorTrackerRebuilt; Selection.selectedObjectWasDestroyed += OnSelectedObjectWasDestroyed; + Selection.nonSelectedObjectWasDestroyed += OnNonSelectedObjectWasDestroyed; Lightmapping.lightingDataUpdated += RepaintAll; onCameraModeChanged += delegate { @@ -1434,6 +1492,7 @@ public override void OnDisable() Lightmapping.lightingDataUpdated -= RepaintAll; ActiveEditorTracker.editorTrackerRebuilt -= OnEditorTrackerRebuilt; Selection.selectedObjectWasDestroyed -= OnSelectedObjectWasDestroyed; + Selection.nonSelectedObjectWasDestroyed -= OnNonSelectedObjectWasDestroyed; sceneViewGrids.gridVisibilityChanged -= GridOnGridVisibilityChanged; sceneViewGrids.OnDisable(this); @@ -1454,13 +1513,19 @@ public override void OnDisable() s_SceneViews.Remove(this); + if (s_SceneViews.Count == 0) + { + m_SceneViewMotion.UnregisterShortcutContexts(); + m_RectSelection.UnregisterShortcutContext(); + } + if (s_LastActiveSceneView == this) lastActiveSceneView = s_SceneViews.Count > 0 ? s_SceneViews[0] as SceneView : null; CleanupEditorDragFunctions(); if (m_StageHandling != null) m_StageHandling.OnDisable(); - SceneViewMotion.DeactivateFlyModeContext(); + m_SceneViewMotion.DeactivateFlyModeContext(); ObjectFactory.componentWasAdded -= OnComponentWasAdded; base.OnDisable(); @@ -1495,7 +1560,7 @@ internal void OnStageChanged(Stage previousStage, Stage newStage) internal override void OnMaximized() { - SceneViewMotion.ResetDragState(); + m_SceneViewMotion.CompleteSceneViewMotionTool(); Repaint(); } @@ -1536,6 +1601,7 @@ void RefreshAudioPlay() if (s_AudioSceneView.m_PlayAudio) { s_AudioSceneView.m_PlayAudio = false; + s_AudioSceneView.sceneAudioChanged?.Invoke(false); s_AudioSceneView.Repaint(); } } @@ -1555,7 +1621,7 @@ void RefreshAudioPlay() } else { - if (!source.isPlaying) + if (!source.isPlaying && source.isActiveAndEnabled) source.Play(); } } @@ -1602,14 +1668,14 @@ public virtual void AddItemsToMenu(GenericMenu menu) } } - public static void AddOverlayToActiveView(T overlay) where T : Overlay, ITransientOverlay + public static void AddOverlayToActiveView(T overlay) where T : Overlay { s_ActiveViewOverlays.Add(overlay); if(lastActiveSceneView != null) lastActiveSceneView.overlayCanvas.Add(overlay); } - public static void RemoveOverlayFromActiveView(T overlay) where T : Overlay, ITransientOverlay + public static void RemoveOverlayFromActiveView(T overlay) where T : Overlay { if (!s_ActiveViewOverlays.Remove(overlay)) return; @@ -1642,7 +1708,7 @@ private static bool ValidateMenuMoveToFrontOrBack(Transform[] transforms, bool i int alreadyInPlaceCounter = 0; foreach (Transform transform in transforms) { - if (transform == null || transform.parent == null || PrefabUtility.IsPartOfNonAssetPrefabInstance(transform)) + if (transform == null || transform.parent == null || PrefabUtility.IsPartOfNonAssetPrefabInstance(transform.parent)) { return false; } @@ -1670,7 +1736,7 @@ private static void RegisterMenuMoveChildrenUndo(Transform[] transforms, string } } - [MenuItem("GameObject/Set as first sibling %=")] + [MenuItem("GameObject/Set as first sibling %=", secondaryPriority = 1)] internal static void MenuMoveToFront() { var selectedTransforms = Selection.transforms; @@ -1688,7 +1754,7 @@ internal static bool ValidateMenuMoveToFront() return ValidateMenuMoveToFrontOrBack(Selection.transforms, true); } - [MenuItem("GameObject/Set as last sibling %-")] + [MenuItem("GameObject/Set as last sibling %-", secondaryPriority = 2)] internal static void MenuMoveToBack() { var selectedTransforms = Selection.transforms; @@ -1706,7 +1772,7 @@ internal static bool ValidateMenuMoveToBack() return ValidateMenuMoveToFrontOrBack(Selection.transforms, false); } - [MenuItem("GameObject/Move To View %&f")] + [MenuItem("GameObject/Move To View %&f", secondaryPriority = 3)] internal static void MenuMoveToView() { if (ValidateMoveToView()) @@ -1719,7 +1785,7 @@ static bool ValidateMoveToView() return lastActiveSceneView != null && (Selection.transforms.Length != 0); } - [MenuItem("GameObject/Align With View %#f")] + [MenuItem("GameObject/Align With View %#f", secondaryPriority = 4)] internal static void MenuAlignWithView() { if (ValidateAlignWithView()) @@ -1732,7 +1798,7 @@ internal static bool ValidateAlignWithView() return lastActiveSceneView != null && (Selection.activeTransform != null); } - [MenuItem("GameObject/Align View to Selected")] + [MenuItem("GameObject/Align View to Selected", secondaryPriority = 5)] internal static void MenuAlignViewToSelected() { if (ValidateAlignViewToSelected()) @@ -1745,7 +1811,7 @@ internal static bool ValidateAlignViewToSelected() return lastActiveSceneView != null && (Selection.activeTransform != null); } - [MenuItem("GameObject/Toggle Active State &#a")] + [MenuItem("GameObject/Toggle Active State &#a", secondaryPriority = 6)] internal static void ActivateSelection() { if (Selection.activeTransform != null) @@ -1841,7 +1907,7 @@ void HandleClickAndDragToFocus() { Tools.s_ButtonDown = evt.button; - if (evt.button == 1 && Application.platform == RuntimePlatform.OSXEditor) + if (Application.platform == RuntimePlatform.OSXEditor) Focus(); } // this is necessary because FPS tool won't get is cleanup logic @@ -2046,9 +2112,9 @@ bool SceneCameraRendersIntoRT() return m_Camera.targetTexture != null; } - private void DoDrawCamera(Rect windowSpaceCameraRect, Rect groupSpaceCameraRect, out bool pushedGUIClip) + private void DoDrawCamera(Rect windowSpaceCameraRect, Rect groupSpaceCameraRect, out bool pushedGUIClipNeedsToBePopped) { - pushedGUIClip = false; + pushedGUIClipNeedsToBePopped = false; if (!m_Camera.gameObject.activeInHierarchy) return; @@ -2062,8 +2128,23 @@ private void DoDrawCamera(Rect windowSpaceCameraRect, Rect groupSpaceCameraRect, if (UseSceneFiltering()) { + bool sceneRendersToRT = SceneCameraRendersIntoRT(); + if (sceneRendersToRT) + { + GUIClip.Push(groupSpaceCameraRect, Vector2.zero, Vector2.zero, true); + GUIClip.Internal_PushParentClip(Matrix4x4.identity, GUIClip.GetParentMatrix(), groupSpaceCameraRect); + } + if (evt.type == EventType.Repaint) RenderFilteredScene(groupSpaceCameraRect); + + DrawPingedObjectSubmeshOutlineIfNeeded(); + + if (sceneRendersToRT) + { + GUIClip.Internal_PopParentClip(); + GUIClip.Pop(); + } if (evt.type == EventType.Repaint) RenderTexture.active = null; @@ -2083,7 +2164,7 @@ private void DoDrawCamera(Rect windowSpaceCameraRect, Rect groupSpaceCameraRect, { GUIClip.Push(new Rect(0f, 0f, position.width, position.height), Vector2.zero, Vector2.zero, true); GUIClip.Internal_PushParentClip(Matrix4x4.identity, GUIClip.GetParentMatrix(), groupSpaceCameraRect); - pushedGUIClip = true; + pushedGUIClipNeedsToBePopped = true; } Handles.DrawCameraStep1(groupSpaceCameraRect, m_Camera, m_CameraMode.drawMode, gridParam, drawGizmos, true); @@ -2092,6 +2173,8 @@ private void DoDrawCamera(Rect windowSpaceCameraRect, Rect groupSpaceCameraRect, if (s_SelectionCacheDirty) { HandleUtility.FilterInstanceIDs(Selection.gameObjects, out s_CachedParentRenderersFromSelection, out s_CachedChildRenderersFromSelection); + s_CachedChildRenderersFromSelectionHashSet.Clear(); + s_CachedChildRenderersFromSelectionHashSet.UnionWith(s_CachedChildRenderersFromSelection); s_SelectionCacheDirty = false; } @@ -2103,8 +2186,15 @@ private void DoDrawCamera(Rect windowSpaceCameraRect, Rect groupSpaceCameraRect, } DrawRenderModeOverlay(groupSpaceCameraRect); + + DrawPingedObjectSubmeshOutlineIfNeeded(); } + ShaderUtil.allowAsyncCompilation = oldAsync; + } + + void DrawPingedObjectSubmeshOutlineIfNeeded() + { if (isPingingObject) { var currentTime = Time.realtimeSinceStartup; @@ -2126,8 +2216,6 @@ private void DoDrawCamera(Rect windowSpaceCameraRect, Rect groupSpaceCameraRect, } } } - - ShaderUtil.allowAsyncCompilation = oldAsync; } void RenderFilteredScene(Rect groupSpaceCameraRect) @@ -2206,6 +2294,8 @@ void RenderFilteredScene(Rect groupSpaceCameraRect) if (s_SelectionCacheDirty) { HandleUtility.FilterInstanceIDs(Selection.gameObjects, out s_CachedParentRenderersFromSelection, out s_CachedChildRenderersFromSelection); + s_CachedChildRenderersFromSelectionHashSet.Clear(); + s_CachedChildRenderersFromSelectionHashSet.UnionWith(s_CachedChildRenderersFromSelection); s_SelectionCacheDirty = false; } @@ -2277,6 +2367,7 @@ void HandleViewToolCursor(Rect cameraRect) cursor = MouseCursor.Zoom; break; } + if (cursor != MouseCursor.Arrow) AddCursorRect(cameraRect, cursor); } @@ -2307,7 +2398,7 @@ void DoOnPreSceneGUICallbacks(Rect cameraRect) { // Don't do callbacks in search mode, as editors calling Handles.BeginGUI // will break camera setup. - if (UseSceneFiltering()) + if (hasSearchFilter) return; CallOnPreSceneGUI(); @@ -2326,7 +2417,7 @@ void DoOnGUI() Event evt = Event.current; //overlay.displayed cannot be changed during the layout event - if(evt.type != EventType.Layout) + if (evt.type != EventType.Layout) { bool shouldShow = lastActiveSceneView == this; foreach(var overlay in overlayCanvas.overlays) @@ -2403,8 +2494,8 @@ void DoOnGUI() GUIUtility.keyboardControl = m_MainViewControlID; // Draw camera - bool pushedGUIClip; - DoDrawCamera(windowSpaceCameraRect, groupSpaceCameraRect, out pushedGUIClip); + bool pushedGUIClipNeedsToBePopped; + DoDrawCamera(windowSpaceCameraRect, groupSpaceCameraRect, out pushedGUIClipNeedsToBePopped); CleanupCustomSceneLighting(); @@ -2418,7 +2509,6 @@ void DoOnGUI() bool hdrDisplayActive = (m_Parent != null && m_Parent.actualView == this && m_Parent.hdrActive); if (!UseSceneFiltering() && evt.type == EventType.Repaint && GraphicsFormatUtility.IsIEEE754Format(m_SceneTargetTexture.graphicsFormat) && !hdrDisplayActive) { - var currentDepthBuffer = Graphics.activeDepthBuffer; var rtDesc = m_SceneTargetTexture.descriptor; rtDesc.graphicsFormat = SystemInfo.GetGraphicsFormat(DefaultFormat.LDR); rtDesc.depthBufferBits = 0; @@ -2426,7 +2516,7 @@ void DoOnGUI() ldrSceneTargetTexture.name = "LDRSceneTarget"; Graphics.Blit(m_SceneTargetTexture, ldrSceneTargetTexture); Graphics.Blit(ldrSceneTargetTexture, m_SceneTargetTexture); - Graphics.SetRenderTarget(m_SceneTargetTexture.colorBuffer, currentDepthBuffer); + Graphics.SetRenderTarget(m_SceneTargetTexture.colorBuffer, m_SceneTargetTexture.depthBuffer); RenderTexture.ReleaseTemporary(ldrSceneTargetTexture); } @@ -2450,7 +2540,7 @@ void DoOnGUI() } // If we reset the offsets pop that clip off now. - if (pushedGUIClip) + if (pushedGUIClipNeedsToBePopped) { GUIClip.Internal_PopParentClip(); GUIClip.Pop(); @@ -2486,7 +2576,7 @@ void DoOnGUI() // Do not pass the camera transform to the SceneViewMotion calculations. // The camera transform is calculation *output* not *input*. // Avoiding using it as input too avoids errors accumulating. - SceneViewMotion.DoViewTool(this); + m_SceneViewMotion.DoViewTool(); Handles.SetCameraFilterMode(Camera.current, UseSceneFiltering() ? Handles.CameraFilterMode.ShowFiltered : Handles.CameraFilterMode.Off); @@ -2731,39 +2821,31 @@ void HandleMouseCursor() Rect cursorRect = new Rect(0, 0, position.width, position.height); var checkMouseRects = evt.type == EventType.MouseMove || evt.type == EventType.Repaint; - if (GUIUtility.hotControl == 0) - s_DraggingCursorIsCached = false; - - if (!s_DraggingCursorIsCached) + // Determine if mouse is inside a new cursor rect + if (checkMouseRects) { - // Determine if mouse is inside a new cursor rect bool repaintView = false; MouseCursor cursor = MouseCursor.Arrow; - if (checkMouseRects) + + foreach (CursorRect r in s_MouseRects) { - foreach (CursorRect r in s_MouseRects) + if (r.rect.Contains(evt.mousePosition)) { - if (r.rect.Contains(evt.mousePosition)) - { - cursor = r.cursor; - cursorRect = r.rect; - repaintView = true; - } + cursor = r.cursor; + cursorRect = r.rect; + repaintView = true; } + } - if (GUIUtility.hotControl != 0) - s_DraggingCursorIsCached = true; - - var cursorChanged = cursor != s_LastCursor; - if (cursorChanged) - { - s_LastCursor = cursor; - InternalEditorUtility.ResetCursor(); - } - if (repaintView || cursorChanged) - { - Repaint(); - } + var cursorChanged = cursor != s_LastCursor; + if (cursorChanged) + { + s_LastCursor = cursor; + InternalEditorUtility.ResetCursor(); + } + if (repaintView || cursorChanged) + { + Repaint(); } } @@ -2805,7 +2887,7 @@ void DrawRenderModeOverlay(Rect cameraRect) private void HandleSelectionAndOnSceneGUI() { - m_RectSelection.OnGUI(); + m_RectSelection.OnGUI(this == lastActiveSceneView); CallOnSceneGUI(); } @@ -2946,8 +3028,19 @@ internal bool CheckDrawModeForRenderingPath(DrawCameraMode mode) return true; } - private void SetSceneCameraHDRAndDepthModes() + private void UpdateSceneCameraSettings() { + var mainCamera = GetMainCamera(); + + // update physical camera properties + if (mainCamera != null) + { + m_Camera.iso = mainCamera.iso; + m_Camera.shutterSpeed = mainCamera.shutterSpeed; + m_Camera.aperture = mainCamera.aperture; + m_Camera.anamorphism = mainCamera.anamorphism; + } + if (!m_SceneIsLit || !DoesCameraDrawModeSupportHDR(m_CameraMode.drawMode)) { m_Camera.allowHDR = false; @@ -2955,7 +3048,7 @@ private void SetSceneCameraHDRAndDepthModes() m_Camera.clearStencilAfterLightingPass = false; return; } - var mainCamera = GetMainCamera(); + if (mainCamera == null) { m_Camera.allowHDR = false; @@ -3040,7 +3133,8 @@ void SetupCamera() m_Camera.renderingPath = GetSceneViewRenderingPath(); if (!CheckDrawModeForRenderingPath(m_CameraMode.drawMode)) m_CameraMode = GetBuiltinCameraMode(DrawCameraMode.Textured); - SetSceneCameraHDRAndDepthModes(); + + UpdateSceneCameraSettings(); if (m_CameraMode.drawMode == DrawCameraMode.Textured || m_CameraMode.drawMode == DrawCameraMode.TexturedWire || @@ -3101,8 +3195,17 @@ void OnBecameInvisible() void UpdateAnimatedMaterials() { var repaint = false; - if (m_lastRenderedTime + 0.033f < EditorApplication.timeSinceStartup) + + // Ensure that we in fact do want to paint when not in focus. + if (!EditorApplication.isFocused && s_PreferenceIgnoreAlwaysRefreshWhenNotFocused.value) + { + // We're going to capture this condition, so that it doesnt fall through. + } + else if (m_lastRenderedTime + 0.033f < EditorApplication.timeSinceStartup) + { repaint = sceneViewState.alwaysRefreshEnabled; + } + repaint |= LODUtility.IsLODAnimating(m_Camera); if (repaint) @@ -3197,7 +3300,7 @@ public void LookAt(Vector3 point, Quaternion direction, float newSize, bool orth // Look at a specific point from a given direction with a given zoom level, enabling and disabling perspective public void LookAt(Vector3 point, Quaternion direction, float newSize, bool ortho, bool instant) { - SceneViewMotion.ResetMotion(); + m_SceneViewMotion.ResetMotion(); FixNegativeSize(); if (instant) @@ -3219,6 +3322,11 @@ public void LookAt(Vector3 point, Quaternion direction, float newSize, bool orth m_OrientationGizmo.UpdateGizmoLabel(this, direction * Vector3.forward, m_Ortho.target); } + internal void UpdateOrientationGizmos() + { + m_OrientationGizmo?.UpdateGizmoLabel(this, rotation * Vector3.forward, m_Ortho.target); + } + void DefaultHandles() { // Note event state. @@ -3343,7 +3451,8 @@ internal void HandleDragging(Event evt) if (DragAndDrop.HasHandler(DragAndDropWindowTarget.sceneView)) { PickObject(ref pickedObject, ref parentTransform); - DragAndDrop.visualMode = DragAndDrop.DropOnSceneWindow(pickedObject, pivot, Event.current.mousePosition, parentTransform, isPerform); + Vector3 worldPosition = HandleUtility.PlaceObject(Event.current.mousePosition, out Vector3 placedPosition, out _) ? placedPosition : pivot; + DragAndDrop.visualMode = DragAndDrop.DropOnSceneWindow(pickedObject, worldPosition, Event.current.mousePosition, parentTransform, isPerform); dropHandled = DragAndDrop.visualMode != DragAndDropVisualMode.None; } @@ -3362,7 +3471,9 @@ internal void HandleDragging(Event evt) { if (pickedObject == null || parentTransform == null) PickObject(ref pickedObject, ref parentTransform); - DragAndDrop.visualMode = InternalEditorUtility.SceneViewDrag(pickedObject, pivot, Event.current.mousePosition, parentTransform, isPerform); + + Vector3 worldPosition = HandleUtility.PlaceObject(Event.current.mousePosition, out Vector3 placedPosition, out _) ? placedPosition : pivot; + DragAndDrop.visualMode = InternalEditorUtility.SceneViewDrag(pickedObject, worldPosition, Event.current.mousePosition, parentTransform, isPerform); } evt.Use(); @@ -3558,7 +3669,7 @@ internal bool IsGameObjectInThisSceneView(GameObject gameObject) if (gameObject == null) return false; - return StageUtility.IsGameObjectRenderedByCamera(gameObject, camera); + return !StageUtility.IsGizmoCulledBySceneCullingMasksOrFocusedScene(gameObject, camera); } public bool FrameSelected() diff --git a/Editor/Mono/SceneView/SceneViewMotion.cs b/Editor/Mono/SceneView/SceneViewMotion.cs index 37e7294be1..67f124573e 100644 --- a/Editor/Mono/SceneView/SceneViewMotion.cs +++ b/Editor/Mono/SceneView/SceneViewMotion.cs @@ -9,290 +9,385 @@ namespace UnityEditor { - internal static class SceneViewMotion + class SceneViewMotion { - [NonSerialized] - static bool s_Initialized; - static SceneView s_CurrentSceneView; // The SceneView that is calling OnGUI - static SceneView s_ActiveSceneView; // The SceneView that is being navigated - static Vector3 s_Motion; - internal static float k_FlySpeed = 9f; - static float s_FlySpeedTarget = 0f; + const string k_TemporaryPanTool2D1 = "Scene View/Temporary Pan Tool for 2D Mode 1"; + const string k_TemporaryPanTool2D2 = "Scene View/Temporary Pan Tool for 2D Mode 2"; + const string k_TemporaryPanTool1 = "Scene View/Temporary Pan Tool 1"; + const string k_TemporaryPanTool2 = "Scene View/Temporary Pan Tool 2"; + const string k_TemporaryPanTool3 = "Scene View/Temporary Pan Tool 3"; + const string k_TemporaryPanTool4 = "Scene View/Temporary Pan Tool 4"; + const string k_TemporaryZoomTool1 = "Scene View/Temporary Zoom Tool 1"; + const string k_TemporaryZoomTool2 = "Scene View/Temporary Zoom Tool 2"; + const string k_TemporaryOrbitTool = "Scene View/Temporary Orbit Tool"; + const string k_TemporaryFpsTool = "Scene View/Temporary FPS Tool"; + const string k_PanFocusTool = "Scene View/Pan Focus Tool"; + const string k_LockedPanTool = "Scene View/Locked Pan Tool"; + const string k_LockedPanFocusTool = "Scene View/Locked Pan Focus Tool"; + + internal const string k_PanFocusEventCommandName = "SceneViewPanFocusEventCommand"; // Used in tests. + internal const string k_SetSceneViewMotionHotControlEventCommandName = "SetSceneViewMotionHotControlEventCommand"; // Also used in tests. + + bool m_Moving; + bool m_ViewportsUnderMouse; + public bool viewportsUnderMouse + { + get { return m_ViewportsUnderMouse; } + set { m_ViewportsUnderMouse = value; } + } + + readonly CameraFlyModeContext m_CameraFlyModeContext = new CameraFlyModeContext(); + + readonly SceneViewViewport m_SceneViewViewportContext = new SceneViewViewport(); + readonly SceneViewViewport2D m_SceneViewViewport2DContext = new SceneViewViewport2D(); + readonly SceneViewViewport3D m_SceneViewViewport3DContext = new SceneViewViewport3D(); + readonly SceneViewViewportLockedPanTool m_SceneViewViewportLockedPanToolContext = new SceneViewViewportLockedPanTool(); + + AnimVector3 m_FlySpeed = new AnimVector3(Vector3.zero); + + public static event Action viewToolActiveChanged; + + Vector3 m_Motion; + + Vector2 m_StartMousePosition; // The start mouse position is used for the pan focus tool. + + float m_FlySpeedTarget = 0f; + float m_StartZoom = 0f; + float m_ZoomSpeed = 0f; + float m_TotalMotion = 0f; + float m_FPSScrollWheelMultiplier = .01f; const float k_FlySpeedAcceleration = 1.8f; - static float s_StartZoom = 0f, s_ZoomSpeed = 0f; - static float s_TotalMotion = 0f; - static float s_FPSScrollWheelMultiplier = .01f; - static bool s_Moving; - static bool s_Drag; - static AnimVector3 s_FlySpeed = new AnimVector3(Vector3.zero); - - internal static Vector3 cameraSpeed + public const float k_FlySpeed = 9f; // Also used in tests. + + readonly int k_ViewToolID = GUIUtility.GetPermanentControlID(); + + readonly char[] k_TrimChars = new char[] { '0' }; + + public Vector3 cameraSpeed + { + get { return m_FlySpeed.value; } + } + + static bool s_ViewToolIsActive = false; + public static bool viewToolIsActive => UpdateViewToolState(); + + public void RegisterShortcutContexts() { - get { return s_FlySpeed.value; } + ShortcutIntegration.instance.contextManager.RegisterToolContext(m_SceneViewViewportContext); + ShortcutIntegration.instance.contextManager.RegisterToolContext(m_SceneViewViewport2DContext); + ShortcutIntegration.instance.contextManager.RegisterToolContext(m_SceneViewViewport3DContext); + ShortcutIntegration.instance.contextManager.RegisterToolContext(m_SceneViewViewportLockedPanToolContext); + ShortcutIntegration.instance.contextManager.RegisterToolContext(m_CameraFlyModeContext); } - enum MotionState + public void UnregisterShortcutContexts() { - kInactive, - kActive, - kDragging + ShortcutIntegration.instance.contextManager.DeregisterToolContext(m_SceneViewViewportContext); + ShortcutIntegration.instance.contextManager.DeregisterToolContext(m_SceneViewViewport2DContext); + ShortcutIntegration.instance.contextManager.DeregisterToolContext(m_SceneViewViewport3DContext); + ShortcutIntegration.instance.contextManager.DeregisterToolContext(m_SceneViewViewportLockedPanToolContext); + ShortcutIntegration.instance.contextManager.DeregisterToolContext(m_CameraFlyModeContext); } - static MotionState s_CurrentState; + interface ISceneViewContext : IShortcutToolContext + { + public SceneView window => EditorWindow.focusedWindow as SceneView; + } + + [ReserveModifiers(ShortcutModifiers.Shift)] + internal class SceneViewViewport : ISceneViewContext + { + public bool active => IsActive; + + public static bool IsActive + { + get + { + if (!(EditorWindow.focusedWindow is SceneView view) || view.sceneViewMotion == null) + return false; + + return view.sceneViewMotion.viewportsUnderMouse && Tools.s_LockedViewTool == ViewTool.None; + } + } + } - static int s_ViewToolID = GUIUtility.GetPermanentControlID(); + [ReserveModifiers(ShortcutModifiers.Shift)] + class SceneViewViewport2D : ISceneViewContext + { + public bool active + { + get + { + if (SceneView.lastActiveSceneView == null) + return false; - static readonly CameraFlyModeContext s_CameraFlyModeContext = new CameraFlyModeContext(); + return SceneViewViewport.IsActive && (SceneView.lastActiveSceneView.in2DMode || SceneView.lastActiveSceneView.isRotationLocked); + } + } + } - static bool s_ViewToolActive = false; - internal static bool viewToolActive + [ReserveModifiers(ShortcutModifiers.Shift)] + class SceneViewViewport3D : ISceneViewContext { - get + public bool active { - if (Event.current != null) - UpdateViewToolState(Event.current); - return s_ViewToolActive; + get + { + if (SceneView.lastActiveSceneView == null) + return false; + + return SceneViewViewport.IsActive && !SceneView.lastActiveSceneView.in2DMode && !SceneView.lastActiveSceneView.isRotationLocked; + } } } - internal static event Action viewToolActiveChanged; - static void Init() + [ReserveModifiers(ShortcutModifiers.Shift)] + class SceneViewViewportLockedPanTool : ISceneViewContext { - if (s_Initialized) - return; - ShortcutIntegration.instance.contextManager.RegisterToolContext(new Context2D()); - ShortcutIntegration.instance.contextManager.RegisterToolContext(new Context3D()); - s_Initialized = true; + public bool active => SceneViewViewport.IsActive && Tools.current == Tool.View; } - class Context2D : IShortcutToolContext + [Shortcut(k_PanFocusTool, typeof(SceneViewViewport), KeyCode.Mouse2)] + [Shortcut(k_LockedPanFocusTool, typeof(SceneViewViewportLockedPanTool), KeyCode.Mouse0)] + static void PanFocus(ShortcutArguments args) { - public bool active => EditorWindow.focusedWindow?.GetType() == typeof(SceneView) - && ((SceneView.lastActiveSceneView?.in2DMode ?? false) || (SceneView.lastActiveSceneView?.isRotationLocked ?? false)); + // Delaying the picking to a command event is necessary because some HandleUtility methods + // need to be called in an OnGUI. + if (args.context is ISceneViewContext ctx && ctx.window != null) + ctx.window.SendEvent(EditorGUIUtility.CommandEvent(k_PanFocusEventCommandName)); } - class Context3D : IShortcutToolContext + void PanFocus(Vector2 mousePos, SceneView currentSceneView, Event evt) { - public bool active => EditorWindow.focusedWindow?.GetType() == typeof(SceneView) - && ((!SceneView.lastActiveSceneView?.in2DMode ?? false) && (!SceneView.lastActiveSceneView?.isRotationLocked ?? false)); + // Move pivot to clicked point. + RaycastHit hit; + if (RaycastWorld(mousePos, out hit)) + { + Vector3 currentPosition = currentSceneView.pivot - currentSceneView.rotation * Vector3.forward * currentSceneView.cameraDistance; + float targetSize = currentSceneView.size; + + if (!currentSceneView.orthographic) + targetSize = currentSceneView.size * Vector3.Dot(hit.point - currentPosition, currentSceneView.rotation * Vector3.forward) / currentSceneView.cameraDistance; + + currentSceneView.LookAt(hit.point, currentSceneView.rotation, targetSize); + } + + evt.Use(); } - [ClutchShortcut("Scene View/Temporary Pan Tool for 2D Mode", typeof(Context2D), KeyCode.Mouse1)] - [ClutchShortcut("Scene View/Temporary Pan Tool 1", typeof(SceneView), KeyCode.Mouse2)] - [ClutchShortcut("Scene View/Temporary Pan Tool 2", typeof(SceneView), KeyCode.Mouse2, ShortcutModifiers.Alt)] + [ClutchShortcut(k_TemporaryPanTool2D1, typeof(SceneViewViewport2D), KeyCode.Mouse1)] + [ClutchShortcut(k_TemporaryPanTool2D2, typeof(SceneViewViewport2D), KeyCode.Mouse0, ShortcutModifiers.Alt)] + [ClutchShortcut(k_TemporaryPanTool1, typeof(SceneViewViewport), KeyCode.Mouse2)] + [ClutchShortcut(k_TemporaryPanTool2, typeof(SceneViewViewport), KeyCode.Mouse2, ShortcutModifiers.Alt)] + [ClutchShortcut(k_TemporaryPanTool3, typeof(SceneViewViewport), KeyCode.Mouse0, ShortcutModifiers.Action | ShortcutModifiers.Alt)] + [ClutchShortcut(k_TemporaryPanTool4, typeof(SceneViewViewport), KeyCode.Mouse2, ShortcutModifiers.Action | ShortcutModifiers.Alt)] + [ClutchShortcut(k_LockedPanTool, typeof(SceneViewViewportLockedPanTool), KeyCode.Mouse0)] static void TemporaryPan(ShortcutArguments args) { - if (args.stage == ShortcutStage.Begin) TemporaryTool(ViewTool.Pan); - else HandleMouseUp(s_CurrentSceneView, s_ViewToolID, 0, 0); + if (args.context is ISceneViewContext ctx && ctx.window != null && ctx.window.sceneViewMotion != null) + ctx.window.sceneViewMotion.HandleSceneViewMotionTool(args, ViewTool.Pan, ctx.window); } - [ClutchShortcut("Scene View/Temporary Zoom Tool", typeof(SceneView), KeyCode.Mouse1, ShortcutModifiers.Alt)] + [ClutchShortcut(k_TemporaryZoomTool1, typeof(SceneViewViewport), KeyCode.Mouse1, ShortcutModifiers.Alt)] + [ClutchShortcut(k_TemporaryZoomTool2, typeof(SceneViewViewport), KeyCode.Mouse1, ShortcutModifiers.Action | ShortcutModifiers.Alt)] static void TemporaryZoom(ShortcutArguments args) { - if (args.stage == ShortcutStage.Begin) TemporaryTool(ViewTool.Zoom); - else HandleMouseUp(s_CurrentSceneView, s_ViewToolID, 0, 0); + if (args.context is ISceneViewContext ctx && ctx.window != null && ctx.window.sceneViewMotion != null) + ctx.window.sceneViewMotion.HandleSceneViewMotionTool(args, ViewTool.Zoom, ctx.window); } - [ClutchShortcut("Scene View/Temporary Orbit Tool", typeof(SceneView), KeyCode.Mouse0, ShortcutModifiers.Alt)] + [ClutchShortcut(k_TemporaryOrbitTool, typeof(SceneViewViewport3D), KeyCode.Mouse0, ShortcutModifiers.Alt)] static void TemporaryOrbit(ShortcutArguments args) { - if (args.stage == ShortcutStage.Begin) TemporaryTool(ViewTool.Orbit); - else HandleMouseUp(s_CurrentSceneView, s_ViewToolID, 0, 0); + if (args.context is ISceneViewContext ctx && ctx.window != null && ctx.window.sceneViewMotion != null) + ctx.window.sceneViewMotion.HandleSceneViewMotionTool(args, ViewTool.Orbit, ctx.window); + } + + void HandleSceneViewMotionTool(ShortcutArguments args, ViewTool viewTool, SceneView view) + { + if (args.stage == ShortcutStage.Begin && GUIUtility.hotControl == 0) + StartSceneViewMotionTool(viewTool, view); + else if (args.stage == ShortcutStage.End && Tools.s_LockedViewTool == viewTool) + CompleteSceneViewMotionTool(); } - [ClutchShortcut("Scene View/Temporary FPS Tool", typeof(Context3D), KeyCode.Mouse1)] + [ClutchShortcut(k_TemporaryFpsTool, typeof(SceneViewViewport3D), KeyCode.Mouse1)] static void TemporaryFPS(ShortcutArguments args) { - if (args.stage == ShortcutStage.Begin) TemporaryTool(ViewTool.FPS); - else HandleMouseUp(s_CurrentSceneView, s_ViewToolID, 0, 0); + var context = args.context as ISceneViewContext; + if (context == null || context.window == null || context.window.sceneViewMotion == null) + return; + + if (args.stage == ShortcutStage.Begin && GUIUtility.hotControl == 0) + { + context.window.sceneViewMotion.StartSceneViewMotionTool(ViewTool.FPS, context.window); + context.window.sceneViewMotion.m_CameraFlyModeContext.active = true; + } + else if (args.stage == ShortcutStage.End && Tools.s_LockedViewTool == ViewTool.FPS) + { + context.window.sceneViewMotion.CompleteSceneViewMotionTool(); + context.window.sceneViewMotion.m_CameraFlyModeContext.active = false; + } } - static KeyCode shortcutKey; - static void TemporaryTool(ViewTool tool) + void StartSceneViewMotionTool(ViewTool viewTool, SceneView view) { - Tools.s_LockedViewTool = Tools.viewTool = tool; - s_CurrentState = MotionState.kDragging; - HandleMouseDown(SceneView.lastActiveSceneView, s_ViewToolID, Event.current?.button ?? 0); - UpdateViewToolState(Event.current); - if(Event.current != null) shortcutKey = Event.current.isMouse ? KeyCode.Mouse0 + Event.current.button : Event.current.keyCode; - else shortcutKey = KeyCode.None; + Tools.s_LockedViewTool = Tools.viewTool = viewTool; + + // Set up zoom parameters + m_StartZoom = view.size; + m_ZoomSpeed = Mathf.Max(Mathf.Abs(m_StartZoom), .3f); + m_TotalMotion = 0; + + if (Toolbar.get) + Toolbar.get.Repaint(); + + EditorGUIUtility.SetWantsMouseJumping(1); + + UpdateViewToolState(); + + // The hot control needs to be set in an OnGUI call. + view.SendEvent(EditorGUIUtility.CommandEvent(k_SetSceneViewMotionHotControlEventCommandName)); } - public static void DoViewTool(SceneView view) + public void CompleteSceneViewMotionTool() { - Init(); + Tools.viewTool = ViewTool.Pan; + Tools.s_LockedViewTool = ViewTool.None; - s_CurrentSceneView = view; + if (viewToolActiveChanged != null) + viewToolActiveChanged.Invoke(); - // If a SceneView is currently taking input, don't let other views accept input - if (s_ActiveSceneView != null && s_CurrentSceneView != s_ActiveSceneView) - return; + Tools.s_ButtonDown = -1; - Event evt = Event.current; + if (Toolbar.get) + Toolbar.get.Repaint(); - // Ensure we always call the GetControlID the same number of times - int id = s_ViewToolID; + EditorGUIUtility.SetWantsMouseJumping(0); + } - EventType eventType = evt.GetTypeForControl(id); + public void DoViewTool() + { + var view = SceneView.lastActiveSceneView; + if (view == null) + return; // In FPS mode we update the pivot for Orbit mode (see below and inside HandleMouseDrag) - if (view && Tools.s_LockedViewTool == ViewTool.FPS) - { + if (Tools.s_LockedViewTool == ViewTool.FPS) view.FixNegativeSize(); - } - using (var inputSamplingScope = new CameraFlyModeContext.InputSamplingScope(s_CameraFlyModeContext, Tools.s_LockedViewTool, id, view, view.orthographic)) + using (var inputSamplingScope = new CameraFlyModeContext.InputSamplingScope + (m_CameraFlyModeContext, Tools.s_LockedViewTool, k_ViewToolID, view, view.orthographic)) { if (inputSamplingScope.currentlyMoving) view.viewIsLockedToObject = false; - s_Motion = inputSamplingScope.currentInputVector; + m_Motion = inputSamplingScope.currentInputVector; } - switch (eventType) + // If a different mouse button is clicked while the current mouse button is held down, + // reset the hot control to the correct id. + if (GUIUtility.hotControl == 0 && Tools.s_LockedViewTool != ViewTool.None) + GUIUtility.hotControl = k_ViewToolID; + + var evt = Event.current; + switch (evt.GetTypeForControl(k_ViewToolID)) { - case EventType.ScrollWheel: HandleScrollWheel(view, view.in2DMode == evt.alt); break; // Default to zooming to mouse position in 2D mode without alt - case EventType.MouseDown: HandleMouseDown(view, id, evt.button); break; - case EventType.KeyUp: - case EventType.MouseUp: HandleMouseUp(view, id, evt.button, evt.clickCount); break; - case EventType.KeyDown: HandleKeyDown(view, id); break; - case EventType.MouseMove: - case EventType.MouseDrag: HandleMouseDrag(view, id); break; + case EventType.ScrollWheel: + // Default to zooming to mouse position in 2D mode without alt. + HandleScrollWheel(view, view.in2DMode == evt.alt); + break; + case EventType.MouseDown: + if (GUIUtility.hotControl == 0) + m_StartMousePosition = evt.mousePosition; + break; + case EventType.MouseUp: + if (GUIUtility.hotControl == k_ViewToolID) + GUIUtility.hotControl = 0; + break; + case EventType.KeyDown: // Escape + HandleKeyDown(); + break; + case EventType.MouseDrag: + HandleMouseDrag(view); + break; case EventType.Layout: - if (GUIUtility.hotControl == id || s_FlySpeed.isAnimating || s_Moving) + if (GUIUtility.hotControl == k_ViewToolID || m_FlySpeed.isAnimating || m_Moving) { - view.pivot = view.pivot + view.rotation * GetMovementDirection(); + view.pivot = view.pivot + view.rotation * GetMovementDirection(view); view.Repaint(); } break; + case EventType.ExecuteCommand: + if (evt.commandName == k_PanFocusEventCommandName) + { + PanFocus(m_StartMousePosition, view, evt); + } + else if (evt.commandName == k_SetSceneViewMotionHotControlEventCommandName) + { + GUIUtility.hotControl = k_ViewToolID; + evt.Use(); + } + break; } - - if (s_CurrentState == MotionState.kDragging && evt.type == EventType.Repaint) - { - HandleMouseDrag(view, id); - } - - if (shortcutKey != KeyCode.None && Tools.viewTool != ViewTool.None) GUIUtility.hotControl = s_ViewToolID; } - static void UpdateViewToolState(Event evt) + static bool UpdateViewToolState() { - bool shouldBeActive = Tools.s_LockedViewTool != ViewTool.None; - if (shouldBeActive != s_ViewToolActive) + bool shouldBeActive = Tools.s_LockedViewTool != ViewTool.None || Tools.current == Tool.View; + if (shouldBeActive != s_ViewToolIsActive) { - s_ViewToolActive = shouldBeActive; - viewToolActiveChanged?.Invoke(); + s_ViewToolIsActive = shouldBeActive; + + if (viewToolActiveChanged != null) + viewToolActiveChanged.Invoke(); } + + return s_ViewToolIsActive; } - static Vector3 GetMovementDirection() + Vector3 GetMovementDirection(SceneView view) { - s_Moving = s_Motion.sqrMagnitude > 0f; + m_Moving = m_Motion.sqrMagnitude > 0f; var deltaTime = CameraFlyModeContext.deltaTime; - var speedModifier = s_CurrentSceneView.cameraSettings.speed; + var speedModifier = view.cameraSettings.speed; if (Event.current.shift) speedModifier *= 5f; - if (s_Moving) + if (m_Moving) { - if (s_CurrentSceneView.cameraSettings.accelerationEnabled) - s_FlySpeedTarget = s_FlySpeedTarget < Mathf.Epsilon ? k_FlySpeed : s_FlySpeedTarget * Mathf.Pow(k_FlySpeedAcceleration, deltaTime); + if (view.cameraSettings.accelerationEnabled) + m_FlySpeedTarget = m_FlySpeedTarget < Mathf.Epsilon ? k_FlySpeed : m_FlySpeedTarget * Mathf.Pow(k_FlySpeedAcceleration, deltaTime); else - s_FlySpeedTarget = k_FlySpeed; + m_FlySpeedTarget = k_FlySpeed; } else { - s_FlySpeedTarget = 0f; + m_FlySpeedTarget = 0f; } - if (s_CurrentSceneView.cameraSettings.easingEnabled) + if (view.cameraSettings.easingEnabled) { - s_FlySpeed.speed = 1f / s_CurrentSceneView.cameraSettings.easingDuration; - s_FlySpeed.target = s_Motion.normalized * s_FlySpeedTarget * speedModifier; + m_FlySpeed.speed = 1f / view.cameraSettings.easingDuration; + m_FlySpeed.target = m_Motion.normalized * m_FlySpeedTarget * speedModifier; } else { - s_FlySpeed.value = s_Motion.normalized * s_FlySpeedTarget * speedModifier; - } - - return s_FlySpeed.value * deltaTime; - } - - private static void HandleMouseDown(SceneView view, int id, int button) - { - Event evt = Event.current; - - // Set up zoom parameters - s_StartZoom = view.size; - s_ZoomSpeed = Mathf.Max(Mathf.Abs(s_StartZoom), .3f); - s_TotalMotion = 0; - - if (view) - view.Focus(); - - if (Toolbar.get) - Toolbar.get.Repaint(); - EditorGUIUtility.SetWantsMouseJumping(1); - s_ActiveSceneView = s_CurrentSceneView; - - if (Tools.s_LockedViewTool == ViewTool.None && Tools.current == Tool.View) - { - bool controlKeyOnMac = (evt.control && Application.platform == RuntimePlatform.OSXEditor); - bool actionKey = EditorGUI.actionKey; - bool noModifiers = (!actionKey && !controlKeyOnMac && !evt.alt); - if (evt.button == 0 && noModifiers) TemporaryPan(new ShortcutArguments() { stage = ShortcutStage.Begin}); + m_FlySpeed.value = m_Motion.normalized * m_FlySpeedTarget * speedModifier; } - } - - internal static void ResetDragState() - { - s_ActiveSceneView = null; - if (GUIUtility.hotControl == s_ViewToolID) - GUIUtility.hotControl = 0; - s_CurrentState = MotionState.kInactive; - Tools.s_LockedViewTool = ViewTool.None; - Tools.s_ButtonDown = -1; - if (Toolbar.get) - Toolbar.get.Repaint(); - EditorGUIUtility.SetWantsMouseJumping(0); - s_Drag = false; - } - internal static void ResetMotion() - { - s_Motion = Vector3.zero; - s_FlySpeed.value = Vector3.zero; - s_Moving = false; + return m_FlySpeed.value * deltaTime; } - private static void HandleMouseUp(SceneView view, int id, int button, int clickCount) + public void ResetMotion() { - if (GUIUtility.hotControl == id && (shortcutKey == KeyCode.None || shortcutKey == (Event.current.keyCode == KeyCode.None ? KeyCode.Mouse0 + Event.current.button : Event.current.keyCode))) - { - // Move pivot to clicked point. - if (Tools.s_LockedViewTool == ViewTool.Pan && !s_Drag) - { - RaycastHit hit; - if (RaycastWorld(Event.current.mousePosition, out hit)) - { - Vector3 currentPosition = view.pivot - view.rotation * Vector3.forward * view.cameraDistance; - float targetSize = view.size; - if (!view.orthographic) - targetSize = view.size * Vector3.Dot(hit.point - currentPosition, view.rotation * Vector3.forward) / view.cameraDistance; - view.LookAt(hit.point, view.rotation, targetSize); - } - } - - Tools.viewTool = ViewTool.Pan; - Tools.s_LockedViewTool = ViewTool.None; - shortcutKey = KeyCode.None; - ResetDragState(); - viewToolActiveChanged?.Invoke(); - } + m_Motion = Vector3.zero; + m_FlySpeed.value = Vector3.zero; + m_Moving = false; } - static bool RaycastWorld(Vector2 position, out RaycastHit hit) + bool RaycastWorld(Vector2 position, out RaycastHit hit) { hit = new RaycastHit(); GameObject picked = HandleUtility.PickGameObject(position, false); @@ -344,10 +439,11 @@ static bool RaycastWorld(Vector2 position, out RaycastHit hit) // If we didn't hit any mesh or collider surface, then use the transform position projected onto the ray. hit.point = Vector3.Project(picked.transform.position - mouseRay.origin, mouseRay.direction) + mouseRay.origin; } + return true; } - private static void OrbitCameraBehavior(SceneView view) + private void OrbitCameraBehavior(SceneView view) { Event evt = Event.current; @@ -355,104 +451,106 @@ private static void OrbitCameraBehavior(SceneView view) Quaternion rotation = view.m_Rotation.target; rotation = Quaternion.AngleAxis(evt.delta.y * .003f * Mathf.Rad2Deg, rotation * Vector3.right) * rotation; rotation = Quaternion.AngleAxis(evt.delta.x * .003f * Mathf.Rad2Deg, Vector3.up) * rotation; + if (view.size < 0) { view.pivot = view.camera.transform.position; view.size = 0; } + view.rotation = rotation; } - private static void HandleMouseDrag(SceneView view, int id) + private void HandleMouseDrag(SceneView view) { - if (!Event.current.isMouse || GUIUtility.hotControl != id) return; + if (!Event.current.isMouse || GUIUtility.hotControl != k_ViewToolID) + return; - s_Drag = true; Event evt = Event.current; switch (Tools.s_LockedViewTool) { case ViewTool.Orbit: - { - if (!view.in2DMode && !view.isRotationLocked) { - OrbitCameraBehavior(view); - // todo gizmo update label - // view.m_OrientationGizmo.UpdateGizmoLabel(view, view.rotation * Vector3.forward, view.m_Ortho.target); + if (!view.in2DMode && !view.isRotationLocked) + { + OrbitCameraBehavior(view); + view.UpdateOrientationGizmos(); + } } - } - break; + break; case ViewTool.FPS: - { - if (!view.in2DMode && !view.isRotationLocked) { - if (!view.orthographic) - { - view.viewIsLockedToObject = false; - - // The reason we calculate the camera position from the pivot, rotation and distance, - // rather than just getting it from the camera transform is that the camera transform - // is the *output* of camera motion calculations. It shouldn't be input and output at the same time, - // otherwise we easily get accumulated error. - // We did get accumulated error before when we did this - the camera would continuously move slightly in FPS mode - // even when not holding down any arrow/ASDW keys or moving the mouse. - Vector3 camPos = view.pivot - view.rotation * Vector3.forward * view.cameraDistance; - - // Normal FPS camera behavior - Quaternion rotation = view.rotation; - rotation = Quaternion.AngleAxis(evt.delta.y * .003f * Mathf.Rad2Deg, rotation * Vector3.right) * rotation; - rotation = Quaternion.AngleAxis(evt.delta.x * .003f * Mathf.Rad2Deg, Vector3.up) * rotation; - view.rotation = rotation; - - view.pivot = camPos + rotation * Vector3.forward * view.cameraDistance; - } - else + if (!view.in2DMode && !view.isRotationLocked) { - // We want orbit behavior in orthograpic when using FPS - OrbitCameraBehavior(view); + if (!view.orthographic) + { + view.viewIsLockedToObject = false; + + // The reason we calculate the camera position from the pivot, rotation and distance, + // rather than just getting it from the camera transform is that the camera transform + // is the *output* of camera motion calculations. It shouldn't be input and output at the same time, + // otherwise we easily get accumulated error. + // We did get accumulated error before when we did this - the camera would continuously move slightly in FPS mode + // even when not holding down any arrow/ASDW keys or moving the mouse. + Vector3 camPos = view.pivot - view.rotation * Vector3.forward * view.cameraDistance; + + // Normal FPS camera behavior + Quaternion rotation = view.rotation; + rotation = Quaternion.AngleAxis(evt.delta.y * .003f * Mathf.Rad2Deg, rotation * Vector3.right) * rotation; + rotation = Quaternion.AngleAxis(evt.delta.x * .003f * Mathf.Rad2Deg, Vector3.up) * rotation; + view.rotation = rotation; + + view.pivot = camPos + rotation * Vector3.forward * view.cameraDistance; + } + else + { + // We want orbit behavior in orthograpic when using FPS + OrbitCameraBehavior(view); + } + + view.UpdateOrientationGizmos(); } - - // todo gizmo update label - // view.m_OrientationGizmo.UpdateGizmoLabel(view, view.rotation * Vector3.forward, view.m_Ortho.target); } - } - break; + break; case ViewTool.Pan: - { - view.viewIsLockedToObject = false; - view.FixNegativeSize(); - - Vector2 screenDelta = Event.current.delta; - Vector3 worldDelta = ScreenToWorldDistance(view, new Vector2(-screenDelta.x, screenDelta.y)); + { + view.viewIsLockedToObject = false; + view.FixNegativeSize(); - if (evt.shift) - worldDelta *= 4; + Vector2 screenDelta = Event.current.delta; + Vector3 worldDelta = ScreenToWorldDistance(view, new Vector2(-screenDelta.x, screenDelta.y)); - view.pivot += worldDelta; - } - break; - case ViewTool.Zoom: - { - float zoomDelta = HandleUtility.niceMouseDeltaZoom * (evt.shift ? 9 : 3); + if (evt.shift) + worldDelta *= 4; - if (view.orthographic) - { - view.size = Mathf.Max(.0001f, view.size * (1 + zoomDelta * .001f)); + view.pivot += worldDelta; } - else + break; + case ViewTool.Zoom: { - s_TotalMotion += zoomDelta; + float zoomDelta = HandleUtility.niceMouseDeltaZoom * (evt.shift ? 9 : 3); - if (s_TotalMotion < 0) - view.size = s_StartZoom * (1 + s_TotalMotion * .001f); + if (view.orthographic) + { + view.size = Mathf.Max(.0001f, view.size * (1 + zoomDelta * .001f)); + } else - view.size = view.size + zoomDelta * s_ZoomSpeed * .003f; + { + m_TotalMotion += zoomDelta; + + if (m_TotalMotion < 0) + view.size = m_StartZoom * (1 + m_TotalMotion * .001f); + else + view.size = view.size + zoomDelta * m_ZoomSpeed * .003f; + } } - } - break; + break; } + + evt.Use(); } - internal static Vector3 ScreenToWorldDistance(SceneView view, Vector2 delta) + public Vector3 ScreenToWorldDistance(SceneView view, Vector2 delta) { // Ensures that the camera matrix doesn't end up with gigantic or minuscule values in the clip to world matrix const float k_MaxCameraSizeForWorldToScreen = 2.5E+7f; @@ -462,6 +560,7 @@ internal static Vector3 ScreenToWorldDistance(SceneView view, Vector2 delta) var near = camera.nearClipPlane; var far = camera.farClipPlane; var pos = camera.transform.position; + var rotation = camera.transform.rotation; var size = view.size; // set camera transform and clip values to safe values @@ -472,12 +571,18 @@ internal static Vector3 ScreenToWorldDistance(SceneView view, Vector2 delta) view.camera.nearClipPlane = clip.x; view.camera.farClipPlane = clip.y; view.camera.transform.position = Vector3.zero; + view.camera.transform.rotation = Quaternion.identity; // do the distance calculation - Vector3 pivot = camera.transform.rotation * new Vector3(0f, 0f, view.cameraDistance); - Vector3 screenPos = camera.WorldToScreenPoint(pivot); - screenPos += new Vector3(delta.x, delta.y, 0); - Vector3 worldDelta = camera.ScreenToWorldPoint(screenPos) - pivot; + Vector3 pivotWorld = camera.transform.rotation * new Vector3(0f, 0f, view.cameraDistance); + Vector3 pivotScreen = camera.WorldToScreenPoint(pivotWorld); + pivotScreen += new Vector3(delta.x, delta.y, 0); + + Vector3 worldDelta = camera.ScreenToWorldPoint(pivotScreen) - pivotWorld; + // We're clearing z here as ScreenToWorldPoint(WorldToScreenPoint(worldPoint)) does not result in the exact same worldPoint that was inputed. + // https://jira.unity3d.com/browse/UUM-56425 + worldDelta.z = 0f; + worldDelta = rotation * worldDelta; worldDelta *= EditorGUIUtility.pixelsPerPoint * scale; // restore original cam and scene values @@ -485,33 +590,36 @@ internal static Vector3 ScreenToWorldDistance(SceneView view, Vector2 delta) view.camera.nearClipPlane = near; view.camera.farClipPlane = far; view.camera.transform.position = pos; + view.camera.transform.rotation = rotation; return worldDelta; } - private static void HandleKeyDown(SceneView sceneView, int id) + // This can't be modified into a shortcut because Escape is an invalid key code binding. + private void HandleKeyDown() { - if (Event.current.keyCode == KeyCode.Escape && GUIUtility.hotControl == s_ViewToolID) + if (Event.current.keyCode == KeyCode.Escape && GUIUtility.hotControl == k_ViewToolID) { GUIUtility.hotControl = 0; - ResetDragState(); + CompleteSceneViewMotionTool(); } } - static readonly char[] k_TrimChars = new char[] { '0' }; - - private static void HandleScrollWheel(SceneView view, bool zoomTowardsCenter) + void HandleScrollWheel(SceneView view, bool zoomTowardsCenter) { if (Tools.s_LockedViewTool == ViewTool.FPS) { - float scrollWheelDelta = Event.current.delta.y * s_FPSScrollWheelMultiplier; + // On some OSs, macOS for example, holding Shift while scrolling is interpreted as horizontal scroll at the system level + // and that would cause the scroll delta to be set on the x coord instead of y. Therefore here we're taking the magnitude instead of specific component's value. + var inputScrollWheelDelta = Event.current.delta.magnitude * Mathf.Sign(Mathf.Min(Event.current.delta.x, Event.current.delta.y)); + float scrollWheelDelta = inputScrollWheelDelta * m_FPSScrollWheelMultiplier; view.cameraSettings.speedNormalized -= scrollWheelDelta; float cameraSettingsSpeed = view.cameraSettings.speed; string cameraSpeedDisplayValue = cameraSettingsSpeed.ToString( - cameraSettingsSpeed < 0.0001f ? "F6" : - cameraSettingsSpeed < 0.001f ? "F5" : - cameraSettingsSpeed < 0.01f ? "F4" : - cameraSettingsSpeed < 0.1f ? "F3" : + cameraSettingsSpeed < 0.0001f ? "F6" : + cameraSettingsSpeed < 0.001f ? "F5" : + cameraSettingsSpeed < 0.01f ? "F4" : + cameraSettingsSpeed < 0.1f ? "F3" : cameraSettingsSpeed < 10f ? "F2" : "F0"); @@ -568,9 +676,9 @@ private static void HandleScrollWheel(SceneView view, bool zoomTowardsCenter) Event.current.Use(); } - public static void DeactivateFlyModeContext() + public void DeactivateFlyModeContext() { - ShortcutIntegration.instance.contextManager.DeregisterToolContext(s_CameraFlyModeContext); + ShortcutIntegration.instance.contextManager.DeregisterToolContext(m_CameraFlyModeContext); } } } //namespace diff --git a/Editor/Mono/SceneView/SceneVisibilityState.bindings.cs b/Editor/Mono/SceneView/SceneVisibilityState.bindings.cs index 63315f74b2..21171a25d1 100644 --- a/Editor/Mono/SceneView/SceneVisibilityState.bindings.cs +++ b/Editor/Mono/SceneView/SceneVisibilityState.bindings.cs @@ -41,7 +41,9 @@ internal class SceneVisibilityState : Object public static extern bool IsPickingEnabledOnAllChildren([NotNull] GameObject gameObject); public static extern bool AreAllChildrenHidden([NotNull] GameObject gameObject); + public static extern bool AreAllRootObjectsHiddenHierarchy(Scene scene); public static extern bool IsPickingDisabledOnAllChildren([NotNull] GameObject gameObject); + public static extern bool IsPickingDisabledOnAllDescendants(Scene scene); public static extern void ShowScene(Scene scene); diff --git a/Editor/Mono/SceneVisibilityManager.cs b/Editor/Mono/SceneVisibilityManager.cs index 277e224023..5f9a978b35 100644 --- a/Editor/Mono/SceneVisibilityManager.cs +++ b/Editor/Mono/SceneVisibilityManager.cs @@ -417,44 +417,12 @@ static bool IsIgnoredBySceneVisibility(GameObject go) public bool AreAllDescendantsHidden(Scene scene) { - if (!scene.IsValid() || !scene.isLoaded) - return false; - - if (scene.rootCount == 0) - return false; - - scene.GetRootGameObjects(m_RootBuffer); - foreach (GameObject root in m_RootBuffer) - { - if (IsIgnoredBySceneVisibility(root)) - continue; - - if (!SceneVisibilityState.IsHierarchyHidden(root)) - return false; - } - - return true; + return SceneVisibilityState.AreAllRootObjectsHiddenHierarchy(scene); } public bool IsPickingDisabledOnAllDescendants(Scene scene) { - if (!scene.IsValid() || !scene.isLoaded) - return false; - - if (scene.rootCount == 0) - return false; - - scene.GetRootGameObjects(m_RootBuffer); - foreach (GameObject root in m_RootBuffer) - { - if (IsIgnoredBySceneVisibility(root)) - continue; - - if (!SceneVisibilityState.IsHierarchyPickingDisabled(root)) - return false; - } - - return true; + return SceneVisibilityState.IsPickingDisabledOnAllDescendants(scene); } public bool AreAnyDescendantsHidden(Scene scene) diff --git a/Editor/Mono/ScriptAttributeGUI/Implementations/ExposedReferenceDrawer.cs b/Editor/Mono/ScriptAttributeGUI/Implementations/ExposedReferenceDrawer.cs index 4641e216af..4aaafc633b 100644 --- a/Editor/Mono/ScriptAttributeGUI/Implementations/ExposedReferenceDrawer.cs +++ b/Editor/Mono/ScriptAttributeGUI/Implementations/ExposedReferenceDrawer.cs @@ -2,13 +2,12 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License -using System.Collections; -using System.Collections.Generic; -using UnityEngine; +using System; using UnityEditor; +using UnityEngine; using UnityEngine.UIElements; -using UnityEditor.UIElements; using ObjectField = UnityEditor.UIElements.ObjectField; +using Object = UnityEngine.Object; abstract class BaseExposedPropertyDrawer : UnityEditor.PropertyDrawer { @@ -17,7 +16,7 @@ abstract class BaseExposedPropertyDrawer : UnityEditor.PropertyDrawer private static Color kMissingOverrideColor = new Color(1.0f, 0.11f, 0.11f, 1.0f); protected static string kSetExposedPropertyMsg = "Set Exposed Property"; protected static string kClearExposedPropertyMsg = "Clear Exposed Property"; - private const string kVisualElementName = "ExposedReference"; + internal const string kVisualElementName = "ExposedReference"; internal readonly GUIContent ExposePropertyContent = EditorGUIUtility.TrTextContent("Expose Property"); internal readonly GUIContent UnexposePropertyContent = EditorGUIUtility.TrTextContent("Unexpose Property"); @@ -149,8 +148,7 @@ public override VisualElement CreatePropertyGUI(SerializedProperty prop) ObjectField obj = new ObjectField() { name = kVisualElementName, - label = prop.displayName, - bindingPath = m_Item.exposedPropertyDefault.propertyPath, + label = preferredLabel, objectType = typeOfExposedReference, value = m_Item.currentReferenceValue, allowSceneObjects = m_Item.exposedPropertyTable != null @@ -159,27 +157,54 @@ public override VisualElement CreatePropertyGUI(SerializedProperty prop) obj.RegisterValueChangedCallback(SetReference); obj.AddManipulator(new ContextualMenuManipulator(BuildContextualMenu)); + Undo.UndoRedoCallback undoRedoCallback = () => + { + m_Item.UpdateValue(); + obj.SetValueWithoutNotify(m_Item.currentReferenceValue); + }; + + obj.RegisterCallback(evt => Undo.undoRedoPerformed += undoRedoCallback); + obj.RegisterCallback(evt => Undo.undoRedoPerformed -= undoRedoCallback); + return obj; } + // Used for tests only + + internal void InitForNamedGUIDTests(SerializedProperty prop) + { + m_Item = new ExposedReferenceObject(prop); + m_Item.propertyMode = ExposedPropertyMode.NamedGUID; + } + // Used for tests only + + internal Object GetObjectReferenceValue() + { + return m_Item.exposedPropertyDefault.objectReferenceValue; + } + void SetReference(ChangeEvent evt) { SetReference(evt.newValue); - m_Item.currentReferenceValue = evt.newValue; + if (m_Item.currentReferenceValue != evt.newValue) + { + m_Item.currentReferenceValue = evt.newValue; + + //save the modified SerializedObject since we are bypassing the binding system + m_Item.exposedPropertyName.serializedObject.ApplyModifiedProperties(); + } } internal void SetReference(Object newValue) { - if (m_Item.exposedPropertyTable == null) - return; - - if (m_Item.propertyMode == ExposedPropertyMode.DefaultValue) + bool isDefaultValueMode = m_Item.propertyMode == ExposedPropertyMode.DefaultValue; + if (isDefaultValueMode || m_Item.propertyMode == ExposedPropertyMode.NamedGUID) { // We can directly assign to the exposed property default value if // * asset we are modifying is in the scene // * object we are assigning to the property is also an asset - if (!EditorUtility.IsPersistent(m_Item.exposedPropertyDefault.serializedObject.targetObject) || - newValue == null || EditorUtility.IsPersistent(newValue)) + if (isDefaultValueMode && (!EditorUtility.IsPersistent(m_Item.exposedPropertyDefault.serializedObject.targetObject) || + newValue == null || EditorUtility.IsPersistent(newValue))) { if (!EditorGUI.CheckForCrossSceneReferencing( m_Item.exposedPropertyDefault.serializedObject.targetObject, newValue)) @@ -189,22 +214,45 @@ internal void SetReference(Object newValue) } else { - var guid = UnityEditor.GUID.Generate(); - var str = guid.ToString(); - m_Item.exposedPropertyNameString = str; - m_Item.exposedPropertyName.stringValue = str; + // If PropertyName already exists, re-use it UUM-25160 + if (String.IsNullOrEmpty(m_Item.exposedPropertyNameString) || String.IsNullOrEmpty(m_Item.exposedPropertyName.stringValue)) + { + var str = UnityEditor.GUID.Generate().ToString(); + m_Item.exposedPropertyNameString = str; + m_Item.exposedPropertyName.stringValue = str; + m_Item.propertyMode = ExposedPropertyMode.NamedGUID; + } - Undo.RecordObject(m_Item.exposedPropertyTable as UnityEngine.Object, kSetExposedPropertyMsg); - m_Item.exposedPropertyTable.SetReferenceValue(m_Item.exposedPropertyNameString, newValue); + // Timeline uses ExposedReference to hold both exposed and regular references, make sure we handle them differently + if (m_Item.isExposedReference) + SetAsExposedReference(newValue); + else + SetAsRegularReference(newValue); } } else { - Undo.RecordObject(m_Item.exposedPropertyTable as UnityEngine.Object, kSetExposedPropertyMsg); - m_Item.exposedPropertyTable.SetReferenceValue(m_Item.exposedPropertyNameString, newValue); + if (m_Item.isExposedReference) + SetAsExposedReference(newValue); + else + SetAsRegularReference(newValue); } } + void SetAsExposedReference(Object value) + { + Undo.RecordObject(m_Item.exposedPropertyTable as UnityEngine.Object, kSetExposedPropertyMsg); + m_Item.exposedPropertyTable.SetReferenceValue(m_Item.exposedPropertyNameString, value); + } + + void SetAsRegularReference(Object value) + { + if (m_Item.currentReferenceValue) + Undo.RecordObject(m_Item.exposedPropertyDefault.serializedObject.targetObject, kSetExposedPropertyMsg); + + m_Item.exposedPropertyDefault.objectReferenceValue = value; + } + Rect DrawLabel(bool showContextMenu, GUIContent label, Rect position, ExposedReferenceObject item) { diff --git a/Editor/Mono/ScriptAttributeGUI/Implementations/ExposedReferenceObject.cs b/Editor/Mono/ScriptAttributeGUI/Implementations/ExposedReferenceObject.cs index b8287fb590..a7b2f9c3cc 100644 --- a/Editor/Mono/ScriptAttributeGUI/Implementations/ExposedReferenceObject.cs +++ b/Editor/Mono/ScriptAttributeGUI/Implementations/ExposedReferenceObject.cs @@ -16,6 +16,7 @@ internal class ExposedReferenceObject internal SerializedProperty exposedPropertyName { get; set; } internal BaseExposedPropertyDrawer.ExposedPropertyMode propertyMode { get; set; } internal IExposedPropertyTable exposedPropertyTable { get; set; } + internal bool isExposedReference = false; internal BaseExposedPropertyDrawer.OverrideState currentOverrideState { @@ -33,6 +34,12 @@ internal ExposedReferenceObject(SerializedProperty property) propertyMode = BaseExposedPropertyDrawer.GetExposedPropertyMode(exposedPropertyNameString); exposedPropertyTable = GetExposedPropertyTable(property); currentOverrideState = BaseExposedPropertyDrawer.OverrideState.DefaultValue; + isExposedReference = exposedPropertyTable != null; + UpdateValue(); + } + + public void UpdateValue() + { currentReferenceValue = Resolve(out m_CurrentOverrideState); } diff --git a/Editor/Mono/ScriptAttributeGUI/Implementations/PropertyDrawers.cs b/Editor/Mono/ScriptAttributeGUI/Implementations/PropertyDrawers.cs index 426f489ae7..385a0c6e2d 100644 --- a/Editor/Mono/ScriptAttributeGUI/Implementations/PropertyDrawers.cs +++ b/Editor/Mono/ScriptAttributeGUI/Implementations/PropertyDrawers.cs @@ -34,6 +34,7 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) if (property.propertyType == SerializedPropertyType.Float) { var slider = new Slider(property.displayName, range.min, range.max); + slider.AddToClassList(Slider.alignedFieldUssClassName); slider.bindingPath = property.propertyPath; slider.showInputField = true; return slider; @@ -41,6 +42,7 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) else if (property.propertyType == SerializedPropertyType.Integer) { var intSlider = new SliderInt(property.displayName, (int)range.min, (int)range.max); + intSlider.AddToClassList(SliderInt.alignedFieldUssClassName); intSlider.bindingPath = property.propertyPath; intSlider.showInputField = true; return intSlider; @@ -143,6 +145,16 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) newField = new LongField(property.displayName); ((BaseField)newField).onValidateValue += OnValidateValue; } + else if (property.type == "uint") + { + newField = new UnsignedIntegerField(preferredLabel); + ((BaseField)newField).onValidateValue += OnValidateValue; + } + else if (property.type == "ulong") + { + newField = new UnsignedLongField(preferredLabel); + ((BaseField)newField).onValidateValue += OnValidateValue; + } } else if (property.propertyType == SerializedPropertyType.Vector2) { @@ -172,6 +184,8 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) if (newField != null) { + var alignmentClass = TextField.alignedFieldUssClassName; + newField.AddToClassList(alignmentClass); newField.bindingPath = property.propertyPath; return newField; } @@ -199,6 +213,16 @@ private long OnValidateValue(long value) return Math.Max((long)minAttribute.min, value); } + private uint OnValidateValue(uint value) + { + return Math.Max((uint)minAttribute.min, value); + } + + private ulong OnValidateValue(ulong value) + { + return Math.Max((ulong)minAttribute.min, value); + } + private Vector2 OnValidateValue(Vector2 value) { return new Vector2(Mathf.Max(minAttribute.min, value.x), Mathf.Max(minAttribute.min, value.y)); @@ -262,6 +286,7 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) field.multiline = true; field.bindingPath = property.propertyPath; field.style.height = EditorGUI.kSingleLineHeight + (lines - 1) * kLineHeight; + field.AddToClassList(TextField.alignedFieldUssClassName); return field; } @@ -279,7 +304,7 @@ public override float GetPropertyHeight(SerializedProperty property, GUIContent [CustomPropertyDrawer(typeof(TextAreaAttribute))] internal sealed class TextAreaDrawer : PropertyDrawer { - private const int kLineHeight = 13; + private const int kLineHeight = 15; private static string s_InvalidTypeMessage = L10n.Tr("Use TextAreaDrawer with string."); private Vector2 m_ScrollPosition = new Vector2(); @@ -306,31 +331,39 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten public override VisualElement CreatePropertyGUI(SerializedProperty property) { - if (property.propertyType == SerializedPropertyType.String) + if (property.propertyType != SerializedPropertyType.String) + return new Label(s_InvalidTypeMessage); + + var textAreaAttribute = attribute as TextAreaAttribute; + + // Label + first line + var initialHeight = EditorGUI.kSingleLineHeight + EditorGUI.kSingleLineHeight; + + var minHeight = initialHeight + (textAreaAttribute!.minLines - 1) * kLineHeight; + var maxHeight = initialHeight + (textAreaAttribute!.maxLines - 1) * kLineHeight; + + if (maxHeight < minHeight) + maxHeight = minHeight; + + var textField = new TextField { - var textAreaAttribute = attribute as TextAreaAttribute; - var element = new VisualElement(); - var label = new Label(property.displayName); - var scrollView = new ScrollView(); - var textField = new TextField(); - textField.multiline = true; - var minHeight = EditorGUI.kSingleLineHeight + (textAreaAttribute.minLines - 1) * kLineHeight; - var maxHeight = minHeight; - - element.Add(label); - element.Add(scrollView); - - scrollView.Add(textField); - scrollView.style.minHeight = minHeight; - scrollView.style.maxHeight = maxHeight; - - textField.style.minHeight = minHeight; - textField.bindingPath = property.propertyPath; - - return element; - } + label = preferredLabel, + multiline = true, + style = + { + flexDirection = FlexDirection.Column, + whiteSpace = WhiteSpace.Normal, + minHeight = minHeight, + maxHeight = maxHeight + } + }; - return new Label(s_InvalidTypeMessage); + textField.SetVerticalScrollerVisibility(ScrollerVisibility.Auto); + + textField.style.minHeight = minHeight; + textField.bindingPath = property.propertyPath; + + return textField; } public override float GetPropertyHeight(SerializedProperty property, GUIContent label) @@ -383,6 +416,7 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) field.showAlpha = colorUsage.showAlpha; field.hdr = colorUsage.hdr; field.bindingPath = property.propertyPath; + field.AddToClassList(ColorField.alignedFieldUssClassName); return field; } @@ -416,6 +450,7 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) field.hdr = gradientUsage.hdr; field.colorSpace = gradientUsage.colorSpace; field.bindingPath = property.propertyPath; + field.AddToClassList(GradientField.alignedFieldUssClassName); return field; } @@ -478,6 +513,7 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) if (newField != null) { newField.bindingPath = property.propertyPath; + newField.AddToClassList(TextField.alignedFieldUssClassName); return newField; } diff --git a/Editor/Mono/ScriptAttributeGUI/PropertyDrawer.cs b/Editor/Mono/ScriptAttributeGUI/PropertyDrawer.cs index 2fa2545c2f..a13ef05d66 100644 --- a/Editor/Mono/ScriptAttributeGUI/PropertyDrawer.cs +++ b/Editor/Mono/ScriptAttributeGUI/PropertyDrawer.cs @@ -14,6 +14,7 @@ public abstract class PropertyDrawer : GUIDrawer { internal PropertyAttribute m_Attribute; internal FieldInfo m_FieldInfo; + internal string m_PreferredLabel; // The [[PropertyAttribute]] for the property. Not applicable for custom class drawers. (RO) public PropertyAttribute attribute { get { return m_Attribute; } } @@ -21,6 +22,9 @@ public abstract class PropertyDrawer : GUIDrawer // The reflection FieldInfo for the member this property represents. (RO) public FieldInfo fieldInfo { get { return m_FieldInfo; } } + // The preferred label for this property. + public string preferredLabel => m_PreferredLabel; + internal void OnGUISafe(Rect position, SerializedProperty property, GUIContent label) { ScriptAttributeUtility.s_DrawerStack.Push(this); diff --git a/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs b/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs index dff46e8072..765306a982 100644 --- a/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs +++ b/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs @@ -11,7 +11,7 @@ namespace UnityEditor { - internal class PropertyHandler + internal class PropertyHandler : IDisposable { private List m_PropertyDrawers = null; private List m_DecoratorDrawers = null; @@ -29,6 +29,7 @@ internal PropertyDrawer propertyDrawer } internal List decoratorDrawers => m_DecoratorDrawers; + internal bool skipDecoratorDrawers { get; set; } private int m_NestingLevel; @@ -153,7 +154,8 @@ internal bool OnGUI(Rect position, SerializedProperty property, GUIContent label float propHeight = position.height; position.height = 0; - if (m_DecoratorDrawers != null && !isCurrentlyNested) + + if (!skipDecoratorDrawers && m_DecoratorDrawers != null && !isCurrentlyNested) { foreach (DecoratorDrawer decorator in m_DecoratorDrawers) { @@ -250,6 +252,7 @@ internal bool OnGUI(Rect position, SerializedProperty property, GUIContent label if (childrenAreExpanded) { SerializedProperty endProperty = prop.GetEndProperty(); + while (prop.NextVisible(childrenAreExpanded) && !SerializedProperty.EqualContents(prop, endProperty)) { if (GUI.isInsideList && prop.depth <= EditorGUI.GetInsideListDepth()) @@ -302,7 +305,7 @@ public float GetHeight(SerializedProperty property, GUIContent label, bool inclu { float height = 0; - if (m_DecoratorDrawers != null && !isCurrentlyNested) + if (!skipDecoratorDrawers && m_DecoratorDrawers != null && !isCurrentlyNested) foreach (DecoratorDrawer drawer in m_DecoratorDrawers) height += drawer.GetHeight(); @@ -346,9 +349,9 @@ public float GetHeight(SerializedProperty property, GUIContent label, bool inclu bool childrenAreExpanded = property.isExpanded && EditorGUI.HasVisibleChildFields(property); // Loop through all child properties - var tc = EditorGUIUtility.TempContent(property.localizedDisplayName, tooltip); if (childrenAreExpanded) { + var tc = EditorGUIUtility.TempContent(property.localizedDisplayName, tooltip); SerializedProperty endProperty = property.GetEndProperty(); while (property.NextVisible(childrenAreExpanded) && !SerializedProperty.EqualContents(property, endProperty)) { @@ -415,8 +418,36 @@ public void AddMenuItems(SerializedProperty property, GenericMenu menu) } } - var propertyPath = property.propertyPath; + var propertyPath = property.propertyPath.Replace(" ", ""); menu.AddItem(new GUIContent("Copy Property Path"), false, () => EditorGUIUtility.systemCopyBuffer = propertyPath); + + if (CanSearchProperty(property)) + { + menu.AddItem(new GUIContent("Search Same Property Value"), false, () => SearchProperty(property)); + if (property.propertyType == SerializedPropertyType.ObjectReference && property.objectReferenceValue) + { + menu.AddItem(new GUIContent($"Find references to {property.objectReferenceValue.GetType().Name} {property.objectReferenceValue.name}"), false, () => FindReferences(property.objectReferenceValue)); + } + } + } + + private void SearchProperty(SerializedProperty property) + { + CommandService.Execute("OpenToSearchByProperty", CommandHint.Menu, property); + } + + private void FindReferences(UnityEngine.Object obj) + { + CommandService.Execute("OpenToFindReferenceOnObject", CommandHint.Menu, obj); + } + + private bool CanSearchProperty(SerializedProperty property) + { + if (!CommandService.Exists("OpenToSearchByProperty") || !CommandService.Exists("IsPropertyValidForQuery")) + return false; + + var result = CommandService.Execute("IsPropertyValidForQuery", CommandHint.Menu, property); + return (bool)result; } public void CallMenuCallback(object[] targets, MethodInfo method) @@ -526,6 +557,29 @@ public NestingContext IncrementNestingContext() return NestingContext.Get(this, m_NestingLevel + 1); } + public void Dispose() + { + if (m_PropertyDrawers?.Count > 0) + { + foreach (var propertyDrawer in m_PropertyDrawers) + { + if (propertyDrawer is IDisposable disposable) + disposable.Dispose(); + } + m_PropertyDrawers.Clear(); + } + + if (m_DecoratorDrawers?.Count > 0) + { + foreach (var decoratorDrawer in m_DecoratorDrawers) + { + if (decoratorDrawer is IDisposable disposable) + disposable.Dispose(); + } + m_DecoratorDrawers.Clear(); + } + } + public struct NestingContext : IDisposable { private PropertyHandler m_Handler; diff --git a/Editor/Mono/ScriptAttributeGUI/ScriptAttributeUtility.cs b/Editor/Mono/ScriptAttributeGUI/ScriptAttributeUtility.cs index 6542e04cd1..d163b99429 100644 --- a/Editor/Mono/ScriptAttributeGUI/ScriptAttributeUtility.cs +++ b/Editor/Mono/ScriptAttributeGUI/ScriptAttributeUtility.cs @@ -3,25 +3,32 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; -using System.Linq; using System.Collections.Generic; using System.Reflection; using System.Text.RegularExpressions; +using Unity.Collections; using UnityEngine; +using UnityEngine.Rendering; +using Object = UnityEngine.Object; namespace UnityEditor { internal class ScriptAttributeUtility { - private struct DrawerKeySet + readonly struct CustomPropertyDrawerContainer { - public Type drawer; - public Type type; + public readonly Type drawerType; + public readonly bool editorForChildClasses; + + public CustomPropertyDrawerContainer(Type drawerType, bool editorForChildClasses) + { + this.drawerType = drawerType; + this.editorForChildClasses = editorForChildClasses; + } } // Internal API members internal static Stack s_DrawerStack = new Stack(); - private static Dictionary s_DrawerTypeForType = null; private static Dictionary> s_BuiltinAttributes = null; static Dictionary> s_AutoLoadProperties; private static PropertyHandler s_SharedNullHandler = new PropertyHandler(); @@ -30,13 +37,19 @@ private struct DrawerKeySet private static PropertyHandlerCache s_GlobalCache = new PropertyHandlerCache(); private static PropertyHandlerCache s_CurrentCache = null; + static readonly Lazy> k_DrawerTypeForType = new(BuildDrawerTypeForTypeDictionary); + static readonly Dictionary k_DrawerStaticTypesCache = new(); + + static void ResetCachedTypesAndAsset() + { + k_DrawerStaticTypesCache.Clear(); + ClearGlobalCache(); + } + internal static PropertyHandlerCache propertyHandlerCache { - get - { - return s_CurrentCache ?? s_GlobalCache; - } - set { s_CurrentCache = value; } + get => s_CurrentCache ?? s_GlobalCache; + set => s_CurrentCache = value; } internal static void ClearGlobalCache() @@ -66,82 +79,218 @@ private static List GetBuiltinAttributes(SerializedProperty p if (property.serializedObject.targetObject == null) return null; Type t = property.serializedObject.targetObject.GetType(); - if (t == null) - return null; string attrKey = t.Name + "_" + property.propertyPath; List attr = null; s_BuiltinAttributes.TryGetValue(attrKey, out attr); return attr; } - // Called on demand - private static void BuildDrawerTypeForTypeDictionary() + // Build a dictionary when k_DrawerTypeForType is first accessed + static Dictionary BuildDrawerTypeForTypeDictionary() { - s_DrawerTypeForType = new Dictionary(); + RenderPipelineManager.activeRenderPipelineDisposed += ResetCachedTypesAndAsset; + RenderPipelineManager.activeRenderPipelineCreated += ResetCachedTypesAndAsset; - foreach (var type in TypeCache.GetTypesDerivedFrom()) + var tempDictionary = new Dictionary>(); + foreach (var drawerType in TypeCache.GetTypesDerivedFrom()) { //Debug.Log("Drawer: " + type); - object[] attrs = type.GetCustomAttributes(typeof(CustomPropertyDrawer), true); - foreach (CustomPropertyDrawer editor in attrs) + var customPropertyDrawers = drawerType.GetCustomAttributes(true); + foreach (CustomPropertyDrawer drawer in customPropertyDrawers) { - //Debug.Log("Base type: " + editor.type); - s_DrawerTypeForType[editor.m_Type] = new DrawerKeySet() - { - drawer = type, - type = editor.m_Type - }; + var propertyType = drawer.m_Type; + if (!tempDictionary.ContainsKey(propertyType)) + tempDictionary.Add(propertyType, new List()); + + tempDictionary[propertyType].Add(new CustomPropertyDrawerContainer(drawerType, drawer.m_UseForChildren)); + } + } + + var dictionaryWithArrays = new Dictionary(); + foreach (var kvp in tempDictionary) + dictionaryWithArrays.Add(kvp.Key, kvp.Value.ToArray()); + return dictionaryWithArrays; + } + + /// + /// We build our CustomPropertyDrawerContainer cache the first time we access it. + /// It used solely to have a quick access to drawer types and their information. + /// We have a separate cache to map Type -> Drawer. It will work with managed references too as we map real type to the drawers. + /// This cache resets each type we create or dispose a Render Pipeline as SupportedOn can exist on some drawers. + /// + /// Find a drawer for provided Type + /// Specify if it's known that we deal with ManagedReference property + /// + internal static Type GetDrawerTypeForType(Type propertyType, bool isPropertyTypeAManagedReference = false) + { + //We map specific types to drawers and it will work with managed references too. + if (k_DrawerStaticTypesCache.TryGetValue(propertyType, out var drawerTypeForType)) + return drawerTypeForType; + + Type[] allInterfaces = null; + bool[] checkedInterfaces = null; + for (var inspectedType = propertyType; inspectedType != null; inspectedType = inspectedType.BaseType) + { + //In the first case we don't need to check for some properties as we know that we deal not with a Base class + //We check only if we have direct drawer and prepare to check interfaces in the next step if they exist + var requestedPropertyType = inspectedType == propertyType; + + //Check for class drawers. For generics it would be 2 checks. One for generic type and one for generic definition. + //For example, for A we will check first for A and then for A. + if (TryGetDrawerTypeForTypeFromCache(requestedPropertyType, isPropertyTypeAManagedReference, inspectedType, out var drawerType)) + { + k_DrawerStaticTypesCache.Add(propertyType, drawerType); + return drawerType; + } + + if (requestedPropertyType) + { + //Gather all interfaces for requested property type + allInterfaces = inspectedType.GetInterfaces(); + checkedInterfaces = new bool[allInterfaces.Length]; - if (!editor.m_UseForChildren) - continue; + // Calculate inheritance level for each interface + var interfaceLevels = new Dictionary(); + foreach (var interfaceType in allInterfaces) + CalculateInheritanceLevel(interfaceType, 0, interfaceLevels); - var candidateTypes = TypeCache.GetTypesDerivedFrom(editor.m_Type); - foreach (var candidateType in candidateTypes) + // Sort interfaces by inheritance level and then by name + Array.Sort(allInterfaces, (x, y) => { - //Debug.Log("Candidate Type: "+ candidateType); - if (s_DrawerTypeForType.ContainsKey(candidateType) - && (editor.m_Type.IsAssignableFrom(s_DrawerTypeForType[candidateType].type))) - { - // Debug.Log("skipping"); + var levelComparison = interfaceLevels[x].CompareTo(interfaceLevels[y]); + return levelComparison != 0 ? levelComparison : string.CompareOrdinal(x.Name, y.Name); + }); + } + + if ((!requestedPropertyType || inspectedType.IsInterface) && allInterfaces != null && allInterfaces.Length != 0) + { + // Get all interfaces for the current inspected type + var baseTypeInterfaces = inspectedType.IsInterface ? null : inspectedType.GetInterfaces(); + for (int i = 0; i < allInterfaces.Length; i++) + { + // Skipped already checked interfaces + if (checkedInterfaces[i]) continue; - } - //Debug.Log("Setting"); - s_DrawerTypeForType[candidateType] = new DrawerKeySet() + // Exclude interfaces that are implemented by the base type + var interfaceType = allInterfaces[i]; + if (baseTypeInterfaces != null && baseTypeInterfaces.ContainsByEquals(interfaceType)) + continue; + + // Check if there's an appropriate drawer for the interface + checkedInterfaces[i] = true; + if (TryGetDrawerTypeForTypeFromCache(requestedPropertyType, isPropertyTypeAManagedReference, interfaceType, out drawerType)) { - drawer = type, - type = editor.m_Type - }; + k_DrawerStaticTypesCache.Add(propertyType, drawerType); + return drawerType; + } } } } + + return null; } - /// - /// Builds the drawer cache and checks for the drawer cache for a statically - /// defined drawer for a given type. - /// NOTE: The world 'statically' in this context means that what is being - /// looked up is only what is in the cache, which might not play well with - /// Managed References types (where the dynamic type matters). - /// - /// - /// - internal static Type GetDrawerTypeForType(Type type) + static int CalculateInheritanceLevel(Type interfaceType, int currentLevel, Dictionary processedInterfaces) + { + // Get parent interfaces + var parentInterfaces = interfaceType.GetInterfaces(); + + // Base case: if there are no parent interfaces, return current inheritance level + if (parentInterfaces.Length == 0) + return AddOrReplaceInterfaceLevel(currentLevel); + + // Recursive case: return the minimum inheritance level minus 1 of the parent interfaces + var minParentLevel = int.MaxValue; + for (var i = 0; i < parentInterfaces.Length; i++) + { + var parentLevel = CalculateInheritanceLevel(parentInterfaces[i], currentLevel + 1, processedInterfaces); + if (parentLevel < minParentLevel) + minParentLevel = parentLevel; + } + + return AddOrReplaceInterfaceLevel(minParentLevel - 1); + + //Local method just to simplify code + int AddOrReplaceInterfaceLevel(int newLevel) + { + // Check if the interface has already been processed and update its inheritance level if necessary + if (processedInterfaces.TryGetValue(interfaceType, out var level)) + { + // If the interface has already been processed and the new inheritance level is greater than the current one, update it + if (level >= newLevel) + return level; + + processedInterfaces[interfaceType] = newLevel; + return newLevel; + } + processedInterfaces.Add(interfaceType, newLevel); + return newLevel; + } + } + + static bool TryGetDrawerTypeForTypeFromCache(bool requestedPropertyType, bool isPropertyTypeIsManagedReference, Type currentType, + out Type drawerType) { - if (s_DrawerTypeForType == null) - BuildDrawerTypeForTypeDictionary(); + drawerType = null; + // check for exact type + if (TryExtractDrawerTypeForTypeFromCache(requestedPropertyType, isPropertyTypeIsManagedReference, currentType, out drawerType)) + return true; - DrawerKeySet drawerType; - s_DrawerTypeForType.TryGetValue(type, out drawerType); - if (drawerType.drawer != null) - return drawerType.drawer; + // check for base generic versions of the drawers + if (!currentType.IsGenericType) + return false; - // - // now check for base generic versions of the drawers... - if (type.IsGenericType) - s_DrawerTypeForType.TryGetValue(type.GetGenericTypeDefinition(), out drawerType); + var genericDefinition = currentType.GetGenericTypeDefinition(); + return TryExtractDrawerTypeForTypeFromCache(requestedPropertyType, isPropertyTypeIsManagedReference, genericDefinition, out drawerType); + } + + static bool TryExtractDrawerTypeForTypeFromCache(bool requestedPropertyType, bool isPropertyTypeIsManagedReference, Type currentType, out Type drawerType) + { + drawerType = null; + // Extract drawers for the current type from the cache + if (!k_DrawerTypeForType.Value.TryGetValue(currentType, out var drawerTypes)) + return false; + + // Check if there's an appropriate drawer for the current type and render pipeline. This is where we check for SupportedOnRenderPipelineAttribute + var result = TryFindDrawers(drawerTypes, out var customPropertyDrawerContainer); + if (!IsAppropriateDrawerFound()) + return false; + + drawerType = customPropertyDrawerContainer.drawerType; + return true; + + //Extracted just to simplify a condition + bool IsAppropriateDrawerFound() + { + if (!result) + return false; + + //When we check for requested property type we don't need to check for editorForChildClasses and managed reference type + if (requestedPropertyType) + return true; + + // Check for drawers with editorForChildClasses set to true and special case for managed references. + // The custom property drawers for those are defined with 'useForChildren=false' + // (otherwise the dynamic type is not taking into account in the custom property + // drawer resolution) so even if 's_DrawerTypeForType' is built (based on static types) + // we have to check base types for custom property drawers manually. + // Managed references with no drawers should properly try to fallback + return (customPropertyDrawerContainer.editorForChildClasses || isPropertyTypeIsManagedReference); + } + } + + static bool TryFindDrawers(CustomPropertyDrawerContainer[] drawerTypes, + out CustomPropertyDrawerContainer customPropertyDrawerContainer) + { + if (drawerTypes?.Length > 0) + { + customPropertyDrawerContainer = drawerTypes[0]; + return true; + } - return drawerType.drawer; + customPropertyDrawerContainer = default; + return false; } /// @@ -154,34 +303,7 @@ internal static Type GetDrawerTypeForType(Type type) /// The custom property drawer type or 'null' if not found. internal static Type GetDrawerTypeForPropertyAndType(SerializedProperty property, Type type) { - // As a side effect this also builds the drawer cache dict - var staticDrawerType = GetDrawerTypeForType(type); - if (staticDrawerType != null) - return staticDrawerType; - - // Special case for managed references. - // The custom property drawers for those are defined with 'useForChildren=false' - // (otherwise the dynamic type is not taking into account in the custom property - // drawer resolution) so even if 's_DrawerTypeForType' is built (based on static types) - // we have to check base types for custom property drawers manually. - // Managed references with no drawers should properly try to fallback - if (property.propertyType == SerializedPropertyType.ManagedReference) - { - DrawerKeySet drawerTypes; - - FieldInfo foundField = null; - for (Type currentType = type; foundField == null && currentType != null; currentType = currentType.BaseType) - { - s_DrawerTypeForType.TryGetValue(currentType, out drawerTypes); - if (drawerTypes.drawer != null) - { - s_DrawerTypeForType.Add(type, drawerTypes); - return drawerTypes.drawer; - } - } - } - - return null; + return GetDrawerTypeForType(type, property.propertyType == SerializedPropertyType.ManagedReference); } private static List GetFieldAttributes(FieldInfo field) @@ -189,11 +311,18 @@ private static List GetFieldAttributes(FieldInfo field) if (field == null) return null; - object[] attrs = field.GetCustomAttributes(typeof(PropertyAttribute), true); - if (attrs != null && attrs.Length > 0) - return new List(attrs.Select(e => e as PropertyAttribute).OrderBy(e => e.order)); + var attrs = field.GetCustomAttributes(true); + Comparer comparer = null; + List propertyAttributeList = null; + foreach (PropertyAttribute attribute in attrs) + { + propertyAttributeList ??= new List(); + comparer ??= Comparer.Create((p1, p2) => p1.order.CompareTo(p2.order)); + + propertyAttributeList.AddSorted(attribute, comparer); + } - return null; + return propertyAttributeList; } /// @@ -224,7 +353,7 @@ internal static FieldInfo GetFieldInfoAndStaticTypeFromProperty(SerializedProper // So we have to: // 1. try to get the FQN from for the current managed type from the serialized data, // 2. get the path *in the current managed instance* of the field we are pointing to, - // 3. foward that to 'GetFieldInfoFromPropertyPath' as if it was a regular field, + // 3. forward that to 'GetFieldInfoFromPropertyPath' as if it was a regular field, var objectTypename = property.GetFullyQualifiedTypenameForCurrentTypeTreeInternal(); GetTypeFromManagedReferenceFullTypeName(objectTypename, out classType); @@ -252,17 +381,15 @@ internal static FieldInfo GetFieldInfoFromProperty(SerializedProperty property, { var fieldInfo = GetFieldInfoAndStaticTypeFromProperty(property, out type); if (fieldInfo == null) - return fieldInfo; + return null; // Managed references are a special case, we need to override the static type // returned by 'GetFieldInfoFromPropertyPath' for custom property handler matching // by the dynamic type of the instance. if (property.propertyType == SerializedPropertyType.ManagedReference) { - Type managedReferenceInstanceType; - // Try to get a Type instance for the managed reference - if (GetTypeFromManagedReferenceFullTypeName(property.managedReferenceFullTypename, out managedReferenceInstanceType)) + if (GetTypeFromManagedReferenceFullTypeName(property.managedReferenceFullTypename, out var managedReferenceInstanceType)) { type = managedReferenceInstanceType; } @@ -284,11 +411,11 @@ internal static bool GetTypeFromManagedReferenceFullTypeName(string managedRefer { managedReferenceInstanceType = null; - var parts = managedReferenceFullTypename.Split(' '); - if (parts.Length == 2) + var splitIndex = managedReferenceFullTypename.IndexOf(' '); + if (splitIndex > 0) { - var assemblyPart = parts[0]; - var nsClassnamePart = parts[1]; + var assemblyPart = managedReferenceFullTypename.Substring(0, splitIndex); + var nsClassnamePart = managedReferenceFullTypename.Substring(splitIndex); managedReferenceInstanceType = Type.GetType($"{nsClassnamePart}, {assemblyPart}"); } @@ -333,7 +460,7 @@ public bool Equals(Cache other) public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; - return obj is Cache && Equals((Cache)obj); + return obj is Cache cache && Equals(cache); } public override int GetHashCode() @@ -357,8 +484,7 @@ private static FieldInfo GetFieldInfoFromPropertyPath(Type host, string path, ou { Cache cache = new Cache(host, path); - FieldInfoCache fieldInfoCache = null; - if (s_FieldInfoFromPropertyPathCache.TryGetValue(cache, out fieldInfoCache)) + if (s_FieldInfoFromPropertyPathCache.TryGetValue(cache, out var fieldInfoCache)) { type = fieldInfoCache?.type; return fieldInfoCache?.fieldInfo; @@ -433,7 +559,7 @@ internal static PropertyHandler GetHandler(SerializedProperty property) FieldInfo field = null; // Determine if SerializedObject target is a script or a builtin type - UnityEngine.Object target = property.serializedObject.targetObject; + Object target = property.serializedObject.targetObject; if (NativeClassExtensionUtilities.ExtendsANativeType(target)) { // For scripts, use reflection to get FieldInfo for the member the property represents @@ -449,8 +575,7 @@ internal static PropertyHandler GetHandler(SerializedProperty property) if (s_BuiltinAttributes == null) PopulateBuiltinAttributes(); - if (attributes == null) - attributes = GetBuiltinAttributes(property); + attributes = GetBuiltinAttributes(property); } handler = s_NextHandler; @@ -461,8 +586,7 @@ internal static PropertyHandler GetHandler(SerializedProperty property) handler.HandleAttribute(property, attributes[i], field, propertyType); } - // Field has no CustomPropertyDrawer attribute with matching drawer so look for default drawer for field type - if (!handler.hasPropertyDrawer && propertyType != null) + if (propertyType != null) handler.HandleDrawnType(property, propertyType, propertyType, field, null); if (handler.empty) @@ -489,12 +613,15 @@ internal static List GetAutoLoadProperties(Type type) if (s_AutoLoadProperties == null) s_AutoLoadProperties = new Dictionary>(); - List list; - if (!s_AutoLoadProperties.TryGetValue(type, out list)) + if (!s_AutoLoadProperties.TryGetValue(type, out var list)) { - list = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance) - .Where(f => f.FieldType == typeof(SerializedProperty) && f.IsDefined(typeof(CachePropertyAttribute), false)) - .ToList(); + list = new List(); + foreach (var field in type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)) + { + if (field.FieldType == typeof(SerializedProperty) && field.IsDefined(typeof(CachePropertyAttribute), false)) + list.Add(field); + } + s_AutoLoadProperties.Add(type, list); } diff --git a/Editor/Mono/ScriptableSingleton.cs b/Editor/Mono/ScriptableSingleton.cs index 2eae2e1973..23aa91478a 100644 --- a/Editor/Mono/ScriptableSingleton.cs +++ b/Editor/Mono/ScriptableSingleton.cs @@ -102,7 +102,8 @@ private static void CreateAndLoad() string filePath = GetFilePath(); if (!string.IsNullOrEmpty(filePath)) { - // If a file exists the + // If a file exists then load it and deserialize it. + // This creates an instance of T which will set s_Instance in the constructor. Then it will deserialize it and call relevant serialization callbacks. InternalEditorUtility.LoadSerializedFileAndForget(filePath); } @@ -110,7 +111,9 @@ private static void CreateAndLoad() { // Create T t = CreateInstance(); - t.hideFlags = HideFlags.HideAndDontSave; + + // Editing should be allowed, but the user is responsible for calling Save() (case uum-40767) + t.hideFlags = HideFlags.HideAndDontSave & ~HideFlags.NotEditable; } System.Diagnostics.Debug.Assert(s_Instance != null); @@ -133,6 +136,10 @@ protected virtual void Save(bool saveAsText) InternalEditorUtility.SaveToSerializedFileAndForget(new[] { s_Instance }, filePath, saveAsText); } + else + { + Debug.LogWarning($"Saving has no effect. Your class '{GetType()}' is missing the FilePathAttribute. Use this attribute to specify where to save your ScriptableSingleton.\nOnly call Save() and use this attribute if you want your state to survive between sessions of Unity."); + } } protected static string GetFilePath() diff --git a/Editor/Mono/Scripting/APIUpdater/APIUpdaterManager.bindings.cs b/Editor/Mono/Scripting/APIUpdater/APIUpdaterManager.bindings.cs index f2ba863eb0..a5f03e490d 100644 --- a/Editor/Mono/Scripting/APIUpdater/APIUpdaterManager.bindings.cs +++ b/Editor/Mono/Scripting/APIUpdater/APIUpdaterManager.bindings.cs @@ -293,6 +293,17 @@ private static void ReportIgnoredAssembliesDueToPreviousErrors(StringBuilder sb, private static int ProcessSuccessfulUpdates(AssemblyUpdaterUpdateTask[] tasks) { var assembliesToUpdate = GetAssembliesToBeUpdated(); + + var noUpdatesRequiredAssemblies = tasks.Where(t => t.Result == APIUpdaterAssemblyHelper.Success); // Assemblies checked which does not requires updates. + if (noUpdatesRequiredAssemblies.Any()) + { + APIUpdaterLogger.WriteToFile("Assemblies not requiring updates:"); + foreach (var noUpdateRequired in noUpdatesRequiredAssemblies) + { + APIUpdaterLogger.WriteToFile($"{noUpdateRequired.Candidate.Path}{FormattedStoutFrom(noUpdateRequired)}"); + } + } + var succeededUpdates = tasks.Where(t => t.Result == APIUpdaterAssemblyHelper.UpdatesApplied); if (!succeededUpdates.Any()) { @@ -311,7 +322,7 @@ private static int ProcessSuccessfulUpdates(AssemblyUpdaterUpdateTask[] tasks) if (!CheckoutFromVCSIfNeeded(updatedAssemblyPaths)) return -1; - APIUpdaterHelper.HandleFilesInPackagesVirtualFolder(updatedAssemblyPaths); + APIUpdaterHelper.HandlePackageFilePaths(updatedAssemblyPaths); if (!APIUpdaterHelper.CheckReadOnlyFiles(updatedAssemblyPaths)) return 0; @@ -355,6 +366,14 @@ IEnumerable FilterOutLocalAndEmbeddedPackagesWhenAskingForConsent(IEnume yield return path; } } + + static string FormattedStoutFrom(AssemblyUpdaterUpdateTask task) + { + if (task.StdOut.Length == 0) + return string.Empty; + + return $" (stdout below):{Environment.NewLine}{task.StdOut}{Environment.NewLine}"; + } } private static void RunAssemblyUpdaterTask(object o) diff --git a/Editor/Mono/Scripting/Compilers/APIUpdaterHelper.cs b/Editor/Mono/Scripting/Compilers/APIUpdaterHelper.cs index ac35565b20..ba9bbc00da 100644 --- a/Editor/Mono/Scripting/Compilers/APIUpdaterHelper.cs +++ b/Editor/Mono/Scripting/Compilers/APIUpdaterHelper.cs @@ -9,7 +9,6 @@ using System.Reflection; using System.Text; using Mono.Cecil; -using NiceIO; using UnityEditor.PackageManager; using UnityEditor.Scripting.ScriptCompilation; using UnityEditor.Utils; @@ -32,52 +31,27 @@ public static bool IsInAssetsFolder(this string filePath) return filePath.StartsWith("Assets/", StringComparison.InvariantCultureIgnoreCase); } - internal static void HandleFilesInPackagesVirtualFolder(string[] destRelativeFilePaths) + internal static void HandlePackageFilePaths(string[] filePathsToUpdate) { - var filesFromReadOnlyPackages = new List(); - foreach (var path in destRelativeFilePaths.Select(path => path.Replace("\\", "/"))) // package manager paths are always separated by / + var filesInPackages = new List(); + foreach (var path in filePathsToUpdate) { - var packageInfo = PackageManager.PackageInfo.FindForAssetPath(path); - if (packageInfo == null) + var absolutePath = Path.GetFullPath(path); + var virtualPath = FileUtil.GetLogicalPath(absolutePath); + if (!virtualPath.StartsWith("Packages/")) { - if (filesFromReadOnlyPackages.Count > 0) - { - Console.WriteLine( - L10n.Tr("[API Updater] At least one file from a readonly package and one file from other location have been updated (that is not expected).{0}File from other location: {0}\t{1}{0}Files from packages already processed: {0}{2}"), - Environment.NewLine, - path, - string.Join($"{Environment.NewLine}\t", filesFromReadOnlyPackages.ToArray())); - } - + // Not a packaged path - skip it continue; } - if (packageInfo.source == PackageSource.BuiltIn) - { - Debug.LogError($"[API Updater] Builtin package '{packageInfo.displayName}' ({packageInfo.version}) files requires updating (Unity version {Application.unityVersion}). This should not happen. Please, report to Unity"); - return; - } - - if (packageInfo.source != PackageSource.Local && packageInfo.source != PackageSource.Embedded) - { - // Packman creates a (readonly) cache under Library/PackageCache in a way that even if multiple projects uses the same package each one should have its own - // private cache so it is safe for the updater to simply remove the readonly attribute and update the file. - filesFromReadOnlyPackages.Add(path); - } - - // PackageSource.Embedded / PackageSource.Local are considered writtable, so nothing to do, i.e, we can simply overwrite the file contents. - } - - foreach (var relativeFilePath in filesFromReadOnlyPackages) - { - var fileAttributes = File.GetAttributes(relativeFilePath); - if ((fileAttributes & FileAttributes.ReadOnly) != FileAttributes.ReadOnly) - continue; + filesInPackages.Add(virtualPath); - File.SetAttributes(relativeFilePath, fileAttributes & ~FileAttributes.ReadOnly); + var fileAttributes = File.GetAttributes(absolutePath); + if ((fileAttributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) + File.SetAttributes(absolutePath, fileAttributes & ~FileAttributes.ReadOnly); } - PackageManager.ImmutableAssets.SetAssetsAllowedToBeModified(filesFromReadOnlyPackages.ToArray()); + PackageManager.ImmutableAssets.SetAssetsToBeModified(filesInPackages.ToArray()); } internal static bool CheckReadOnlyFiles(string[] destRelativeFilePaths) diff --git a/Editor/Mono/Scripting/Compilers/CompilerOutputParserBase.cs b/Editor/Mono/Scripting/Compilers/CompilerOutputParserBase.cs index 166ccfe87e..190c4f08e4 100644 --- a/Editor/Mono/Scripting/Compilers/CompilerOutputParserBase.cs +++ b/Editor/Mono/Scripting/Compilers/CompilerOutputParserBase.cs @@ -101,6 +101,11 @@ public virtual IEnumerable Parse(string[] errorOutput, string[] return msgs; } + protected virtual bool ShouldParseLine(string line) + { + return true; + } + protected abstract string GetErrorIdentifier(); protected virtual string GetInformationIdentifier() diff --git a/Editor/Mono/Scripting/ScriptCompilation/AssemblyFlags.cs b/Editor/Mono/Scripting/ScriptCompilation/AssemblyFlags.cs index 2c29ca81b4..79473487ca 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/AssemblyFlags.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/AssemblyFlags.cs @@ -25,5 +25,6 @@ enum AssemblyFlags SuppressCompilerWarnings = (1 << 10), UserOverride = (1 << 11), UserOverrideCandidate = (1 << 12), + ValidateAssembly = (1 << 13), } } diff --git a/Editor/Mono/Scripting/ScriptCompilation/AssemblyGraphBuilder.cs b/Editor/Mono/Scripting/ScriptCompilation/AssemblyGraphBuilder.cs index 05956a9893..25c6ab007d 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/AssemblyGraphBuilder.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/AssemblyGraphBuilder.cs @@ -12,7 +12,7 @@ namespace UnityEditor.Scripting.ScriptCompilation internal interface IAssemblyGraphBuilder { Dictionary> Match( - IReadOnlyCollection sources); + IReadOnlyCollection sources, bool logWarningOnMiss = true); } internal class AssemblyGraphBuilder : IAssemblyGraphBuilder @@ -131,7 +131,7 @@ public void Initialize(IReadOnlyCollection assemblies, } public Dictionary> Match( - IReadOnlyCollection sources) + IReadOnlyCollection sources, bool logWarningOnMiss = true) { var assemblyNameSources = new Dictionary>(); @@ -144,8 +144,12 @@ public Dictionary> Match( if (currentMatchingAssemblyDefinition == null) { - Console.WriteLine( - $"Script '{source}' will not be compiled because it exists outside the Assets folder and does not to belong to any assembly definition file."); + if (logWarningOnMiss) + { + Console.WriteLine( + $"Script '{source}' will not be compiled because it exists outside the Assets folder and does not to belong to any assembly definition file."); + } + continue; } @@ -216,7 +220,11 @@ internal static bool HasEditorSpecialFolder(ReadOnlySpan remainingPath) { continue; } - else if (i >= remainingPath.Length || Utility.IsPathSeparator(remainingPath[i + 1])) + + // We have match the "editor" folder, if we are at the end of the + // match or do we have a separator as our next character + if (i + 1 >= remainingPath.Length + || Utility.IsPathSeparator(remainingPath[i + 1])) { return true; } diff --git a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/BeeScriptCompilation.cs b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/BeeScriptCompilation.cs index 006e2e5cfe..92170c7e47 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/BeeScriptCompilation.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/BeeScriptCompilation.cs @@ -34,7 +34,7 @@ public static ScriptCompilationData ScriptCompilationDataFor( BuildTarget buildTarget, bool buildingForEditor, bool enableScriptUpdater, - string[] extraScriptingDefines = null) + string[] extraScriptingDefines = null, ScriptAssemblySettings settings = null) { // Need to call AssemblyDataFrom before calling CompilationPipeline.GetScriptAssemblies, // as that acts on the same ScriptAssemblies, and modifies them with different build settings. @@ -55,12 +55,21 @@ public static ScriptCompilationData ScriptCompilationDataFor( if (LocalizationDatabase.currentEditorLanguage != SystemLanguage.English && EditorPrefs.GetBool("Editor.kEnableCompilerMessagesLocalization", false)) localization = LocalizationDatabase.GetCulture(LocalizationDatabase.currentEditorLanguage); - var assembliesToScanForTypeDB = new HashSet(); var searchPaths = new HashSet(BuildPlayerDataGenerator.GetStaticSearchPaths(buildTarget)); - var options = EditorScriptCompilationOptions.BuildingIncludingTestAssemblies; - if (buildingForEditor) - options |= EditorScriptCompilationOptions.BuildingForEditor; + EditorScriptCompilationOptions options; + + if (settings is null) + { + options = EditorScriptCompilationOptions.BuildingIncludingTestAssemblies; + if (buildingForEditor) + options |= EditorScriptCompilationOptions.BuildingForEditor; + } + else + { + options = settings.CompilationOptions; + } + foreach (var a in editorCompilation.GetAllScriptAssemblies(options, extraScriptingDefines)) { if (!a.Flags.HasFlag(AssemblyFlags.EditorOnly)) @@ -129,7 +138,7 @@ private static AssemblyData[] AssemblyDataFrom(ScriptAssembly[] assemblies) private static AssemblyData AssemblyDataFrom(ScriptAssembly a, ScriptAssembly[] allAssemblies, int index) { - Array.Sort(a.Files, StringComparer.InvariantCulture); + Array.Sort(a.Files, StringComparer.Ordinal); var references = a.ScriptAssemblyReferences.Select(r => Array.IndexOf(allAssemblies, r)).ToArray(); Array.Sort(references); return new AssemblyData diff --git a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityBeeDriver.cs b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityBeeDriver.cs index 30cbac69b0..614a77bef1 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityBeeDriver.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityBeeDriver.cs @@ -25,7 +25,7 @@ static class UnityBeeDriver internal static readonly string BeeBackendExecutable = new NPath($"{EditorApplication.applicationContentsPath}/bee_backend{BeeScriptCompilation.ExecutableExtension}").ToString(); internal static readonly string BeeCacheToolExecutable = $"{EditorApplication.applicationContentsPath}/Tools/BuildPipeline/BeeLocalCacheTool{BeeScriptCompilation.ExecutableExtension}"; internal static readonly string BeeCacheDirEnvVar = "BEE_CACHE_DIRECTORY"; - internal static string BeeCacheDir => Environment.GetEnvironmentVariable(BeeCacheDirEnvVar) ?? new NPath($"{InternalEditorUtility.userAppDataFolder}/cache/bee").ToString(SlashMode.Native); + internal static string BeeCacheDir => Environment.GetEnvironmentVariable(BeeCacheDirEnvVar) ?? new NPath($"{OSUtil.GetDefaultCachePath()}/bee").ToString(SlashMode.Native); [Serializable] internal class BeeBackendInfo @@ -88,6 +88,11 @@ private static void RecreateDagDirectoryIfNeeded(NPath dagDirectory) // Clear dag directory if it was produced with a different bee_backend, to avoid problem where bee_backend sometimes looses track of files. if (dagDirectory.Exists()) { + // When used DeleteMode.Normal, it sometimes was causing an error on Windows: + // Win32Exception: The directory is not empty. + // at NiceIO.NPath + WindowsFileSystem.Directory_Delete(NiceIO.NPath path, System.Boolean recursive)[0x000f4] in C:\buildslave\unity\build\External\NiceIO\NiceIO.cs:1792 + // Since we're recreating a directory anyways, using DeleteMode.Soft should be fine. + var deleteMode = DeleteMode.Soft; if (beeBackendInfoPath.Exists()) { var contents = beeBackendInfoPath.ReadAllText(); @@ -99,13 +104,13 @@ private static void RecreateDagDirectoryIfNeeded(NPath dagDirectory) !diskInfo.BeeBackendHash.Equals(currentInfo.BeeBackendHash)) { Console.WriteLine($"Clearing Bee directory '{dagDirectory}', since bee backend hash ('{beeBackendInfoPath}') is different, previous hash was {diskInfo.BeeBackendHash} (Unity version: {diskInfo.UnityVersion}), current hash is {currentInfo.BeeBackendHash} (Unity version: {currentInfo.UnityVersion})."); - dagDirectory.Delete(); + dagDirectory.Delete(deleteMode); } } else { Console.WriteLine($"Clearing Bee directory '{dagDirectory}', since bee backend information ('{beeBackendInfoPath}') is missing."); - dagDirectory.Delete(); + dagDirectory.Delete(deleteMode); } } @@ -142,6 +147,10 @@ public static BuildRequest BuildRequestFor( StdOutMode stdoutMode, RunnableProgram beeBackendProgram = null) { + // ensure ilpp server is running before staring the backend in defered validation mode. + // getting the property value enforces that as it makes sure the server is running and answering a ping request + var ilppNamedPipeOrSocket = ilpp.EnsureRunningAndGetSocketOrNamedPipe(); + NPath dagDir = dagDirectory ?? "Library/Bee"; RecreateDagDirectoryIfNeeded(dagDir); var performingPlayerBuild = UnityBeeDriverProfilerSession.PerformingPlayerBuild; @@ -178,7 +187,7 @@ public static BuildRequest BuildRequestFor( AdvancedLicense = PlayerSettings.advancedLicense, Batchmode = InternalEditorUtility.inBatchMode, EmitDataForBeeWhy = (Debug.GetDiagnosticSwitch("EmitDataForBeeWhy").value as bool?)?? false, - NamedPipeOrUnixSocket = ilpp.NamedPipeOrUnixSocket, + NamedPipeOrUnixSocket = ilppNamedPipeOrSocket, } } }; diff --git a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityBeeDriverProfilerSession.cs b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityBeeDriverProfilerSession.cs index fa913dffb6..5b0e6b08ef 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityBeeDriverProfilerSession.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityBeeDriverProfilerSession.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Bee.Core; using NiceIO; @@ -16,6 +17,7 @@ static class UnityBeeDriverProfilerSession private static TinyProfiler2 _tinyProfiler; private static Stack m_ProfilerSections = new Stack(); private static Dictionary _lastWriteCache = new (); + private static List m_TasksToWaitForBeforeFinishing = new(); public static TinyProfiler2 ProfilerInstance => _tinyProfiler; @@ -23,6 +25,7 @@ static public void Start(NPath path) { m_CurrentPlayerBuildProfilerOutputFile = path; m_BeeDriverForCurrentPlayerBuildIndex = 0; + m_TasksToWaitForBeforeFinishing.Clear(); _tinyProfiler = new TinyProfiler2(); // This is a workaround for the switch to BeeDriver2 breaking merging tool trace event files into the buildreport. @@ -37,6 +40,9 @@ static public void Finish() if (m_CurrentPlayerBuildProfilerOutputFile == null) return; + foreach (var task in m_TasksToWaitForBeforeFinishing) + task.Wait(); + // This is a workaround for the switch to BeeDriver2 breaking merging tool trace event files into the buildreport. // This is temporary and will be reverted once bee is fixed foreach (var file in CollectToolTraceEventsFiles()) @@ -82,6 +88,8 @@ static public void EndSection() } } + static public void AddTaskToWaitForBeforeFinishing(Task t) => m_TasksToWaitForBeforeFinishing.Add(t); + static public bool PerformingPlayerBuild => m_CurrentPlayerBuildProfilerOutputFile != null; static public NPath GetTraceEventsOutputForPlayerBuild() diff --git a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityScriptUpdater.cs b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityScriptUpdater.cs index 1ebb8ffc0d..002e4d9522 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityScriptUpdater.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityScriptUpdater.cs @@ -3,10 +3,12 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.IO; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Text.RegularExpressions; using Bee.BeeDriver; using Bee.Serialization; using NiceIO; @@ -23,6 +25,10 @@ class UnityScriptUpdater : SourceFileUpdaterBase NPath ProjectRoot { get; } RunnableProgram _scriptUpdaterProgram { get; } + // this is the return code used by ScriptUpdater to report that + // some updates were not applied. + const int k_UpdatesToFilesInSameProjectWereNotApplied = 3; + public UnityScriptUpdater(NPath projectRoot) { ProjectRoot = projectRoot; @@ -94,58 +100,110 @@ public override Task StartIfYouCanFixProblemsInTheseMessages(in NodeFin var tempOutputDirectory = new NPath($"Temp/ScriptUpdater/{Math.Abs(nodeFinishedMessage.Node.OutputFile.GetHashCode())}").EnsureDirectoryExists(); var updateTxtFile = tempOutputDirectory.MakeAbsolute().Combine("updates.txt").DeleteIfExists(); + var updaterMessagesToConsoleFile = tempOutputDirectory.MakeAbsolute().Combine($"messages_{new Random().Next()}.txt").DeleteIfExists(); var args = new[] { - "cs", $"\"{EditorApplication.applicationContentsPath}\"", $"\"{tempOutputDirectory}\"", $"\"{_configurationSourcesFilter}\"", - assemblyInfo.ScriptUpdaterRsp.ToNPath().InQuotes() + assemblyInfo.ScriptUpdaterRsp.ToNPath().InQuotes(), + updaterMessagesToConsoleFile.InQuotes() }; var runningScriptUpdater = _scriptUpdaterProgram.Start(ProjectRoot.ToString(), args); - return AwaitScriptUpdaterResults(runningScriptUpdater, updateTxtFile, containsUpdatableCompilerMessage, nodeFinishedMessage.Node.OutputFile); + return AwaitScriptUpdaterResults(runningScriptUpdater, updateTxtFile, updaterMessagesToConsoleFile, containsUpdatableCompilerMessage, nodeFinishedMessage.Node.OutputFile, assemblyInfo); } - async Task AwaitScriptUpdaterResults(RunningProgram runningScriptUpdater, NPath updateTxtFile, CanUpdateAny containsUpdatableCompilerMessage, string nodeOutputFile) + async Task AwaitScriptUpdaterResults(RunningProgram runningScriptUpdater, NPath updateTxtFile, NPath updaterMessagesToConsoleFile, CanUpdateAny containsUpdatableCompilerMessage, string nodeOutputFile, AssemblyData_Out assemblyInfo) { //when the user asks us to cancel a build, we do not want to leave stray unity script updaters laying around. we will //just wait for them to finish. var noToken = CancellationToken.None; var scriptUpdaterResult = await runningScriptUpdater.WaitForExitAsync(noToken); - if (scriptUpdaterResult.ExitCode != 0) - return ErrorResult( - $"Script updater for {nodeOutputFile} failed with exitcode {scriptUpdaterResult.ExitCode} and stdout: {scriptUpdaterResult.Output}"); + var messages = ResultsFromUpdaterImportantMessages(updaterMessagesToConsoleFile); + if (scriptUpdaterResult.ExitCode == k_UpdatesToFilesInSameProjectWereNotApplied) + { + return CollectResultsIfAny(messages, nodeOutputFile, updateTxtFile, CanUpdateAny.Maybe); + } + else if (scriptUpdaterResult.ExitCode != 0) + return ErrorResult($"Script updater for {nodeOutputFile} failed with exitcode {scriptUpdaterResult.ExitCode} and stdout: {scriptUpdaterResult.Output}"); if (!updateTxtFile.FileExists()) - return ErrorResult($"Script updater for {nodeOutputFile} failed to produce updates.txt file"); + { + return WarningResult($"Script updater for {nodeOutputFile} failed to produce updates.txt file (response file: {assemblyInfo.ScriptUpdaterRsp}, MovedFromCache: {assemblyInfo.MovedFromExtractorFile}"); + } - var updateLines = updateTxtFile.ReadAllLines(); - var updates = updateLines.Select(ParseLineIntoUpdate).ToArray(); - if (updates.Contains(null)) - return ErrorResult($"Script updater for {nodeOutputFile} emitted an invalid line to updates.txt"); + return CollectResultsIfAny(messages, nodeOutputFile, updateTxtFile, containsUpdatableCompilerMessage); - return new Results() + static Results ErrorResult(string message) => new() { - Messages = (containsUpdatableCompilerMessage == CanUpdateAny.Certainly && updates.Length == 0) - ? new[] - { - new BeeDriverResult.Message( - $"Script updater for {nodeOutputFile} expected to be able to make an update, but wasn't able to", - BeeDriverResult.MessageKind.Warning) - } - : Array.Empty(), - ProducedUpdates = updates.ToArray() + Messages = new[] {new BeeDriverResult.Message(message, BeeDriverResult.MessageKind.Error)}, + ProducedUpdates = Array.Empty() }; - static Results ErrorResult(string message) => new() + static Results WarningResult(string message) => new() { - Messages = new[] {new BeeDriverResult.Message(message, BeeDriverResult.MessageKind.Error)}, + Messages = new[] {new BeeDriverResult.Message(message, BeeDriverResult.MessageKind.Warning)}, ProducedUpdates = Array.Empty() }; + + BeeDriverResult.Message[] ResultsFromUpdaterImportantMessages(NPath updaterMessagesToConsoleFile) + { + try + { + var lines = Regex.Split(updaterMessagesToConsoleFile.ReadAllText(), "^(?Warning|Error):", RegexOptions.Multiline, TimeSpan.FromSeconds(5)); + if (lines.Length <= 1) // first split line is always empty.. + return Array.Empty(); + + // first line = warning/error, second line = actual message + var messages = new List((lines.Length - 1)/2); + for (int i = 1; i < lines.Length; i+=2) + { + var messageKind = lines[i] switch + { + "Error" => BeeDriverResult.MessageKind.Error, + "Warning" => BeeDriverResult.MessageKind.Warning, + _ => BeeDriverResult.MessageKind.Warning, + }; + messages.Add(new BeeDriverResult.Message(lines[i + 1], messageKind)); + } + + return messages.ToArray(); + } + catch(System.IO.IOException) + { + return Array.Empty(); + } + } + + static Results CollectResultsIfAny(BeeDriverResult.Message[] messages, string nodeOutputFile, NPath updateTxtFile, CanUpdateAny containsUpdatableCompilerMessage) + { + var updates = Array.Empty(); + if (updateTxtFile.FileExists()) + { + var updateLines = updateTxtFile.ReadAllLines(); + + updates = updateLines.Select(ParseLineIntoUpdate).ToArray(); + if (updates.Contains(null)) + return ErrorResult($"Script updater for {nodeOutputFile} emitted an invalid line to {updateTxtFile}"); + + if (containsUpdatableCompilerMessage == CanUpdateAny.Certainly && updates.Length == 0) + { + var temp = new List(messages); + temp.Add(new BeeDriverResult.Message($"Script updater for {nodeOutputFile} expected to be able to make an update, but wasn't able to", BeeDriverResult.MessageKind.Warning)); + messages = temp.ToArray(); + } + } + + return new Results() + { + Messages = messages, + ProducedUpdates = updates + }; + } } internal static Update ParseLineIntoUpdate(string line) diff --git a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityScriptUpdaterConsentAPI.cs b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityScriptUpdaterConsentAPI.cs index 27bc22f2a0..a6982c8135 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityScriptUpdaterConsentAPI.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityScriptUpdaterConsentAPI.cs @@ -4,7 +4,6 @@ using System; using System.Linq; -using NiceIO; using UnityEditor.Scripting.ScriptCompilation; using UnityEditorInternal.APIUpdating; @@ -12,7 +11,7 @@ namespace UnityEditor.ScriptUpdater { class UnityScriptUpdaterConsentAPI { - public UnitySourceFileUpdatersResultHandler.ScriptUpdaterConsentType AskFor(NPath[] filesToOverWrite) + public UnitySourceFileUpdatersResultHandler.ScriptUpdaterConsentType AskFor(string[] filesToOverWrite) { APIUpdaterManager.numberOfTimesAsked = APIUpdaterManager.numberOfTimesAsked + 1; @@ -39,10 +38,10 @@ public UnitySourceFileUpdatersResultHandler.ScriptUpdaterConsentType AskFor(NPat return AskThroughDialog(filesToOverWrite); } - private static UnitySourceFileUpdatersResultHandler.ScriptUpdaterConsentType AskThroughDialog(NPath[] filesToOverWrite) + private static UnitySourceFileUpdatersResultHandler.ScriptUpdaterConsentType AskThroughDialog(string[] filesToOverWrite) { var selection = filesToOverWrite.Take(30).ToArray(); - var displayedFiles = selection.Select(f => f.ToString()).SeparateWith(Environment.NewLine); + var displayedFiles = selection.SeparateWith(Environment.NewLine); var omitted = filesToOverWrite.Length - selection.Length; if (omitted > 0) displayedFiles = displayedFiles + $"\n<+{omitted} more files>"; diff --git a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnitySourceFileUpdatersResultHandler.cs b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnitySourceFileUpdatersResultHandler.cs index b724035a60..09da19adea 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnitySourceFileUpdatersResultHandler.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnitySourceFileUpdatersResultHandler.cs @@ -15,6 +15,7 @@ using UnityEngine; using System.Threading.Tasks; using UnityEditorInternal; +using APIUpdaterHelper = UnityEditor.Scripting.Compilers.APIUpdaterHelper; namespace UnityEditor.Scripting.ScriptCompilation { @@ -35,7 +36,6 @@ public UnitySourceFileUpdatersResultHandler() : base(captureSynchronizationConte protected override bool ProcessUpdaterResults(SourceFileUpdaterBase.Update[] updates) { - NPath libraryPackageCache = "Library/PackageCache"; var problemUpdates = new List<(SourceFileUpdaterBase.Update update, Exception exception)>(); bool didUpdate = false; void ExecuteUpdates(IEnumerable updates) @@ -55,29 +55,20 @@ void ExecuteUpdates(IEnumerable updates) } } - var packageResolvePathsAndNames = PackageManager.PackageInfo.GetAllRegisteredPackages().Where(p => new NPath(p.resolvedPath).ToString().Contains("Library/PackageCache")).Select(p => (p.resolvedPath, p.name)).ToArray(); - - string VirtualizedPathFor(NPath file) - { - foreach (var packageResolvePathAndName in packageResolvePathsAndNames) - if (file.IsChildOf(packageResolvePathAndName.resolvedPath)) - return $"Packages/{packageResolvePathAndName.name}/{file.RelativeTo(packageResolvePathAndName.resolvedPath)}"; - throw new ArgumentException($"Failed to virtualize path: {file}"); - } - - var(immutablePackageUpdates, nonImmutableUpdates) = updates.SplitBy(u => new NPath(u.originalFileWithError).IsChildOf(libraryPackageCache)); + var libraryPackageCache = "Library/PackageCache/"; + var(immutablePackageUpdates, nonImmutableUpdates) = updates.SplitBy(u => u.originalFileWithError.StartsWith(libraryPackageCache)); Console.WriteLine("[API Updater] Updated Files:"); if (immutablePackageUpdates.Any()) { - var virtualizedPackageFiles = immutablePackageUpdates.Select(u => VirtualizedPathFor(u.originalFileWithError)).ToArray(); - ImmutableAssets.SetAssetsAllowedToBeModified(virtualizedPackageFiles); + var immutablePackageFiles = immutablePackageUpdates.Select(u => u.originalFileWithError).ToArray(); + APIUpdaterHelper.HandlePackageFilePaths(immutablePackageFiles); ExecuteUpdates(immutablePackageUpdates); } if (nonImmutableUpdates.Any()) { - var nonImmutableTargetFiles = nonImmutableUpdates.Select(u => new NPath(u.originalFileWithError)).ToArray(); + var nonImmutableTargetFiles = nonImmutableUpdates.Select(u => u.originalFileWithError).ToArray(); if (MayOverwrite(nonImmutableTargetFiles) && PrepareForOverwritingUpdatedFiles(nonImmutableTargetFiles)) ExecuteUpdates(nonImmutableUpdates); @@ -97,7 +88,7 @@ string VirtualizedPathFor(NPath file) return didUpdate; } - bool MayOverwrite(NPath[] files) + bool MayOverwrite(string[] files) { if (m_HaveConsentToOverwriteUserScripts) return true; @@ -116,7 +107,7 @@ bool MayOverwrite(NPath[] files) } } - bool PrepareForOverwritingUpdatedFiles(NPath[] destFiles) + bool PrepareForOverwritingUpdatedFiles(string[] destFiles) { if (!APIUpdaterManager.WaitForVCSServerConnection()) { @@ -127,7 +118,7 @@ bool PrepareForOverwritingUpdatedFiles(NPath[] destFiles) return false; } - if (!AssetDatabase.MakeEditable(destFiles.Select(d => d.ToString()).ToArray())) + if (!AssetDatabase.MakeEditable(destFiles)) { Debug.LogError($"Failed to make VCS provider make the scripts to be update editable.{Environment.NewLine}" + string.Join(Environment.NewLine, destFiles.Select(d => d.ToString()))); return false; diff --git a/Editor/Mono/Scripting/ScriptCompilation/CustomScriptAssembly.cs b/Editor/Mono/Scripting/ScriptCompilation/CustomScriptAssembly.cs index 5254624fd3..203b207628 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/CustomScriptAssembly.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/CustomScriptAssembly.cs @@ -321,6 +321,7 @@ static CustomScriptAssembly() new CustomScriptAssemblyPlatform("PSVita", BuildTarget.PSP2), new CustomScriptAssemblyPlatform("LinuxStandalone32", BuildTarget.StandaloneLinux), new CustomScriptAssemblyPlatform("LinuxStandaloneUniversal", BuildTarget.StandaloneLinuxUniversal), + new CustomScriptAssemblyPlatform("Lumin", BuildTarget.Lumin), }; #pragma warning restore 0618 } diff --git a/Editor/Mono/Scripting/ScriptCompilation/EditorBuildRules.cs b/Editor/Mono/Scripting/ScriptCompilation/EditorBuildRules.cs index d6b6a74232..477fc59387 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/EditorBuildRules.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/EditorBuildRules.cs @@ -78,6 +78,19 @@ internal static int PathFilter(string scriptPath, string pathPrefix, string lowe return depth; } + private static string[] CombineDefineArrays(string[] defines, string[] extraPlayerDefines) + { + if (defines == null) + return null; + + extraPlayerDefines = extraPlayerDefines ?? Array.Empty(); + + var combined = new string[defines.Length + extraPlayerDefines.Length]; + Array.Copy(defines, combined, defines.Length); + Array.Copy(extraPlayerDefines, 0, combined, defines.Length, extraPlayerDefines.Length); + return combined; + } + public static Dictionary CreateTargetAssemblies(IEnumerable customScriptAssemblies) { if (customScriptAssemblies == null) @@ -98,7 +111,7 @@ public static Dictionary CreateTargetAssemblies(IEnumera customAssembly.PathPrefix, customAssembly.AdditionalPrefixes, path => PathFilter(path, customAssembly.PathPrefix, lowerPathPrefix, customAssembly.AdditionalPrefixes, lowerAdditionalPathPrefixes), - (settings, defines) => customAssembly.IsCompatibleWith(settings.BuildTarget, settings.CompilationOptions, defines), + (settings, defines) => customAssembly.IsCompatibleWith(settings.BuildTarget, settings.CompilationOptions, CombineDefineArrays(defines, settings.ExtraGeneralDefines)), customAssembly.CompilerOptions) { ExplicitPrecompiledReferences = customAssembly.PrecompiledReferences?.ToList() ?? new List(), @@ -304,7 +317,7 @@ internal static ScriptAssembly[] ToScriptAssemblies( } } - if ((settings.CompilationOptions & EditorScriptCompilationOptions.BuildingWithRoslynAnalysis) != 0) + if (assemblies.RoslynAnalyzerDllPaths != null) RoslynAnalyzers.SetAnalyzers(scriptAssemblies, assemblies.CustomTargetAssemblies.Values.ToArray(), assemblies.RoslynAnalyzerDllPaths, false); return scriptAssemblies; @@ -337,7 +350,7 @@ internal static void AddScriptAssemblyReferences(ref ScriptAssembly scriptAssemb if (shouldProcessPredefinedCustomTargets && assemblies.PredefinedAssembliesCustomTargetReferences != null) predefinedCustomTargetReferences = assemblies.PredefinedAssembliesCustomTargetReferences; - var unityReferences = Array.Empty(); + var unityReferences = new Dictionary(); // Add Unity assemblies (UnityEngine.dll, UnityEditor.dll) references, as long as the target // doesn't specify that it doesn't want them. @@ -345,11 +358,14 @@ internal static void AddScriptAssemblyReferences(ref ScriptAssembly scriptAssemb { // Add predefined custom target references in a hash-set for fast lookup var predefinedCustomTargetRefs = new HashSet(predefinedCustomTargetReferences.Select(x => x.Filename)); - unityReferences = GetUnityReferences(scriptAssembly, targetAssembly, assemblies.UnityAssemblies, predefinedCustomTargetRefs, settings.CompilationOptions, UnityReferencesOptions.None); - references.AddRange(unityReferences - .Where(r => !r.Flags.HasFlag(AssemblyFlags.UserOverride)) - .Select(r => r.Path) - ); + foreach (var unityReference in GetUnityReferences(scriptAssembly, targetAssembly, + assemblies.UnityAssemblies, predefinedCustomTargetRefs, settings.CompilationOptions, + UnityReferencesOptions.None)) + { + unityReferences.Add(Path.GetFileName(unityReference.Path), unityReference); + if (!unityReference.Flags.HasFlag(AssemblyFlags.UserOverride)) + references.Add(unityReference.Path); + } } AddTestRunnerCustomReferences(ref targetAssembly, assemblies.CustomTargetAssemblies); @@ -362,7 +378,7 @@ internal static void AddScriptAssemblyReferences(ref ScriptAssembly scriptAssemb // If the assembly already showed up in the unity references, don't reference it here. // This can happen when an assembly is configured as a unity assembly override, but // overrides are disabled. The Unity assembly should take precedence in that case. - if (unityReferences.Any(r => Path.GetFileName(r.Path) == reference.Filename)) + if (unityReferences.ContainsKey(reference.Filename)) continue; // Add ScriptAssembly references to other dirty script assemblies that also need to be rebuilt. @@ -388,8 +404,8 @@ internal static void AddScriptAssemblyReferences(ref ScriptAssembly scriptAssemb } var unityReferencesGenerated = unityReferences - .Where(r => r.Flags.HasFlag(AssemblyFlags.UserOverride)) - .Select(r => Path.GetFileName(r.Path)); + .Where(kvp => kvp.Value.Flags.HasFlag(AssemblyFlags.UserOverride)) + .Select(kvp => kvp.Key); foreach (var assembly in unityReferencesGenerated) { var scriptAssemblyReference = targetToScriptAssembly.FirstOrDefault(kvp => kvp.Key.Filename == assembly); diff --git a/Editor/Mono/Scripting/ScriptCompilation/EditorCompilation.cs b/Editor/Mono/Scripting/ScriptCompilation/EditorCompilation.cs index c802bd551a..f0acb044bc 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/EditorCompilation.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/EditorCompilation.cs @@ -65,6 +65,8 @@ public struct TargetAssemblyInfo { public string Name; public AssemblyFlags Flags; + + public static readonly TargetAssemblyInfo Unknown = new TargetAssemblyInfo { Flags = AssemblyFlags.None, Name = null }; } public struct CustomScriptAssemblyAndReference @@ -122,7 +124,6 @@ class BeeScriptCompilationState BeeScriptCompilationState _currentBeeScriptCompilationState; CompilerMessage[] _currentEditorCompilationCompilerMessages = new CompilerMessage[0]; - public bool IsRunningRoslynAnalysisSynchronously { get; private set; } public void SetProjectDirectory(string projectDirectory) { @@ -207,7 +208,22 @@ public void RequestScriptCompilation(string reason = null, RequestScriptCompilat internal static void ClearBeeBuildArtifacts() { - new NPath("Library/Bee").DeleteIfExists(DeleteMode.Soft); + NPath beeFolder = "Library/Bee"; + if (beeFolder.DirectoryExists()) + { + foreach (var path in beeFolder.Contents() + .Where(p => + // We should not delete the tundra build state file, as we then lose any information on which files we built. + // This information is used to delete stale output files which are no longer needed. If we delete this file, + // you can end up with old output files included in your builds, which we should not ship with. + p.FileName != "TundraBuildState.state" && + + // If the bee_backend.info file changes, we delete the dag folder (See `UnityBeeDriver.RecreateDagDirectoryIfNeeded`) + // which makes the above not work, unless we also keep this file. + p.FileName != "bee_backend.info")) + path.Delete(DeleteMode.Soft); + } + Mono.Utils.Pram.PramDataDirectory.DeleteIfExists(DeleteMode.Soft); } @@ -696,9 +712,6 @@ public CompileStatus CompileScripts( string[] extraScriptingDefines ) { - IsRunningRoslynAnalysisSynchronously = - (editorScriptCompilationOptions & EditorScriptCompilationOptions.BuildingWithRoslynAnalysis) != 0 && PlayerSettings.EnableRoslynAnalyzers; - var scriptAssemblySettings = CreateScriptAssemblySettings(platformGroup, platform, editorScriptCompilationOptions, extraScriptingDefines); CompileStatus compilationResult; @@ -804,10 +817,6 @@ public CompileStatus CompileScriptsWithSettings(ScriptAssemblySettings scriptAss m_UnityVersionRanges.Clear(); m_SemVersionRanges.Clear(); - IsRunningRoslynAnalysisSynchronously = - PlayerSettings.EnableRoslynAnalyzers && - (scriptAssemblySettings.CompilationOptions & EditorScriptCompilationOptions.BuildingWithRoslynAnalysis) != 0; - ScriptAssembly[] scriptAssemblies; if (scriptAssemblySettings.CompilationOptions.HasFlag(EditorScriptCompilationOptions.BuildingSkipCompile)) scriptAssemblies = Array.Empty(); @@ -860,10 +869,13 @@ public CompileStatus CompileScriptsWithSettings(ScriptAssemblySettings scriptAss scriptAssemblySettings.OutputDirectory, buildTarget, scriptAssemblySettings.BuildingForEditor, - !scriptAssemblySettings.BuildingWithoutScriptUpdater)); + !scriptAssemblySettings.BuildingWithoutScriptUpdater, + scriptAssemblySettings.ExtraGeneralDefines, scriptAssemblySettings)); var cts = new CancellationTokenSource(); + ConsoleWindow.ClearConsoleOnRecompile(); + var activeBeeBuild = BeeDriver.BuildAsync(buildRequest, cts.Token); _currentBeeScriptCompilationState = new BeeScriptCompilationState() @@ -939,6 +951,7 @@ public ScriptAssemblySettings CreateScriptAssemblySettings(BuildTargetGroup buil { additionalCompilationArguments.Add("/nowarn:0169"); additionalCompilationArguments.Add("/nowarn:0649"); + additionalCompilationArguments.Add("/nowarn:0282"); // The msbuild tool disables warnings 1701 and 1702 by default, so Unity should do the same. additionalCompilationArguments.Add("/nowarn:1701"); @@ -1112,20 +1125,15 @@ public CompileStatus TickCompilationPipeline(EditorScriptCompilationOptions opti ? kLogIdentifierFor_EditorMessages : kLogIdentifierFor_PlayerMessages; - if (_currentBeeScriptCompilationState.Settings.BuildingForEditor) + var buildingForEditor = _currentBeeScriptCompilationState.Settings.BuildingForEditor; + if (buildingForEditor) { _currentEditorCompilationCompilerMessages = compilerMessages; } if (_logCompilationMessages) { - Debug.RemoveLogEntriesByIdentifier(logIdentifier); - foreach (var message in compilerMessages) - { - // Ensure that we don't emit info messages (user cannot do anything with these and they are generated by DiagnosticSuppressors) - if (message.type != CompilerMessageType.Information) - Debug.LogCompilerMessage(message.message, message.file, message.line, message.column, _currentBeeScriptCompilationState.Settings.BuildingForEditor, message.type == CompilerMessageType.Error, logIdentifier); - } + LogCompilerMessages(logIdentifier, compilerMessages, buildingForEditor); } result.ProfileOutputWritingTask?.Wait(); @@ -1136,6 +1144,58 @@ public CompileStatus TickCompilationPipeline(EditorScriptCompilationOptions opti : CompileStatus.CompilationFailed; } + private static void LogCompilerMessages(int logIdentifier, IEnumerable compilerMessages, bool buildingForEditor) + { + Debug.RemoveLogEntriesByIdentifier(logIdentifier); + var fileInstanceIdCache = new Dictionary(); + + foreach (var message in compilerMessages) + { + if (message.type == CompilerMessageType.Information) + { + // ensure that we don't emit info messages (user cannot do anything with these and they are generated by DiagnosticSuppressors) + continue; + } + + // the instance id identifies an asset in the project window, used for pinging the asset when double clicking on a log message with a compilation error + var instanceId = LookupInstanceId(fileInstanceIdCache, message.file); + + Debug.LogCompilerMessage(message.message, message.file, message.line, message.column, + buildingForEditor, message.type == CompilerMessageType.Error, logIdentifier, instanceId); + } + } + + private static int LookupInstanceId(IDictionary fileInstanceIdCache, string filePath) + { + // in batch mode, we don't have a Console Window, so we don't need an instance id + if (Application.isBatchMode || string.IsNullOrEmpty(filePath)) + { + return 0; + } + + if (fileInstanceIdCache.TryGetValue(filePath, out var instanceId)) + { + return instanceId; + } + + // The AssetDatabase does not expect absolute paths. In this case, we + // try to get the Logical path for the supplied filePath and pass that along + var logicalFilePath = FileUtil.GetLogicalPath(filePath); + if (string.IsNullOrEmpty(logicalFilePath)) + { + return 0; + } + + var guid = AssetDatabase.GUIDFromAssetPath(logicalFilePath); + + // script compilation errors can happen before the asset database is initialized, so we reserve the instance id ahead of time (it is deterministic) + instanceId = AssetDatabase.ReserveMonoScriptInstanceID(guid); + + fileInstanceIdCache.Add(filePath, instanceId); + + return instanceId; + } + void CompleteActiveBuildWhilePumping() { var synchroContext = (UnitySynchronizationContext) SynchronizationContext.Current; @@ -1343,7 +1403,11 @@ public TargetAssemblyInfo GetTargetAssembly(string scriptPath) path = Path.Combine(projectDirectory, scriptPath); } - var matchedAssembly = GetAssemblyGraphBuilder().Match(new []{path}); + var matchedAssembly = GetAssemblyGraphBuilder().Match(new []{path}, false); + if (matchedAssembly.Count == 0) + { + return TargetAssemblyInfo.Unknown; + } TargetAssembly targetAssembly; var scriptAssembly = matchedAssembly.Single().Key; @@ -1642,8 +1706,6 @@ static EditorScriptCompilationOptions ToEditorScriptCompilationOptions(AssemblyB options |= EditorScriptCompilationOptions.BuildingForEditor; } - options |= EditorScriptCompilationOptions.BuildingWithRoslynAnalysis; - return options; } @@ -1721,23 +1783,22 @@ public ScriptAssembly CreateScriptAssembly(AssemblyBuilder assemblyBuilder) scriptAssembly.References = references.ToArray(); scriptAssembly.Defines = defines.ToArray(); - if (options.HasFlag(EditorScriptCompilationOptions.BuildingWithRoslynAnalysis)) - { - RoslynAnalyzers.SetAnalyzers( - new[] { scriptAssembly }, - customTargetAssemblies.Values.ToArray(), - PrecompiledAssemblyProvider.GetRoslynAnalyzerPaths(), - true); + RoslynAnalyzers.SetAnalyzers( + new[] { scriptAssembly }, + customTargetAssemblies.Values.ToArray(), + PrecompiledAssemblyProvider.GetRoslynAnalyzerPaths(), + true); - // AssemblyBuilder can explicitly set analyzers and rule set - if (assemblyBuilder.compilerOptions.RoslynAnalyzerDllPaths != null) - scriptAssembly.CompilerOptions.RoslynAnalyzerDllPaths = assemblyBuilder.compilerOptions.RoslynAnalyzerDllPaths - .Concat(scriptAssembly.CompilerOptions.RoslynAnalyzerDllPaths) - .ToArray(); + // AssemblyBuilder can explicitly set analyzers and rule set + if (assemblyBuilder.compilerOptions.RoslynAnalyzerDllPaths != null) + scriptAssembly.CompilerOptions.RoslynAnalyzerDllPaths = assemblyBuilder.compilerOptions.RoslynAnalyzerDllPaths + .Concat(scriptAssembly.CompilerOptions.RoslynAnalyzerDllPaths) + .Distinct() + .ToArray(); + + if (!string.IsNullOrEmpty(assemblyBuilder.compilerOptions.RoslynAnalyzerRulesetPath)) + scriptAssembly.CompilerOptions.RoslynAnalyzerRulesetPath = assemblyBuilder.compilerOptions.RoslynAnalyzerRulesetPath; - if (!string.IsNullOrEmpty(assemblyBuilder.compilerOptions.RoslynAnalyzerRulesetPath)) - scriptAssembly.CompilerOptions.RoslynAnalyzerRulesetPath = assemblyBuilder.compilerOptions.RoslynAnalyzerRulesetPath; - } return scriptAssembly; } diff --git a/Editor/Mono/Scripting/ScriptCompilation/EditorCompilationInterface.cs b/Editor/Mono/Scripting/ScriptCompilation/EditorCompilationInterface.cs index 1fd2435b71..88c6ed4268 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/EditorCompilationInterface.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/EditorCompilationInterface.cs @@ -289,8 +289,6 @@ public static EditorScriptCompilationOptions GetAdditionalEditorScriptCompilatio if (PlayerSettings.UseDeterministicCompilation) options |= EditorScriptCompilationOptions.BuildingUseDeterministicCompilation; - options |= EditorScriptCompilationOptions.BuildingWithRoslynAnalysis; - return options; } } diff --git a/Editor/Mono/Scripting/ScriptCompilation/EditorScriptCompilationOptions.cs b/Editor/Mono/Scripting/ScriptCompilation/EditorScriptCompilationOptions.cs index 5f9f6f1c46..0791461999 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/EditorScriptCompilationOptions.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/EditorScriptCompilationOptions.cs @@ -20,7 +20,6 @@ enum EditorScriptCompilationOptions BuildingPredefinedAssembliesAllowUnsafeCode = 1 << 6, BuildingForHeadlessPlayer = 1 << 7, BuildingUseDeterministicCompilation = 1 << 9, - BuildingWithRoslynAnalysis = 1 << 10, BuildingWithoutScriptUpdater = 1 << 11, BuildingExtractTypeDB = 1 << 12, BuildingSkipCompile = 1 << 13, diff --git a/Editor/Mono/Scripting/ScriptCompilation/ILPostProcessingProgram.cs b/Editor/Mono/Scripting/ScriptCompilation/ILPostProcessingProgram.cs index f90f3d93c4..702ec1bd37 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/ILPostProcessingProgram.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/ILPostProcessingProgram.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Security.Cryptography; @@ -12,26 +13,61 @@ using UnityEditor.Utils; using UnityEngine; using UnityEngine.Bindings; +using UnityEngine.Scripting; namespace UnityEditor.Scripting.ScriptCompilation { class ILPostProcessingProgram { - public virtual string NamedPipeOrUnixSocket + public virtual string EnsureRunningAndGetSocketOrNamedPipe() { - get + var name = EnsureRunningAndGetSocketOrNamedPipeImpl(); + if (string.IsNullOrEmpty(name)) { - var name = EnsureRunningAndGetSocketOrNamedPipe(); - if (string.IsNullOrEmpty(name)) - { - UnityEngine.Debug.LogWarning("IL Post Processing agent failed to start. Any IL Post Processing task will fail"); - } - return name; + UnityEngine.Debug.LogWarning("IL Post Processing agent failed to start. Any IL Post Processing task will fail"); } + return name; } [NativeHeader("Editor/Src/ScriptCompilation/ILPPExternalProcess.h")] [FreeFunction("ILPPExternalProcess::EnsureRunningAndGetSocketOrNamedPipe", IsThreadSafe = true)] - private static extern string EnsureRunningAndGetSocketOrNamedPipe(); + private static extern string EnsureRunningAndGetSocketOrNamedPipeImpl(); + + [RequiredByNativeCode(GenerateProxy = true)] + private static void KillLingeringILPPRunner() + { + var pidFilePath = Path.Combine("Library", "ilpp.pid"); + if (!File.Exists(pidFilePath)) + { + return; + } + int processId; + try + { + processId = int.Parse(File.ReadAllText(pidFilePath), CultureInfo.InvariantCulture); + } + catch(FormatException) { + // corrupted pid file, return + return; + } + try + { + var process = Process.GetProcessById(processId); + if(process.ProcessName == "Unity.ILPP.Runner" || process.ProcessName == "Unity.ILPP.Runner.exe") + { + UnityEngine.Debug.Log($"Found a lingering IL Post Processing runner process with PID {processId}. Killing it."); + process.Kill(); + } + } + catch (ArgumentException) + { + // no process with this ID + return; + } + catch (InvalidOperationException) + { + // process exitted while the Process instance is manipulated + } + } } } diff --git a/Editor/Mono/Scripting/ScriptCompilation/PathMultidimensionalDivisionTree.cs b/Editor/Mono/Scripting/ScriptCompilation/PathMultidimensionalDivisionTree.cs index 7684e2ead1..cf15b38bd5 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/PathMultidimensionalDivisionTree.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/PathMultidimensionalDivisionTree.cs @@ -91,9 +91,8 @@ private Node(ReadOnlyMemory fullPrefix, int offsetFromParent, List c } private Node WithDifferentOffsetFromParent(int offsetFromParent) - { - return new Node(Value, FullPrefix, offsetFromParent, ChildNodes); - } + => IsLeaf ? new Node(Value, FullPrefix, offsetFromParent, ChildNodes) + : new Node(FullPrefix, offsetFromParent, ChildNodes); private bool IsMatch(ReadOnlySpan span) { diff --git a/Editor/Mono/Scripting/ScriptCompilation/PostProcessorOutputParser.cs b/Editor/Mono/Scripting/ScriptCompilation/PostProcessorOutputParser.cs index 30e34b42af..f65f52e72a 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/PostProcessorOutputParser.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/PostProcessorOutputParser.cs @@ -2,8 +2,12 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; +using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEditor.Scripting.Compilers; +using UnityEditorInternal; +using UnityEngine; namespace UnityEditor.Scripting.ScriptCompilation { @@ -11,6 +15,67 @@ internal class PostProcessorOutputParser : CompilerOutputParserBase { private static Regex sCompilerOutput = new Regex(@"(?[^:]*)\((?\d+),(?\d+)\):\s*(?warning|error)\s*(?.*)", RegexOptions.ExplicitCapture | RegexOptions.Compiled); + protected override bool ShouldParseLine(string line) + { + return line.Contains("warning", StringComparison.Ordinal) || + line.Contains("error", StringComparison.Ordinal); + } + + public override IEnumerable Parse(string[] errorOutput, string[] standardOutput, bool compilationHadFailure, string assemblyName_unused = null) + { + var hasErrors = false; + var msgs = new List(); + var regex = GetOutputRegex(); + var internalErrorRegex = GetInternalErrorOutputRegex(); + + var isWarningError = false; + var completeWarningErrors = new List(); + for (int i = 0; i < errorOutput.Length; i++) + { + if (!ShouldParseLine(errorOutput[i])) + { + if (isWarningError) + completeWarningErrors[completeWarningErrors.Count - 1] += "\n" + errorOutput[i]; + + continue; + } + + if (regex.Match(errorOutput[i]).Success || (internalErrorRegex != null && internalErrorRegex.Match(errorOutput[i]).Success)) + { + completeWarningErrors.Add(errorOutput[i]); + isWarningError = true; + } + else if (completeWarningErrors.Count > 0) + completeWarningErrors[completeWarningErrors.Count - 1] += "\n" + errorOutput[i]; + } + + foreach (var line in completeWarningErrors) + { + //Jamplus can fail with enormous lines in the stdout, parsing of which can take 30! seconds. + var line2 = line.Length > 1000 ? line.Substring(0, 100) : line; + var regrexLineToMatch = line2.Split("\n"); + Match m = regex.Match(regrexLineToMatch[0]); + if (!m.Success) + { + if (internalErrorRegex != null) + m = internalErrorRegex.Match(regrexLineToMatch[0]); + if (!m.Success) + continue; + } + CompilerMessage message = CreateCompilerMessageFromMatchedRegex(line, m, GetErrorIdentifier(), GetInformationIdentifier()); + + if (message.type == CompilerMessageType.Error) + hasErrors = true; + + msgs.Add(message); + } + if (compilationHadFailure && !hasErrors) + { + msgs.Add(CreateInternalCompilerErrorMessage(errorOutput)); + } + return msgs; + } + protected override string GetInformationIdentifier() { return default; diff --git a/Editor/Mono/Scripting/ScriptCompilation/PrecompiledAssembly.cs b/Editor/Mono/Scripting/ScriptCompilation/PrecompiledAssembly.cs index cf0354d643..db4ee22da8 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/PrecompiledAssembly.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/PrecompiledAssembly.cs @@ -31,6 +31,7 @@ abstract class PrecompiledAssemblyProviderBase public abstract PrecompiledAssembly[] GetPrecompiledAssemblies(EditorScriptCompilationOptions compilationOptions, BuildTargetGroup buildTargetGroup, BuildTarget buildTarget, string[] extraScriptingDefines = null); public abstract Dictionary GetPrecompiledAssembliesDictionary(EditorScriptCompilationOptions compilationOptions, BuildTargetGroup buildTargetGroup, BuildTarget buildTarget, string[] extraScriptingDefines); public abstract PrecompiledAssembly[] GetUnityAssemblies(bool isEditor, BuildTarget buildTarget); + public abstract PrecompiledAssembly[] GetAllPrecompiledAssemblies(); public abstract PrecompiledAssembly[] CachedEditorPrecompiledAssemblies { get; } public abstract PrecompiledAssembly[] CachedUnityAssemblies { get; } public abstract void Dirty(); @@ -46,11 +47,19 @@ protected virtual PrecompiledAssembly[] GetPrecompiledAssembliesInternal(EditorS return GetPrecompiledAssembliesNative(compilationOptions, buildTargetGroup, target, extraScriptingDefines); } + protected virtual PrecompiledAssembly[] GetAllPrecompiledAssembliesInternal() + { + return GetAllPrecompiledAssembliesNative(); + } + [FreeFunction("GetPrecompiledAssembliesManaged")] protected static extern PrecompiledAssembly[] GetPrecompiledAssembliesNative(EditorScriptCompilationOptions compilationOptions, BuildTargetGroup buildTargetGroup, BuildTarget target, string[] extraScriptingDefines); [FreeFunction("GetUnityAssembliesManaged")] protected static extern PrecompiledAssembly[] GetUnityAssembliesNative(bool buildingForEditor, BuildTarget target); + + [FreeFunction("GetAllPrecompiledAssembliesManaged")] + protected static extern PrecompiledAssembly[] GetAllPrecompiledAssembliesNative(); } [NativeHeader("Editor/Src/ScriptCompilation/RoslynAnalyzers.bindings.h")] @@ -94,6 +103,7 @@ public override int GetHashCode() .SelectMany(x => x).ToArray(); private Dictionary m_EditorPrecompiledAssemblies; + private PrecompiledAssembly[] m_AllPrecompiledAssemblies; public override PrecompiledAssembly[] CachedEditorPrecompiledAssemblies => m_EditorPrecompiledAssemblies?.Values.ToArray(); public override PrecompiledAssembly[] GetUnityAssemblies(bool isEditor, BuildTarget buildTarget) @@ -119,6 +129,15 @@ public override PrecompiledAssembly[] GetPrecompiledAssemblies(EditorScriptCompi return GetPrecompiledAssembliesDictionary(compilationOptions, buildTargetGroup, buildTarget, extraScriptingDefines).Values.ToArray(); } + public override PrecompiledAssembly[] GetAllPrecompiledAssemblies() + { + if (m_AllPrecompiledAssemblies != null) + return m_AllPrecompiledAssemblies; + + m_AllPrecompiledAssemblies = GetAllPrecompiledAssembliesInternal(); + return m_AllPrecompiledAssemblies; + } + public override string[] GetRoslynAnalyzerPaths() { return GetAllRoslynAnalyzerPaths(); @@ -153,6 +172,7 @@ public override void Dirty() { m_EditorPrecompiledAssemblies = null; m_UnityAssemblies = null; + m_AllPrecompiledAssemblies = null; } private static Dictionary FilenameToPrecompiledAssembly(PrecompiledAssembly[] precompiledAssemblies) diff --git a/Editor/Mono/Scripting/ScriptCompilation/UnitySpecificCompilerMessageProcessor.cs b/Editor/Mono/Scripting/ScriptCompilation/UnitySpecificCompilerMessageProcessor.cs index df61d10fb1..fc26fc9780 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/UnitySpecificCompilerMessageProcessor.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/UnitySpecificCompilerMessageProcessor.cs @@ -90,6 +90,10 @@ private static CustomScriptAssembly CustomScriptAssemblyFor(CompilerMessage m, E if (editorCompilation == null) return null; + // This can happen if the Parsing of the error message failed or we dont have a parser to this error type. + if (m.file == null) + return null; + var file = new NPath(m.file).MakeAbsolute(editorCompilation.projectDirectory); return editorCompilation diff --git a/Editor/Mono/Scripting/ScriptCompilation/Utility.cs b/Editor/Mono/Scripting/ScriptCompilation/Utility.cs index fe4522cd85..658f8fe039 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/Utility.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/Utility.cs @@ -141,6 +141,28 @@ public static string FileNameWithoutExtension(string path) return path.Substring(indexOfSlash, indexOfDot - indexOfSlash); } + public static bool IsPathsEqual(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + return false; + + for (var index = 0; index < left.Length; index++) + { + var leftCurrentChar = left[index]; + var rightCurrentChar = right[index]; + + if (IsPathSeparator(leftCurrentChar) && IsPathSeparator(rightCurrentChar)) + { + continue; + } + + if (leftCurrentChar != rightCurrentChar) + return false; + } + + return true; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsPathSeparator(char character) { diff --git a/Editor/Mono/ScriptingDefinesHelper.cs b/Editor/Mono/ScriptingDefinesHelper.cs new file mode 100644 index 0000000000..d7ebb6f29f --- /dev/null +++ b/Editor/Mono/ScriptingDefinesHelper.cs @@ -0,0 +1,52 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; + +namespace UnityEditor; + +internal static class ScriptingDefinesHelper +{ + private static readonly char[] DefineSeparators = { ';', ',', ' ' }; + + internal static string ConvertScriptingDefineArrayToString(string[] defines) + { + if (defines == null) + { + throw new ArgumentNullException(nameof(defines)); + } + + var flattenedDefines = new List(); + foreach (var define in defines) + { + flattenedDefines.AddRange(define.Split(DefineSeparators, StringSplitOptions.RemoveEmptyEntries)); + } + + var distinctDefines = new HashSet(); + foreach (var define in flattenedDefines) + { + distinctDefines.Add(define); + } + + return string.Join(";", distinctDefines); + } + + internal static string[] ConvertScriptingDefineStringToArray(string defines) + { + var splitDefines = string.IsNullOrEmpty(defines) + ? Array.Empty() + : defines.Split(DefineSeparators, StringSplitOptions.RemoveEmptyEntries); + + var distinctDefines = new HashSet(); + foreach (var define in splitDefines) + { + distinctDefines.Add(define); + } + + var distinctDefinesArray = new string[distinctDefines.Count]; + distinctDefines.CopyTo(distinctDefinesArray); + return distinctDefinesArray; + } +} diff --git a/Editor/Mono/Search/ObjectSelectorSearch.cs b/Editor/Mono/Search/ObjectSelectorSearch.cs index 1897045acb..9ea7fe817c 100644 --- a/Editor/Mono/Search/ObjectSelectorSearch.cs +++ b/Editor/Mono/Search/ObjectSelectorSearch.cs @@ -21,6 +21,13 @@ public enum VisibleObjects All = Assets | Scene } + [Flags] + public enum ObjectSelectorSearchEndSessionModes + { + None = 0, + CloseSelector = 1 + } + public partial class ObjectSelectorSearchContext : ISearchContext { public Guid guid { get; } = Guid.NewGuid(); @@ -31,6 +38,7 @@ public partial class ObjectSelectorSearchContext : ISearchContext public IEnumerable requiredTypeNames { get; set; } public VisibleObjects visibleObjects { get; set; } public IEnumerable allowedInstanceIds { get; set; } + public ObjectSelectorSearchEndSessionModes endSessionModes { get; set; } internal SearchFilter searchFilter { get; set; } } @@ -134,6 +142,23 @@ class ObjectSelectorSearchSessionHandler : SearchSessionHandler public ObjectSelectorSearchSessionHandler() : base(SearchEngineScope.ObjectSelector) {} + protected override void OnActiveEngineChanged(string newSearchEngineName) + { + CloseSelector(); + } + + public void CloseSelector() + { + if (context is ObjectSelectorSearchContext selectorContext) + { + // Context is set to null after EndSession, so no need to set the + // endSessionModes back to its original value. It is also valid that context + // is null when calling CloseSelector/EndSession if no session was started before. + selectorContext.endSessionModes |= ObjectSelectorSearchEndSessionModes.CloseSelector; + } + EndSession(); + } + public bool SelectObject(Action onObjectSelectorClosed, Action onObjectSelectedUpdated) { using (new SearchSessionOptionsApplicator(m_Api, m_Options)) diff --git a/Editor/Mono/Search/OpenSearchHelper.cs b/Editor/Mono/Search/OpenSearchHelper.cs index ed6ef2793e..c0e3c3a249 100644 --- a/Editor/Mono/Search/OpenSearchHelper.cs +++ b/Editor/Mono/Search/OpenSearchHelper.cs @@ -15,8 +15,9 @@ static class Styles public static GUIContent gotoSearch = EditorGUIUtility.TrIconContent("SearchJump Icon"); } - const string k_SearchAllShortcutName = "Main Menu/Edit/Search All..."; - const string k_OpenSearchInContextCommand = "OpenQuickSearchInContext"; + internal const string k_SearchMenuName = "Edit/Search/Search All..."; + internal const string k_SearchAllShortcutName = $"Main Menu/{k_SearchMenuName}"; + public const string k_OpenSearchInContextCommand = "OpenQuickSearchInContext"; static ShortcutBinding s_ShortcutBinding = ShortcutBinding.empty; public static ShortcutBinding shortcutBinding diff --git a/Editor/Mono/Search/SearchService.cs b/Editor/Mono/Search/SearchService.cs index d8fa52f3e3..6d2f58e5f9 100644 --- a/Editor/Mono/Search/SearchService.cs +++ b/Editor/Mono/Search/SearchService.cs @@ -133,7 +133,7 @@ void PullEngines() } } - void OnActiveEngineChanged(string newSearchEngineName) + protected virtual void OnActiveEngineChanged(string newSearchEngineName) { EndSession(); } @@ -222,17 +222,6 @@ public enum SyncSearchEvent static SearchService() { - Build.BuildDefines.getScriptCompilationDefinesDelegates += AddSearchServiceBuildDefines; - } - - private static void AddSearchServiceBuildDefines(BuildTarget target, HashSet defines) - { - defines.Add("USE_SEARCH_ENGINE_API"); - defines.Add("USE_SEARCH_TABLE"); - defines.Add("USE_SEARCH_MODULE"); - defines.Add("USE_PROPERTY_DATABASE"); - defines.Add("USE_QUERY_BUILDER"); - defines.Add("USE_SEARCH_EXTENSION_API"); } public static void NotifySyncSearchChanged(SyncSearchEvent evt, string syncViewId, string searchQuery) diff --git a/Editor/Mono/SearchUtility.cs b/Editor/Mono/SearchUtility.cs index a3308135d2..0cdc60f3cd 100644 --- a/Editor/Mono/SearchUtility.cs +++ b/Editor/Mono/SearchUtility.cs @@ -6,8 +6,6 @@ using System.Collections.Generic; using UnityEditor.AssetImporters; using Object = UnityEngine.Object; -using UnityEditor.Collaboration; -using UnityEditor.Connect; namespace UnityEditor { @@ -119,27 +117,6 @@ internal static bool CheckForKeyWords(string searchString, SearchFilter filter, parsed = true; } - // Support: 'v:versionState' syntax - index = searchString.IndexOf("v:"); - if (index >= 0) - { - string versionStateString = searchString.Substring(index + 2); - List tmp = new List(filter.versionControlStates); - tmp.Add(versionStateString); - filter.versionControlStates = tmp.ToArray(); - parsed = true; - } - - // Support: 's:softLockState' syntax - index = searchString.IndexOf("s:"); - if (index >= 0) - { - string softLockStateString = searchString.Substring(index + 2); - List tmp = new List(filter.softLockControlStates); - tmp.Add(softLockStateString); - filter.softLockControlStates = tmp.ToArray(); - parsed = true; - } // Support: 'a:area' syntax index = searchString.IndexOf("a:"); diff --git a/Editor/Mono/Selection.bindings.cs b/Editor/Mono/Selection.bindings.cs index d366b3244a..d3f7cd9f17 100644 --- a/Editor/Mono/Selection.bindings.cs +++ b/Editor/Mono/Selection.bindings.cs @@ -82,17 +82,17 @@ extern public static Object activeObject } [StaticAccessor("SelectionBindings", StaticAccessorType.DoubleColon)] - extern internal static void SetSelectionWithActiveObject(Object[] newSelection, Object activeObject); + extern internal static void SetSelectionWithActiveObject([Unmarshalled] Object[] newSelection, Object activeObject); [StaticAccessor("SelectionBindings", StaticAccessorType.DoubleColon)] [NativeThrows] - extern internal static void SetSelectionWithActiveInstanceID(int[] newSelection, int activeObject); + extern internal static void SetSelectionWithActiveInstanceID([Unmarshalled] int[] newSelection, int activeObject); [StaticAccessor("SelectionBindings", StaticAccessorType.DoubleColon)] - internal static extern void SetFullSelection(Object[] newSelection, Object activeObject, Object context, DataMode dataModeHint); + internal static extern void SetFullSelection([Unmarshalled] Object[] newSelection, Object activeObject, Object context, DataMode dataModeHint); [StaticAccessor("SelectionBindings", StaticAccessorType.DoubleColon), NativeThrows] - internal static extern void SetFullSelectionByID(int[] newSelection, int activeObjectInstanceID, int contextInstanceID, DataMode dataModeHint); + internal static extern void SetFullSelectionByID([Unmarshalled] int[] newSelection, int activeObjectInstanceID, int contextInstanceID, DataMode dataModeHint); [StaticAccessor("SelectionBindings", StaticAccessorType.DoubleColon)] internal static extern void UpdateSelectionMetaData(Object context, DataMode dataModeHint); @@ -122,11 +122,11 @@ internal extern static DataMode dataModeHint // The actual unfiltered selection from the Scene. [StaticAccessor("SelectionBindings", StaticAccessorType.DoubleColon)] - extern public static Object[] objects { get; set; } + extern public static Object[] objects { get; [param:Unmarshalled] set; } // The actual unfiltered selection from the Scene returned as instance ids instead of ::ref::objects. [StaticAccessor("SelectionBindings", StaticAccessorType.DoubleColon)] - extern public static int[] instanceIDs { get; [NativeThrows] set; } + extern public static int[] instanceIDs { get; [NativeThrows][param: Unmarshalled] set; } [StaticAccessor("GetSceneTracker()", StaticAccessorType.Dot)] [NativeMethod("IsSelected")] diff --git a/Editor/Mono/Selection.cs b/Editor/Mono/Selection.cs index ca52350c0f..953b971f4b 100644 --- a/Editor/Mono/Selection.cs +++ b/Editor/Mono/Selection.cs @@ -7,6 +7,7 @@ using System.Collections; using System.Collections.Generic; using JetBrains.Annotations; +using UnityEngine.Scripting; namespace UnityEditor { @@ -17,17 +18,24 @@ public sealed partial class Selection public static System.Action selectionChanged; private static DelegateWithPerformanceTracker m_SelectionChangedEvent = new DelegateWithPerformanceTracker($"{nameof(Selection)}.{nameof(selectionChanged)}"); internal static event System.Action selectedObjectWasDestroyed; + internal static event System.Action nonSelectedObjectWasDestroyed; [PublicAPI] // Used by packages with internal access. Not actually intended for users. internal static event System.Action postProcessSelectionMetadata; - [UsedImplicitly] + [UsedImplicitly, RequiredByNativeCode] private static void Internal_SelectedObjectWasDestroyed(int instanceID) { if (selectedObjectWasDestroyed != null) selectedObjectWasDestroyed(instanceID); } + [UsedImplicitly, RequiredByNativeCode] + private static void Internal_NonSelectedObjectWasDestroyed(int instanceID) + { + nonSelectedObjectWasDestroyed?.Invoke(instanceID); + } + [UsedImplicitly] private static void Internal_PostProcessSelectionMetadata() { diff --git a/Editor/Mono/SerializedObject.bindings.cs b/Editor/Mono/SerializedObject.bindings.cs index dfb176f481..98cfa8a5e6 100644 --- a/Editor/Mono/SerializedObject.bindings.cs +++ b/Editor/Mono/SerializedObject.bindings.cs @@ -85,6 +85,19 @@ public SerializedProperty FindProperty(string propertyPath) return null; } + // Find serialized property by name ignoring case. + internal SerializedProperty FindPropertyIgnoreCase(string propertyPath) + { + SerializedProperty i = GetIterator_Internal(); + // This is so the garbage collector won't clean up SerializedObject behind the scenes, + // when we are still iterating properties + i.m_SerializedObject = this; + if (i.FindPropertyIgnoreCaseInternal(propertyPath)) + return i; + else + return null; + } + /// /// Given a path of the form "managedReferences[refid].field" this finds the first field /// that references that reference id and returns a serialized property based on that path. @@ -208,6 +221,12 @@ internal extern InspectorMode inspectorMode set; } + internal extern DataMode inspectorDataMode + { + get; + set; + } + // Does the serialized object represents multiple objects due to multi-object editing? (RO) public extern bool isEditingMultipleObjects { diff --git a/Editor/Mono/SerializedProperty.bindings.cs b/Editor/Mono/SerializedProperty.bindings.cs index e8cadd434b..9fd79023f5 100644 --- a/Editor/Mono/SerializedProperty.bindings.cs +++ b/Editor/Mono/SerializedProperty.bindings.cs @@ -476,6 +476,9 @@ public void ClearArray() [NativeName("FindProperty")] extern internal bool FindPropertyInternal(string propertyPath); + [NativeName("FindPropertyIgnoreCase")] + extern internal bool FindPropertyIgnoreCaseInternal(string propertyPath); + [NativeName("FindFirstPropertyFromManagedReferencePath")] extern internal bool FindFirstPropertyFromManagedReferencePathInternal(string managedReferencePath); @@ -2000,12 +2003,22 @@ internal bool ValueEquals(string value) } internal bool unsafeMode {get; set; } - internal extern bool isValid + internal bool isValid { - [NativeMethod("IsValid")] - get; + get + { + // SerializedProperty should only be accessed while the SerializedObject that created them is still alive + // Without this check IsValidInternal will crash in that case + if (m_NativePropertyPtr == IntPtr.Zero || m_SerializedObject == null || m_SerializedObject.m_NativeObjectPtr == IntPtr.Zero) + return false; + + return IsValidInternal(); + } } + [NativeName("IsValid")] + extern private bool IsValidInternal(); + public uint contentHash { get diff --git a/Editor/Mono/SerializedProperty/SerializedPropertyTable.cs b/Editor/Mono/SerializedProperty/SerializedPropertyTable.cs index 667d05bee6..41f9da3574 100644 --- a/Editor/Mono/SerializedProperty/SerializedPropertyTable.cs +++ b/Editor/Mono/SerializedProperty/SerializedPropertyTable.cs @@ -123,13 +123,14 @@ public void OnGUI() r.y += m_FilterHeight; Rect tableRect = r; + // filter + m_TreeView.OnFilterGUI(filterRect); + // table Profiler.BeginSample("TreeView.OnGUI"); m_TreeView.OnGUI(tableRect); Profiler.EndSample(); - m_TreeView.OnFilterGUI(filterRect); - if (m_TreeView.IsFilteredDirty()) m_TreeView.Reload(); diff --git a/Editor/Mono/Settings.cs b/Editor/Mono/Settings.cs index 5dd4feb09f..e50899f978 100644 --- a/Editor/Mono/Settings.cs +++ b/Editor/Mono/Settings.cs @@ -179,6 +179,7 @@ internal class PrefSettings { static List m_AddedPrefs = new List(); static SortedList m_Prefs = new SortedList(); + public static Action settingChanged; static internal void Add(IPrefType value) { @@ -218,6 +219,7 @@ static internal void Set(string name, T value) EditorPrefs.SetString(name, value.ToUniqueString()); m_Prefs[name] = value; + settingChanged?.Invoke(name, typeof(T)); } static internal IEnumerable> Prefs() @@ -232,6 +234,16 @@ static internal IEnumerable> Prefs() } } + static internal void RevertAll() + { + foreach (KeyValuePair kvp in Prefs()) + { + kvp.Value.ResetToDefault(); + EditorPrefs.SetString(kvp.Value.Name, kvp.Value.ToUniqueString()); + settingChanged?.Invoke(kvp.Key, typeof(T)); + } + } + static void Load() { if (!m_AddedPrefs.Any()) diff --git a/Editor/Mono/Settings/SettingsProvider.cs b/Editor/Mono/Settings/SettingsProvider.cs index 8f9197e4b2..56e7c72fba 100644 --- a/Editor/Mono/Settings/SettingsProvider.cs +++ b/Editor/Mono/Settings/SettingsProvider.cs @@ -23,6 +23,7 @@ public class SettingsProvider private string m_Label; private string m_Name; private HashSet m_Keywords; + bool m_Activated; internal SettingsWindow settingsWindow { get; set; } internal string[] pathTokens { get; } @@ -136,6 +137,24 @@ internal virtual void FocusLost() { } + internal void Activate(string searchContext, VisualElement rootElement) + { + // If OnActivate fails, it should not be considered activated + if (!m_Activated) + OnActivate(searchContext, rootElement); + m_Activated = true; + } + + internal void Deactivate() + { + if (m_Activated) + { + // Set activated=false first, so even if OnDeactivate fails it will be considered deactivated. + m_Activated = false; + OnDeactivate(); + } + } + #region Helper public static IEnumerable GetSearchKeywordsFromGUIContentProperties() { diff --git a/Editor/Mono/Settings/SettingsTreeView.cs b/Editor/Mono/Settings/SettingsTreeView.cs index 3fb4f01bd8..5cee9bf00d 100644 --- a/Editor/Mono/Settings/SettingsTreeView.cs +++ b/Editor/Mono/Settings/SettingsTreeView.cs @@ -32,7 +32,7 @@ private static class Styles public SettingsProvider currentProvider { get; private set; } public string searchContext { get; set; } - public delegate void ProviderChangedHandler(SettingsProvider lastSelectedProvider, SettingsProvider newlySelectedProvider); + public delegate bool ProviderChangedHandler(SettingsProvider lastSelectedProvider, SettingsProvider newlySelectedProvider); public event ProviderChangedHandler currentProviderChanged; public SettingsTreeView(TreeViewState state, SettingsProvider[] providers) @@ -67,8 +67,8 @@ protected override bool CanMultiSelect(TreeViewItem item) protected override void SelectionChanged(IList selectedIds) { SettingsProvider selectedProvider = GetFirstValidProvider(selectedIds.Count > 0 ? selectedIds.First() : -1); - currentProviderChanged?.Invoke(currentProvider, selectedProvider); - currentProvider = selectedProvider; + if (currentProviderChanged?.Invoke(currentProvider, selectedProvider) ?? true) + currentProvider = selectedProvider; } protected SettingsProvider GetFirstValidProvider(int id) diff --git a/Editor/Mono/Settings/SettingsWindow.cs b/Editor/Mono/Settings/SettingsWindow.cs index 9f57d3ed71..ce5f40d96c 100644 --- a/Editor/Mono/Settings/SettingsWindow.cs +++ b/Editor/Mono/Settings/SettingsWindow.cs @@ -32,10 +32,29 @@ internal class SettingsWindow : EditorWindow, IHasCustomMenu private VisualElement m_TreeViewContainer; private VisualElement m_Toolbar; private bool m_ProviderChanging; + private bool m_ProviderReloadNeeded; private bool m_SearchFieldGiveFocus; const string k_SearchField = "SearchField"; + internal bool ProviderReloadNeeded => m_ProviderReloadNeeded; + + struct ProviderChangingScope : IDisposable + { + SettingsWindow m_Window; + + public ProviderChangingScope(SettingsWindow window) + { + m_Window = window; + window.m_ProviderChanging = true; + } + + public void Dispose() + { + m_Window.m_ProviderChanging = false; + } + } + private static class ImguiStyles { public static readonly GUIStyle header = "SettingsHeader"; @@ -116,7 +135,9 @@ internal void OnEnable() { titleContent.image = EditorGUIUtility.IconContent("Settings").image; - + // If this ever needs to be called from CreateGUI instead of OnEnable, + // make sure to change how we handle selection/activation of the + // initial provider in Init(). SetupUI(); } @@ -129,11 +150,7 @@ internal void OnDisable() EditorPrefs.SetFloat(GetPrefKeyName(nameof(m_Splitter)), flexGrow); } - if (m_TreeView != null && m_TreeView.currentProvider != null) - { - m_TreeView.currentProvider.OnDeactivate(); - EditorPrefs.SetString(GetPrefKeyName(titleContent.text + "_current_provider"), m_TreeView.currentProvider.settingsPath); - } + DeactivateAndSaveCurrentProvider(); SettingsService.settingsProviderChanged -= OnSettingsProviderChanged; SettingsService.repaintAllSettingsWindow -= OnRepaintAllWindows; @@ -141,12 +158,17 @@ internal void OnDisable() EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; } + internal void Update() + { + if (m_ProviderReloadNeeded) + ReloadProviders(); + } + internal void InitProviders() { if (m_Providers != null) return; Init(); - RestoreSelection(); SettingsService.settingsProviderChanged -= OnSettingsProviderChanged; SettingsService.settingsProviderChanged += OnSettingsProviderChanged; @@ -180,7 +202,7 @@ private void OnPlayModeStateChanged(PlayModeStateChange state) { if (m_TreeView.currentProvider != null) { - if (state == PlayModeStateChange.ExitingPlayMode) + if (state == PlayModeStateChange.ExitingEditMode) { ProviderChanged(m_TreeView.currentProvider, null); } @@ -208,11 +230,22 @@ private void PrintProviderKeywords() private void OnSettingsProviderChanged() { + // Prevent infinite changing + if (m_ProviderChanging) + return; + + m_ProviderReloadNeeded = true; + } + + void ReloadProviders() + { + // Prevent recursive changing if (m_ProviderChanging) return; + DeactivateAndSaveCurrentProvider(); Init(); - RestoreSelection(); Repaint(); + m_ProviderReloadNeeded = false; } private void RestoreSelection() @@ -243,27 +276,46 @@ private void Init() m_TreeViewState = m_TreeViewState ?? new TreeViewState(); m_TreeView = new SettingsTreeView(m_TreeViewState, m_Providers); m_TreeView.searchString = m_SearchText = m_SearchText ?? string.Empty; - RestoreSelection(); m_TreeView.currentProviderChanged += ProviderChanged; + // Restore selection after setting the ProviderChanged callback so we can activate the initial selected provider + RestoreSelection(); } - private void ProviderChanged(SettingsProvider lastSelectedProvider, SettingsProvider newlySelectedProvider) + private bool ProviderChanged(SettingsProvider lastSelectedProvider, SettingsProvider newlySelectedProvider) { if (m_SettingsPanel == null) - return; + return true; - m_ProviderChanging = true; - lastSelectedProvider?.OnDeactivate(); + using var pcd = new ProviderChangingScope(this); + // If we fail to deactivate the last provider, still continue to select the new one. + try + { + lastSelectedProvider?.Deactivate(); + } + catch (Exception e) + { + Debug.LogException(e); + } m_SettingsPanel.Clear(); if (newlySelectedProvider != null) { - newlySelectedProvider?.OnActivate(m_SearchText, m_SettingsPanel); - EditorPrefs.SetString(GetPrefKeyName(titleContent.text + "_current_provider"), newlySelectedProvider.settingsPath); + // If activating the new provider fails, restore the last selected provider. + try + { + newlySelectedProvider?.Activate(m_SearchText, m_SettingsPanel); + EditorPrefs.SetString(GetPrefKeyName(titleContent.text + "_current_provider"), newlySelectedProvider.settingsPath); + } + catch (Exception e) + { + Debug.LogException(e); + RestoreSelection(); + return false; + } } SetupIMGUIForCurrentProviderIfNeeded(); - m_ProviderChanging = false; + return true; } internal void SetupIMGUIForCurrentProviderIfNeeded() @@ -482,6 +534,23 @@ private void HandleSearchFiltering() m_TreeView.searchString = m_SearchText; } + void DeactivateAndSaveCurrentProvider() + { + if (m_TreeView != null && m_TreeView.currentProvider != null) + { + using var _ = new ProviderChangingScope(this); + try + { + m_TreeView.currentProvider.Deactivate(); + EditorPrefs.SetString(GetPrefKeyName(titleContent.text + "_current_provider"), m_TreeView.currentProvider.settingsPath); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + } + [MenuItem("Edit/Project Settings...", false, 259, false)] internal static void OpenProjectSettings() { diff --git a/Editor/Mono/SettingsWindow/GraphicsSettingsEditors.cs b/Editor/Mono/SettingsWindow/GraphicsSettingsEditors.cs index b0239880f8..c51697d854 100644 --- a/Editor/Mono/SettingsWindow/GraphicsSettingsEditors.cs +++ b/Editor/Mono/SettingsWindow/GraphicsSettingsEditors.cs @@ -296,6 +296,8 @@ internal partial class Styles public static readonly GUIContent shaderPreloadSave = EditorGUIUtility.TrTextContent("Save to asset...", "Save currently tracked shaders into a Shader Variant Manifest asset."); public static readonly GUIContent shaderPreloadClear = EditorGUIUtility.TrTextContent("Clear", "Clear currently tracked shader variant information."); + + public static readonly GUIContent cullingSettings = EditorGUIUtility.TrTextContent("Culling Settings"); } } } diff --git a/Editor/Mono/ShaderUtil.bindings.cs b/Editor/Mono/ShaderUtil.bindings.cs index 68b1d27174..3f87a69453 100644 --- a/Editor/Mono/ShaderUtil.bindings.cs +++ b/Editor/Mono/ShaderUtil.bindings.cs @@ -175,6 +175,7 @@ public static ShaderMessage[] GetShaderMessages(Shader s) extern internal static Rect rawScissorRect { get; set; } extern public static bool hardwareSupportsRectRenderTexture { get; } extern internal static bool hardwareSupportsFullNPOT { get; } + public static extern bool disableShaderOptimization { get; set; } extern internal static void RequestLoadRenderDoc(); extern internal static void RecreateGfxDevice(); @@ -203,6 +204,8 @@ public static void UpdateShaderAsset(Shader shader, string source, bool compileI UpdateShaderAsset(null, shader, source, compileInitialShaderVariants); } + extern public static ComputeShader CreateComputeShaderAsset(AssetImportContext context, string source); + [FreeFunction("GetShaderNameRegistry().AddShader")] extern public static void RegisterShader([NotNull("NullExceptionObject")] Shader shader); @@ -341,9 +344,9 @@ extern internal static ShaderData.PreprocessedVariant PreprocessShaderVariant([N [FreeFunction("ShaderUtil::GetPassKeywords")] extern private static LocalKeyword[] GetPassAllStageKeywords(Shader s, in PassIdentifier passIdentifier); [FreeFunction("ShaderUtil::GetPassKeywords")] extern private static LocalKeyword[] GetPassStageKeywords(Shader s, in PassIdentifier passIdentifier, ShaderType shaderType); [FreeFunction("ShaderUtil::GetPassKeywords")] extern private static LocalKeyword[] GetPassStageKeywordsForAPI(Shader s, in PassIdentifier passIdentifier, ShaderType shaderType, ShaderCompilerPlatform shaderCompilerPlatform); - [FreeFunction("ShaderUtil::PassHasKeyword")] extern private static bool PassAnyStageHasKeyword(Shader s, in PassIdentifier passIdentifier, in LocalKeyword keyword); - [FreeFunction("ShaderUtil::PassHasKeyword")] extern private static bool PassStageHasKeyword(Shader s, in PassIdentifier passIdentifier, in LocalKeyword keyword, ShaderType shaderType); - [FreeFunction("ShaderUtil::PassHasKeyword")] extern private static bool PassStageHasKeywordForAPI(Shader s, in PassIdentifier passIdentifier, in LocalKeyword keyword, ShaderType shaderType, ShaderCompilerPlatform shaderCompilerPlatform); + [FreeFunction("ShaderUtil::PassHasKeyword")] extern private static bool PassAnyStageHasKeyword(Shader s, in PassIdentifier passIdentifier, uint keywordIndex); + [FreeFunction("ShaderUtil::PassHasKeyword")] extern private static bool PassStageHasKeyword(Shader s, in PassIdentifier passIdentifier, uint keywordIndex, ShaderType shaderType); + [FreeFunction("ShaderUtil::PassHasKeyword")] extern private static bool PassStageHasKeywordForAPI(Shader s, in PassIdentifier passIdentifier, uint keywordIndex, ShaderType shaderType, ShaderCompilerPlatform shaderCompilerPlatform); public static LocalKeyword[] GetPassKeywords(Shader s, in PassIdentifier passIdentifier) { @@ -362,17 +365,17 @@ public static LocalKeyword[] GetPassKeywords(Shader s, in PassIdentifier passIde public static bool PassHasKeyword(Shader s, in PassIdentifier passIdentifier, in LocalKeyword keyword) { - return PassAnyStageHasKeyword(s, passIdentifier, keyword); + return PassAnyStageHasKeyword(s, passIdentifier, keyword.m_Index); } public static bool PassHasKeyword(Shader s, in PassIdentifier passIdentifier, in LocalKeyword keyword, ShaderType shaderType) { - return PassStageHasKeyword(s, passIdentifier, keyword, shaderType); + return PassStageHasKeyword(s, passIdentifier, keyword.m_Index, shaderType); } public static bool PassHasKeyword(Shader s, in PassIdentifier passIdentifier, in LocalKeyword keyword, ShaderType shaderType, ShaderCompilerPlatform shaderCompilerPlatform) { - return PassStageHasKeywordForAPI(s, passIdentifier, keyword, shaderType, shaderCompilerPlatform); + return PassStageHasKeywordForAPI(s, passIdentifier, keyword.m_Index, shaderType, shaderCompilerPlatform); } } } diff --git a/Editor/Mono/Shaders/ShaderKeywordFilterAttributes.cs b/Editor/Mono/Shaders/ShaderKeywordFilterAttributes.cs new file mode 100644 index 0000000000..1749a28d54 --- /dev/null +++ b/Editor/Mono/Shaders/ShaderKeywordFilterAttributes.cs @@ -0,0 +1,214 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.IO; +using System.Runtime.CompilerServices; +using UnityEngine; +using ATarget = System.AttributeTargets; + +namespace UnityEditor.ShaderKeywordFilter +{ + public enum FilterAction + { + Select, // Only selected keywords will remain in the tuple + Remove, // Remove specific keywords from the tuple + SelectOrRemove // Select if the condition is evaluated true, remove otherwise + }; + + /* FilterAttribute and the derived attributes below enable users to control multi_compile keywords based + * on settings data. The attribute marks a data field with condition and filter action. If the comparison + * between condition and field data passes, the attribute filter action is applied to the keywords + * passed in as attribute arguments. Any keyword tuple (i.e. keywords from a single multi_compile) that + * has one of these keywords is filtered using this action. Filter actions are evaluated in the order of + * appearance in the data type tree. Overriding previous actions only happens if explicitly requested + * by the attribute. + */ + [System.AttributeUsage(ATarget.Field, AllowMultiple = true)] + public class FilterAttribute : System.Attribute + { + public enum Precedence + { + Normal, // Any previous filter rule will take precedence + Override // This filter rule will be taken over any previous ones + }; + + public enum EvaluationMode + { + Normal, // This filter rule applies if condition value matches with the target field data + Negated // This filter rule applies if condition value does NOT match with the target field data + }; + + public FilterAttribute(FilterAction action, Precedence precedence, EvaluationMode evaluationMode, object condition, string fileName, int lineNumber, params string[] keywordNames) + { + m_Action = action; + m_Precedence = precedence; + m_EvaluationMode = evaluationMode; + m_Condition = condition; + m_Names = keywordNames; + m_FileName = fileName; + m_LineNumber = lineNumber; + + if (m_DoDebugLogging) + { + // TODO - Need to add a setting around the logging/analytics/stats reporting. + // Should have have levels or modes to allow the user to validate their rules. + Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, + "{0} attribute declared at {1}:{2} for keyword(s) '{3}' predicated on the value '{4}'", + GetFilterActionName(m_Action), m_FileName, m_LineNumber, String.Join(", ", m_Names), m_Condition); + } + } + + internal string GetFilterActionName(FilterAction action) + { + switch (action) + { + case FilterAction.Select: + return "Select"; + case FilterAction.Remove: + return "Remove"; + case FilterAction.SelectOrRemove: + return "SelectOrRemove"; + default: + return "Unknown"; + } + } + + internal void EnableLogging() + { + m_DoDebugLogging = true; + } + + internal void DisableLogging() + { + m_DoDebugLogging = false; + } + + internal string GetFormattedResolutionMessageActive(object data) + { + var s = String.Format("Applying {0} attribute declared at {1}:{2} because condition was '{3}'", + GetFilterActionName(m_Action), m_FileName, m_LineNumber, data); + + if (m_DoDebugLogging) + Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, s); + + return s; + } + + internal string GetFormattedResolutionMessageInactive(object data) + { + var s = String.Format("Skipping {0} attribute declared at {1}:{2} because condition was '{3}'", + GetFilterActionName(m_Action), m_FileName, m_LineNumber, data); + + if (m_DoDebugLogging) + Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, s); + + return s; + } + + internal string GetFormattedResolutionMessageOverride(object data, FilterRule originalRule) + { + var s = String.Format("Overriding {0} attribute for keyword '{1}' with {2} attribute declared at {3}:{4}", + GetFilterActionName(originalRule.action), originalRule.keywordName, GetFilterActionName(m_Action), + m_FileName, m_LineNumber); + + if (m_DoDebugLogging) + Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, s); + + return s; + } + + internal Precedence RulePrecedence + { + get => m_Precedence; + } + + internal string[] KeywordNames + { + get => m_Names; + } + + internal FilterAction Action + { + get => m_Action; + } + + // Based on input data evaluate the correct filter action (either Select or Remove). + // Returns false if the filter is inactive and therefore to be ignored. + internal bool GetActiveFilterAction(object data, out FilterAction action) + { + bool match = m_Condition.Equals(data); + + if (m_EvaluationMode == EvaluationMode.Negated) + match = !match; + + if (m_Action == FilterAction.SelectOrRemove) + { + action = match ? FilterAction.Select : FilterAction.Remove; + return true; // SelectOrRemove always returns an active filter action + } + + action = m_Action; + return match; + } + + static private bool m_DoDebugLogging = false; + private FilterAction m_Action; + private Precedence m_Precedence; + private EvaluationMode m_EvaluationMode; + private string[] m_Names; + private object m_Condition; + // These two record the location of this particular filter attribute declaration + private string m_FileName; + private int m_LineNumber; + } + + public class SelectIfAttribute : FilterAttribute + { + public SelectIfAttribute(object condition, bool overridePriority = false, [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, params string[] keywordNames) + : base(FilterAction.Select, overridePriority ? Precedence.Override : Precedence.Normal, EvaluationMode.Normal, condition, Path.GetFileName(filePath), lineNumber, keywordNames) + { + } + } + + public class RemoveIfAttribute : FilterAttribute + { + public RemoveIfAttribute(object condition, bool overridePriority = false, [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, params string[] keywordNames) + : base(FilterAction.Remove, overridePriority ? Precedence.Override : Precedence.Normal, EvaluationMode.Normal, condition, Path.GetFileName(filePath), lineNumber, keywordNames) + { + } + } + + public class SelectOrRemoveAttribute : FilterAttribute + { + public SelectOrRemoveAttribute(object condition, bool overridePriority = false, [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, params string[] keywordNames) + : base(FilterAction.SelectOrRemove, overridePriority ? Precedence.Override : Precedence.Normal, EvaluationMode.Normal, condition, Path.GetFileName(filePath), lineNumber, keywordNames) + { + } + } + + public class SelectIfNotAttribute : FilterAttribute + { + public SelectIfNotAttribute(object condition, bool overridePriority = false, [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, params string[] keywordNames) + : base(FilterAction.Select, overridePriority ? Precedence.Override : Precedence.Normal, EvaluationMode.Negated, condition, Path.GetFileName(filePath), lineNumber, keywordNames) + { + } + } + + public class RemoveIfNotAttribute : FilterAttribute + { + public RemoveIfNotAttribute(object condition, bool overridePriority = false, [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, params string[] keywordNames) + : base(FilterAction.Remove, overridePriority ? Precedence.Override : Precedence.Normal, EvaluationMode.Negated, condition, Path.GetFileName(filePath), lineNumber, keywordNames) + { + } + } + + public class RemoveOrSelectAttribute : FilterAttribute + { + public RemoveOrSelectAttribute(object condition, bool overridePriority = false, [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, params string[] keywordNames) + : base(FilterAction.SelectOrRemove, overridePriority ? Precedence.Override : Precedence.Normal, EvaluationMode.Negated, condition, Path.GetFileName(filePath), lineNumber, keywordNames) + { + } + } +} diff --git a/Editor/Mono/Shaders/ShaderKeywordFilterConstraintAttributes.cs b/Editor/Mono/Shaders/ShaderKeywordFilterConstraintAttributes.cs new file mode 100644 index 0000000000..8fc84596a6 --- /dev/null +++ b/Editor/Mono/Shaders/ShaderKeywordFilterConstraintAttributes.cs @@ -0,0 +1,114 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using UnityEngine.Rendering; +using ATarget = System.AttributeTargets; + +namespace UnityEditor.ShaderKeywordFilter +{ + /* These constraint attributes are used for limiting which parts of the settings data + structure can affect the variant filtering on specific conditions (tags, graphics API, etc)*/ + [System.AttributeUsage(ATarget.Class | ATarget.Struct | ATarget.Field, AllowMultiple = true)] + public class TagConstraintAttribute : System.Attribute + { + public TagConstraintAttribute(bool negate, params string[] tags) + { + m_Negate = negate; + m_Tags = tags; + } + + internal bool ShouldApplyRules(params string[] currentTags) + { + for (int i = 0, n = currentTags.Length - 1; i < n; i += 2) // Tags should be in name/value pairs. Ignore possible odd one. + { + for (int j = 0, m = m_Tags.Length - 1; j < m; j += 2) + { + if (currentTags[i].Equals(m_Tags[j]) && currentTags[i + 1].Equals(m_Tags[j + 1])) + return !m_Negate; + } + } + + return m_Negate; + } + + internal bool Negated + { + get => m_Negate; + } + + internal string[] Tags + { + get => m_Tags; + } + + bool m_Negate; + string[] m_Tags; + } + + public class ApplyRulesIfTagsEqualAttribute : TagConstraintAttribute + { + public ApplyRulesIfTagsEqualAttribute(params string[] tags) + : base(false, tags) + { + } + } + + public class ApplyRulesIfTagsNotEqualAttribute : TagConstraintAttribute + { + public ApplyRulesIfTagsNotEqualAttribute(params string[] tags) + : base(true, tags) + { + } + } + + [System.AttributeUsage(ATarget.Class | ATarget.Struct | ATarget.Field, AllowMultiple = false)] + public class GraphicsAPIConstraintAttribute : System.Attribute + { + public GraphicsAPIConstraintAttribute(bool negate, params GraphicsDeviceType[] graphicsDeviceTypes) + { + m_Negate = negate; + m_GraphicsDeviceTypes = graphicsDeviceTypes; + } + + internal bool ShouldApplyRules(GraphicsDeviceType currentGraphicDeviceType) + { + foreach (var device in m_GraphicsDeviceTypes) + { + if (device == currentGraphicDeviceType) + return !m_Negate; + } + + return m_Negate; + } + + internal bool Negated + { + get => m_Negate; + } + + internal GraphicsDeviceType[] GraphicsAPIs + { + get => m_GraphicsDeviceTypes; + } + + bool m_Negate; + GraphicsDeviceType[] m_GraphicsDeviceTypes; + } + + public class ApplyRulesIfGraphicsAPIAttribute : GraphicsAPIConstraintAttribute + { + public ApplyRulesIfGraphicsAPIAttribute(params GraphicsDeviceType[] graphicsDeviceTypes) + : base(false, graphicsDeviceTypes) + { + } + } + + public class ApplyRulesIfNotGraphicsAPIAttribute : GraphicsAPIConstraintAttribute + { + public ApplyRulesIfNotGraphicsAPIAttribute(params GraphicsDeviceType[] graphicsDeviceTypes) + : base(true, graphicsDeviceTypes) + { + } + } +} diff --git a/Editor/Mono/Shaders/ShaderKeywordFilterData.cs b/Editor/Mono/Shaders/ShaderKeywordFilterData.cs new file mode 100644 index 0000000000..aff8c49d87 --- /dev/null +++ b/Editor/Mono/Shaders/ShaderKeywordFilterData.cs @@ -0,0 +1,449 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.CompilerServices; +using UnityEngine; + +[assembly: InternalsVisibleTo("Assembly-CSharp-Editor-testable")] + +namespace UnityEditor.ShaderKeywordFilter +{ + // An internal constraint state. This is used for tracking which constraints affect which data field/attribute. + // Provides the functionality to check whether the constraint state of the current pass makes a filter rule active or inactive. + // Note: Don't mix up with struct ConstraintState which is the interop struct for getting the state from C++. + internal class Constraints + { + internal Constraints(Constraints other) + { + m_TagAttributes = new List(other.m_TagAttributes); + m_GraphicsAPIAttributes = new List(other.m_GraphicsAPIAttributes); + } + + internal Constraints(TagConstraintAttribute[] tagConstraints, GraphicsAPIConstraintAttribute[] graphicsAPIConstraints) + { + m_TagAttributes = new List(tagConstraints); + m_GraphicsAPIAttributes = new List(graphicsAPIConstraints); + } + + internal void AddParentConstraints(Constraints parent) + { + m_TagAttributes.InsertRange(0, parent.m_TagAttributes); + m_GraphicsAPIAttributes.InsertRange(0, parent.m_GraphicsAPIAttributes); + } + + internal List TagConstraints + { + get => m_TagAttributes; + } + + internal List GraphicsAPIConstraints + { + get => m_GraphicsAPIAttributes; + } + + internal bool ShouldApplyFilterRules(ConstraintState state) + { + if (state.graphicsAPIs != null) + { + bool gfxAPIMismatch = false; + foreach (var gfxAPIConstraint in m_GraphicsAPIAttributes) + { + foreach (var api in state.graphicsAPIs) + { + gfxAPIMismatch |= !gfxAPIConstraint.ShouldApplyRules(api); // can't filter if any of the APIs must be included (DX11/DX12 shared compiler platform) + } + } + if (gfxAPIMismatch) + return false; + } + + if (state.tags != null) + { + bool tagMismatch = false; + foreach (var tagConstraint in m_TagAttributes) + { + tagMismatch |= !tagConstraint.ShouldApplyRules(state.tags); + } + + if (tagMismatch) + return false; + } + + return true; + } + + List m_TagAttributes; + List m_GraphicsAPIAttributes; + } + + // Represents a single filter attribute in the settings tree structure. + // Tracks the field it's attached to and all the accumulated constraints that affect it. + internal struct FilterData + { + internal FilterData(string fieldName, FilterAttribute attribute, Constraints constraints, object value) + { + this.fieldName = fieldName; + this.attribute = attribute; + this.constraints = constraints; + this.value = value; + } + + internal string fieldName; + internal FilterAttribute attribute; + internal Constraints constraints; + internal object value; + } + + internal struct FilterStats + { + internal void CountActionUnresolved(FilterAction a) + { + switch (a) + { + case FilterAction.Select: + ++m_NumberOfSelectKeywords; + break; + case FilterAction.Remove: + ++m_NumberOfRemoveKeywords; + break; + case FilterAction.SelectOrRemove: + ++m_NumberOfSelectKeywords; + ++m_NumberOfRemoveKeywords; + break; + } + + // Always count against the total number of filter rules + ++m_NumberOfRules; + } + + internal void CountActionResolved(FilterAction a) + { + switch (a) + { + case FilterAction.Select: + ++m_NumberOfSelectKeywordsResolved; + break; + case FilterAction.Remove: + ++m_NumberOfRemoveKeywordsResolved; + break; + case FilterAction.SelectOrRemove: + // Resolved actions cannot be both + break; + } + } + + internal UInt32 m_NumberOfRules; // Total number of filter rules for a settings node + internal UInt32 m_NumberOfRemoveKeywords; // Total number of remove actions (includes select or remove) + internal UInt32 m_NumberOfSelectKeywords; // Total number of select actions (includes select or remove) + internal UInt32 m_NumberOfRemoveKeywordsResolved; // Number of remove actions based on current setting values + internal UInt32 m_NumberOfSelectKeywordsResolved; // Number of select actions based on current setting values + } + + // A single instance of keyword name/FilterAction pair. Used as the output format when flattening the settings tree + // to an array form with GetVariantArray(). + internal struct FilterRule + { + internal FilterRule(string keywordName, bool withEmptyKeyword, FilterAction action, string resolutionMessage) + { + this.keywordName = keywordName; + this.withEmptyKeyword = withEmptyKeyword; + this.action = action; + this.resolutionMessage = resolutionMessage; + } + + internal string keywordName; + internal bool withEmptyKeyword; + internal FilterAction action; + internal string resolutionMessage; + } + + // SettingsNode represents a node in the settings tree, containing filter data and child nodes. + // Static method GatherFilterData() is used for constructing SettingsNode tree from a type tree. + internal class SettingsNode + { + internal SettingsNode(string name) + { + m_Name = name; + m_Children = new List(); + m_FilterData = new List(); + } + + private static Constraints GetConstraintAttributes(ICustomAttributeProvider ap) + { + var tagConstraints = ap.GetCustomAttributes(typeof(TagConstraintAttribute), false); + var graphicsAPIConstraints = ap.GetCustomAttributes(typeof(GraphicsAPIConstraintAttribute), false); + if (tagConstraints.Length > 0 || graphicsAPIConstraints.Length > 0) + { + return new Constraints((TagConstraintAttribute[])tagConstraints, (GraphicsAPIConstraintAttribute[])graphicsAPIConstraints); + } + + return null; + } + + // Traverses through the type tree of the given object to find filter attributes and creates + // settings tree structure out of it. + // Returns null if no filter attributes were found. + // This method is called recursively for the tree traversing purposes. + internal static SettingsNode GatherFilterData(string nodeName, object containerObject, HashSet visited, Constraints parentConstraints = null) + { + SettingsNode node = null; // defer construction to when filter data is actually found + if (containerObject == null) + return node; + + // Unity objects have their own validity checking + if (containerObject is UnityEngine.Object) + { + var unityObject = containerObject as UnityEngine.Object; + if (!unityObject) + return node; + } + + if (!visited.Add(containerObject)) + return node; + + var containerConstraints = GetConstraintAttributes(containerObject.GetType()); + if (parentConstraints != null) + { + // Merge constraints with the parent + if (containerConstraints == null) + containerConstraints = new Constraints(parentConstraints); + else + containerConstraints.AddParentConstraints(parentConstraints); + } + + // Reflect all fields from the container's type. We must manually traverse the type's inheritance chain + // since, otherwise, there's no way to expose private fields belonging to the type's base class. + const BindingFlags kFieldReflectionFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance + | BindingFlags.Static | BindingFlags.DeclaredOnly; + var fields = new List(); + var containerType = containerObject.GetType(); + while (containerType != null) + { + var declaredFields = containerType.GetFields(kFieldReflectionFlags); + fields.AddRange(declaredFields); + containerType = containerType.BaseType; + } + + // Go through all fields that could potentially contain filter attributes (directly or through children) + foreach (var f in fields) + { + bool isConst = f.IsLiteral && !f.IsInitOnly; + + // Only public fields, constants and private fields with [SerializeField] attribute are accepted sources + if (!f.IsPublic && !isConst && f.GetCustomAttributes(typeof(SerializeField), false).Length == 0) + continue; + + // Drop deprecated fields + if (f.GetCustomAttributes(typeof(System.ObsoleteAttribute), false).Length != 0) + continue; + + var value = f.GetValue(containerObject); + if (value == null) + continue; + + Type type = value.GetType(); + + // Enumerable (array/list) is a potential branch in the settings tree + if (value is IEnumerable) + { + var enumerable = value as IEnumerable; + + // UUM-72309 - A crash occured due to a third party package throwing when accessing its enumerator. + // This try/catch should prevent such issues and log an error. + try + { + bool hasDifferentChildren = (node != null) && (node.m_Children.Count > 0); + foreach (var e in enumerable) + { + var childVisited = new HashSet(visited); + SettingsNode newBranch = GatherFilterData(f.Name, e, childVisited, containerConstraints); + if (newBranch != null) + { + if (node == null) + node = new SettingsNode(nodeName); + + if (hasDifferentChildren) + { + Debug.LogError("ShaderKeywordFilter attributes cannot be placed on a settings tree with multiple different branches on the same level."); + } + else + { + node.m_Children.Add(newBranch); + } + } + } + } + catch(Exception ex) + { + Debug.LogError($"An error occured while processing ShaderKeywordFilter attributes; Reading node: {nodeName} and field: {f.Name}\n {ex.Message}"); + } + } + else if (!type.IsValueType || (type.IsValueType && !type.IsPrimitive && !type.IsEnum)) // class or struct + { + var childVisited = new HashSet(visited); + var nestedNode = GatherFilterData(f.Name, value, childVisited, containerConstraints); + if (nestedNode != null) + { + if (node == null) + node = new SettingsNode(nodeName); + + node.m_FilterData.AddRange(nestedNode.m_FilterData); + foreach (var fd in nestedNode.m_FilterData) + { + node.m_FilterStats.CountActionUnresolved(fd.attribute.Action); + } + + if (node.m_Children.Count > 0 && nestedNode.m_Children.Count > 0) + { + Debug.LogError("ShaderKeywordFilter attributes cannot be placed on a settings tree with multiple different branches on the same level."); + } + else + { + node.m_Children.AddRange(nestedNode.m_Children); + } + } + } + else // plain data fields + { + var constraints = GetConstraintAttributes(f); + if (containerConstraints != null) + { + // Merge constraints with parent. Each field sees all the constraints from the root up to it. + if (constraints != null) + constraints.AddParentConstraints(containerConstraints); + else + constraints = new Constraints(containerConstraints); + } + + var attributes = f.GetCustomAttributes(typeof(FilterAttribute), false); + foreach (var a in attributes) + { + if (a is FilterAttribute) + { + if (node == null) + node = new SettingsNode(nodeName); + + var fa = a as FilterAttribute; + node.m_FilterData.Add(new FilterData(f.Name, fa, constraints, value)); + node.m_FilterStats.CountActionUnresolved(fa.Action); + } + } + } + } + + return node; + } + + // Recursively traversing through the settings tree, resolving the rules on its way from + // the root to the leaves. When a leaf is encountered, a new SettingsVariant is added to the list. + // The filter rule resolving happens from root to leaf with this logic: + // * Whatever rule for a specific keyword is encountered first takes precedence by default. + // * Later encountered rules are ignored, unless the attribute explicitly asks for override. + // In that case this override rule will take precedence. + internal void GetVariantArray(ConstraintState constraintState, List variants, List parentRules = null) + { + // Grab a copy of the rules resolved so far. We don't want to modify the parent data + // as siblings of this node could resolve things differently. + var currentRules = parentRules != null ? new List(parentRules) : new List(); + foreach(var filter in m_FilterData) + { + // Skip the filter if constraint rules require so + if (filter.constraints != null) + { + if(!filter.constraints.ShouldApplyFilterRules(constraintState)) + continue; + } + + FilterAction filterAction; + bool isActive = filter.attribute.GetActiveFilterAction(filter.value, out filterAction); + + // Early out for inactive filter attributes. + if (!isActive) + { + filter.attribute.GetFormattedResolutionMessageInactive(filter.value); + continue; + } + + bool overrideRule = filter.attribute.RulePrecedence == FilterAttribute.Precedence.Override; + bool addRule = true; + bool affectsEmptyKw = false; + + // check if the attribute affects "empty keyword" case + foreach (var keywordName in filter.attribute.KeywordNames) + { + if (keywordName.Length == 0) + affectsEmptyKw = true; + } + + foreach (var keywordName in filter.attribute.KeywordNames) + { + if (keywordName.Length == 0) + continue; + + for (int i = 0; i < currentRules.Count; ++i) + { + if (currentRules[i].keywordName.Equals(keywordName)) + { + if (overrideRule) + { + var resolutionMessage = filter.attribute.GetFormattedResolutionMessageOverride(filter.value, currentRules[i]); + currentRules[i] = new FilterRule(keywordName, affectsEmptyKw, filterAction, resolutionMessage); + } + + addRule = false; + break; + } + } + if (addRule) + { + var resolutionMessage = filter.attribute.GetFormattedResolutionMessageActive(filter.value); + currentRules.Add(new FilterRule(keywordName, affectsEmptyKw, filterAction, resolutionMessage)); + m_FilterStats.CountActionResolved(filterAction); + } + } + } + + if (m_Children.Count > 0) + { + foreach (var child in m_Children) + { + child.GetVariantArray(constraintState, variants, currentRules); + } + } + else + { + variants.Add(new SettingsVariant(currentRules)); + } + } + + internal string Name + { + get => m_Name; + } + + internal List Children + { + get => m_Children; + } + + internal List FilterData + { + get => m_FilterData; + } + + internal FilterStats FilterStats + { + get => m_FilterStats; + } + + string m_Name; + List m_Children; + List m_FilterData; + FilterStats m_FilterStats; + } +} diff --git a/Editor/Mono/Shaders/ShaderKeywordFilterUtil.cs b/Editor/Mono/Shaders/ShaderKeywordFilterUtil.cs new file mode 100644 index 0000000000..3e923fe666 --- /dev/null +++ b/Editor/Mono/Shaders/ShaderKeywordFilterUtil.cs @@ -0,0 +1,153 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using UnityEngine; +using UnityEngine.Bindings; +using UnityEngine.Rendering; +using UnityEngine.Scripting; + +[assembly: InternalsVisibleTo("Assembly-CSharp-Editor-testable")] + +namespace UnityEditor.ShaderKeywordFilter +{ + // SettingsVariant represents a leaf node in the settings tree. + // The keyword arrays there tell which keywords need to be selected and which removed. + // This data is used in the native code to prune the enumeration data, thus reducing + // the size of enumerated variant space. + [RequiredByNativeCode (GenerateProxy = true)] + [StructLayout(LayoutKind.Sequential)] + internal struct SettingsVariant + { + internal SettingsVariant(List rules) + { + var selected = new List(); + var selectedMessages = new List(); + var emptyKwSelectors = new List(); + var removed = new List(); + var removedMessages = new List(); + var emptyKwRemovers = new List(); + + foreach (var rule in rules) + { + if (rule.action == FilterAction.Select) + { + selected.Add(rule.keywordName); + if(rule.withEmptyKeyword) + emptyKwSelectors.Add(selected.Count - 1); + + selectedMessages.Add(rule.resolutionMessage); + } + else if (rule.action == FilterAction.Remove) + { + removed.Add(rule.keywordName); + if (rule.withEmptyKeyword) + emptyKwRemovers.Add(removed.Count - 1); + + removedMessages.Add(rule.resolutionMessage); + } + } + + selectKeywords = selected.ToArray(); + removeKeywords = removed.ToArray(); + emptyKeywordSelectingKeywords = emptyKwSelectors.ToArray(); + emptyKeywordRemovingKeywords = emptyKwRemovers.ToArray(); + selectReasons = selectedMessages.ToArray(); + removeReasons = removedMessages.ToArray(); + } + + readonly internal string[] selectKeywords; // Keywords that have resolved to be selected in the build by a filter rule + readonly internal string[] selectReasons; // Human readable message on why the keyword was selected + readonly internal int[] emptyKeywordSelectingKeywords; // Indices of keywords that will also retain "empty keyword" case + + readonly internal string[] removeKeywords; // Keywords that have resolved to be removed from the build by a filter rule + readonly internal string[] removeReasons; // Human readable message on why the keyword was removed + readonly internal int[] emptyKeywordRemovingKeywords; // Indices of keywords that will also remove "empty keyword" case + } + + // This struct is for passing the constraint state of the currently compiled shader pass from C++ to C#. + // It is used for comparing against the filter attribute constraints and this way + // we can select only the filter rules that are meeting the constraint requirements. + [RequiredByNativeCode (GenerateProxy = true)] + internal struct ConstraintState + { + internal string[] tags; + internal GraphicsDeviceType[] graphicsAPIs; + } + + // This is the main class for shader keyword filtering C++/C# interop + [RequiredByNativeCode] + internal static class ShaderKeywordFilterUtil + { + internal struct CachedFilterData + { + public Hash128 dependencyHash; + public SettingsNode settingsNode; + }; + + // In memory cache for filter data per renderpipeline asset. + // This is to avoid redundant attribute search for each shader/pass/stage. + internal static Dictionary PerAssetFilterDataCache = new Dictionary(); + + internal static SettingsNode GetFilterDataCached(string nodeName, UnityEngine.Object containerObject) + { + string assetPath = AssetDatabase.GetAssetPath(containerObject); + Hash128 dependencyHash = AssetDatabase.GetAssetDependencyHash(assetPath); + + CachedFilterData cachedData; + if (PerAssetFilterDataCache.TryGetValue(assetPath, out cachedData)) + { + // Cached data is valid only if dependency hash hasn't changed + if (cachedData.dependencyHash == dependencyHash) + return cachedData.settingsNode; + } + + // No valid data found in the cache so we need to do the full processing + // and then enter the result into the cache. + var visited = new HashSet(); + cachedData.dependencyHash = dependencyHash; + cachedData.settingsNode = SettingsNode.GatherFilterData(nodeName, containerObject, visited); + + PerAssetFilterDataCache[assetPath] = cachedData; + + return cachedData.settingsNode; + } + + // For the current build target with given constraint state, gets the list of active filter rule sets. + [RequiredByNativeCode] + internal static SettingsVariant[] GetKeywordFilterVariants(string buildTargetGroupName, ConstraintState constraintState) + { + var rpAssets = new List(); + QualitySettings.GetAllRenderPipelineAssetsForPlatform(buildTargetGroupName, ref rpAssets); + + // Gather the settings/attribute tree from the renderpipe assets + SettingsNode root = new SettingsNode("root"); + foreach(var rpAsset in rpAssets) + { + if (rpAsset == null) + continue; + + var node = GetFilterDataCached(rpAsset.name, rpAsset); + if (node != null) + root.Children.Add(node); + } + + // Extract the filter rules for each leaf node in the settings tree and return that in an array form. + var variants = new List(); + root.GetVariantArray(constraintState, variants); + + // We need to return at least a blank settings variant, even if there were no rules + if (variants.Count == 0) + { + variants.Add(new SettingsVariant(new List())); + } + + return variants.ToArray(); + } + } +} diff --git a/Editor/Mono/TagManager.bindings.cs b/Editor/Mono/TagManager.bindings.cs index d302d23b68..fe8e838b11 100644 --- a/Editor/Mono/TagManager.bindings.cs +++ b/Editor/Mono/TagManager.bindings.cs @@ -39,8 +39,20 @@ private TagManager() {} [NativeMethod] internal extern void SetSortingLayerName(int index, string name); - [NativeMethod("StringToTagAddIfUnavailable")] - internal extern int AddTag(string tag); + [NativeMethod("StringToTagAddIfUnavailable")] + extern int StringToTagAddIfUnavailable(string tag); + + /* + * After adding a tag, set the TagManager as dirty. We may not call "SetDirty" in "StringToTagAddIfUnavailable" itself because + * it may be called off of the main thread. + */ + internal int AddTag(string tag) + { + int result = StringToTagAddIfUnavailable(tag); + EditorUtility.SetDirty(this); + + return result; + } [NativeMethod] internal extern void RemoveTag(string tag); diff --git a/Editor/Mono/TooltipView/TooltipView.cs b/Editor/Mono/TooltipView/TooltipView.cs index 685175b210..d59b488c92 100644 --- a/Editor/Mono/TooltipView/TooltipView.cs +++ b/Editor/Mono/TooltipView/TooltipView.cs @@ -191,7 +191,7 @@ Vector2 Size // If when fitted to screen, the tooltip would overlap the hover area // (and thus potentially mouse) -- for example when the control is near // the bottom of screen, place it atop of the hover area instead. - var fittedToScreen = ContainerWindow.FitRectToScreen(popupPosition, true, true); + var fittedToScreen = ContainerWindow.FitRectToMouseScreen(popupPosition, true, null); if (fittedToScreen.Overlaps(m_hoverRect)) { popupPosition.y = m_hoverRect.y - m_optimalSize.y - 10.0f; diff --git a/Editor/Mono/Tutorial/Highlighter.bindings.cs b/Editor/Mono/Tutorial/Highlighter.bindings.cs index ccc814aa72..421b68fddd 100644 --- a/Editor/Mono/Tutorial/Highlighter.bindings.cs +++ b/Editor/Mono/Tutorial/Highlighter.bindings.cs @@ -31,5 +31,14 @@ public static extern string activeText [NativeProperty("s_ActiveVisible", false, TargetType.Field)] public static extern bool activeVisible { get; private set; } + + [NativeProperty("s_UseUIToolkitScrolling", false, TargetType.Field)] + internal static extern bool useUIToolkitScrolling { get; private set; } + + [NativeProperty("k_Padding", false, TargetType.Field)] + internal static extern float padding { get; } + + [NativeProperty("k_ScrollSpeed", false, TargetType.Field)] + internal static extern float scrollSpeed { get; } } } diff --git a/Editor/Mono/Tutorial/Highlighter.cs b/Editor/Mono/Tutorial/Highlighter.cs index 6837c11f06..b1a8012c09 100644 --- a/Editor/Mono/Tutorial/Highlighter.cs +++ b/Editor/Mono/Tutorial/Highlighter.cs @@ -4,6 +4,7 @@ using UnityEngine; using UnityEditorInternal; +using UnityEngine.UIElements; namespace UnityEditor { @@ -28,9 +29,15 @@ public partial class Highlighter private const float kPulseSpeed = 0.45f; // Pulses per second private const float kPopupDuration = 0.33f; // How long time in seconds the popup takes private const int kExpansionMovementSize = 5; // How many pixels the rect expands out in the pulsing movement + // Twice the IMGUI speed because UIToolkit ScrollView scroll offset has a frame delay between updates + private static readonly float kUIToolkitScrollSpeed = scrollSpeed * 2; private static bool s_RecursionLock = false; + private static VisualElement activeElement = null; + private static ScrollView activeScrollView = null; + private static bool activeIsImgui = false; + private static GUIStyle s_HighlightStyle; private static GUIStyle highlightStyle { @@ -49,8 +56,13 @@ public static void Stop() active = false; activeVisible = false; activeText = string.Empty; + activeElement = null; + activeIsImgui = false; + activeScrollView = null; + useUIToolkitScrolling = true; activeRect = new Rect(); + searchMode = HighlightSearchMode.None; s_LastTime = 0; s_HighlightElapsedTime = 0; } @@ -140,7 +152,8 @@ private static void Update() // If view's actualView has changed, we might still find a property with the right name in the other view, // but it's not the one we're looking for. - if (activeRect.width == 0 || !ViewWindowIsActive()) + var elementHidden = activeElement != null && (!activeElement.isHierarchyDisplayed || activeElement.panel == null); + if (activeRect.width == 0 || !ViewWindowIsActive() || elementHidden) { EditorApplication.update -= Update; if (s_View != null && s_View.windowBackend is IEditorWindowBackend ewb) @@ -156,6 +169,9 @@ private static void Update() Search(); } + if (useUIToolkitScrolling) + HandleScroll(); + // Keep elapsed time explicitly rather than measuring time since highlight began. // This way all views use the same elapsed time even if some realtime elapsed // between redraws of the different views. @@ -233,9 +249,45 @@ private static bool ViewWindowIsActive() private static bool Search() { searchMode = s_SearchMode; + + if (isUIToolkitWindow && !activeIsImgui) + { + var found = activeElement != null; + if (!found) + found = SearchVisualElement(s_ViewWindow.rootVisualElement); + + if (found) + { + var windowPos = s_ViewWindow.position.position; + var pos = activeElement.worldBound.position + windowPos; + activeRect = new Rect(pos.x, pos.y, activeElement.worldBound.width, + activeElement.worldBound.height); + searchMode = HighlightSearchMode.None; + return true; + } + + activeIsImgui = true; + } + + // Try IMGUI s_View.RepaintImmediately(); if (searchMode == HighlightSearchMode.None) - return true; // Success - control was found + { + if (activeIsImgui && !useUIToolkitScrolling) + return true; // Success - control was found in window that doesn't use UIToolkit ScrollView + + if (activeElement != null) + return true; // Already found the IMGUIContainer + + var windowPos = s_ViewWindow.position.position; + var r = activeRect; + r.x -= windowPos.x; + r.y -= windowPos.y; + + activeElement = SearchIMGUIContainer(s_ViewWindow.rootVisualElement, r.center); + useUIToolkitScrolling = activeScrollView != null; + return true; + } s_SearchMode = HighlightSearchMode.None; Stop(); @@ -293,6 +345,166 @@ private static void ControlHighlightGUI() GUI.matrix = oldMatrix; } + private static void HandleScroll() + { + if (activeScrollView == null || s_ViewWindow == null) + return; + + // activeRect is in screen space convert back to window space + var windowPos = s_ViewWindow.position.position; + var r = activeRect; + r.x -= windowPos.x; + r.y -= windowPos.y; + + if (!activeVisible) + { + var paddedRect = new Rect(r.x - padding, r.y - padding, + r.width + padding * 2, r.height + padding * 2); + + activeVisible = !ScrollTowardsRect(activeScrollView, paddedRect, kUIToolkitScrollSpeed); + } + else + { + // If the highlight rect (without padding) was already visible but is no longer, stop the highlight. + if (ScrollTowardsRect(activeScrollView, r, 0f)) + activeRect = Rect.zero; + } + } + + private static bool ScrollTowardsRect(ScrollView sv, Rect pos, float maxDelta) + { + var scrollVector = CalculateScrollVector(sv, pos); + + // If we don't need scrolling, return false + if (scrollVector.sqrMagnitude < 0.0001f) + return false; + + // If we need scrolling but don't actually allow any, just return true to + // indicate scrolling is needed to be able to see pos + if (Mathf.Approximately(maxDelta, 0)) + return true; + + if (scrollVector.magnitude > maxDelta) + scrollVector = scrollVector.normalized * maxDelta; + + sv.scrollOffset += scrollVector; + return true; + } + + private static Vector2 CalculateScrollVector(ScrollView sv, Rect pos) + { + var scrollVector = Vector2.zero; + var viewport = sv.contentViewport.worldBound; + + // If the rect we want to see is larger than the visible rect, then trim it, + // otherwise we can get oscillation or other unwanted behavior + float excess = pos.width - viewport.width; + if (excess > 0) + { + pos.width -= excess; + pos.x += excess * 0.5f; + } + excess = pos.height - viewport.height; + if (excess > 0) + { + pos.height -= excess; + pos.y += excess * 0.5f; + } + + // Calculate needed x scrolling + if (sv.scrollableWidth > 0) + { + if (pos.xMax > viewport.xMax) + scrollVector.x += pos.xMax - viewport.xMax; + else if (pos.xMin < viewport.xMin) + scrollVector.x -= viewport.xMin - pos.xMin; + } + + // Calculate needed y scrolling + if (sv.scrollableHeight > 0) + { + if (pos.yMax > viewport.yMax) + scrollVector.y += pos.yMax - viewport.yMax; + else if (pos.yMin < viewport.yMin) + scrollVector.y -= viewport.yMin - pos.yMin; + } + + return scrollVector; + } + + private static bool SearchVisualElement(VisualElement ve) + { + ScrollView currentScrollView = null; + for (var i = 0; i < ve.childCount; i++) + { + var child = ve[i]; + if (child.resolvedStyle.display == DisplayStyle.None) + continue; + + switch (child) + { + case Foldout foldout when !foldout.value: + continue; // Do not search inside collapsed foldout + case IPrefixLabel prefixLabel when prefixLabel.label == activeText && (searchMode == HighlightSearchMode.Auto || searchMode == HighlightSearchMode.PrefixLabel): + activeElement = child; + return true; + case BindableElement bindableElement when bindableElement.bindingPath == activeText && IsSearchingForIdentifier(): + activeElement = child; + return true; + case TextElement textElement when textElement.text == activeText && (searchMode == HighlightSearchMode.Auto || searchMode == HighlightSearchMode.Content): + activeElement = child; + return true; + case ScrollView sv when activeScrollView == null: + activeScrollView = currentScrollView = sv; + break; + case ScrollView sv: + // Skip searching inside nested ScrollView + continue; + } + + if (SearchVisualElement(child)) + return true; + + // Element not part of this ScrollView, make sure to reset the activeScrollView or else other + // ScrollView will be considered as nested + if (currentScrollView != null) + activeScrollView = null; + } + + return false; + } + + private static IMGUIContainer SearchIMGUIContainer(VisualElement root, Vector2 pos) + { + ScrollView currentScrollView = null; + for (var i = 0; i < root.childCount; i++) + { + var child = root[i]; + switch (child) + { + case IMGUIContainer imguiContainer when imguiContainer.worldBound.Contains(pos): + return imguiContainer; + case ScrollView sv when activeScrollView == null: + activeScrollView = currentScrollView = sv; + break; + case ScrollView sv: + // Skip searching inside nested ScrollView + continue; + } + + var c = SearchIMGUIContainer(child, pos); + if (c != null) + return c; + + // Element not part of this ScrollView, make sure to reset the activeScrollView or else other + // ScrollView will be considered as nested + if (currentScrollView != null) + activeScrollView = null; + } + return null; + } + internal static bool searching => searchMode != HighlightSearchMode.None; + internal static bool isUIToolkitWindow => s_ViewWindow != null && s_ViewWindow.isUIToolkitWindow; } } diff --git a/Editor/Mono/TypeCache.bindings.cs b/Editor/Mono/TypeCache.bindings.cs index 17c96e0e53..934b68d76a 100644 --- a/Editor/Mono/TypeCache.bindings.cs +++ b/Editor/Mono/TypeCache.bindings.cs @@ -3,702 +3,44 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; using System.Reflection; -using System.Runtime.InteropServices; using UnityEngine.Bindings; namespace UnityEditor { - [NativeHeader("Runtime/Mono/TypeCache.h")] + [NativeHeader("Runtime/Scripting/TypeCache.h")] public static partial class TypeCache { - [StructLayout(LayoutKind.Sequential)] - [DebuggerDisplay("Count = {" + nameof(count) + "}")] - [DebuggerTypeProxy(typeof(DebugView))] - public struct TypeCollection : IList, IList - { - [NonSerialized] - readonly IntPtr ptr; - readonly int count; - - internal TypeCollection(IntPtr p, int s) { ptr = p; count = s; } - - public int Count => count; - - public bool IsReadOnly => true; - - public bool IsFixedSize => true; - - public bool IsSynchronized => true; - - object ICollection.SyncRoot => null; - - public Type this[int index] - { - get - { - if (index >= 0 && index < count) - return GetValue(ptr, index); - throw new IndexOutOfRangeException($"Index {index} is out of range of '{count}' Count."); - } - set - { - ThrowNotSupported(); - } - } - - public bool Contains(Type item) => IndexOf(item) != -1; - - public bool Contains(object item) => IndexOf(item) != -1; - - public Enumerator GetEnumerator() => new Enumerator(ref this); - - public void CopyTo(Type[] array, int arrayIndex) - { - if (array == null) - throw new ArgumentNullException(nameof(array)); - if (arrayIndex < 0) - throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - if (arrayIndex + count > array.Length) - throw new ArgumentOutOfRangeException("arrayIndex"); - - Internal_CopyTo(array, arrayIndex); - } - - public void CopyTo(Array array, int arrayIndex) - { - if (array == null) - throw new ArgumentNullException(nameof(array)); - if (array.Rank != 1) - throw new ArgumentException(nameof(array)); - if (arrayIndex < 0) - throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - if (arrayIndex + count > array.Length) - throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - var typedArray = array as Type[]; - if (typedArray == null) - throw new ArrayTypeMismatchException(nameof(array)); - - Internal_CopyTo(typedArray, arrayIndex); - } - - public int IndexOf(Type item) - { - if (item == null) - throw new ArgumentNullException(nameof(item)); - - for (int i = 0; i < count; ++i) - { - // We can use == here as type object is unique and stored in a hashtable for the domain lifetime (Mono). - if (item == GetValue(ptr, i)) - return i; - } - - return -1; - } - - public int IndexOf(object item) - { - if (item == null) - throw new ArgumentNullException(nameof(item)); - var typedItem = item as Type; - if (typedItem == null) - throw new ArgumentException(nameof(item) + " is not of type " + nameof(Type)); - - return IndexOf(typedItem); - } - - [ThreadSafe] - static extern Type GetValue(IntPtr key, int index); - - [ThreadSafe] - extern void Internal_CopyTo(Type[] array, int arrayIndex); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - void ICollection.Add(Type item) - { - ThrowNotSupported(); - } - - void ICollection.Clear() - { - ThrowNotSupported(); - } - - bool ICollection.Remove(Type item) - { - ThrowNotSupported(); - return false; - } - - void IList.Insert(int index, Type item) - { - ThrowNotSupported(); - } - - void IList.RemoveAt(int index) - { - ThrowNotSupported(); - } - - object IList.this[int index] - { - get - { - return this[index]; - } - set - { - ThrowNotSupported(); - } - } - - int IList.Add(object value) - { - ThrowNotSupported(); - return -1; - } - - void IList.Clear() - { - ThrowNotSupported(); - } - - void IList.Insert(int index, object value) - { - ThrowNotSupported(); - } - - void IList.Remove(object value) - { - ThrowNotSupported(); - } - - void IList.RemoveAt(int index) - { - ThrowNotSupported(); - } - - static void ThrowNotSupported() - { - throw new NotSupportedException(nameof(TypeCollection) + " is read-only. Modification is not supported."); - } - - public struct Enumerator : IEnumerator - { - readonly TypeCollection m_Collection; - int m_Index; - - internal Enumerator(ref TypeCollection collection) - { - m_Collection = collection; - m_Index = -1; - } - - public void Dispose() - { - } - - public bool MoveNext() - { - m_Index++; - return m_Index < m_Collection.Count; - } - - public Type Current => m_Collection[m_Index]; - - void IEnumerator.Reset() => m_Index = -1; - object IEnumerator.Current => Current; - } - - class DebugView - { - readonly TypeCollection m_Collection; - - public DebugView(ref TypeCollection collection) - { - m_Collection = collection; - } - - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public Type[] values - { - get - { - var values = new Type[m_Collection.count]; - m_Collection.CopyTo(values, 0); - return values; - } - } - } - } - - [StructLayout(LayoutKind.Sequential)] - [DebuggerDisplay("Count = {" + nameof(count) + "}")] - [DebuggerTypeProxy(typeof(DebugView))] - public struct MethodCollection : IList, IList - { - [NonSerialized] - readonly IntPtr ptr; - readonly int count; - - internal MethodCollection(IntPtr p, int s) { ptr = p; count = s; } - - public int Count => count; - - public bool IsReadOnly => true; - - public bool IsFixedSize => true; - - public bool IsSynchronized => true; - - object ICollection.SyncRoot => null; - - public MethodInfo this[int index] - { - get - { - if (index >= 0 && index < count) - return GetValue(ptr, index); - throw new IndexOutOfRangeException($"Index {index} is out of range of '{count}' Count."); - } - set - { - ThrowNotSupported(); - } - } - - public bool Contains(MethodInfo item) => IndexOf(item) != -1; - - public bool Contains(object item) => IndexOf(item) != -1; - - public Enumerator GetEnumerator() => new Enumerator(ref this); - - public void CopyTo(MethodInfo[] array, int arrayIndex) - { - if (array == null) - throw new ArgumentNullException(nameof(array)); - if (arrayIndex < 0) - throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - if (arrayIndex + count > array.Length) - throw new ArgumentOutOfRangeException("arrayIndex"); - - Internal_CopyTo(array, arrayIndex); - } - - public void CopyTo(Array array, int arrayIndex) - { - if (array == null) - throw new ArgumentNullException(nameof(array)); - if (array.Rank != 1) - throw new ArgumentException(nameof(array)); - if (arrayIndex < 0) - throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - if (arrayIndex + count > array.Length) - throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - var typedArray = array as MethodInfo[]; - if (typedArray == null) - throw new ArrayTypeMismatchException(nameof(array)); - - Internal_CopyTo(typedArray, arrayIndex); - } - - public int IndexOf(MethodInfo item) - { - if (item == null) - throw new ArgumentNullException(nameof(item)); - - for (int i = 0; i < count; ++i) - { - if (item.Equals(GetValue(ptr, i))) - return i; - } - - return -1; - } - - public int IndexOf(object item) - { - if (item == null) - throw new ArgumentNullException(nameof(item)); - var typedItem = item as MethodInfo; - if (typedItem == null) - throw new ArgumentException(nameof(item) + " is not of type " + nameof(MethodInfo)); - - return IndexOf(typedItem); - } - - [ThreadSafe] - static extern MethodInfo GetValue(IntPtr key, int index); - - [ThreadSafe] - extern void Internal_CopyTo(MethodInfo[] array, int arrayIndex); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - void ICollection.Add(MethodInfo item) - { - ThrowNotSupported(); - } - - void ICollection.Clear() - { - ThrowNotSupported(); - } - - bool ICollection.Remove(MethodInfo item) - { - ThrowNotSupported(); - return false; - } - - void IList.Insert(int index, MethodInfo item) - { - ThrowNotSupported(); - } - - void IList.RemoveAt(int index) - { - ThrowNotSupported(); - } - - object IList.this[int index] - { - get - { - return this[index]; - } - set - { - ThrowNotSupported(); - } - } - - int IList.Add(object value) - { - ThrowNotSupported(); - return -1; - } - - void IList.Clear() - { - ThrowNotSupported(); - } - - void IList.Insert(int index, object value) - { - ThrowNotSupported(); - } - - void IList.Remove(object value) - { - ThrowNotSupported(); - } - - void IList.RemoveAt(int index) - { - ThrowNotSupported(); - } - - static void ThrowNotSupported() - { - throw new NotSupportedException(nameof(TypeCollection) + " is read-only. Modification is not supported."); - } - - public struct Enumerator : IEnumerator - { - readonly MethodCollection m_Collection; - int m_Index; - - internal Enumerator(ref MethodCollection collection) - { - m_Collection = collection; - m_Index = -1; - } - - public void Dispose() - { - } - - public bool MoveNext() - { - m_Index++; - return m_Index < m_Collection.Count; - } - - public MethodInfo Current => m_Collection[m_Index]; - - void IEnumerator.Reset() => m_Index = -1; - object IEnumerator.Current => Current; - } - - class DebugView - { - readonly MethodCollection m_Collection; - - public DebugView(ref MethodCollection collection) - { - m_Collection = collection; - } - - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public MethodInfo[] Values - { - get - { - var values = new MethodInfo[m_Collection.count]; - m_Collection.CopyTo(values, 0); - return values; - } - } - } - } - - - [StructLayout(LayoutKind.Sequential)] - [DebuggerDisplay("Count = {" + nameof(count) + "}")] - [DebuggerTypeProxy(typeof(DebugView))] - public struct FieldInfoCollection : IList, IList - { - [NonSerialized] - readonly IntPtr ptr; - readonly int count; - - internal FieldInfoCollection(IntPtr p, int s) { ptr = p; count = s; } - - public int Count => count; - - public bool IsReadOnly => true; - - public bool IsFixedSize => true; - - public bool IsSynchronized => true; - - object ICollection.SyncRoot => null; - - public FieldInfo this[int index] - { - get - { - if (index >= 0 && index < count) - return GetValue(ptr, index); - throw new IndexOutOfRangeException($"Index {index} is out of range of '{count}' Count."); - } - set - { - ThrowNotSupported(); - } - } - - public bool Contains(FieldInfo item) => IndexOf(item) != -1; - - public bool Contains(object item) => IndexOf(item) != -1; - - public Enumerator GetEnumerator() => new Enumerator(ref this); - - public void CopyTo(FieldInfo[] array, int arrayIndex) - { - if (array == null) - throw new ArgumentNullException(nameof(array)); - if (arrayIndex < 0) - throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - if (arrayIndex + count > array.Length) - throw new ArgumentOutOfRangeException("arrayIndex"); - - Internal_CopyTo(array, arrayIndex); - } - - public void CopyTo(Array array, int arrayIndex) - { - if (array == null) - throw new ArgumentNullException(nameof(array)); - if (array.Rank != 1) - throw new ArgumentException(nameof(array)); - if (arrayIndex < 0) - throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - if (arrayIndex + count > array.Length) - throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - var typedArray = array as FieldInfo[]; - if (typedArray == null) - throw new ArrayTypeMismatchException(nameof(array)); - - Internal_CopyTo(typedArray, arrayIndex); - } - - public int IndexOf(FieldInfo item) - { - if (item == null) - throw new ArgumentNullException(nameof(item)); - - for (int i = 0; i < count; ++i) - { - if (item.Equals(GetValue(ptr, i))) - return i; - } - - return -1; - } - - public int IndexOf(object item) - { - if (item == null) - throw new ArgumentNullException(nameof(item)); - var typedItem = item as FieldInfo; - if (typedItem == null) - throw new ArgumentException(nameof(item) + " is not of type " + nameof(FieldInfo)); - - return IndexOf(typedItem); - } - - [ThreadSafe] - static extern FieldInfo GetValue(IntPtr key, int index); - - [ThreadSafe] - extern void Internal_CopyTo(FieldInfo[] array, int arrayIndex); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - void ICollection.Add(FieldInfo item) - { - ThrowNotSupported(); - } - - void ICollection.Clear() - { - ThrowNotSupported(); - } - - bool ICollection.Remove(FieldInfo item) - { - ThrowNotSupported(); - return false; - } - - void IList.Insert(int index, FieldInfo item) - { - ThrowNotSupported(); - } - - void IList.RemoveAt(int index) - { - ThrowNotSupported(); - } - - object IList.this[int index] - { - get - { - return this[index]; - } - set - { - ThrowNotSupported(); - } - } - - int IList.Add(object value) - { - ThrowNotSupported(); - return -1; - } - - void IList.Clear() - { - ThrowNotSupported(); - } - - void IList.Insert(int index, object value) - { - ThrowNotSupported(); - } - - void IList.Remove(object value) - { - ThrowNotSupported(); - } - - void IList.RemoveAt(int index) - { - ThrowNotSupported(); - } - - static void ThrowNotSupported() - { - throw new NotSupportedException(nameof(TypeCollection) + " is read-only. Modification is not supported."); - } - - public struct Enumerator : IEnumerator - { - readonly FieldInfoCollection m_Collection; - int m_Index; - - internal Enumerator(ref FieldInfoCollection collection) - { - m_Collection = collection; - m_Index = -1; - } - - public void Dispose() - { - } - - public bool MoveNext() - { - m_Index++; - return m_Index < m_Collection.Count; - } - - public FieldInfo Current => m_Collection[m_Index]; + [ThreadSafe] + static extern Type[] Internal_GetTypesWithAttribute(Type attrType); - void IEnumerator.Reset() => m_Index = -1; - object IEnumerator.Current => Current; - } + [ThreadSafe] + static extern MethodInfo[] Internal_GetMethodsWithAttribute(Type attrType); - class DebugView - { - readonly FieldInfoCollection m_Collection; + [ThreadSafe] + static extern FieldInfo[] Internal_GetFieldsWithAttribute(Type attrType); - public DebugView(ref FieldInfoCollection collection) - { - m_Collection = collection; - } + [ThreadSafe] + static extern Type[] Internal_GetTypesDerivedFromInterface(Type interfaceType); - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public FieldInfo[] Values - { - get - { - var values = new FieldInfo[m_Collection.count]; - m_Collection.CopyTo(values, 0); - return values; - } - } - } - } + [ThreadSafe] + static extern Type[] Internal_GetTypesDerivedFromType(Type parentType); [ThreadSafe] - public static extern TypeCollection GetTypesWithAttribute(Type attrType); + static extern Type[] Internal_GetTypesWithAttributeFromAssembly(Type attrType, string assemblyName); [ThreadSafe] - public static extern MethodCollection GetMethodsWithAttribute(Type attrType); + static extern MethodInfo[] Internal_GetMethodsWithAttributeFromAssembly(Type attrType, string assemblyName); [ThreadSafe] - public static extern FieldInfoCollection GetFieldsWithAttribute(Type attrType); + static extern FieldInfo[] Internal_GetFieldsWithAttributeFromAssembly(Type attrType, string assemblyName); [ThreadSafe] - static extern TypeCollection GetTypesDerivedFromInterface(Type interfaceType); + static extern Type[] Internal_GetTypesDerivedFromInterfaceFromAssembly(Type interfaceType, string assemblyName); [ThreadSafe] - static extern TypeCollection GetTypesDerivedFromType(Type parentType); + static extern Type[] Internal_GetTypesDerivedFromTypeFromAssembly(Type parentType, string assemblyName); + + internal static extern ulong GetCurrentAge(); } } diff --git a/Editor/Mono/TypeCache.cs b/Editor/Mono/TypeCache.cs index 568917606f..a4f535801c 100644 --- a/Editor/Mono/TypeCache.cs +++ b/Editor/Mono/TypeCache.cs @@ -3,14 +3,11 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Collections; using System.Collections.Generic; -using System.Linq; +using System.Diagnostics; +using System.Runtime.InteropServices; using System.Reflection; -using System.Runtime.CompilerServices; -using Unity.Profiling; -using UnityEngine; -using UnityEngine.Internal; -using UnityEngine.Scripting; namespace UnityEditor { @@ -42,7 +39,674 @@ public static TypeCollection GetTypesDerivedFrom() public static TypeCollection GetTypesDerivedFrom(Type parentType) { - return parentType.IsInterface ? GetTypesDerivedFromInterface(parentType) : GetTypesDerivedFromType(parentType); + return parentType.IsInterface ? + new TypeCollection(Internal_GetTypesDerivedFromInterface(parentType)) : + new TypeCollection(Internal_GetTypesDerivedFromType(parentType)); + } + + public static TypeCollection GetTypesWithAttribute(Type attrType) + { + return new TypeCollection(Internal_GetTypesWithAttribute(attrType)); + } + + public static MethodCollection GetMethodsWithAttribute(Type attrType) + { + return new MethodCollection(Internal_GetMethodsWithAttribute(attrType)); + } + + public static FieldInfoCollection GetFieldsWithAttribute(Type attrType) + { + return new FieldInfoCollection(Internal_GetFieldsWithAttribute(attrType)); + } + + public static TypeCollection GetTypesWithAttribute(string assemblyName) + where T : Attribute + { + return GetTypesWithAttribute(typeof(T), assemblyName); + } + + public static MethodCollection GetMethodsWithAttribute(string assemblyName) + where T : Attribute + { + return GetMethodsWithAttribute(typeof(T), assemblyName); + } + + public static FieldInfoCollection GetFieldsWithAttribute(string assemblyName) + where T : Attribute + { + return GetFieldsWithAttribute(typeof(T), assemblyName); + } + + public static TypeCollection GetTypesDerivedFrom(string assemblyName) + { + var parentType = typeof(T); + return GetTypesDerivedFrom(parentType, assemblyName); + } + + public static TypeCollection GetTypesDerivedFrom(Type parentType, string assemblyName) + { + return parentType.IsInterface ? + new TypeCollection(Internal_GetTypesDerivedFromInterfaceFromAssembly(parentType, assemblyName)) : + new TypeCollection(Internal_GetTypesDerivedFromTypeFromAssembly(parentType, assemblyName)); + } + + public static TypeCollection GetTypesWithAttribute(Type attrType, string assemblyName) + { + return new TypeCollection(Internal_GetTypesWithAttributeFromAssembly(attrType, assemblyName)); + + } + + public static MethodCollection GetMethodsWithAttribute(Type attrType, string assemblyName) + { + return new MethodCollection(Internal_GetMethodsWithAttributeFromAssembly(attrType, assemblyName)); + } + + public static FieldInfoCollection GetFieldsWithAttribute(Type attrType, string assemblyName) + { + return new FieldInfoCollection(Internal_GetFieldsWithAttributeFromAssembly(attrType, assemblyName)); + } + + [StructLayout(LayoutKind.Sequential)] + [DebuggerDisplay("Count = {" + nameof(Count) + "}")] + [DebuggerTypeProxy(typeof(DebugView))] + public struct TypeCollection : IList, IList + { + [NonSerialized] + readonly Type[] listOfTypes; + + internal TypeCollection(Type[] types) { listOfTypes = types; } + + public int Count => listOfTypes.Length; + + public bool IsReadOnly => true; + + public bool IsFixedSize => true; + + public bool IsSynchronized => true; + + object ICollection.SyncRoot => null; + + public Type this[int index] + { + get + { + return listOfTypes[index]; + } + set + { + ThrowNotSupported(); + } + } + + public bool Contains(Type item) => IndexOf(item) != -1; + + public bool Contains(object item) => IndexOf(item) != -1; + + public Enumerator GetEnumerator() => new Enumerator(ref this); + + public void CopyTo(Type[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + if (arrayIndex + Count > array.Length) + throw new ArgumentOutOfRangeException("arrayIndex"); + + for (int i = 0; i < Count; ++i) + array[i + arrayIndex] = listOfTypes[i]; + } + + public void CopyTo(Array array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + if (array.Rank != 1) + throw new ArgumentException(nameof(array)); + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + if (arrayIndex + Count > array.Length) + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + var typedArray = array as Type[]; + if (typedArray == null) + throw new ArrayTypeMismatchException(nameof(array)); + + for (int i = 0; i < Count; ++i) + typedArray[i + arrayIndex] = listOfTypes[i]; + } + + public int IndexOf(Type item) + { + return Array.IndexOf(listOfTypes, item); + } + + public int IndexOf(object item) + { + return Array.IndexOf(listOfTypes, item); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + void ICollection.Add(Type item) + { + ThrowNotSupported(); + } + + void ICollection.Clear() + { + ThrowNotSupported(); + } + + bool ICollection.Remove(Type item) + { + ThrowNotSupported(); + return false; + } + + void IList.Insert(int index, Type item) + { + ThrowNotSupported(); + } + + void IList.RemoveAt(int index) + { + ThrowNotSupported(); + } + + object IList.this[int index] + { + get + { + return this[index]; + } + set + { + ThrowNotSupported(); + } + } + + int IList.Add(object value) + { + ThrowNotSupported(); + return -1; + } + + void IList.Clear() + { + ThrowNotSupported(); + } + + void IList.Insert(int index, object value) + { + ThrowNotSupported(); + } + + void IList.Remove(object value) + { + ThrowNotSupported(); + } + + void IList.RemoveAt(int index) + { + ThrowNotSupported(); + } + + static void ThrowNotSupported() + { + throw new NotSupportedException(nameof(TypeCollection) + " is read-only. Modification is not supported."); + } + + public struct Enumerator : IEnumerator + { + readonly TypeCollection m_Collection; + int m_Index; + + internal Enumerator(ref TypeCollection collection) + { + m_Collection = collection; + m_Index = -1; + } + + public void Dispose() + { + } + + public bool MoveNext() + { + m_Index++; + return m_Index < m_Collection.Count; + } + + public Type Current => m_Collection[m_Index]; + + void IEnumerator.Reset() => m_Index = -1; + object IEnumerator.Current => Current; + } + + class DebugView + { + readonly TypeCollection m_Collection; + + public DebugView(ref TypeCollection collection) + { + m_Collection = collection; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public Type[] values + { + get + { + var values = new Type[m_Collection.Count]; + m_Collection.CopyTo(values, 0); + return values; + } + } + } + } + + [StructLayout(LayoutKind.Sequential)] + [DebuggerDisplay("Count = {" + nameof(Count) + "}")] + [DebuggerTypeProxy(typeof(DebugView))] + public struct MethodCollection : IList, IList + { + [NonSerialized] + readonly MethodInfo[] listOfMethods; + + internal MethodCollection(MethodInfo[] methods) { listOfMethods = methods; } + + public int Count => listOfMethods.Length; + + public bool IsReadOnly => true; + + public bool IsFixedSize => true; + + public bool IsSynchronized => true; + + object ICollection.SyncRoot => null; + + public MethodInfo this[int index] + { + get + { + return listOfMethods[index]; + } + set + { + ThrowNotSupported(); + } + } + + public bool Contains(MethodInfo item) => IndexOf(item) != -1; + + public bool Contains(object item) => IndexOf(item) != -1; + + public Enumerator GetEnumerator() => new Enumerator(ref this); + + public void CopyTo(MethodInfo[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + if (arrayIndex + Count > array.Length) + throw new ArgumentOutOfRangeException("arrayIndex"); + + for (int i = 0; i < Count; ++i) + array[i + arrayIndex] = listOfMethods[i]; + } + + public void CopyTo(Array array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + if (array.Rank != 1) + throw new ArgumentException(nameof(array)); + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + if (arrayIndex + Count > array.Length) + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + var typedArray = array as MethodInfo[]; + if (typedArray == null) + throw new ArrayTypeMismatchException(nameof(array)); + + for (int i = 0; i < Count; ++i) + typedArray[i + arrayIndex] = listOfMethods[i]; + } + + public int IndexOf(MethodInfo item) + { + return Array.IndexOf(listOfMethods, item); + } + + public int IndexOf(object item) + { + return Array.IndexOf(listOfMethods, item); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + void ICollection.Add(MethodInfo item) + { + ThrowNotSupported(); + } + + void ICollection.Clear() + { + ThrowNotSupported(); + } + + bool ICollection.Remove(MethodInfo item) + { + ThrowNotSupported(); + return false; + } + + void IList.Insert(int index, MethodInfo item) + { + ThrowNotSupported(); + } + + void IList.RemoveAt(int index) + { + ThrowNotSupported(); + } + + object IList.this[int index] + { + get + { + return this[index]; + } + set + { + ThrowNotSupported(); + } + } + + int IList.Add(object value) + { + ThrowNotSupported(); + return -1; + } + + void IList.Clear() + { + ThrowNotSupported(); + } + + void IList.Insert(int index, object value) + { + ThrowNotSupported(); + } + + void IList.Remove(object value) + { + ThrowNotSupported(); + } + + void IList.RemoveAt(int index) + { + ThrowNotSupported(); + } + + static void ThrowNotSupported() + { + throw new NotSupportedException(nameof(TypeCollection) + " is read-only. Modification is not supported."); + } + + public struct Enumerator : IEnumerator + { + readonly MethodCollection m_Collection; + int m_Index; + + internal Enumerator(ref MethodCollection collection) + { + m_Collection = collection; + m_Index = -1; + } + + public void Dispose() + { + } + + public bool MoveNext() + { + m_Index++; + return m_Index < m_Collection.Count; + } + + public MethodInfo Current => m_Collection[m_Index]; + + void IEnumerator.Reset() => m_Index = -1; + object IEnumerator.Current => Current; + } + + class DebugView + { + readonly MethodCollection m_Collection; + + public DebugView(ref MethodCollection collection) + { + m_Collection = collection; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public MethodInfo[] Values + { + get + { + var values = new MethodInfo[m_Collection.Count]; + m_Collection.CopyTo(values, 0); + return values; + } + } + } + } + + [StructLayout(LayoutKind.Sequential)] + [DebuggerDisplay("Count = {" + nameof(Count) + "}")] + [DebuggerTypeProxy(typeof(DebugView))] + public struct FieldInfoCollection : IList, IList + { + [NonSerialized] + readonly FieldInfo[] listOfFields; + + internal FieldInfoCollection(FieldInfo[] fields) { listOfFields = fields; } + + public int Count => listOfFields.Length; + + public bool IsReadOnly => true; + + public bool IsFixedSize => true; + + public bool IsSynchronized => true; + + object ICollection.SyncRoot => null; + + public FieldInfo this[int index] + { + get + { + return listOfFields[index]; + } + set + { + ThrowNotSupported(); + } + } + + public bool Contains(FieldInfo item) => IndexOf(item) != -1; + + public bool Contains(object item) => IndexOf(item) != -1; + + public Enumerator GetEnumerator() => new Enumerator(ref this); + + public void CopyTo(FieldInfo[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + if (arrayIndex + Count > array.Length) + throw new ArgumentOutOfRangeException("arrayIndex"); + + for (int i = 0; i < Count; ++i) + array[i + arrayIndex] = listOfFields[i]; + } + + public void CopyTo(Array array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + if (array.Rank != 1) + throw new ArgumentException(nameof(array)); + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + if (arrayIndex + Count > array.Length) + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + var typedArray = array as FieldInfo[]; + if (typedArray == null) + throw new ArrayTypeMismatchException(nameof(array)); + + for (int i = 0; i < Count; ++i) + typedArray[i + arrayIndex] = listOfFields[i]; + } + + public int IndexOf(FieldInfo item) + { + return Array.IndexOf(listOfFields, item); + } + + public int IndexOf(object item) + { + return Array.IndexOf(listOfFields, item); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + void ICollection.Add(FieldInfo item) + { + ThrowNotSupported(); + } + + void ICollection.Clear() + { + ThrowNotSupported(); + } + + bool ICollection.Remove(FieldInfo item) + { + ThrowNotSupported(); + return false; + } + + void IList.Insert(int index, FieldInfo item) + { + ThrowNotSupported(); + } + + void IList.RemoveAt(int index) + { + ThrowNotSupported(); + } + + object IList.this[int index] + { + get + { + return this[index]; + } + set + { + ThrowNotSupported(); + } + } + + int IList.Add(object value) + { + ThrowNotSupported(); + return -1; + } + + void IList.Clear() + { + ThrowNotSupported(); + } + + void IList.Insert(int index, object value) + { + ThrowNotSupported(); + } + + void IList.Remove(object value) + { + ThrowNotSupported(); + } + + void IList.RemoveAt(int index) + { + ThrowNotSupported(); + } + + static void ThrowNotSupported() + { + throw new NotSupportedException(nameof(TypeCollection) + " is read-only. Modification is not supported."); + } + + public struct Enumerator : IEnumerator + { + readonly FieldInfoCollection m_Collection; + int m_Index; + + internal Enumerator(ref FieldInfoCollection collection) + { + m_Collection = collection; + m_Index = -1; + } + + public void Dispose() + { + } + + public bool MoveNext() + { + m_Index++; + return m_Index < m_Collection.Count; + } + + public FieldInfo Current => m_Collection[m_Index]; + + void IEnumerator.Reset() => m_Index = -1; + object IEnumerator.Current => Current; + } + + class DebugView + { + readonly FieldInfoCollection m_Collection; + + public DebugView(ref FieldInfoCollection collection) + { + m_Collection = collection; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public FieldInfo[] Values + { + get + { + var values = new FieldInfo[m_Collection.Count]; + m_Collection.CopyTo(values, 0); + return values; + } + } + } } } } diff --git a/Editor/Mono/UIElements/Bindings/BindingsInterface.cs b/Editor/Mono/UIElements/Bindings/BindingsInterface.cs index fe9ce068f9..79f0b72cc0 100644 --- a/Editor/Mono/UIElements/Bindings/BindingsInterface.cs +++ b/Editor/Mono/UIElements/Bindings/BindingsInterface.cs @@ -26,7 +26,7 @@ internal interface ISerializedObjectBindingImplementation } /// - /// Provides VisualElement extension methods that implement data binding between INotivyValueChanged fields and SerializedObjects. + /// Provides VisualElement extension methods that implement data binding between INotifyValueChanged fields and SerializedObjects. /// public static class BindingExtensions { @@ -37,6 +37,7 @@ public static class BindingExtensions internal static readonly string prefabOverrideBarName = "unity-binding-prefab-override-bar"; internal static readonly string prefabOverrideBarContainerName = "unity-prefab-override-bars-container"; internal static readonly string prefabOverrideBarUssClassName = "unity-binding__prefab-override-bar"; + internal static readonly string prefabOverrideBarNotApplicableUssClassName = "unity-binding__prefab-override-bar-not-applicable"; internal static readonly string animationAnimatedUssClassName = "unity-binding--animation-animated"; internal static readonly string animationRecordedUssClassName = "unity-binding--animation-recorded"; internal static readonly string animationCandidateUssClassName = "unity-binding--animation-candidate"; @@ -54,6 +55,9 @@ public static class BindingExtensions /// /// Root VisualElement containing IBindable fields. /// Data object. + /// + /// Don’t call @@Bind()@@ from the @@Editor.CreateInspectorGUI()@2 or @@PropertyDrawer.CreatePropertyGUI()@@ override. It is called automatically on the hierarchy that these methods return. + /// public static void Bind(this VisualElement element, SerializedObject obj) { bindingImpl.Bind(element, obj); @@ -95,7 +99,7 @@ internal static void HandleStyleUpdate(VisualElement element) } /// - /// Checks the property values for changes at regular intervals. Executes the callback when the property value changes. + /// Executes the callback when the property value changes. Unity checks properties for changes at regular intervals during the update loop. /// If no callback is specified, a SerializedPropertyChangeEvent is sent to the target element. /// /// VisualElement tracking a property. @@ -107,11 +111,11 @@ public static void TrackPropertyValue(this VisualElement element, SerializedProp } /// - /// Checks the object for changes at regular intervals. Executes the callback when the object value changes. - /// If no callback is specified, a SerializedObjectChangeEvent is sent to the target element. + /// Executes the callback when the property value changes. Unity checks properties for changes at regular intervals during the update loop. + /// If no callback is specified, a SerializedPropertyChangeEvent is sent to the target element. /// /// VisualElement tracking an object. - /// The SerializedObject to track. + /// The SerializedObject to track. /// Invoked when one of the tracked SerializedObject's value changes. public static void TrackSerializedObjectValue(this VisualElement element, SerializedObject obj, Action callback = null) diff --git a/Editor/Mono/UIElements/Controls/ColorField.cs b/Editor/Mono/UIElements/Controls/ColorField.cs index 2b8aac9ac7..2087bbd935 100644 --- a/Editor/Mono/UIElements/Controls/ColorField.cs +++ b/Editor/Mono/UIElements/Controls/ColorField.cs @@ -9,7 +9,7 @@ namespace UnityEditor.UIElements { /// - /// Makes a field for selecting a color. + /// Makes a field for selecting a color. For more information, refer to [[wiki:UIE-uxml-element-ColorField|UXML element ColorField]]. /// public class ColorField : BaseField { diff --git a/Editor/Mono/UIElements/Controls/CurveField.cs b/Editor/Mono/UIElements/Controls/CurveField.cs index d7c39f6a23..c0e97c130d 100644 --- a/Editor/Mono/UIElements/Controls/CurveField.cs +++ b/Editor/Mono/UIElements/Controls/CurveField.cs @@ -12,7 +12,7 @@ namespace UnityEditor.UIElements { /// - /// Makes a field for editing an . + /// Makes a field for editing an . For more information, refer to [[wiki:UIE-uxml-element-CurveField|UXML element CurveField]]. /// public class CurveField : BaseField { @@ -265,10 +265,18 @@ void ShowCurveEditor() CurveEditorSettings settings = new CurveEditorSettings(); if (rawValue == null) rawValue = new AnimationCurve(); - CurveEditorWindow.instance.Show(OnCurveChanged, settings); + if (ranges != Rect.zero) + { + settings.hRangeMin = ranges.xMin; + settings.hRangeMax = ranges.xMax; + settings.vRangeMin = ranges.yMin; + settings.vRangeMax = ranges.yMax; + } + CurveEditorWindow.curve = rawValue; CurveEditorWindow.color = curveColor; + CurveEditorWindow.instance.Show(OnCurveChanged, settings); } [EventInterest(typeof(KeyDownEvent), typeof(PointerDownEvent), typeof(DetachFromPanelEvent), diff --git a/Editor/Mono/UIElements/Controls/EnumFlagsField.cs b/Editor/Mono/UIElements/Controls/EnumFlagsField.cs index c03d1c6024..c524739654 100644 --- a/Editor/Mono/UIElements/Controls/EnumFlagsField.cs +++ b/Editor/Mono/UIElements/Controls/EnumFlagsField.cs @@ -12,6 +12,13 @@ namespace UnityEditor.UIElements /// /// Makes a dropdown for switching between enum flag values that are marked with the Flags attribute. /// + /// + /// An option for the value 0 with name "Nothing" and an option for the value ~0 (that is, all bits set) with the + /// name "Everything" are always displayed at the top of the menu. The names for the values 0 and ~0 can be + /// overriden by defining these values in the enum type. + /// + /// For more information, refer to [[wiki:UIE-uxml-element-EnumFlagsField|UXML element EnumFlagsField]]. + /// public class EnumFlagsField : BaseMaskField { /// @@ -141,8 +148,8 @@ public void Init(Enum defaultValue, bool includeObsoleteValues = false) if (!m_EnumData.flags) Debug.LogWarning("EnumMaskField is not bound to enum type with the [Flags] attribute"); - choices = new List(m_EnumData.displayNames); choicesMasks = new List(m_EnumData.flagValues); + choices = new List(m_EnumData.displayNames); SetValueWithoutNotify(defaultValue); } @@ -178,5 +185,37 @@ internal void PopulateDataFromType(Type enumType) m_EnumType = enumType; m_EnumData = EnumDataUtility.GetCachedEnumData(m_EnumType, !includeObsoleteValues); } + + internal override string GetNothingName() + { + if (m_EnumData.flagValues is not { Length: > 0 }) + return base.GetNothingName(); + + for (var i = 0; i < m_EnumData.flagValues.Length; i++) + { + if (m_EnumData.flagValues[i] == 0) + { + return m_EnumData.displayNames[i]; + } + } + + return base.GetNothingName(); + } + + internal override string GetEverythingName() + { + if (m_EnumData.flagValues is not { Length: > 0 }) + return base.GetEverythingName(); + + for (var i = 0; i < m_EnumData.flagValues.Length; i++) + { + if (m_EnumData.flagValues[i] == ~0) + { + return m_EnumData.displayNames[i]; + } + } + + return base.GetEverythingName(); + } } } diff --git a/Editor/Mono/UIElements/Controls/GradientField.cs b/Editor/Mono/UIElements/Controls/GradientField.cs index 840cd3abf4..2fd3ac3d0c 100644 --- a/Editor/Mono/UIElements/Controls/GradientField.cs +++ b/Editor/Mono/UIElements/Controls/GradientField.cs @@ -11,7 +11,7 @@ namespace UnityEditor.UIElements { /// - /// Makes a field for editing an . + /// Makes a field for editing an . For more information, refer to [[wiki:UIE-uxml-element-GradientField|UXML element GradientField]]. /// public class GradientField : BaseField { @@ -93,6 +93,11 @@ internal static Gradient GradientCopy(Gradient other) /// public static readonly string contentUssClassName = ussClassName + "__content"; + /// + /// USS class name for the background of the gradient visual in the element. + /// + public static readonly string backgroundUssClassName = ussClassName + "__background"; + /// /// USS class name for border elements in elements of this type. /// @@ -102,6 +107,8 @@ internal static Gradient GradientCopy(Gradient other) VisualElement m_GradientTextureImage; readonly Background m_DefaultBackground = new Background(); + bool isShowingGradientPicker => GradientPicker.visible && rawValue != null && ReferenceEquals(GradientPicker.gradient, rawValue); + /// /// Constructor. /// @@ -118,9 +125,13 @@ public GradientField(string label) labelElement.AddToClassList(labelUssClassName); visualInput.AddToClassList(inputUssClassName); - m_GradientTextureImage = new VisualElement() { pickingMode = PickingMode.Ignore }; + var background = new VisualElement { pickingMode = PickingMode.Ignore }; + background.AddToClassList(backgroundUssClassName); + visualInput.Add(background); + + m_GradientTextureImage = new VisualElement { pickingMode = PickingMode.Ignore }; m_GradientTextureImage.AddToClassList(contentUssClassName); - visualInput.Add(m_GradientTextureImage); + background.Add(m_GradientTextureImage); // Keep creating and adding a VisualElement for the border even though it is not used anymore. // It is done to remain backwards compatible (c.f. obsoleted borderUssClassName). @@ -177,6 +188,9 @@ protected override void ExecuteDefaultAction(EventBase evt) void OnDetach() { + if (isShowingGradientPicker) + GradientPicker.CloseWindow(false); + if (style.backgroundImage.value.texture != null) { Object.DestroyImmediate(style.backgroundImage.value.texture); @@ -240,6 +254,12 @@ public override void SetValueWithoutNotify(Gradient newValue) rawValue.mode = GradientMode.Blend; } UpdateGradientTexture(); + + // Update the GradientPicker if it's open and not currently being interacted with. (UUM-100664) + if (isShowingGradientPicker && GUIUtility.hotControl == 0) + { + GradientPicker.RefreshGradientData(); + } } protected override void UpdateMixedValueContent() diff --git a/Editor/Mono/UIElements/Controls/LayerField.cs b/Editor/Mono/UIElements/Controls/LayerField.cs index 80bce447f2..04455aba92 100644 --- a/Editor/Mono/UIElements/Controls/LayerField.cs +++ b/Editor/Mono/UIElements/Controls/LayerField.cs @@ -11,7 +11,7 @@ namespace UnityEditor.UIElements { /// - /// A editor. + /// A LayerField editor. For more information, refer to [[wiki:UIE-uxml-element-LayerField|UXML element LayerField]]. /// public class LayerField : PopupField { @@ -181,7 +181,7 @@ internal override void AddMenuItems(IGenericMenu menu) { var item = layerList[i]; var menuItemIndex = m_Choices[i]; - var isSelected = (menuItemIndex == value); + var isSelected = (menuItemIndex == value) && !showMixedValue; menu.AddItem(item, isSelected, () => ChangeValueFromMenu(menuItemIndex)); } menu.AddSeparator(String.Empty); diff --git a/Editor/Mono/UIElements/Controls/LayerMaskField.cs b/Editor/Mono/UIElements/Controls/LayerMaskField.cs index ceb3de3b7c..19e9e8a898 100644 --- a/Editor/Mono/UIElements/Controls/LayerMaskField.cs +++ b/Editor/Mono/UIElements/Controls/LayerMaskField.cs @@ -11,7 +11,7 @@ namespace UnityEditor.UIElements { /// - /// Make a field for layer as masks. + /// A LayerMaskField editor. For more information, refer to [[wiki:UIE-uxml-element-LayerMaskField|UXML element LayerMaskField]]. /// public class LayerMaskField : MaskField { diff --git a/Editor/Mono/UIElements/Controls/MaskField.cs b/Editor/Mono/UIElements/Controls/MaskField.cs index e93af73814..b33b434d6b 100644 --- a/Editor/Mono/UIElements/Controls/MaskField.cs +++ b/Editor/Mono/UIElements/Controls/MaskField.cs @@ -4,15 +4,14 @@ using System; using System.Collections.Generic; -using System.Linq; -using UnityEngine; +using System.Text; +using UnityEngine.Pool; using UnityEngine.UIElements; -using UnityEngine.Profiling; namespace UnityEditor.UIElements { /// - /// Base class implementing the shared functionality for editing bit mask values. + /// Base class implementing the shared functionality for editing bit mask values. For more information, refer to [[wiki:UIE-uxml-element-MaskField|UXML element MaskField]]. /// public abstract class BaseMaskField : BasePopupField { @@ -23,6 +22,10 @@ public abstract class BaseMaskField : BasePopupField static readonly int s_EverythingIndex = 1; static readonly int s_TotalIndex = 2; + static readonly string s_MixedLabel = L10n.Tr("Mixed..."); + static readonly string s_EverythingLabel = L10n.Tr("Everything"); + static readonly string s_NothingLabel = L10n.Tr("Nothing"); + // This is the list of string representing all the user choices List m_UserChoices; @@ -38,9 +41,47 @@ public abstract class BaseMaskField : BasePopupField // This is containing a mask to cover all the choices from the list. Computed with the help of m_UserChoicesMasks // or based on the power of 2 mask values. int m_FullChoiceMask; + internal int fullChoiceMask => m_FullChoiceMask; + + EditorGenericDropdownMenu m_GenericDropdownMenu; internal BaseMaskField(string label) : base(label) { + createMenuCallback = () => + { + m_GenericDropdownMenu = new EditorGenericDropdownMenu(); + return m_GenericDropdownMenu; + }; + + textElement.RegisterCallback(OnTextElementGeometryChanged); + } + + private void OnTextElementGeometryChanged(GeometryChangedEvent evt) + { + var mask = ValueToMask(value); + + switch (mask) + { + case 0: + case ~0: + // Don't do anything for Nothing or Everything + break; + default: + // Mixed values + if (!IsPowerOf2(mask)) + { + // If the current text is "Mixed..." and we now have more space, we might need to check if the + // actual values would fit. + // If the current label contains the actual values and we now have less space, we might need to + // change it to "Mixed..." + if (textElement.text == s_MixedLabel && evt.oldRect.width < evt.newRect.width + || textElement.text != s_MixedLabel && evt.oldRect.width > evt.newRect.width) + { + textElement.text = GetMixedString(); + } + } + break; + } } /// @@ -76,17 +117,28 @@ public override List choices { m_Choices.Clear(); } - m_Choices.Add(L10n.Tr("Nothing")); - m_Choices.Add(L10n.Tr("Everything")); - m_Choices.AddRange(m_UserChoices); ComputeFullChoiceMask(); + m_Choices.Add(GetNothingName()); + m_Choices.Add(GetEverythingName()); + m_Choices.AddRange(m_UserChoices); + // Make sure to update the text displayed SetValueWithoutNotify(rawValue); } } + internal virtual string GetNothingName() + { + return s_NothingLabel; + } + + internal virtual string GetEverythingName() + { + return s_EverythingLabel; + } + /// /// The list of list of masks for every specific choice to display in the popup menu. /// @@ -138,6 +190,11 @@ void ComputeFullChoiceMask() m_FullChoiceMask = 0; foreach (int itemMask in m_UserChoicesMasks) { + if (itemMask == ~0) + { + continue; + } + m_FullChoiceMask |= itemMask; } } @@ -175,9 +232,6 @@ internal override string GetListItemToDisplay(TChoice item) internal string GetDisplayedValue(int itemIndex) { - if (showMixedValue) - return mixedValueString; - var newValueToShowUser = ""; switch (itemIndex) @@ -200,7 +254,7 @@ internal string GetDisplayedValue(int itemIndex) // Find the actual index of the selected choice... foreach (int itemMask in m_UserChoicesMasks) { - if ((itemMask & itemIndex) == itemIndex) + if (itemMask != ~0 && ((itemMask & itemIndex) == itemIndex)) { indexOfValue = m_UserChoicesMasks.IndexOf(itemMask); break; @@ -224,13 +278,78 @@ internal string GetDisplayedValue(int itemIndex) } else { - newValueToShowUser = L10n.Tr("Mixed..."); + if (m_UserChoicesMasks != null) + { + // Check if there's a name defined for this value + for (int i = 0; i < m_UserChoicesMasks.Count; i++) + { + var itemMask = m_UserChoicesMasks[i]; + if (itemMask == itemIndex) + { + var index = i + s_TotalIndex; + newValueToShowUser = m_Choices[index]; + break; + } + } + } + + if (string.IsNullOrEmpty(newValueToShowUser)) + { + newValueToShowUser = GetMixedString(); + } } break; } return newValueToShowUser; } + private string GetMixedString() + { + var sb = GenericPool.Get(); + + foreach (var item in m_Choices) + { + var maskOfItem = GetMaskValueOfItem(item); + + if (!IsItemSelected(maskOfItem)) + { + continue; + } + + if (sb.Length > 0) + { + sb.Append(", "); + } + + sb.Append(item); + } + + var mixedString = sb.ToString(); + var minSize = textElement.MeasureTextSize(mixedString, 0, MeasureMode.Undefined, 0, MeasureMode.Undefined); + + // If text doesn't fit, we use "Mixed..." + if (float.IsNaN(textElement.resolvedStyle.width) || minSize.x > textElement.resolvedStyle.width) + { + mixedString = s_MixedLabel; + } + + sb.Clear(); + GenericPool.Release(sb); + + return mixedString; + } + + public override TChoice value + { + get => base.value; + set + { + // We need to convert the value to an accepted mask value so that the comparision with the old value works (UUM-56605) + // For example, if the value is null, we need to convert it to the mask value of the Nothing choice or it will be considered as different. + base.value = MaskToValue(UpdateMaskIfEverything(ValueToMask(value))); + } + } + public override void SetValueWithoutNotify(TChoice newValue) { base.SetValueWithoutNotify(MaskToValue(UpdateMaskIfEverything(ValueToMask(newValue)))); @@ -243,37 +362,60 @@ internal override void AddMenuItems(IGenericMenu menu) throw new ArgumentNullException(nameof(menu)); } - int valueMask = ValueToMask(value); - foreach (var item in m_Choices) { var maskOfItem = GetMaskValueOfItem(item); - var isSelected = false; - switch (maskOfItem) - { - case 0: - if (valueMask == 0) - { - isSelected = true; - } - break; + var isSelected = IsItemSelected(maskOfItem) && !showMixedValue; - case ~0: - if (valueMask == ~0) - { - isSelected = true; - } - break; + menu.AddItem(GetListItemToDisplay(MaskToValue(maskOfItem)), isSelected, () => ChangeValueFromMenu(item)); + } + } - default: - if ((maskOfItem & valueMask) == maskOfItem) - { - isSelected = true; - } - break; - } + private bool IsItemSelected(int maskOfItem) + { + int valueMask = ValueToMask(value); + var isSelected = false; - menu.AddItem(GetListItemToDisplay(MaskToValue(maskOfItem)), isSelected, () => ChangeValueFromMenu(item)); + switch (maskOfItem) + { + case 0: + if (valueMask == 0) + { + isSelected = true; + } + break; + + case ~0: + if (valueMask == ~0) + { + isSelected = true; + } + break; + + default: + if ((maskOfItem & valueMask) == maskOfItem) + { + isSelected = true; + } + break; + } + + return isSelected; + } + + private void UpdateMenuItems() + { + if (m_GenericDropdownMenu == null) + { + return; + } + + foreach (var item in m_Choices) + { + var maskOfItem = GetMaskValueOfItem(item); + var isSelected = IsItemSelected(maskOfItem); + + m_GenericDropdownMenu.UpdateItem(GetListItemToDisplay(MaskToValue(maskOfItem)), isSelected); } } @@ -298,9 +440,9 @@ int UpdateMaskIfEverything(int currentMask) return newMask; } - private void ChangeValueFromMenu(string menuItem) + internal void ChangeValueFromMenu(string menuItem) { - var newMask = ValueToMask(value); + var newMask = showMixedValue ? 0 : ValueToMask(value); var maskFromItem = GetMaskValueOfItem(menuItem); switch (maskFromItem) { @@ -334,6 +476,7 @@ private void ChangeValueFromMenu(string menuItem) } // Finally, make sure to update the value of the mask... value = MaskToValue(newMask); + UpdateMenuItems(); } // Returns the mask to be used for the item... @@ -453,7 +596,7 @@ internal override string GetListItemToDisplay(int itemIndex) internal override string GetValueToDisplay() { - string displayedValue = GetDisplayedValue(rawValue); + string displayedValue = showMixedValue ? mixedValueString : GetDisplayedValue(rawValue); if (ShouldFormatSelectedValue()) displayedValue = m_FormatSelectedValueCallback(displayedValue); return displayedValue; diff --git a/Editor/Mono/UIElements/Controls/ObjectField.cs b/Editor/Mono/UIElements/Controls/ObjectField.cs index cd3300054b..9e184d5e16 100644 --- a/Editor/Mono/UIElements/Controls/ObjectField.cs +++ b/Editor/Mono/UIElements/Controls/ObjectField.cs @@ -11,10 +11,12 @@ namespace UnityEditor.UIElements { /// - /// Makes a field to receive any object type. + /// Makes a field to receive any object type. For more information, refer to [[wiki:UIE-uxml-element-ObjectField|UXML element ObjectField]]. /// public class ObjectField : BaseField { + internal event Action onObjectSelectorShow; + /// /// Instantiates an using the data read from a UXML file. /// @@ -42,6 +44,14 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext ((ObjectField)ve).objectType = m_ObjectType.GetValueFromBag(bag, cc); } } + + internal override bool EqualsCurrentValue(Object value) + { + // If the current value is a missing object reference then we allow anything to be set, even null which is technically the same value here. + if (m_ObjectFieldDisplay.isObjectMissing) + return false; + return base.EqualsCurrentValue(value); + } public override void SetValueWithoutNotify(Object newValue) { @@ -93,16 +103,20 @@ internal void UpdateDisplay() { m_ObjectFieldDisplay.Update(); } + + internal static bool IsMissingObjectReference(SerializedProperty p) => p.propertyType == SerializedPropertyType.ObjectReference && p.objectReferenceInstanceIDValue != 0 && p.objectReferenceValue == null; - private class ObjectFieldDisplay : VisualElement + internal class ObjectFieldDisplay : VisualElement { private readonly ObjectField m_ObjectField; private readonly Image m_ObjectIcon; private readonly Label m_ObjectLabel; + + public bool isObjectMissing { get; private set; } static readonly string ussClassName = "unity-object-field-display"; static readonly string iconUssClassName = ussClassName + "__icon"; - static readonly string labelUssClassName = ussClassName + "__label"; + internal static readonly string labelUssClassName = ussClassName + "__label"; static readonly string acceptDropVariantUssClassName = ussClassName + "--accept-drop"; internal void ShowMixedValue(bool show) @@ -137,7 +151,18 @@ public ObjectFieldDisplay(ObjectField objectField) public void Update() { + isObjectMissing = false; var property = m_ObjectField.GetProperty(serializedPropertyKey) as SerializedProperty; + // UUM-53334, need to check if property is still valid before updating + if (property != null) + { + if (!property.isValid) + { + m_ObjectField.SetProperty(serializedPropertyKey, null); + return; + } + isObjectMissing = IsMissingObjectReference(property); + } var content = EditorGUIUtility.ObjectContent(m_ObjectField.value, m_ObjectField.objectType, property); m_ObjectIcon.image = content.image; m_ObjectLabel.text = content.text; @@ -198,7 +223,7 @@ internal override void ExecuteDefaultActionDisabledAtTarget(EventBase evt) private void OnDragLeave() { // Make sure we've cleared the accept drop look, whether we we in a drop operation or not. - RemoveFromClassList(acceptDropVariantUssClassName); + EnableInClassList(acceptDropVariantUssClassName, false); } private void OnMouseDown(MouseDownEvent evt) @@ -241,6 +266,7 @@ private void OnKeyboardEnter() private void OnKeyboardDelete() { + m_ObjectField.SetProperty(serializedPropertyKey, null); m_ObjectField.value = null; } @@ -265,7 +291,7 @@ private void OnDragUpdated(EventBase evt) if (validatedObject != null) { DragAndDrop.visualMode = DragAndDropVisualMode.Generic; - AddToClassList(acceptDropVariantUssClassName); + EnableInClassList(acceptDropVariantUssClassName, true); evt.StopPropagation(); } @@ -389,6 +415,7 @@ internal void ShowObjectSelector() // Since we have nothing useful to do on the object selector closing action, we just do not assign any callback // All the object changes will be notified through the OnObjectChanged and a "cancellation" (Escape key) on the ObjectSelector is calling the closing callback without any good object ObjectSelector.get.Show(value, objectType, null, allowSceneObjects, null, null, OnObjectChanged); + onObjectSelectorShow?.Invoke(); } private Object TryReadComponentFromGameObject(Object obj, Type type) diff --git a/Editor/Mono/UIElements/Controls/PropertyField.cs b/Editor/Mono/UIElements/Controls/PropertyField.cs index 8a6cddfb5d..cd137b1db2 100644 --- a/Editor/Mono/UIElements/Controls/PropertyField.cs +++ b/Editor/Mono/UIElements/Controls/PropertyField.cs @@ -12,13 +12,15 @@ namespace UnityEditor.UIElements { /// - /// A SerializedProperty wrapper VisualElement that, on Bind(), will generate the correct field elements with the correct bindingPaths. + /// A SerializedProperty wrapper VisualElement that, on Bind(), will generate the correct field elements with the correct binding paths. For more information, refer to [[wiki:UIE-uxml-element-PropertyField|UXML element PropertyField]]. /// public class PropertyField : VisualElement, IBindable { private static readonly Regex s_MatchPPtrTypeName = new Regex(@"PPtr\<(\w+)\>"); internal static readonly string foldoutTitleBoundLabelProperty = "unity-foldout-bound-title"; internal static readonly string decoratorDrawersContainerClassName = "unity-decorator-drawers-container"; + internal static readonly string listViewBoundFieldProperty = "unity-list-view-property-field-bound"; + static readonly string listViewNamePrefix = "unity-list-"; /// /// Instantiates a using the data read from a UXML file. @@ -53,8 +55,7 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext field.label = m_Label.GetValueFromBag(bag, cc); string propPath = m_PropertyPath.GetValueFromBag(bag, cc); - if (!string.IsNullOrEmpty(propPath)) - field.bindingPath = propPath; + field.bindingPath = (string.IsNullOrEmpty(propPath)) ? string.Empty : propPath; } } @@ -71,15 +72,30 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext /// /// Optionally overwrite the label of the generate property field. If no label is provided the string will be taken from the SerializedProperty. /// - public string label { get; set; } + public string label + { + get => m_Label; + set + { + if (m_Label == value) return; + m_Label = value; - private SerializedProperty m_SerializedProperty; - private PropertyField m_ParentPropertyField; - private int m_FoldoutDepth; + // Refresh current label + Rebind(); + } + } - private int m_DrawNestingLevel; - private PropertyField m_DrawParentProperty; - private VisualElement m_DecoratorDrawersContainer; + string m_Label; + SerializedObject m_SerializedObject; + SerializedProperty m_SerializedProperty; + string m_SerializedPropertyReferenceTypeName; + PropertyField m_ParentPropertyField; + int m_FoldoutDepth; + VisualElement m_InspectorElement; + VisualElement m_ContextWidthElement; + int m_DrawNestingLevel; + PropertyField m_DrawParentProperty; + VisualElement m_DecoratorDrawersContainer; SerializedProperty serializedProperty => m_SerializedProperty; @@ -95,6 +111,12 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext /// USS class name of input elements in elements of this type. /// public static readonly string inputUssClassName = ussClassName + "__input"; + /// + /// USS class name of property fields in inspector elements + /// + public static readonly string inspectorElementUssClassName = ussClassName + "__inspector-property"; + + internal static readonly string imguiContainerPropertyUssClassName = ussClassName + "__imgui-container-property"; /// /// PropertyField constructor. @@ -127,6 +149,7 @@ public PropertyField(SerializedProperty property, string label) this.label = label; RegisterCallback(OnAttachToPanel); + RegisterCallback(OnDetachFromPanelEvent); if (property == null) return; @@ -140,6 +163,28 @@ void OnAttachToPanel(AttachToPanelEvent evt) return; m_FoldoutDepth = this.GetFoldoutDepth(); + var currentElement = parent; + while (currentElement != null) + { + if (currentElement.ClassListContains(InspectorElement.ussClassName)) + { + AddToClassList(inspectorElementUssClassName); + m_InspectorElement = currentElement; + } + + if (currentElement.ClassListContains(PropertyEditor.s_MainContainerClassName)) + { + m_ContextWidthElement = currentElement; + break; + } + + currentElement = currentElement.parent; + } + } + + private void OnDetachFromPanelEvent(DetachFromPanelEvent evt) + { + RemoveFromClassList(inspectorElementUssClassName); } [EventInterest(typeof(SerializedPropertyBindEvent))] @@ -158,43 +203,68 @@ protected override void ExecuteDefaultActionAtTarget(EventBase evt) evt.StopPropagation(); } - void Reset(SerializedProperty property) + void Reset(SerializedProperty newProperty) { - m_SerializedProperty = property; + string newPropertyTypeName = null; + var newPropertyType = newProperty.propertyType; - if (m_SerializedProperty != null && m_SerializedProperty.isValid) + if (newPropertyType == SerializedPropertyType.ManagedReference) { - // if we already have a serialized property, determine if the property field can be reused without reset - // this is only supported for non propertydrawer types - if (m_ChildField != null && m_SerializedProperty.propertyType == property.propertyType) + newPropertyTypeName = newProperty.managedReferenceFullTypename; + } + + bool newPropertyTypeIsDifferent = true; + + var serializedObjectIsValid = m_SerializedObject != null && m_SerializedObject.m_NativeObjectPtr != IntPtr.Zero && m_SerializedObject.isValid; + + if (serializedObjectIsValid && m_SerializedProperty != null && m_SerializedProperty.isValid && newPropertyType == m_SerializedProperty.propertyType) + { + if(newPropertyType == SerializedPropertyType.ManagedReference) { - var propertyHandler = ScriptAttributeUtility.GetHandler(m_SerializedProperty); - ResetDecoratorDrawers(propertyHandler); + newPropertyTypeIsDifferent = newPropertyTypeName != m_SerializedPropertyReferenceTypeName; + } + else + { + newPropertyTypeIsDifferent = false; + } + } - var newField = CreateOrUpdateFieldFromProperty(property, m_ChildField); - // there was an issue where we weren't able to swap the bindings on the original field - if (newField != m_ChildField) + m_SerializedProperty = newProperty; + m_SerializedPropertyReferenceTypeName = newPropertyTypeName; + m_SerializedObject = newProperty.serializedObject; + + // if we already have a serialized property, determine if the property field can be reused without reset + // this is only supported for non propertydrawer types + if (m_ChildField != null && !newPropertyTypeIsDifferent) + { + var propertyHandler = ScriptAttributeUtility.GetHandler(m_SerializedProperty); + ResetDecoratorDrawers(propertyHandler); + + var newField = CreateOrUpdateFieldFromProperty(newProperty, m_ChildField); + // there was an issue where we weren't able to swap the bindings on the original field + if (newField != m_ChildField) + { + m_ChildField.Unbind(); + var childIndex = IndexOf(m_ChildField); + if (childIndex >= 0) { - m_ChildField.Unbind(); - var childIndex = IndexOf(m_ChildField); - if (childIndex >= 0) - { - m_ChildField.RemoveFromHierarchy(); - m_ChildField = newField; - hierarchy.Insert(childIndex, m_ChildField); - } + m_ChildField.RemoveFromHierarchy(); + m_ChildField = newField; + hierarchy.Insert(childIndex, m_ChildField); } - - return; } + + return; } Clear(); m_ChildField?.Unbind(); m_ChildField = null; + m_CustomPropertyGUI?.Unbind(); + m_CustomPropertyGUI = null; m_DecoratorDrawersContainer = null; - if (property == null) + if (m_SerializedProperty == null || !m_SerializedProperty.isValid) return; ComputeNestingLevel(); @@ -208,11 +278,20 @@ void Reset(SerializedProperty property) { if (handler.hasPropertyDrawer) { - customPropertyGUI = handler.propertyDrawer.CreatePropertyGUI(m_SerializedProperty); + handler.propertyDrawer.m_PreferredLabel = label ?? serializedProperty.localizedDisplayName; + + // UUM-12851: copy the property, as user code may iterate on it or leave it in a different state. + customPropertyGUI = handler.propertyDrawer.CreatePropertyGUI(m_SerializedProperty.Copy()); + + m_CustomPropertyGUI = customPropertyGUI; if (customPropertyGUI == null) { customPropertyGUI = CreatePropertyIMGUIContainer(); + + AddToClassList(imguiContainerPropertyUssClassName); + + m_imguiChildField = customPropertyGUI; } else { @@ -235,14 +314,18 @@ void Reset(SerializedProperty property) } if (m_SerializedProperty.propertyType == SerializedPropertyType.ManagedReference) - BindingExtensions.TrackPropertyValue(this, m_SerializedProperty, Reset); + { + var fieldToBind = m_ChildField == null ? m_CustomPropertyGUI : m_ChildField; + BindingExtensions.TrackPropertyValue(fieldToBind, m_SerializedProperty, + (e) => this.Bind(m_SerializedProperty.serializedObject)); + } } private void ResetDecoratorDrawers(PropertyHandler handler) { var decorators = handler.decoratorDrawers; - if (decorators == null || decorators.Count == 0) + if (decorators == null || decorators.Count == 0 || m_DrawNestingLevel > 0) { if (m_DecoratorDrawersContainer != null) { @@ -274,8 +357,9 @@ private void ResetDecoratorDrawers(PropertyHandler handler) { var decoratorRect = new Rect(); decoratorRect.height = decorator.GetHeight(); - decoratorRect.width = resolvedStyle.width; + decoratorRect.width = ve.rect.width; decorator.OnGUI(decoratorRect); + ve.style.height = decoratorRect.height; }); ve.style.height = decorator.GetHeight(); } @@ -291,54 +375,115 @@ private void Reset(SerializedPropertyBindEvent evt) private VisualElement CreatePropertyIMGUIContainer() { - GUIContent customLabel = string.IsNullOrEmpty(label) ? null : new GUIContent(label); - - return new IMGUIContainer(() => + var imguiContainer = new IMGUIContainer(() => { var originalWideMode = InspectorElement.SetWideModeForWidth(this); + var originalHierarchyMode = EditorGUIUtility.hierarchyMode; + EditorGUIUtility.hierarchyMode = true; + var oldLabelWidth = EditorGUIUtility.labelWidth; + try { if (!serializedProperty.isValid) return; - EditorGUI.BeginChangeCheck(); - serializedProperty.serializedObject.Update(); - - if (m_FoldoutDepth > 0) - EditorGUI.indentLevel += m_FoldoutDepth; - - // Wait at last minute to call GetHandler, sometimes the handler cache is cleared between calls. - var handler = ScriptAttributeUtility.GetHandler(serializedProperty); - using (var nestingContext = handler.ApplyNestingContext(m_DrawNestingLevel)) + EditorGUILayout.BeginVertical(EditorStyles.inspectorHorizontalDefaultMargins); { - if (label == null) + if (m_InspectorElement is InspectorElement inspectorElement) { - EditorGUILayout.PropertyField(serializedProperty, true); + //set the current PropertyHandlerCache to the current editor + ScriptAttributeUtility.propertyHandlerCache = inspectorElement.editor.propertyHandlerCache; } - else if (label == string.Empty) + + EditorGUI.BeginChangeCheck(); + serializedProperty.serializedObject.Update(); + + if (classList.Contains(inspectorElementUssClassName)) { - EditorGUILayout.PropertyField(serializedProperty, GUIContent.none, true); + var spacing = 0f; + + if (m_imguiChildField != null) + { + spacing = m_imguiChildField.worldBound.x - m_InspectorElement.worldBound.x - m_InspectorElement.resolvedStyle.paddingLeft - resolvedStyle.marginLeft; + } + + var imguiSpacing = EditorGUI.kLabelWidthMargin; + var contextWidthElement = m_ContextWidthElement ?? m_InspectorElement; + var contextWidth = contextWidthElement.resolvedStyle.width; + var labelWidth = (contextWidth * EditorGUI.kLabelWidthRatio - imguiSpacing - spacing); + var minWidth = EditorGUI.kMinLabelWidth + EditorGUI.kLabelWidthPadding; + var minLabelWidth = Mathf.Max(minWidth - spacing, 0f); + + EditorGUIUtility.labelWidth = Mathf.Max(labelWidth, minLabelWidth); } else { - EditorGUILayout.PropertyField(serializedProperty, new GUIContent(label), true); + if (m_FoldoutDepth > 0) + EditorGUI.indentLevel += m_FoldoutDepth; } - } - if (m_FoldoutDepth > 0) - EditorGUI.indentLevel -= m_FoldoutDepth; + // Wait at last minute to call GetHandler, sometimes the handler cache is cleared between calls. + var handler = ScriptAttributeUtility.GetHandler(serializedProperty); + using (var nestingContext = handler.ApplyNestingContext(m_DrawNestingLevel)) + { + // Decorator drawers are already handled on the uitk side + handler.skipDecoratorDrawers = true; - serializedProperty.serializedObject.ApplyModifiedProperties(); - if (EditorGUI.EndChangeCheck()) - { - DispatchPropertyChangedEvent(); + var previousLeftMarginCoord = EditorGUIUtility.leftMarginCoord; + + if (m_InspectorElement != null && m_imguiChildField != null) + { + // Set a left margin offset to align the prefab override bar with the property field + var extraLeftMargin = m_InspectorElement.worldBound.x; + EditorGUIUtility.leftMarginCoord = -m_imguiChildField.worldBound.x + extraLeftMargin; + } + + if (label == null) + { + EditorGUILayout.PropertyField(serializedProperty, true); + } + else if (label == string.Empty) + { + EditorGUILayout.PropertyField(serializedProperty, GUIContent.none, true); + } + else + { + EditorGUILayout.PropertyField(serializedProperty, new GUIContent(label), true); + } + + if (m_InspectorElement != null && m_imguiChildField != null) + { + // Reset the left margin to the original value + EditorGUIUtility.leftMarginCoord = previousLeftMarginCoord; + } + } + + if (!classList.Contains(inspectorElementUssClassName)) + { + if (m_FoldoutDepth > 0) + EditorGUI.indentLevel -= m_FoldoutDepth; + } + + serializedProperty.serializedObject.ApplyModifiedProperties(); + if (EditorGUI.EndChangeCheck()) + { + DispatchPropertyChangedEvent(); + } } + EditorGUILayout.EndVertical(); } finally { EditorGUIUtility.wideMode = originalWideMode; + EditorGUIUtility.hierarchyMode = originalHierarchyMode; + if (classList.Contains(inspectorElementUssClassName)) + { + EditorGUIUtility.labelWidth = oldLabelWidth; + } } }); + + return imguiContainer; } private void ComputeNestingLevel() @@ -413,6 +558,9 @@ private void UpdateArrayFoldout( /// stores the child field if there is only a single child. Used for updating bindings when this field is rebound. /// private VisualElement m_ChildField; + private VisualElement m_CustomPropertyGUI; + + private VisualElement m_imguiChildField; private VisualElement m_ChildrenContainer; void TrimChildrenContainerSize(int targetSize) @@ -456,11 +604,7 @@ void RefreshChildrenProperties(SerializedProperty property, bool bindNewFields) if (propCount < m_ChildrenProperties.Count) { field = m_ChildrenProperties[propCount]; - if (field.bindingPath != propPath) - { - field.bindingPath = property.propertyPath; - field.Bind(property.serializedObject); - } + field.bindingPath = propPath; } else { @@ -468,12 +612,12 @@ void RefreshChildrenProperties(SerializedProperty property, bool bindNewFields) field.m_ParentPropertyField = this; m_ChildrenProperties.Add(field); field.bindingPath = property.propertyPath; - - if (bindNewFields) - field.Bind(property.serializedObject); } field.name = "unity-property-field-" + property.propertyPath; + if (bindNewFields) + field.Bind(property.serializedObject); + // Not yet knowing what type of field we are dealing with, we defer the showMixedValue value setting // to be automatically done via the next Reset call m_ChildrenContainer.Add(field); @@ -487,10 +631,13 @@ void RefreshChildrenProperties(SerializedProperty property, bool bindNewFields) private VisualElement CreateFoldout(SerializedProperty property, object originalField = null) { property = property.Copy(); - var foldout = originalField != null && originalField is Foldout ? originalField as Foldout : new Foldout(); - bool hasCustomLabel = !string.IsNullOrEmpty(label); + if (originalField is not Foldout foldout) + { + foldout = new Foldout(); + } + + var hasCustomLabel = !string.IsNullOrEmpty(label); foldout.text = hasCustomLabel ? label : property.localizedDisplayName; - foldout.value = property.isExpanded; foldout.bindingPath = property.propertyPath; foldout.name = "unity-foldout-" + property.propertyPath; @@ -512,17 +659,47 @@ private VisualElement CreateFoldout(SerializedProperty property, object original m_ChildrenContainer = foldout; - RefreshChildrenProperties(property, false); + if (property.isExpanded) + { + RefreshChildrenProperties(property, false); + } + else + { + foldout.RegisterValueChangedCallback(RefreshOnlyWhenExpanded); + } return foldout; } - private VisualElement ConfigureField(TField field, SerializedProperty property, Func factory) + void RefreshOnlyWhenExpanded(ChangeEvent evt) + { + if (evt.newValue && evt.target is Foldout foldout && object.ReferenceEquals(evt.target, m_ChildField)) + { + foldout.UnregisterCallback>(RefreshOnlyWhenExpanded); + RefreshChildrenProperties(m_SerializedProperty, true); + } + } + + void OnFieldValueChanged(EventBase evt) + { + if (evt.target == m_ChildField && m_SerializedProperty.isValid) + { + if (m_SerializedProperty.propertyType == SerializedPropertyType.ArraySize && evt is ChangeEvent changeEvent) + { + UpdateArrayFoldout(changeEvent, this, m_ParentPropertyField); + } + + DispatchPropertyChangedEvent(); + } + } + + private TField ConfigureField(TField field, SerializedProperty property, Func factory) where TField : BaseField { if (field == null) { field = factory(); + field.RegisterValueChangedCallback((evt) => OnFieldValueChanged(evt)); } var fieldLabel = label ?? property.localizedDisplayName; field.bindingPath = property.propertyPath; @@ -530,37 +707,89 @@ private VisualElement ConfigureField(TField field, SerializedPro field.name = "unity-input-" + property.propertyPath; field.label = fieldLabel; + ConfigureFieldStyles(field); + ConfigureTooltip(property, field); + + return field; + } + + private void ConfigureTooltip(SerializedProperty property, BaseField field) + { + var fieldInfo = ScriptAttributeUtility.GetFieldInfoFromProperty(property, out _); + + if (fieldInfo == null) + { + return; + } + + var hasTooltipAttribute = fieldInfo.IsDefined(typeof(TooltipAttribute), false); + + // Don't display elided tooltip if the property has a TooltipAttribute. + field.labelElement.displayTooltipWhenElided = !hasTooltipAttribute; + } + + private VisualElement ConfigureLabelOnly(SerializedProperty property) + { + var wrapper = new VisualElement(); + // add the base classes for the field which will provide the correct margins + wrapper.AddToClassList(BaseField.ussClassName); + wrapper.AddToClassList(BaseField.alignedFieldUssClassName); + + var labelOnly = new Label(); + var fieldLabel = label ?? property.localizedDisplayName; + labelOnly.name = "unity-input-" + property.propertyPath; + labelOnly.text = fieldLabel; + + labelOnly.AddToClassList(labelUssClassName); + wrapper.Add(labelOnly); + + return wrapper; + } + + internal static void ConfigureFieldStyles(TField field) where TField : BaseField + { field.labelElement.AddToClassList(labelUssClassName); field.visualInput.AddToClassList(inputUssClassName); field.AddToClassList(BaseField.alignedFieldUssClassName); - field.RegisterValueChangedCallback((evt) => + var nestedFields = field.visualInput.Query( + classes: new []{BaseField.ussClassName, BaseCompositeField.ussClassName} ); + + nestedFields.ForEach(x => { - if (evt.target == field) - { - DispatchPropertyChangedEvent(); - } + x.AddToClassList(BaseField.alignedFieldUssClassName); }); - - return field; } - VisualElement ConfigureListView(ListView listView, SerializedProperty property) + VisualElement ConfigureListView(ListView listView, SerializedProperty property, Func factory) { + if (listView == null) + { + listView = factory(); + listView.showBorder = true; + listView.selectionType = SelectionType.Multiple; + listView.showAddRemoveFooter = true; + listView.showBoundCollectionSize = true; + listView.showFoldoutHeader = true; + listView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight; + listView.showAlternatingRowBackgrounds = AlternatingRowBackground.None; + listView.itemsSourceSizeChanged += DispatchPropertyChangedEvent; + } + var propertyCopy = property.Copy(); - listView.reorderMode = ListViewReorderMode.Animated; - listView.showBorder = true; - listView.showAddRemoveFooter = true; - listView.showBoundCollectionSize = true; - listView.showFoldoutHeader = true; + var listViewName = $"{listViewNamePrefix}{property.propertyPath}"; listView.headerTitle = string.IsNullOrEmpty(label) ? propertyCopy.localizedDisplayName : label; - listView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight; listView.userData = propertyCopy; - listView.showAlternatingRowBackgrounds = AlternatingRowBackground.None; listView.bindingPath = property.propertyPath; - listView.viewDataKey = property.propertyPath; - listView.name = "unity-list-" + property.propertyPath; - listView.Bind(property.serializedObject); + listView.viewDataKey = listViewName; + listView.name = listViewName; + listView.SetProperty(listViewBoundFieldProperty, this); + + // Make list view foldout react even when disabled, like EditorGUILayout.Foldout. + var toggle = listView.Q(className: Foldout.toggleUssClassName); + if (toggle != null) + toggle.m_Clickable.acceptClicksIfDisabled = true; + return listView; } @@ -568,7 +797,8 @@ private VisualElement CreateOrUpdateFieldFromProperty(SerializedProperty propert { var propertyType = property.propertyType; - if (EditorGUI.HasVisibleChildFields(property, true)) + + if (EditorGUI.HasVisibleChildFields(property, true) && !property.isArray) return CreateFoldout(property, originalField); TrimChildrenContainerSize(0); @@ -578,9 +808,27 @@ private VisualElement CreateOrUpdateFieldFromProperty(SerializedProperty propert { case SerializedPropertyType.Integer: if (property.type == "long") - return ConfigureField(originalField as LongField, property, () => new LongField()); - return ConfigureField(originalField as IntegerField, property, () => new IntegerField()); + return ConfigureField(originalField as LongField, property, + () => new LongField()); + if (property.type == "ulong") + return ConfigureField(originalField as UnsignedLongField, property, + () => new UnsignedLongField()); + if (property.type == "uint") + return ConfigureField(originalField as UnsignedIntegerField, property, + () => new UnsignedIntegerField()); + + { + var intField = ConfigureField(originalField as IntegerField, property, + () => new IntegerField()) as IntegerField; + if (intField != null) + { + // If the field was recycled from an ArraySize property + intField.isDelayed = false; + } + + return intField; + } case SerializedPropertyType.Boolean: return ConfigureField(originalField as Toggle, property, () => new Toggle()); @@ -590,16 +838,23 @@ private VisualElement CreateOrUpdateFieldFromProperty(SerializedProperty propert return ConfigureField(originalField as FloatField, property, () => new FloatField()); case SerializedPropertyType.String: - return ConfigureField(originalField as TextField, property, () => new TextField()); + { + var strField = ConfigureField(originalField as TextField, property, + () => new TextField()) as TextField; + strField.maxLength = -1; //Can happen when used from Character + return strField; + } case SerializedPropertyType.Color: return ConfigureField(originalField as ColorField, property, () => new ColorField()); + case SerializedPropertyType.ManagedReference: + // the previous behavior was to show the label even if the field wasn't initialized + return ConfigureLabelOnly(property); + case SerializedPropertyType.ObjectReference: { - ObjectField field = originalField as ObjectField; - if (field == null) - field = new ObjectField(); + var field = ConfigureField(originalField as ObjectField, property, () => new ObjectField()); Type requiredType = null; @@ -618,16 +873,21 @@ private VisualElement CreateOrUpdateFieldFromProperty(SerializedProperty propert { if (!objectTypes.Name.Equals(targetTypeName, StringComparison.OrdinalIgnoreCase)) continue; + + // We ignore C# types as they can can be confused with a built-in type with the same name, + // we can use the FieldInfo to find MonoScript types. (UUM-29499) + if (typeof(MonoBehaviour).IsAssignableFrom(objectTypes) || typeof(ScriptableObject).IsAssignableFrom(objectTypes)) + continue; + requiredType = objectTypes; break; } } - field.SetProperty(ObjectField.serializedPropertyKey, property); field.SetObjectTypeWithoutDisplayUpdate(requiredType); field.UpdateDisplay(); - return ConfigureField(field, property, () => new ObjectField()); + return field; } case SerializedPropertyType.LayerMask: return ConfigureField(originalField as LayerMaskField, property, () => new LayerMaskField()); @@ -692,13 +952,18 @@ private VisualElement CreateOrUpdateFieldFromProperty(SerializedProperty propert { IntegerField field = originalField as IntegerField; if (field == null) + { field = new IntegerField(); + field.RegisterValueChangedCallback((evt) => OnFieldValueChanged(evt)); + } field.SetValueWithoutNotify(property.intValue); // This avoids the OnValueChanged/Rebind feedback loop. field.isDelayed = true; // To match IMGUI. Also, focus is lost anyway due to the rebind. - field.RegisterValueChangedCallback((e) => { UpdateArrayFoldout(e, this, m_ParentPropertyField); }); return ConfigureField(field, property, () => new IntegerField()); } + case SerializedPropertyType.FixedBufferSize: + return ConfigureField(originalField as IntegerField, property, () => new IntegerField()); + case SerializedPropertyType.Character: { TextField field = originalField as TextField; @@ -720,8 +985,6 @@ private VisualElement CreateOrUpdateFieldFromProperty(SerializedProperty propert return null; case SerializedPropertyType.ExposedReference: return null; - case SerializedPropertyType.FixedBufferSize: - return null; case SerializedPropertyType.Vector2Int: return ConfigureField(originalField as Vector2IntField, property, () => new Vector2IntField()); @@ -735,10 +998,12 @@ private VisualElement CreateOrUpdateFieldFromProperty(SerializedProperty propert case SerializedPropertyType.BoundsInt: return ConfigureField(originalField as BoundsIntField, property, () => new BoundsIntField()); + case SerializedPropertyType.Hash128: + return ConfigureField(originalField as Hash128Field, property, () => new Hash128Field()); case SerializedPropertyType.Generic: return property.isArray - ? ConfigureListView(new ListView(), property) + ? ConfigureListView(originalField as ListView, property, () => new ListView()) : null; default: @@ -775,7 +1040,6 @@ private void RegisterPropertyChangesOnCustomDrawerElement(VisualElement customPr customPropertyDrawer.RegisterCallback>((changeEvent) => AsyncDispatchPropertyChangedEvent()); customPropertyDrawer.RegisterCallback>((changeEvent) => AsyncDispatchPropertyChangedEvent()); customPropertyDrawer.RegisterCallback>((changeEvent) => AsyncDispatchPropertyChangedEvent()); - customPropertyDrawer.RegisterCallback>((changeEvent) => AsyncDispatchPropertyChangedEvent()); customPropertyDrawer.RegisterCallback>((changeEvent) => AsyncDispatchPropertyChangedEvent()); customPropertyDrawer.RegisterCallback>((changeEvent) => AsyncDispatchPropertyChangedEvent()); customPropertyDrawer.RegisterCallback>((changeEvent) => AsyncDispatchPropertyChangedEvent()); diff --git a/Editor/Mono/UIElements/Controls/TagField.cs b/Editor/Mono/UIElements/Controls/TagField.cs index 56ce9fb9ce..956806f59c 100644 --- a/Editor/Mono/UIElements/Controls/TagField.cs +++ b/Editor/Mono/UIElements/Controls/TagField.cs @@ -11,7 +11,7 @@ namespace UnityEditor.UIElements { /// - /// A editor. + /// A editor. For more information, refer to [[wiki:UIE-uxml-element-TagField|UXML element TagField]]. /// public class TagField : PopupField { @@ -115,16 +115,16 @@ static List InitializeTags() public new static readonly string inputUssClassName = ussClassName + "__input"; /// - /// Initializes and returns an instance of CurveField. + /// Initializes and returns an instance of TagField. /// public TagField() : this(null) {} /// - /// Initializes and returns an instance of CurveField. + /// Initializes and returns an instance of TagField. /// /// The text to use as a label for the field. - /// The initial tag value this field should use. + /// The initial tag value this field uses. public TagField(string label, string defaultValue = null) : base(label) { @@ -149,7 +149,7 @@ internal override void AddMenuItems(IGenericMenu menu) choices = InitializeTags(); foreach (var menuItem in choices) { - var isSelected = (menuItem == value); + var isSelected = (menuItem == value) && !showMixedValue; menu.AddItem(menuItem, isSelected, () => ChangeValueFromMenu(menuItem)); } menu.AddSeparator(String.Empty); diff --git a/Editor/Mono/UIElements/Controls/Toolbar/IToolbarMenuElement.cs b/Editor/Mono/UIElements/Controls/Toolbar/IToolbarMenuElement.cs index 4fa347fe68..027ccbb8ba 100644 --- a/Editor/Mono/UIElements/Controls/Toolbar/IToolbarMenuElement.cs +++ b/Editor/Mono/UIElements/Controls/Toolbar/IToolbarMenuElement.cs @@ -45,7 +45,7 @@ public static void ShowMenu(this IToolbarMenuElement tbe) // windows are stuck to the left side of the screen as well as our menus) worldBound.x = 1f; } - tbe.menu.DoDisplayEditorMenu(worldBound); + tbe.menu.DoDisplayEditorMenu(worldBound, ve); } } } diff --git a/Editor/Mono/UIElements/Controls/Toolbar/SearchFieldBase.cs b/Editor/Mono/UIElements/Controls/Toolbar/SearchFieldBase.cs index 4957210cda..e27992a6f9 100644 --- a/Editor/Mono/UIElements/Controls/Toolbar/SearchFieldBase.cs +++ b/Editor/Mono/UIElements/Controls/Toolbar/SearchFieldBase.cs @@ -70,6 +70,24 @@ public T value /// public static readonly string popupVariantUssClassName = ussClassName + "--popup"; + /// + /// Defines for the . + /// + /// + /// This class defines the properties of a SearchFieldBase element that you can + /// use in a UXML asset. + /// + public new class UxmlTraits : VisualElement.UxmlTraits + { + /// + /// Constructor. + /// + public UxmlTraits() + { + focusable.defaultValue = true; + } + } + protected SearchFieldBase() { isCompositeRoot = true; diff --git a/Editor/Mono/UIElements/Controls/Toolbar/Toolbar.cs b/Editor/Mono/UIElements/Controls/Toolbar/Toolbar.cs index 051f644454..a621abc7b5 100644 --- a/Editor/Mono/UIElements/Controls/Toolbar/Toolbar.cs +++ b/Editor/Mono/UIElements/Controls/Toolbar/Toolbar.cs @@ -7,7 +7,7 @@ namespace UnityEditor.UIElements { /// - /// A toolbar for tool windows. + /// A toolbar for tool windows. For more information, refer to [[wiki:UIE-uxml-element-Toolbar|UXML element Toolbar]]. /// public class Toolbar : VisualElement { diff --git a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarBreadcrumbs.cs b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarBreadcrumbs.cs index d66a1a8719..09843a4803 100644 --- a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarBreadcrumbs.cs +++ b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarBreadcrumbs.cs @@ -9,7 +9,7 @@ namespace UnityEditor.UIElements { /// - /// Creates a breadcrumb UI element for the toolbar to help users navigate a hierarchy. For example, the visual scripting breadcrumb toolbar makes it easier to explore scripts because users can jump to any level of the script by clicking a breadcrumb item. + /// Creates a breadcrumb UI element for the toolbar to help users navigate a hierarchy. For example, the visual scripting breadcrumb toolbar makes it easier to explore scripts because users can jump to any level of the script by clicking a breadcrumb item. For more information, refer to [[wiki:UIE-uxml-element-ToolbarBreadcrumbs|UXML element ToolbarBreadcrumbs]]. /// /// /// Represents a breadcrumb trail to facilitate navigation between related items in a hierarchy. diff --git a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarButton.cs b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarButton.cs index 77ae366fea..04f028e864 100644 --- a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarButton.cs +++ b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarButton.cs @@ -8,7 +8,7 @@ namespace UnityEditor.UIElements { /// - /// A button for the toolbar. + /// A button for the toolbar. For more information, refer to [[wiki:UIE-uxml-element-ToolbarButton|UXML element ToolbarButton]]. /// public class ToolbarButton : Button { diff --git a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarMenu.cs b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarMenu.cs index c100eac81b..5e0610b12a 100644 --- a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarMenu.cs +++ b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarMenu.cs @@ -7,7 +7,7 @@ namespace UnityEditor.UIElements { /// - /// A drop-down menu for the toolbar. + /// A drop-down menu for the toolbar. For more information, refer to [[wiki:UIE-uxml-element-ToolbarMenu|UXML element ToolbarMenu]]. /// public class ToolbarMenu : TextElement, IToolbarMenuElement { diff --git a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarPopupSearchField.cs b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarPopupSearchField.cs index 1d88118fd8..0aec5b6518 100644 --- a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarPopupSearchField.cs +++ b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarPopupSearchField.cs @@ -7,14 +7,22 @@ namespace UnityEditor.UIElements { /// - /// The pop-up search field for the toolbar. The search field includes a menu button. + /// The pop-up search field for the toolbar. The search field includes a menu button. For more information, refer to [[wiki:UIE-uxml-element-ToolbarPopupSearchField|UXML element ToolbarPopupSearchField]]. /// public class ToolbarPopupSearchField : ToolbarSearchField, IToolbarMenuElement { /// /// Instantiates a using the data read from a UXML file. /// - public new class UxmlFactory : UxmlFactory {} + public new class UxmlFactory : UxmlFactory {} + /// + /// Defines for the . + /// + /// + /// This class defines the properties of a ToolbarPopupSearchField element that you can + /// use in a UXML asset. + /// + public new class UxmlTraits : ToolbarSearchField.UxmlTraits {} /// /// The menu used by the pop-up search field element. diff --git a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarSearchField.cs b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarSearchField.cs index c99d6cd9e6..ddb4c52ebc 100644 --- a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarSearchField.cs +++ b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarSearchField.cs @@ -9,7 +9,7 @@ namespace UnityEditor.UIElements { /// - /// A search field for the toolbar. + /// A search field for the toolbar. For more information, refer to [[wiki:UIE-uxml-element-ToolbarSearchField|UXML element ToolbarSearchField]]. /// public class ToolbarSearchField : SearchFieldBase { @@ -72,7 +72,16 @@ public override void SetValueWithoutNotify(string newValue) /// /// Instantiates a using the data read from a UXML file. /// - public new class UxmlFactory : UxmlFactory {} + public new class UxmlFactory : UxmlFactory {} + + /// + /// Defines for the . + /// + /// + /// This class defines the properties of a ToolbarSearchField element that you can + /// use in a UXML asset. + /// + public new class UxmlTraits : SearchFieldBase.UxmlTraits {} /// /// Constructor. diff --git a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarSpacer.cs b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarSpacer.cs index a2651ef286..6414b1196f 100644 --- a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarSpacer.cs +++ b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarSpacer.cs @@ -8,7 +8,7 @@ namespace UnityEditor.UIElements { /// - /// A toolbar spacer of static size. + /// A toolbar spacer of static size. For more information, refer to [[wiki:UIE-uxml-element-ToolbarSpacer|UXML element ToolbarSpacer]]. /// public class ToolbarSpacer : VisualElement { @@ -43,7 +43,7 @@ public ToolbarSpacer() } /// - /// True if the spacer will stretch or shring to occupy available space. + /// Return true if the spacer stretches or shrinks to occupy available space. /// public bool flex { diff --git a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarToggle.cs b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarToggle.cs index c64ace3828..ddcccf7e3e 100644 --- a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarToggle.cs +++ b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarToggle.cs @@ -7,7 +7,7 @@ namespace UnityEditor.UIElements { /// - /// A toggle for the toolbar. + /// A toggle for the toolbar. For more information, refer to [[wiki:UIE-uxml-element-ToolbarToggle|UXML element ToolbarToggle]]. /// public class ToolbarToggle : Toggle { @@ -18,7 +18,16 @@ public class ToolbarToggle : Toggle /// /// Defines for the . /// - public new class UxmlTraits : Toggle.UxmlTraits {} + public new class UxmlTraits : Toggle.UxmlTraits + { + /// + /// Constructor. + /// + public UxmlTraits() + { + focusable.defaultValue = false; + } + } /// /// USS class name of elements of this type. diff --git a/Editor/Mono/UIElements/DefaultMainToolbar.cs b/Editor/Mono/UIElements/DefaultMainToolbar.cs index e8d5b0e296..ba86015b08 100644 --- a/Editor/Mono/UIElements/DefaultMainToolbar.cs +++ b/Editor/Mono/UIElements/DefaultMainToolbar.cs @@ -14,8 +14,12 @@ static IEnumerable leftToolbar { get { + //Modules/EditorToolbar/ToolbarElements/*.cs yield return "Services/Account"; yield return "Services/Cloud"; + //com.unity.collab-proxy/Editor/PlasticSCM/Toolbar/ToolbarButton.cs + yield return "Services/Version Control"; + //Editor/Mono/GUI/Toolbars/MainToolbarImguiContainer.cs yield return "Editor Utility/Imgui Subtoolbars"; } } @@ -24,6 +28,7 @@ static IEnumerable middleToolbar { get { + // Modules/EditorToolbar/ToolbarElements/PlayModeButtons.cs yield return "Editor Utility/Play Mode"; } } @@ -32,6 +37,7 @@ static IEnumerable rightToolbar { get { + // Modules/EditorToolbar/ToolbarElements/*.cs yield return "Editor Utility/Layout"; yield return "Editor Utility/Layers"; yield return "Editor Utility/Search"; diff --git a/Editor/Mono/UIElements/Drawers/Internal/UnityEventItem.cs b/Editor/Mono/UIElements/Drawers/Internal/UnityEventItem.cs index 13f72b900f..2dc4abce1c 100644 --- a/Editor/Mono/UIElements/Drawers/Internal/UnityEventItem.cs +++ b/Editor/Mono/UIElements/Drawers/Internal/UnityEventItem.cs @@ -97,6 +97,7 @@ public UnityEventItem() internal void BindFields(UnityEventDrawer.PropertyData propertyData, Func createMenuCallback, Func formatSelectedValueCallback, Func getArgumentCallback) { + functionDropdown.formatSelectedValueCallback = formatSelectedValueCallback; callStateDropdown.BindProperty(propertyData.callState); listenerTarget.BindProperty(propertyData.listenerTarget); functionDropdown.BindProperty(propertyData.methodName); @@ -128,8 +129,6 @@ internal void BindFields(UnityEventDrawer.PropertyData propertyData, Func + /// Checks if any Editor windows have a bindable element currently focused. + /// + /// True if a bindable element is focused; false otherwise. + public static bool AreBindableElementsSelected() + { + foreach (var window in EditorWindow.activeEditorWindows) + { + var focusController = window.rootVisualElement?.panel?.focusController; + + // We only care about elements that are bindable, as they are the only ones that could be making changes to the prefab. + if (focusController?.focusedElement is BindableElement) + return true; + } + return false; + } + } +} diff --git a/Editor/Mono/UIElements/EditorGenericDropdownMenu.cs b/Editor/Mono/UIElements/EditorGenericDropdownMenu.cs new file mode 100644 index 0000000000..fb691075de --- /dev/null +++ b/Editor/Mono/UIElements/EditorGenericDropdownMenu.cs @@ -0,0 +1,101 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using UnityEngine; +using UnityEngine.UIElements; + +namespace UnityEditor.UIElements +{ + internal class EditorGenericDropdownMenu : IGenericMenu + { + private GenericDropdownMenu m_GenericDropdownMenu; + internal GenericDropdownMenu genericDropdownMenu => m_GenericDropdownMenu; + + private EditorGenericDropdownMenuWindowContent m_WindowContent; + internal EditorGenericDropdownMenuWindowContent windowContent => m_WindowContent; + + private Rect m_DropdownPosition; + + public EditorGenericDropdownMenu() + { + m_GenericDropdownMenu = new GenericDropdownMenu() + { + isSingleSelectionDropdown = false, + closeOnParentResize = false + }; + + m_GenericDropdownMenu.menuContainer.RegisterCallback(OnAttachToPanel); + m_GenericDropdownMenu.menuContainer.RegisterCallback(OnDetachFromPanel); + } + + private void OnAttachToPanel(AttachToPanelEvent evt) + { + m_GenericDropdownMenu.scrollView.RegisterCallback(OnGeometryChanged); + } + + private void OnDetachFromPanel(DetachFromPanelEvent evt) + { + m_GenericDropdownMenu.scrollView.UnregisterCallback(OnGeometryChanged); + if (m_WindowContent != null && m_WindowContent.editorWindow != null) + { + m_WindowContent.editorWindow.Close(); + } + } + + private void OnGeometryChanged(GeometryChangedEvent evt) + { + if (m_WindowContent != null && !float.IsNaN(m_GenericDropdownMenu.scrollView.layout.size.x)) + { + var scrollHeight = m_GenericDropdownMenu.scrollView.layout.height + + m_GenericDropdownMenu.scrollView.resolvedStyle.borderBottomWidth + + m_GenericDropdownMenu.outerContainer.resolvedStyle.borderTopWidth; + var dropdownRect = new Rect(0f, 0f, m_DropdownPosition.width, scrollHeight); + var adjustedDropdownRect = ContainerWindow.FitRectToScreen(dropdownRect, dropdownRect.center, true, null); + + if (dropdownRect.height > adjustedDropdownRect.height) + { + // Dropdown didn't fit on screen. Add some padding + adjustedDropdownRect.height -= 10f; + } + + m_GenericDropdownMenu.outerContainer.style.width = adjustedDropdownRect.width; + m_GenericDropdownMenu.outerContainer.style.height = adjustedDropdownRect.height; + m_WindowContent.windowSize = adjustedDropdownRect.size; + } + } + + public void AddItem(string itemName, bool isChecked, Action action) + { + m_GenericDropdownMenu.AddItem(itemName, isChecked, action); + } + + public void AddItem(string itemName, bool isChecked, Action action, object data) + { + m_GenericDropdownMenu.AddItem(itemName, isChecked, action, data); + } + + public void AddDisabledItem(string itemName, bool isChecked) + { + m_GenericDropdownMenu.AddDisabledItem(itemName, isChecked); + } + + public void AddSeparator(string path) + { + m_GenericDropdownMenu.AddSeparator(path); + } + + public void DropDown(Rect position, VisualElement targetElement = null, bool anchored = false) + { + m_DropdownPosition = position; + m_WindowContent = new EditorGenericDropdownMenuWindowContent(m_GenericDropdownMenu); + PopupWindow.Show(position, m_WindowContent); + } + + internal void UpdateItem(string itemName, bool isChecked) + { + m_GenericDropdownMenu.UpdateItem(itemName, isChecked); + } + } +} diff --git a/Editor/Mono/UIElements/EditorMenuExtensions.cs b/Editor/Mono/UIElements/EditorMenuExtensions.cs index 4b8dd955bf..e949c312ff 100644 --- a/Editor/Mono/UIElements/EditorMenuExtensions.cs +++ b/Editor/Mono/UIElements/EditorMenuExtensions.cs @@ -2,14 +2,16 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; using UnityEngine; using UnityEngine.UIElements; +using static UnityEngine.GraphicsBuffer; namespace UnityEditor.UIElements { static class EditorMenuExtensions { - static IGenericMenu PrepareMenu(DropdownMenu menu, EventBase triggerEvent) + static GenericOSMenu PrepareMenu(DropdownMenu menu, EventBase triggerEvent) { menu.PrepareForDisplay(triggerEvent); @@ -53,9 +55,17 @@ static IGenericMenu PrepareMenu(DropdownMenu menu, EventBase triggerEvent) return genericMenu; } - public static void DoDisplayEditorMenu(this DropdownMenu menu, Rect rect) + public static void DoDisplayEditorMenu(this DropdownMenu menu, Rect rect, VisualElement ve) { - PrepareMenu(menu, null).DropDown(rect); + PrepareMenu(menu, null).DropDown(rect, ve); + } + + // This is for backward compatibility with code triggering from imgui, but it wont allow spanning the menu from code (menu item, across window) + // Try using DoDisplayEditorMenu that takes an EventBase or an visualElement instead + [Obsolete("Use DoDisplayEditorMenu instead")] + public static void DoDisplayEditorMenuFromImGUI(this DropdownMenu menu, Rect rect) + { + PrepareMenu(menu, null).DropDownIMGUI(rect); } public static void DoDisplayEditorMenu(this DropdownMenu menu, EventBase triggerEvent) @@ -76,7 +86,7 @@ public static void DoDisplayEditorMenu(this DropdownMenu menu, EventBase trigger position = ((VisualElement)triggerEvent.target).layout.center; } - genericMenu.DropDown(new Rect(position, Vector2.zero)); + genericMenu.DropDown(new Rect(position, Vector2.zero), triggerEvent.target as VisualElement); } } @@ -120,9 +130,37 @@ public void AddSeparator(string path) m_GenericMenu.AddSeparator(path); } - public void DropDown(Rect position, VisualElement targetElement = null, bool anchored = false) + public void DropDown(Rect position, VisualElement targetElement , bool anchored = false) + { + if(targetElement is null || targetElement.panel == null) + { + Debug.LogError("Cannot show dropdown menu with a target visualElement not in a panel"); + return; + } + var panel = targetElement.elementPanel; + + if (panel.contextType ==ContextType.Editor && panel.ownerObject is View view) + { + // Convert first the postion in the panel to the position in UI pixels as per the editor's window definition + // This will not work in test where we disconnect the panel DPI from the window DPI + + position.x *= panel.scale; + position.y *= panel.scale; + position.width *= panel.scale; + position.height *= panel.scale; + + // Add the offset of window to get the position in screen space + // It include the position from the guiView to the root of the containerWindow and from the containerWindow to the screen + position.position += view.screenPosition.position; + } + + m_GenericMenu.DropDownScreenSpace(position, false); + } + + [Obsolete("Use DropDown(Rect position, VisualElement targetElement, bool anchored) instead")] + public void DropDownIMGUI(Rect position, bool anchored = false) { - m_GenericMenu.DropDown(position); + m_GenericMenu.DropDown(position, anchored); } } } diff --git a/Editor/Mono/UIElements/GenericDropdownMenuWindowContent.cs b/Editor/Mono/UIElements/GenericDropdownMenuWindowContent.cs new file mode 100644 index 0000000000..d2c2e4b887 --- /dev/null +++ b/Editor/Mono/UIElements/GenericDropdownMenuWindowContent.cs @@ -0,0 +1,50 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using UnityEngine; +using UnityEngine.UIElements; + +namespace UnityEditor.UIElements +{ + internal class EditorGenericDropdownMenuWindowContent : PopupWindowContent + { + internal Vector2 windowSize { get; set; } + + private GenericDropdownMenu m_GenericDropdownMenu; + private static readonly string s_PopupUssClassName = GenericDropdownMenu.ussClassName + "__popup"; + + public EditorGenericDropdownMenuWindowContent(GenericDropdownMenu genericDropdownMenu) + { + m_GenericDropdownMenu = genericDropdownMenu; + } + + public override Vector2 GetWindowSize() + { + if (windowSize == Vector2.zero) + { + return base.GetWindowSize(); + } + + return windowSize; + } + + public override void OnOpen() + { + base.OnOpen(); + + editorWindow.rootVisualElement.AddToClassList(s_PopupUssClassName); + editorWindow.rootVisualElement.Add(m_GenericDropdownMenu.menuContainer); + + editorWindow.rootVisualElement.schedule.Execute(() => + { + if (editorWindow != null) + { + m_GenericDropdownMenu.contentContainer.Focus(); + } + }); + } + + public override void OnGUI(Rect rect) { } + } +} diff --git a/Editor/Mono/UIElements/Inspector/EditorElement.cs b/Editor/Mono/UIElements/Inspector/EditorElement.cs index 08181d1df0..9f2b8b802b 100644 --- a/Editor/Mono/UIElements/Inspector/EditorElement.cs +++ b/Editor/Mono/UIElements/Inspector/EditorElement.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEditorInternal; @@ -14,6 +15,22 @@ namespace UnityEditor.UIElements { internal class EditorElement : VisualElement, IEditorElement { + // Local method use only -- created here to reduce garbage collection. Collection must be cleared before use + static readonly List s_Decorators = new List(); + static readonly EditorElementDecoratorCollection s_EditorDecoratorCollection = new EditorElementDecoratorCollection(); + + /// + /// Adds the given editor decorator. + /// + /// The editor decorator instance to be added. + internal static void AddDecorator(IEditorElementDecorator editorDecorator) => s_EditorDecoratorCollection.Add(editorDecorator); + + /// + /// Removes the given editor decorator. + /// + /// The editor decorator instance to be removed. + internal static void RemoveDecorator(IEditorElementDecorator editorDecorator) => s_EditorDecoratorCollection.Remove(editorDecorator); + readonly IPropertyView inspectorWindow; Editor[] m_EditorCache; @@ -37,6 +54,7 @@ Editor[] m_Editors public IEnumerable Editors => m_Editors.AsEnumerable(); Object m_EditorTarget; + Editor m_EditorUsedInDecorators; int m_EditorIndex; public Editor editor @@ -67,6 +85,7 @@ private bool IsEditorValid() IMGUIContainer m_Header; InspectorElement m_InspectorElement; + VisualElement m_DecoratorsElement; IMGUIContainer m_Footer; bool m_WasVisible; @@ -91,6 +110,12 @@ internal EditorElement(int editorIndex, IPropertyView iw, bool isCulled = false) m_IsCulled = isCulled; pickingMode = PickingMode.Ignore; + name = GetNameFromEditor(editor); + + // Register ui callbacks + RegisterCallback(OnAttachToPanel); + RegisterCallback(OnDetachFromPanel); + if (isCulled) { InitCulled(); @@ -131,6 +156,13 @@ void Init() Add(m_Header); Add(m_Footer); + // For GameObjects we want to ensure the first component's title bar is flush with the header, + // so we apply a small offset to the margin. (UUM-16138) + if (m_EditorTarget is GameObject) + { + AddToClassList("game-object-inspector"); + } + if (InspectorElement.disabledThrottling) CreateInspectorElement(); } @@ -140,23 +172,7 @@ InspectorElement BuildInspectorElement() var editors = PopulateCache(); var editorTitle = ObjectNames.GetInspectorTitle(m_EditorTarget); - InspectorElement.Mode inspectorElementMode; - - var propertyEditor = inspectorWindow as PropertyEditor; - - if (null == propertyEditor || propertyEditor.inspectorElementModeOverride == 0) - { - inspectorElementMode = InspectorElement.GetModeFromInspectorMode(inspectorWindow.inspectorMode); - - if (!EditorSettings.inspectorUseIMGUIDefaultInspector) - inspectorElementMode &= ~(InspectorElement.Mode.IMGUIDefault); - } - else - { - inspectorElementMode = (InspectorElement.Mode) propertyEditor.inspectorElementModeOverride; - } - - var inspectorElement = new InspectorElement(editor, inspectorElementMode) + var inspectorElement = new InspectorElement(editor) { focusable = false, name = editorTitle + "Inspector", @@ -174,12 +190,27 @@ InspectorElement BuildInspectorElement() return inspectorElement; } + void OnAttachToPanel(AttachToPanelEvent evt) + { + s_EditorDecoratorCollection.OnAdd += OnEditorDecoratorAdded; + s_EditorDecoratorCollection.OnRemove += OnEditorDecoratorRemoved; + } + + void OnDetachFromPanel(DetachFromPanelEvent evt) + { + s_EditorDecoratorCollection.OnAdd -= OnEditorDecoratorAdded; + s_EditorDecoratorCollection.OnRemove -= OnEditorDecoratorRemoved; + } + public void ReinitCulled(int editorIndex) { if (m_Header != null) { m_EditorIndex = editorIndex; m_Header = m_Footer = null; + m_EditorUsedInDecorators = null; + m_DecoratorsElement = null; + m_IsCulled = true; Clear(); InitCulled(); return; @@ -193,6 +224,8 @@ public void Reinit(int editorIndex) if (m_Header == null) { m_EditorIndex = editorIndex; + m_EditorUsedInDecorators = null; + m_IsCulled = false; Clear(); Init(); return; @@ -200,6 +233,7 @@ public void Reinit(int editorIndex) PopulateCache(); Object editorTarget = editor.targets[0]; + name = GetNameFromEditor(editor); string editorTitle = ObjectNames.GetInspectorTitle(editorTarget); // If the target change we need to invalidate IMGUI container cached measurements @@ -216,17 +250,23 @@ public void Reinit(int editorIndex) m_Header.onGUIHandler = HeaderOnGUI; m_Footer.onGUIHandler = FooterOnGUI; - name = editorTitle; m_Header.name = editorTitle + "Header"; m_Footer.name = editorTitle + "Footer"; if (m_InspectorElement != null) { - m_InspectorElement.AssignExistingEditor(editor); + m_InspectorElement.SetEditor(editor); m_InspectorElement.name = editorTitle + "Inspector"; // InspectorElement should be enabled only if the Editor is open for edit. m_InspectorElement.SetEnabled(editor.IsOpenForEdit()); + + // Update decorators + if (m_EditorUsedInDecorators != editor) + { + m_EditorUsedInDecorators = editor; + UpdateDecoratorsElement(m_EditorUsedInDecorators, editorTitle); + } } UpdateInspectorVisibility(); @@ -237,18 +277,48 @@ public void CreateInspectorElement() if (null == editor || null != m_InspectorElement || m_IsCulled) return; + //set the current PropertyHandlerCache to the current editor + ScriptAttributeUtility.propertyHandlerCache = editor.propertyHandlerCache; + // Need to update the cache for multi-object edit detection. if (editor.targets.Length != Selection.objects.Length) - inspectorWindow.tracker.RebuildIfNecessary(); + { + // Cannot force rebuild if locked, otherwise we get an infinite update loop. + var tracker = inspectorWindow.tracker; + if (tracker.isLocked) + tracker.RebuildIfNecessary(); + else + tracker.ForceRebuild(); + } + + var updateInspectorVisibility = false; // If the editor targets contain many targets and multi editing is not supported, we should not add this inspector. if (null != editor && (editor.targets.Length <= 1 || PropertyEditor.IsMultiEditingSupported(editor, editor.target, inspectorWindow.inspectorMode))) { m_InspectorElement = BuildInspectorElement(); Insert(IndexOf(m_Header) + 1, m_InspectorElement); - UpdateInspectorVisibility(); SetElementVisible(m_InspectorElement, m_WasVisible); + updateInspectorVisibility = true; + } + + // Create decorators + if (m_InspectorElement != null && m_EditorUsedInDecorators != editor) + { + m_EditorUsedInDecorators = editor; + UpdateDecoratorsElement(m_EditorUsedInDecorators); + updateInspectorVisibility = true; } + + if (updateInspectorVisibility) + UpdateInspectorVisibility(); + } + + string GetNameFromEditor(Editor editor) + { + return editor == null ? + "Nothing Selected" : + $"{editor.GetType().Name}_{editor.targets[0].GetType().Name}_{editor.targets[0].GetInstanceID()}"; } void UpdateInspectorVisibility() @@ -258,13 +328,34 @@ void UpdateInspectorVisibility() if (m_Footer != null) m_Footer.style.marginTop = m_WasVisible ? 0 : -kFooterDefaultHeight; - if (m_InspectorElement != null) - m_InspectorElement.style.paddingBottom = PropertyEditor.kEditorElementPaddingBottom; + if (m_DecoratorsElement != null) + { + if (m_InspectorElement != null) + m_InspectorElement.style.paddingBottom = 0; + + m_DecoratorsElement.style.paddingBottom = PropertyEditor.kEditorElementPaddingBottom; + } + else + { + if (m_InspectorElement != null) + m_InspectorElement.style.paddingBottom = PropertyEditor.kEditorElementPaddingBottom; + } } else { - if (m_Footer != null) - m_Footer.style.marginTop = -kFooterDefaultHeight; + if (m_DecoratorsElement != null) + { + if (m_Footer != null) + m_Footer.style.marginTop = m_WasVisible ? 0 : -kFooterDefaultHeight; + + m_DecoratorsElement.style.paddingBottom = PropertyEditor.kEditorElementPaddingBottom; + } + else + { + if (m_Footer != null) + m_Footer.style.marginTop = -kFooterDefaultHeight; + } + if (m_InspectorElement != null) m_InspectorElement.style.paddingBottom = 0; @@ -319,6 +410,10 @@ void HeaderOnGUI() { SetElementVisible(m_InspectorElement, false); } + if (m_DecoratorsElement != null) + { + SetElementVisible(m_DecoratorsElement, false); + } return; } @@ -334,6 +429,10 @@ void HeaderOnGUI() { SetElementVisible(m_InspectorElement, false); } + if (m_DecoratorsElement != null) + { + SetElementVisible(m_DecoratorsElement, false); + } return; } @@ -375,6 +474,10 @@ void HeaderOnGUI() { SetElementVisible(m_InspectorElement, m_WasVisible); } + if (m_DecoratorsElement != null && m_WasVisible != IsElementVisible(m_DecoratorsElement)) + { + SetElementVisible(m_DecoratorsElement, m_WasVisible); + } UpdateInspectorVisibility(); @@ -480,17 +583,6 @@ Rect DrawEditorSmallHeader(Editor[] editors, Object target, bool wasVisible) if (currentEditor == null) return GUILayoutUtility.GetLastRect(); - // ensure first component's title bar is flush with the header - if (EditorNeedsVerticalOffset(editors, target)) - { - // TODO: Check if we can fix this in the GameObjectInspector instead - GUILayout.Space( - -3f // move back up so line overlaps - - EditorStyles.inspectorBig.margin.bottom - - EditorStyles.inspectorTitlebar.margin.top // move back up margins - ); - } - using (new EditorGUI.DisabledScope(!currentEditor.IsEnabled())) { bool isVisible = EditorGUILayout.InspectorTitlebar(wasVisible, currentEditor); @@ -517,7 +609,7 @@ private bool IsElementVisible(VisualElement ve) return (ve.resolvedStyle.display == DisplayStyle.Flex); } - internal static void SetElementVisible(InspectorElement ve, bool visible) + internal static void SetElementVisible(VisualElement ve, bool visible) { if (visible) { @@ -531,7 +623,7 @@ internal static void SetElementVisible(InspectorElement ve, bool visible) } } - static void SetInspectorElementChildIMGUIContainerFocusable(InspectorElement ve, bool focusable) + static void SetInspectorElementChildIMGUIContainerFocusable(VisualElement ve, bool focusable) { var childCount = ve.childCount; @@ -611,6 +703,128 @@ void HandleComponentScreenshot(Rect content, Editor editor) #endregion Footer + #region Decorator + private class EditorElementDecoratorCollection : IEnumerable + { + readonly List m_EditorDecorators = new List(); + + internal Action OnAdd; + internal Action OnRemove; + + internal int Count => m_EditorDecorators.Count; + + internal void Add(IEditorElementDecorator editorDecorator) + { + if (m_EditorDecorators.Contains(editorDecorator)) + return; + + m_EditorDecorators.Add(editorDecorator); + OnAdd?.Invoke(editorDecorator); + } + + internal void Remove(IEditorElementDecorator editorDecorator) + { + if (!m_EditorDecorators.Contains(editorDecorator)) + return; + + m_EditorDecorators.Remove(editorDecorator); + OnRemove?.Invoke(editorDecorator); + } + + IEnumerator IEnumerable.GetEnumerator() + => m_EditorDecorators.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => m_EditorDecorators.GetEnumerator(); + } + + static bool TryGetDecorators(Editor editor, List decorators) + { + if (null == editor || editor.inspectorMode != InspectorMode.Normal) + return false; + + decorators.Clear(); + foreach (var editorDecorator in s_EditorDecoratorCollection) + { + var decorator = editorDecorator.OnCreateFooter(editor); + if (decorator != null) + decorators.Add(decorator); + } + + return decorators.Count != 0; + } + + void OnEditorDecoratorAdded(IEditorElementDecorator editorDecorator) + { + if (null == editor || null == m_InspectorElement || m_IsCulled) + return; + + m_EditorUsedInDecorators = editor; + UpdateDecoratorsElement(m_EditorUsedInDecorators); + UpdateInspectorVisibility(); + } + + void OnEditorDecoratorRemoved(IEditorElementDecorator editorDecorator) + { + if (null == editor || null == m_InspectorElement || m_IsCulled) + return; + + m_EditorUsedInDecorators = editor; + UpdateDecoratorsElement(m_EditorUsedInDecorators); + UpdateInspectorVisibility(); + } + + void UpdateDecoratorsElement(Editor editor, string editorTitle = null) + { + if (s_EditorDecoratorCollection.Count != 0 && TryGetDecorators(editor, s_Decorators)) + { + editorTitle ??= ObjectNames.GetInspectorTitle(editor.targets[0], editor.targets.Length > 1); + CreateDecoratorsElement(editorTitle, s_Decorators); + SetElementVisible(m_DecoratorsElement, m_WasVisible); + } + else if (m_DecoratorsElement != null) + { + m_DecoratorsElement.Clear(); + m_DecoratorsElement.RemoveFromHierarchy(); + m_DecoratorsElement = null; + } + } + + void CreateDecoratorsElement(string editorTitle, List children) + { + if (m_DecoratorsElement == null) + { + m_DecoratorsElement = BuildDecoratorsElement(editorTitle); + } + else + { + m_DecoratorsElement.Clear(); + m_DecoratorsElement.RemoveFromClassList(InspectorElement.uIEInspectorVariantUssClassName); + m_DecoratorsElement.name = editorTitle + "Decorators"; + } + + if (editor.UseDefaultMargins()) + { + m_DecoratorsElement.AddToClassList(InspectorElement.uIEInspectorVariantUssClassName); + m_DecoratorsElement.style.paddingTop = 0; + } + + foreach (var child in children) + m_DecoratorsElement.Add(child); + + if (m_DecoratorsElement.parent != this) + Insert(IndexOf(m_Footer), m_DecoratorsElement); + } + + VisualElement BuildDecoratorsElement(string editorTitle) + { + var decoratorsParent = new VisualElement() { name = editorTitle + "Decorators" }; + decoratorsParent.AddToClassList(PropertyField.decoratorDrawersContainerClassName); + return decoratorsParent; + } + + #endregion Decorator + internal bool EditorNeedsVerticalOffset(Editor[] editors, Object target) { return m_EditorIndex > 0 && IsEditorValid() && editors[m_EditorIndex - 1]?.target is GameObject && target is Component; diff --git a/Editor/Mono/UIElements/Inspector/IEditorElementDecorator.cs b/Editor/Mono/UIElements/Inspector/IEditorElementDecorator.cs new file mode 100644 index 0000000000..92599b45a3 --- /dev/null +++ b/Editor/Mono/UIElements/Inspector/IEditorElementDecorator.cs @@ -0,0 +1,26 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using UnityEngine.UIElements; + +namespace UnityEditor.UIElements +{ + /// + /// Create decorators (additional s) for the Inspector window. + /// + /// + /// Unity automatically calls the methods in this interface for instances added to . + /// + /// + internal interface IEditorElementDecorator + { + /// + /// Invoked once when the hierarchy element is constructed. + /// You can create and return a that appears after the given editor in the InspectorWindow. + /// + /// The editor to create the footer decorator. + /// A footer for the editor or null if you don't want to create a decorator. + VisualElement OnCreateFooter(Editor editor); + } +} diff --git a/Editor/Mono/UIElements/Inspector/InspectorElement.cs b/Editor/Mono/UIElements/Inspector/InspectorElement.cs index 9b695ae61f..2d2094bb02 100644 --- a/Editor/Mono/UIElements/Inspector/InspectorElement.cs +++ b/Editor/Mono/UIElements/Inspector/InspectorElement.cs @@ -3,13 +3,11 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; -using System.Collections.Generic; using Unity.Profiling; using UnityEditor.Profiling; using UnityEngine; using UnityEngine.UIElements; using Object = UnityEngine.Object; -using AssetImporterEditor = UnityEditor.AssetImporters.AssetImporterEditor; namespace UnityEditor.UIElements { @@ -21,14 +19,34 @@ namespace UnityEditor.UIElements /// public class InspectorElement : BindableElement { + /// + /// The framework that should be used to build and run the UI. This is only used for default inspectors (i.e. editors of type ) + /// + internal enum DefaultInspectorFramework + { + /// + /// UIToolkit should be used to generate property fields for default inspectors. + /// + UIToolkit, + + /// + /// IMGUI should be used to generate property fields for default inspectors. These will be placed inside of a . + /// + IMGUI + } + + static readonly ProfilerMarker k_CreateInspectorElementFromSerializedObject = new ProfilerMarker("InspectorElement.CreateInspectorElementFromSerializedObject"); + /// /// USS class name of elements of this type. /// public static readonly string ussClassName = "unity-inspector-element"; + /// /// USS class name of custom inspector elements in elements of this type. /// public static readonly string customInspectorUssClassName = ussClassName + "__custom-inspector-container"; + /// /// USS class name of IMGUI containers in elements of this type. /// @@ -38,6 +56,7 @@ public class InspectorElement : BindableElement /// USS class name of elements of this type, when they are displayed in IMGUI inspector mode. /// public static readonly string iMGUIInspectorVariantUssClassName = ussClassName + "--imgui"; + /// /// USS class name of elements of this type, when they are displayed in UIElements inspector mode. /// @@ -47,31 +66,40 @@ public class InspectorElement : BindableElement /// USS class name of elements of this type, when no inspector is found. /// public static readonly string noInspectorFoundVariantUssClassName = ussClassName + "--no-inspector-found"; + /// /// USS class name of elements of this type, when they are displayed in UIElements custom mode. /// public static readonly string uIECustomVariantUssClassName = ussClassName + "--uie-custom"; + /// /// USS class name of elements of this type, when they are displayed in IMGUI custom mode. /// public static readonly string iMGUICustomVariantUssClassName = ussClassName + "--imgui-custom"; + /// /// USS class name of elements of this type, when they are displayed in IMGUI default mode. /// public static readonly string iMGUIDefaultVariantUssClassName = ussClassName + "--imgui-default"; + /// /// USS class name of elements of this type, when they are displayed in UIElements default mode. /// public static readonly string uIEDefaultVariantUssClassName = ussClassName + "--uie-default"; + /// /// USS class name of elements of this type, when they are displayed in debug USS mode. /// public static readonly string debugVariantUssClassName = ussClassName + "--debug"; + /// /// USS class name of elements of this type, when they are displayed in debug internal mode. /// public static readonly string debugInternalVariantUssClassName = ussClassName + "--debug-internal"; + /// + /// USS class name of elements of this type, when they are displayed without a missing script type. + /// internal static readonly string noScriptErrorContainerName = "unity-inspector-no-script-error-container"; internal static bool disabledThrottling { get; set; } = false; @@ -95,269 +123,246 @@ public UxmlTraits() } } - [Flags] - internal enum Mode - { - UIECustom = 1 << 0, - IMGUICustom = 1 << 1, - IMGUIDefault = 1 << 2, - UIEDefault = 1 << 3, - - DebugMod = 1 << 4, - DebugInternalMod = 1 << 5, - - Normal = UIECustom | IMGUICustom | IMGUIDefault | UIEDefault, - Default = IMGUIDefault | UIEDefault, - Custom = UIECustom | IMGUICustom, - IMGUI = IMGUICustom | IMGUIDefault, - UIE = UIECustom | UIEDefault, - - Debug = Default | DebugMod, - DebugInternal = Default | DebugInternalMod - } - - internal Mode mode { get; private set; } - - internal Editor editor - { - get { return m_Editor; } - private set - { - if (m_Editor != value) - { - DestroyOwnedEditor(); - m_Editor = value; - ownsEditor = false; - } - } - } - - private string m_TrackerName; - internal string trackerName => m_TrackerName ?? (m_TrackerName = GetInspectorTrackerName(this)); + /// + /// Gets the default backend to use based on the current editor settings. + /// + /// The default backend type to use. + static DefaultInspectorFramework GetDefaultInspectorFramework() => EditorSettings.inspectorUseIMGUIDefaultInspector ? DefaultInspectorFramework.IMGUI : DefaultInspectorFramework.UIToolkit; - internal bool ownsEditor { get; private set; } = false; + /// + /// The editor this element is inspecting. + /// + /// An must ALWAYS be backed by an . If one does not exist the inspector will create and manage one. + /// + /// + Editor m_Editor; - internal SerializedObject boundObject { get; private set; } + /// + /// Flag indicating if the InspectorElement is managing it's own editor instance. + /// + bool m_OwnsEditor; - internal VisualElement prefabOverrideBlueBarsContainer { get; private set; } - internal VisualElement livePropertyYellowBarsContainer { get; private set; } + /// + /// The currently bound serialized object. + /// + SerializedObject m_BoundObject; - private bool m_IgnoreOnInspectorGUIErrors; + /// + /// The currently bound object type, this can be used as an optional optimization to avoid rebuilding the UI if the type doesn't change. + /// + Type m_BoundObjectType; - static readonly ProfilerMarker k_Reset = new ProfilerMarker("InspectorElement.Reset"); - static readonly ProfilerMarker k_CreateInspectorGUI = new ProfilerMarker("InspectorElement.CreateInspectorGUI"); + /// + /// The root element of this inspector. This can either be a full visual hierarchy or an IMGUIContainer. + /// + VisualElement m_InspectorElement; /// - /// InspectorElement constructor. + /// The default framework to use for generic inspectors. /// - public InspectorElement() : this(null as Object) {} + DefaultInspectorFramework m_DefaultInspectorFramework; /// - /// InspectorElement constructor. + /// The cached tracker name. /// - /// Create a SerializedObject from given obj and automatically Bind() to it. - public InspectorElement(Object obj) : this(obj, Mode.Normal) {} + string m_TrackerName; - internal InspectorElement(Object obj, Mode mode) - { - m_IgnoreOnInspectorGUIErrors = false; + bool m_IgnoreOnInspectorGUIErrors; + bool m_IsOpenForEdit; + bool m_InvalidateGUIBlockCache = true; + bool m_Rebind; + VisualElement m_ContextWidthElement; - pickingMode = PickingMode.Ignore; - AddToClassList(ussClassName); + /// + /// Gets or sets the editor backing this inspector element. + /// + internal Editor editor => m_Editor; - this.mode = mode; - if (obj == null) - { - if (!GenericInspector.ObjectIsMonoBehaviourOrScriptableObjectWithoutScript(obj)) - { - return; - } - } + /// + /// Returns true if the inspector element owns and manages it's own editor instance. + /// + internal bool ownsEditor => m_OwnsEditor; - this.Bind(new SerializedObject(obj)); - } + /// + /// Returns the current tracker name for this element. This is used for editor performance tracking. + /// + internal string trackerName => m_TrackerName ??= GetInspectorTrackerName(this); /// - /// InspectorElement constructor. + /// The currently bound object. /// - /// Create a SerializedObject from given obj and automatically Bind() to it. - public InspectorElement(SerializedObject obj) : this(obj, Mode.Normal) {} + internal SerializedObject boundObject => m_BoundObject; - internal InspectorElement(SerializedObject obj, Mode mode) - { - pickingMode = PickingMode.Ignore; - AddToClassList(ussClassName); + /// + /// Visual element reference for prefab override bar. + /// + internal VisualElement prefabOverrideBlueBarsContainer { get; private set; } - this.mode = mode; - if (obj.targetObject == null) - { - if (!GenericInspector.ObjectIsMonoBehaviourOrScriptableObjectWithoutScript(obj.targetObject)) - { - return; - } - } + /// + /// Visual element reference for live property bar. + /// + internal VisualElement livePropertyYellowBarsContainer { get; private set; } - this.Bind(obj); - } + internal EditorGUIUtility.ComparisonViewMode comparisonViewMode { get; set; } /// - /// InspectorElement constructor. + /// Gets or sets the default inspector framework to use for this inspector. This will take affect during the next bind. /// - public InspectorElement(Editor editor) : this(editor, Mode.Normal) {} - - internal InspectorElement(Editor editor, Mode mode) + internal DefaultInspectorFramework defaultInspectorFramework { - pickingMode = PickingMode.Ignore; - AddToClassList(ussClassName); - - this.mode = mode; - - this.editor = editor; - - if (editor.targets.Length == 0) + get => m_DefaultInspectorFramework; + set { - return; + m_Rebind = true; + m_DefaultInspectorFramework = value; } - - var targetObject = editor.targets[0]; - if (targetObject == null) - { - if (!GenericInspector.ObjectIsMonoBehaviourOrScriptableObjectWithoutScript(targetObject)) - { - return; - } - } - - this.Bind(editor.serializedObject); } - void OnDetachFromPanel(DetachFromPanelEvent evt) + /// + /// Returns the editor performance tracker name for this inspector element. + /// + /// The element to generate a name for. + /// The generated performance tracker name. + internal static string GetInspectorTrackerName(VisualElement element) { - DestroyOwnedEditor(); - } + var editorElementParent = element.parent as EditorElement; - internal void AssignExistingEditor(Editor value) - { - if (m_Editor != value) - { - editor = value; - PartialReset(m_Editor.serializedObject); - } + return editorElementParent == null + ? $"Editor.Unknown.OnInspectorGUI" + : $"Editor.{editorElementParent.name}.OnInspectorGUI"; } - void DestroyOwnedEditor() + /// + /// Constructs a instance for a given target. + /// + static SerializedObject CreateSerializedObjectForTarget(Object target) { - if (ownsEditor && editor != null) + if (target == null) { - Object.DestroyImmediate(editor); - editor = null; - ownsEditor = false; - RegisterCallback(OnAttachToPanel); + // ReSharper disable once ExpressionIsAlwaysNull + // Check if this is a 'fake null' object (i.e. equals operator reports null but the object still exists) + if (!GenericInspector.ObjectIsMonoBehaviourOrScriptableObjectWithoutScript(target)) + return null; } - UnregisterCallback(OnDetachFromPanel); + return new SerializedObject(target); } - void OnAttachToPanel(AttachToPanelEvent evt) - { - Reset(boundObject); - UnregisterCallback(OnAttachToPanel); - } + /// + /// Initialized a new instance of . + /// + public InspectorElement() : this(null as Object) {} - internal static Mode GetModeFromInspectorMode(InspectorMode mode) - { - switch (mode) - { - case InspectorMode.Debug: - return Mode.Debug; - case InspectorMode.DebugInternal: - return Mode.DebugInternal; - default: - return Mode.Normal; - } - } + /// + /// Initialized a new instance of for the specified . + /// + /// The object to bind to. + public InspectorElement(Object obj) : this(CreateSerializedObjectForTarget(obj), GetDefaultInspectorFramework()) {} - private void Reset(SerializedObject bindObject) - { - k_Reset.Begin(); + /// + /// Initialized a new instance of for the specified . + /// + /// The object to bind to. + public InspectorElement(SerializedObject obj) : this(obj, GetDefaultInspectorFramework()) {} - Clear(); + /// + /// Initialized a new instance of for the specified . + /// + /// The editor to bind to. + public InspectorElement(Editor editor) : this(editor, GetDefaultInspectorFramework()) {} - prefabOverrideBlueBarsContainer = new VisualElement(); - prefabOverrideBlueBarsContainer.name = BindingExtensions.prefabOverrideBarContainerName; - prefabOverrideBlueBarsContainer.style.position = Position.Absolute; - Add(prefabOverrideBlueBarsContainer); + internal InspectorElement(Object obj, DefaultInspectorFramework defaultInspectorFramework) : this(new SerializedObject(obj), null, defaultInspectorFramework) { } + internal InspectorElement(SerializedObject serializedObject, DefaultInspectorFramework defaultInspectorFramework) : this(serializedObject, null, defaultInspectorFramework) {} + internal InspectorElement(Editor editor, DefaultInspectorFramework defaultInspectorFramework) : this(editor.serializedObject, editor, defaultInspectorFramework) {} - livePropertyYellowBarsContainer = new VisualElement(); - livePropertyYellowBarsContainer.name = BindingExtensions.livePropertyBarContainerName; - livePropertyYellowBarsContainer.style.position = Position.Absolute; - Add(livePropertyYellowBarsContainer); + InspectorElement(SerializedObject obj, Editor editor, DefaultInspectorFramework defaultInspectorFramework) + { + pickingMode = PickingMode.Ignore; + AddToClassList(ussClassName); - RemoveFromClassList(iMGUIInspectorVariantUssClassName); - RemoveFromClassList(uIEInspectorVariantUssClassName); - RemoveFromClassList(noInspectorFoundVariantUssClassName); - RemoveFromClassList(uIECustomVariantUssClassName); - RemoveFromClassList(iMGUICustomVariantUssClassName); - RemoveFromClassList(iMGUIDefaultVariantUssClassName); - RemoveFromClassList(uIEDefaultVariantUssClassName); - RemoveFromClassList(debugVariantUssClassName); - RemoveFromClassList(debugInternalVariantUssClassName); + // Register ui callbacks + RegisterCallback(OnAttachToPanel); + RegisterCallback(OnDetachFromPanel); - if (bindObject == null) - return; + prefabOverrideBlueBarsContainer = new VisualElement + { + name = BindingExtensions.prefabOverrideBarContainerName, + style = { position = Position.Absolute } + }; - var editor = GetOrCreateEditor(bindObject); - if (editor == null) + livePropertyYellowBarsContainer = new VisualElement { - return; - } + name = BindingExtensions.livePropertyBarContainerName, + style = { position = Position.Absolute } + }; - boundObject = bindObject; + Add(prefabOverrideBlueBarsContainer); + Add(livePropertyYellowBarsContainer); - CreateInspectorGUI(false); + m_DefaultInspectorFramework = defaultInspectorFramework; - k_Reset.End(); - } + // Find or construct an editor for this object. + if (editor == null) + { + if (obj == null) + return; - private void PartialReset(SerializedObject bindObject) - { - boundObject = bindObject; - if (boundObject == null) + m_Editor = Editor.CreateEditor(obj.targetObjects); + m_OwnsEditor = true; + } + else { - Reset(null); - return; + m_Editor = editor; + + // If an editor was provided but the targets are not set. + // This should never happen in normal operation since all multi argument constructors are internal/private. + if (m_Editor.targets.Length == 0) + return; } - CreateInspectorGUI(true); + // We have a valid target, initiate binding. + this.Bind(obj); } - void CreateInspectorGUI(bool updateBinding) + void OnAttachToPanel(AttachToPanelEvent evt) { - k_CreateInspectorGUI.Begin(); - - Clear(); - - // Add prefabOverrideBlueBarsContainer again. - if (prefabOverrideBlueBarsContainer != null) + if (boundObject != null && m_Editor == null) { - Add(prefabOverrideBlueBarsContainer); + m_Rebind = true; + this.Bind(boundObject); } - // Add livePropertyYellowBarsContainer again. - if (livePropertyYellowBarsContainer != null) + var currentElement = parent; + while (currentElement != null) { - Add(livePropertyYellowBarsContainer); + if (!currentElement.ClassListContains(PropertyEditor.s_MainContainerClassName)) + { + currentElement = currentElement.parent; + continue; + } + + m_ContextWidthElement = currentElement; + break; } + } - var element = CreateInspectorElementFromEditor(editor, true) ?? CreateDefaultInspector(boundObject); + void OnDetachFromPanel(DetachFromPanelEvent evt) + { + DestroyOwnedEditor(); + } - if (element != null && element != this) - hierarchy.Add(element); + /// + /// Destroys and cleans up the editor instance managed by this inspector element. If any. + /// + void DestroyOwnedEditor() + { + if (!m_OwnsEditor || m_Editor == null) + return; - if (updateBinding) - element?.Bind(boundObject); + Object.DestroyImmediate(m_Editor); - k_CreateInspectorGUI.End(); + m_Editor = null; + m_OwnsEditor = false; } [EventInterest(typeof(SerializedObjectBindEvent))] @@ -373,49 +378,149 @@ protected override void ExecuteDefaultActionAtTarget(EventBase evt) // so we need to ignore SerializedObjectBindEvent that aren't meant for them. // We use the DataSource property to store a reference to the target object that is being bound // so we can ignore the binding process that targets a parent inspector. - var dataSource = this.GetProperty(BindingExtensions.s_DataSourceProperty); + var dataSource = GetProperty(BindingExtensions.s_DataSourceProperty); if (dataSource != null && dataSource != bindEvent.bindObject) { evt.StopPropagation(); return; } - Reset(bindEvent.bindObject); + // Determine if we need to rebuild. This should only be done when our serialized object target changes or something forces us to rebuild (i.e. backend changing). + var shouldRebuildElements = m_Rebind || m_BoundObject != bindEvent.bindObject; + + if (shouldRebuildElements) + { + m_Rebind = false; + CreateInspectorElementFromSerializedObject(bindEvent.bindObject); + } } - private Editor GetOrCreateEditor(SerializedObject serializedObject) + /// + /// Assigns the given editor to this inspector element instance. This will trigger a re-bind. + /// + /// The editor to assign. + internal void SetEditor(Editor value) { - var target = serializedObject?.targetObject; + // NOTE: We need a special check here for undo/redo cases when the script is changing. + var targetTypeMatches = value.target && value.target.GetType() == m_BoundObjectType; + var editorInstanceMatches = m_Editor == value; - if (editor != null) - { - if (editor.target == target || editor.serializedObject == serializedObject) - return editor; + if (targetTypeMatches && editorInstanceMatches) + return; - if (ownsEditor) - { - DestroyOwnedEditor(); - } + DestroyOwnedEditor(); + + m_Editor = value; + m_Rebind = true; + + this.Bind(m_Editor.serializedObject); + } + + void ClearInspectorElement() + { + // Clear any previously generated element. + m_InspectorElement?.RemoveFromHierarchy(); + + // Clear all top level styles + RemoveFromClassList(iMGUIInspectorVariantUssClassName); + RemoveFromClassList(uIEInspectorVariantUssClassName); + RemoveFromClassList(noInspectorFoundVariantUssClassName); + RemoveFromClassList(uIECustomVariantUssClassName); + RemoveFromClassList(iMGUICustomVariantUssClassName); + RemoveFromClassList(iMGUIDefaultVariantUssClassName); + RemoveFromClassList(uIEDefaultVariantUssClassName); + RemoveFromClassList(debugVariantUssClassName); + RemoveFromClassList(debugInternalVariantUssClassName); + } + + void CreateInspectorElementFromSerializedObject(SerializedObject bindObject) + { + k_CreateInspectorElementFromSerializedObject.Begin(); + + // Unpack the given serialized object. We want to cache the target type to facilitate re-using UI later down the pipeline. + var targetObject = bindObject?.targetObject; + var targetObjectType = targetObject != null ? targetObject.GetType() : null; + + var bindObjectTypeMatches = m_BoundObjectType == targetObjectType; + + m_BoundObject = bindObject; + m_BoundObjectType = targetObjectType; + + if (m_BoundObject == null) + { + ClearInspectorElement(); + return; } - foreach (var inspectorWindow in InspectorWindow.GetInspectors()) + m_Editor = GetOrCreateEditor(bindObject); + + if (m_Editor != null) { - foreach (var trackerEditor in inspectorWindow.tracker.activeEditors) + if (m_Editor is GenericInspector) { - if (trackerEditor.target == target || trackerEditor.serializedObject == serializedObject) + // Only re-build default inspectors when the type changes or when using the IMGUI backend. + // NOTE: When using IMGUI the delegate captures locals so we must rebuild it. + if (!bindObjectTypeMatches || m_InspectorElement == null || m_DefaultInspectorFramework == DefaultInspectorFramework.IMGUI) { - return editor = trackerEditor; + ClearInspectorElement(); + + // When looking at a generic inspector we choose the backend based on a user provided default. + switch (m_DefaultInspectorFramework) + { + case DefaultInspectorFramework.UIToolkit: + m_InspectorElement = CreateInspectorElementUsingUIToolkit(m_Editor); + break; + case DefaultInspectorFramework.IMGUI: + m_InspectorElement = CreateInspectorElementUsingIMGUI(m_Editor); + break; + } } } + else + { + // Always clear and re-build when dealing with custom inspectors. User code is assumed to take a reference to the ScriptableObject in `CreateInspectorGUI()`. + ClearInspectorElement(); + + // This is a custom editor type. Try to use UI toolkit first with an IMGUI fallback. + m_InspectorElement = CreateInspectorElementUsingUIToolkit(m_Editor) ?? CreateInspectorElementUsingIMGUI(m_Editor); + m_InspectorElement.AddToClassList(customInspectorUssClassName); + } + + // Re-add the generated element if it was re-created. + if (m_InspectorElement != null && m_InspectorElement.parent != this) + hierarchy.Add(m_InspectorElement); } - RegisterCallback(OnDetachFromPanel); + k_CreateInspectorElementFromSerializedObject.End(); + } + + /// + /// This method handles all the internals of getting a object for the given serialized object. + /// + /// The serialized object to get an editor for. + /// The found or created instance. + Editor GetOrCreateEditor(SerializedObject serializedObject) + { + Object[] targets = null; - var ed = Editor.CreateEditor(serializedObject?.targetObject); - editor = ed; - ownsEditor = true; + if (serializedObject != null && serializedObject.m_NativeObjectPtr != IntPtr.Zero) + targets = serializedObject.targetObjects; - return ed; + if (m_Editor != null) + { + // First try to re-use the instance we have on hand. If this matches our given object we can simply re-use it. + if (m_Editor.serializedObject == serializedObject || ArrayUtility.ArrayReferenceEquals(m_Editor.targets, targets)) + return m_Editor; + + // We need to generate a new editor instance, first cleanup any owned editor resources we have. + DestroyOwnedEditor(); + } + + // Fallback to creating our own editor instance for the given object. + m_Editor = Editor.CreateEditor(targets); + m_OwnsEditor = true; + + return m_Editor; } /// @@ -436,12 +541,17 @@ public static void FillDefaultInspector(VisualElement container, SerializedObjec { do { - var field = new PropertyField(property); - field.name = "PropertyField:" + property.propertyPath; + var field = new PropertyField(property) + { + name = "PropertyField:" + property.propertyPath + }; if (property.propertyPath == "m_Script") { - if (serializedObject.targetObject != null || property.objectReferenceValue != null || isPartOfPrefabInstance) + // Allow script re-assignment in debug mode. + var isDebugMode = editor != null && (editor.inspectorMode == InspectorMode.Debug || editor.inspectorMode == InspectorMode.DebugInternal); + + if (!isDebugMode && (serializedObject.targetObject != null || property.objectReferenceValue != null || isPartOfPrefabInstance)) field.SetEnabled(false); } @@ -451,99 +561,70 @@ public static void FillDefaultInspector(VisualElement container, SerializedObjec } if (serializedObject.targetObject == null) - AddMissingScriptLabel(container, serializedObject, isPartOfPrefabInstance); - } - - private VisualElement CreateDefaultInspector(SerializedObject serializedObject) - { - if (serializedObject == null) - return null; - - FillDefaultInspector(this, serializedObject, editor); - - if ((mode & Mode.DebugMod) > 0) - AddToClassList(debugVariantUssClassName); - else if ((mode & Mode.DebugInternalMod) > 0) - AddToClassList(debugInternalVariantUssClassName); - - AddToClassList(uIEDefaultVariantUssClassName); - AddToClassList(uIEInspectorVariantUssClassName); - - return this; - } - - static bool AddMissingScriptLabel(VisualElement container, SerializedObject serializedObject, bool isPartOfPrefabInstance) - { - SerializedProperty scriptProperty = serializedObject.FindProperty("m_Script"); - if (scriptProperty != null) { - var noScriptErrorContainer = new IMGUIContainer(() => GenericInspector.ShowScriptNotLoadedWarning(scriptProperty, isPartOfPrefabInstance)); - noScriptErrorContainer.name = noScriptErrorContainerName; - container.Add(noScriptErrorContainer); - return true; - } + var scriptProperty = serializedObject.FindProperty("m_Script"); - return false; + if (scriptProperty != null) + { + var noScriptErrorContainer = new IMGUIContainer(() => + { + if (scriptProperty.isValid) + GenericInspector.ShowScriptNotLoadedWarning(scriptProperty, isPartOfPrefabInstance); + }); + noScriptErrorContainer.name = noScriptErrorContainerName; + container.Add(noScriptErrorContainer); + } + } } - internal static bool SetWideModeForWidth(VisualElement displayElement) + VisualElement CreateInspectorElementUsingUIToolkit(Editor targetEditor) { - var previousWideMode = EditorGUIUtility.wideMode; - - float inspectorWidth = 0; + var element = targetEditor.CreateInspectorGUI(); - // the inspector's width can be NaN if this is our first layout check. - // or when the inspector display is changed from none to flex, the width will be zero during the measuring phase. - // we try to find a parent with a a width. If none are found, we'll set wideMode to true to avoid computing - // too tall an inspector on the first layout calculation - while (displayElement != null && (float.IsNaN(inspectorWidth) || inspectorWidth == 0)) + if (element != null) { - inspectorWidth = displayElement.layout.width; - displayElement = displayElement.hierarchy.parent; - } + // Decorate the InspectorElement based on the editor type (i.e. custom vs generic). + if (targetEditor is GenericInspector) + { + AddToClassList(uIEDefaultVariantUssClassName); + AddToClassList(uIEInspectorVariantUssClassName); - if (!float.IsNaN(inspectorWidth) && inspectorWidth > 0) - { - EditorGUIUtility.wideMode = inspectorWidth > Editor.k_WideModeMinWidth; - } - else - { - EditorGUIUtility.wideMode = true; + switch (targetEditor.inspectorMode) + { + case InspectorMode.Debug: + AddToClassList(debugVariantUssClassName); + break; + case InspectorMode.DebugInternal: + AddToClassList(debugInternalVariantUssClassName); + break; + } + } + else + { + AddToClassList(uIECustomVariantUssClassName); + + if (editor.UseDefaultMargins()) + AddToClassList(uIEInspectorVariantUssClassName); + } } - return previousWideMode; + return element; } - IMGUIContainer m_IMGUIContainer; - - private VisualElement CreateIMGUIInspectorFromEditor(SerializedObject serializedObject, Editor editor, - bool reuseIMGUIContainer) + VisualElement CreateInspectorElementUsingIMGUI(Editor targetEditor) { - if ((mode & (Mode.IMGUICustom | Mode.IMGUIDefault)) == 0) - return null; - - if ((mode & Mode.IMGUICustom) > 0 && (mode & Mode.IMGUIDefault) == 0 && editor is GenericInspector) - return null; - - if ((mode & Mode.IMGUICustom) == 0 && (mode & Mode.IMGUIDefault) > 0 && !(editor is GenericInspector) && !(editor is AssetImporterEditor) && !(editor is GameObjectInspector)) - { - editor = ScriptableObject.CreateInstance(); - editor.hideFlags = HideFlags.HideAndDontSave; - editor.InternalSetTargets(new[] { serializedObject.targetObject }); - } - - if (editor is GenericInspector) + if (targetEditor is GenericInspector) { AddToClassList(iMGUIDefaultVariantUssClassName); - if ((mode & Mode.DebugMod) > 0) - { - AddToClassList(debugVariantUssClassName); - editor.inspectorMode = InspectorMode.Debug; - } - else if ((mode & Mode.DebugInternalMod) > 0) + + switch (targetEditor.inspectorMode) { - AddToClassList(debugInternalVariantUssClassName); - editor.inspectorMode = InspectorMode.DebugInternal; + case InspectorMode.Debug: + AddToClassList(debugVariantUssClassName); + break; + case InspectorMode.DebugInternal: + AddToClassList(debugInternalVariantUssClassName); + break; } } else @@ -551,16 +632,8 @@ private VisualElement CreateIMGUIInspectorFromEditor(SerializedObject serialized AddToClassList(iMGUICustomVariantUssClassName); } - IMGUIContainer inspector; - // Reusing the existing IMGUIContainer allows us to re-use the existing gui state, when we are drawing the same inspector this will let us keep the same control ids - if (reuseIMGUIContainer && m_IMGUIContainer != null) - { - inspector = m_IMGUIContainer; - } - else - { - inspector = new IMGUIContainer(); - } + // Always try to re-use the imgui container if possible. + var inspector = m_InspectorElement as IMGUIContainer ?? new IMGUIContainer(); m_IgnoreOnInspectorGUIErrors = false; inspector.onGUIHandler = () => @@ -587,49 +660,33 @@ private VisualElement CreateIMGUIInspectorFromEditor(SerializedObject serialized // rely heavily on yields and timing of UI redraws. Yes.. // // case 1119612 - if (editor.m_SerializedObject == null) + if (targetEditor.m_SerializedObject == null) { - editor.Repaint(); + targetEditor.Repaint(); m_IgnoreOnInspectorGUIErrors = true; } - if ((editor.target == null && !GenericInspector.ObjectIsMonoBehaviourOrScriptableObjectWithoutScript(editor.target)) || - !editor.serializedObject.isValid) + if ((targetEditor.target == null && !GenericInspector.ObjectIsMonoBehaviourOrScriptableObjectWithoutScript(targetEditor.target)) || + !targetEditor.serializedObject.isValid) { return; } EditorGUIUtility.ResetGUIState(); - using (new EditorGUI.DisabledScope(!editor.IsEnabled())) + using (new EditorGUI.DisabledScope(!targetEditor.IsEnabled() || !enabledInHierarchy)) { - var genericEditor = editor as GenericInspector; - if (genericEditor != null) - { - switch (mode) - { - case Mode.Normal: - genericEditor.inspectorMode = InspectorMode.Normal; - break; - case Mode.Default: - genericEditor.inspectorMode = InspectorMode.Debug; - break; - case Mode.Custom: - genericEditor.inspectorMode = InspectorMode.DebugInternal; - break; - case Mode.IMGUI: - break; - } - } - //set the current PropertyHandlerCache to the current editor - ScriptAttributeUtility.propertyHandlerCache = editor.propertyHandlerCache; + ScriptAttributeUtility.propertyHandlerCache = targetEditor.propertyHandlerCache; + var originalViewWidth = EditorGUIUtility.currentViewWidth; var originalHierarchyMode = EditorGUIUtility.hierarchyMode; + var originalComparisonMode = EditorGUIUtility.comparisonViewMode; EditorGUIUtility.hierarchyMode = true; + EditorGUIUtility.comparisonViewMode = comparisonViewMode; - var originalWideMode = SetWideModeForWidth(inspector); + var originalWideMode = SetWideModeForWidth(m_ContextWidthElement ?? inspector); - GUIStyle editorWrapper = (editor.UseDefaultMargins() && editor.CanBeExpandedViaAFoldoutWithoutUpdate() + GUIStyle editorWrapper = (targetEditor.UseDefaultMargins() && targetEditor.CanBeExpandedViaAFoldoutWithoutUpdate() ? EditorStyles.inspectorDefaultMargins : GUIStyle.none); try @@ -648,34 +705,34 @@ private VisualElement CreateIMGUIInspectorFromEditor(SerializedObject serialized var layoutCacheState = GUILayoutUtility.current.State; try { - var rebuildOptimizedGUIBlocks = GetRebuildOptimizedGUIBlocks(editor.target); - rebuildOptimizedGUIBlocks |= editor.isInspectorDirty; - float height; - if (editor.GetOptimizedGUIBlock(rebuildOptimizedGUIBlocks, visible, out height)) + var rebuildOptimizedGUIBlocks = GetRebuildOptimizedGUIBlocks(targetEditor.target); + rebuildOptimizedGUIBlocks |= targetEditor.isInspectorDirty; + + if (targetEditor.GetOptimizedGUIBlock(rebuildOptimizedGUIBlocks, visible, out var height)) { - var contentRect = GUILayoutUtility.GetRect(0, visible ? height : 0); + var contentHeightRect = GUILayoutUtility.GetRect(0, visible ? height : 0); // Layout events are ignored in the optimized code path // The exception is when we are drawing a GenericInspector, they always use the optimized path and must therefore run at least one layout calculation in it - if (Event.current.type == EventType.Layout && !(editor is GenericInspector)) + if (Event.current.type == EventType.Layout && !(targetEditor is GenericInspector)) { return; } - InspectorWindowUtils.DrawAddedComponentBackground(contentRect, editor.targets); + InspectorWindowUtils.DrawAddedComponentBackground(contentHeightRect, targetEditor.targets); // Draw content if (visible) { GUI.changed = false; - editor.OnOptimizedInspectorGUI(contentRect); + targetEditor.OnOptimizedInspectorGUI(contentHeightRect); } } else { - InspectorWindowUtils.DrawAddedComponentBackground(contentRect, editor.targets); + InspectorWindowUtils.DrawAddedComponentBackground(contentRect, targetEditor.targets); using (new EditorPerformanceTracker(trackerName)) - editor.OnInspectorGUI(); + targetEditor.OnInspectorGUI(); } } catch (Exception e) @@ -700,7 +757,7 @@ private VisualElement CreateIMGUIInspectorFromEditor(SerializedObject serialized { if (GUI.changed) { - // This forces a relayout of all imguicontainers in this inspector window. + // This forces a re-layout of all imgui containers in this inspector window. // fixes part of case 1148706 var element = inspector.GetFirstAncestorOfType(); if (element != null) @@ -708,15 +765,13 @@ private VisualElement CreateIMGUIInspectorFromEditor(SerializedObject serialized } EditorGUIUtility.wideMode = originalWideMode; EditorGUIUtility.hierarchyMode = originalHierarchyMode; + EditorGUIUtility.currentViewWidth = originalViewWidth; + EditorGUIUtility.comparisonViewMode = originalComparisonMode; } } }; inspector.style.overflow = Overflow.Visible; - m_IMGUIContainer = inspector; - - if (!(editor is GenericInspector)) - inspector.AddToClassList(customInspectorUssClassName); inspector.AddToClassList(iMGUIContainerUssClassName); @@ -725,63 +780,36 @@ private VisualElement CreateIMGUIInspectorFromEditor(SerializedObject serialized return inspector; } - internal static string GetInspectorTrackerName(VisualElement el) + internal static bool SetWideModeForWidth(VisualElement displayElement) { - var editorElementParent = el.parent as EditorElement; - if (editorElementParent == null) - return $"Editor.Unknown.OnInspectorGUI"; + var previousWideMode = EditorGUIUtility.wideMode; - return $"Editor.{editorElementParent.name}.OnInspectorGUI"; - } + float inspectorWidth = 0; - private VisualElement CreateInspectorElementFromEditor(Editor editor, bool reuseIMGUIContainer = false) - { - var serializedObject = editor.serializedObject; - var target = editor.targets[0]; - if (target == null) + // the inspector's width can be NaN if this is our first layout check. + // or when the inspector display is changed from none to flex, the width will be zero during the measuring phase. + // we try to find a parent with a a width. If none are found, we'll set wideMode to true to avoid computing + // too tall an inspector on the first layout calculation + while (displayElement != null && (float.IsNaN(inspectorWidth) || inspectorWidth == 0)) { - if (!GenericInspector.ObjectIsMonoBehaviourOrScriptableObjectWithoutScript(target)) - { - return null; - } + inspectorWidth = displayElement.layout.width; + displayElement = displayElement.hierarchy.parent; } - VisualElement inspectorElement = null; - - if ((mode & Mode.UIECustom) > 0) + if (!float.IsNaN(inspectorWidth) && inspectorWidth > 0) { - inspectorElement = editor.CreateInspectorGUI(); - - if (inspectorElement != null) - { - AddToClassList(uIECustomVariantUssClassName); - if (editor.UseDefaultMargins()) - AddToClassList(uIEInspectorVariantUssClassName); - inspectorElement.AddToClassList(customInspectorUssClassName); - } + EditorGUIUtility.wideMode = inspectorWidth > Editor.k_WideModeMinWidth; + EditorGUIUtility.currentViewWidth = inspectorWidth; } - - if (inspectorElement == null) - inspectorElement = CreateIMGUIInspectorFromEditor(serializedObject, editor, reuseIMGUIContainer); - - if (inspectorElement == null && (mode & Mode.UIEDefault) > 0) - inspectorElement = CreateDefaultInspector(serializedObject); - - if (inspectorElement == null) + else { - AddToClassList(noInspectorFoundVariantUssClassName); - AddToClassList(uIEInspectorVariantUssClassName); - inspectorElement = new Label("No inspector found given the current Inspector.Mode."); + EditorGUIUtility.wideMode = true; } - return inspectorElement; + return previousWideMode; } - bool m_IsOpenForEdit; - bool m_InvalidateGUIBlockCache = true; - Editor m_Editor; - - private bool GetRebuildOptimizedGUIBlocks(Object inspectedObject) + bool GetRebuildOptimizedGUIBlocks(Object inspectedObject) { var rebuildOptimizedGUIBlocks = false; diff --git a/Editor/Mono/UIElements/StyleSheets/StyleSheetImporter.cs b/Editor/Mono/UIElements/StyleSheets/StyleSheetImporter.cs index 7de519b3e4..deddf99739 100644 --- a/Editor/Mono/UIElements/StyleSheets/StyleSheetImporter.cs +++ b/Editor/Mono/UIElements/StyleSheets/StyleSheetImporter.cs @@ -13,6 +13,7 @@ namespace UnityEditor.UIElements.StyleSheets { // Make sure style sheets importer after allowed dependent assets: textures, fonts and json // Has to be higher then AssetImportOrder.kImportOrderLate + [HelpURL("UIE-USS")] [ScriptedImporter(version: 12, ext: "uss", importQueueOffset: 1100)] [ExcludeFromPreset] class StyleSheetImporter : ScriptedImporter diff --git a/Editor/Mono/UIElements/StyleSheets/StyleSheetImporterImpl.cs b/Editor/Mono/UIElements/StyleSheets/StyleSheetImporterImpl.cs index de46dbb7f4..2928fc91be 100644 --- a/Editor/Mono/UIElements/StyleSheets/StyleSheetImporterImpl.cs +++ b/Editor/Mono/UIElements/StyleSheets/StyleSheetImporterImpl.cs @@ -122,6 +122,11 @@ public virtual UnityEngine.Object DeclareDependencyAndLoad(string path, string s if (o.name == subAssetPath) return o; } + + // We sometimes include the main asset name in the sub-asset name. (UUM-49355) + if (mainAsset != null && mainAsset.name == subAssetPath) + return mainAsset; + return null; } @@ -344,9 +349,9 @@ private struct StoredAsset } } + using (var so = new SerializedObject(font)) { - var so = new SerializedObject(font); - var oldTex = so.FindProperty("m_Texture").objectReferenceValue; + var oldTex = so.FindProperty("m_Texture").objectReferenceValue; if (oldTex != null) { //Reuse the same texture if the reference was equal @@ -779,10 +784,10 @@ static bool TryParseKeyword(string rawStr, out StyleValueKeyword value) s_NameCache = new Dictionary(); foreach (StyleValueKeyword kw in System.Enum.GetValues(typeof(StyleValueKeyword))) { - s_NameCache[kw.ToString().ToLower()] = kw; + s_NameCache[kw.ToString().ToLowerInvariant()] = kw; } } - return s_NameCache.TryGetValue(rawStr.ToLower(), out value); + return s_NameCache.TryGetValue(rawStr.ToLowerInvariant(), out value); } } @@ -834,7 +839,8 @@ internal static void PopulateDependencies(string assetPath, List depende } } - foreach (var projectRelativeImportPath in s_StyleSheetProjectRelativeImportPaths) + // Array copy to iterate over the paths. This avoids in-place editing in the recursive call + foreach (var projectRelativeImportPath in s_StyleSheetProjectRelativeImportPaths.ToArray()) { if (s_StyleSheetsUnsortedDependencies.Contains(projectRelativeImportPath)) { diff --git a/Editor/Mono/UIElements/StyleSheets/ThemeRegistry.cs b/Editor/Mono/UIElements/StyleSheets/ThemeRegistry.cs index 4c5b43304e..d0dd483112 100644 --- a/Editor/Mono/UIElements/StyleSheets/ThemeRegistry.cs +++ b/Editor/Mono/UIElements/StyleSheets/ThemeRegistry.cs @@ -2,20 +2,18 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License -using System; using System.Collections.Generic; -using System.IO; -using UnityEngine.UIElements; +using UnityEngine.Bindings; namespace UnityEditor.UIElements.StyleSheets { + [VisibleToOtherModules("UnityEditor.UIBuilderModule")] static class ThemeRegistry { internal static string k_DefaultStyleSheetPath { - get { - return "StyleSheets/Generated/Default.tss.asset"; - } + [VisibleToOtherModules("UnityEditor.UIBuilderModule")] + get => "StyleSheets/Generated/Default.tss.asset"; } public const string kThemeScheme = "unity-theme"; diff --git a/Editor/Mono/UIElements/StyleSheets/ThemeStyleSheetImporter.cs b/Editor/Mono/UIElements/StyleSheets/ThemeStyleSheetImporter.cs index e70b0e7848..33198b3fb0 100644 --- a/Editor/Mono/UIElements/StyleSheets/ThemeStyleSheetImporter.cs +++ b/Editor/Mono/UIElements/StyleSheets/ThemeStyleSheetImporter.cs @@ -11,7 +11,8 @@ namespace UnityEditor.UIElements.StyleSheets { // Make sure style sheets importer after allowed dependent assets: textures, fonts, json and uss. // Has to be higher then AssetImportOrder.kImportOrderLate - [ScriptedImporter(version: 1, ext: "tss", importQueueOffset: 1101)] + [HelpURL("UIE-tss")] + [ScriptedImporter(version: 4, ext: "tss", importQueueOffset: 1101)] [ExcludeFromPreset] class ThemeStyleSheetImporter : StyleSheetImporter { diff --git a/Editor/Mono/UIElements/UIBuildAnalyticsEvent.cs b/Editor/Mono/UIElements/UIBuildAnalyticsEvent.cs new file mode 100644 index 0000000000..344be9b004 --- /dev/null +++ b/Editor/Mono/UIElements/UIBuildAnalyticsEvent.cs @@ -0,0 +1,140 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEngine; +using UnityEngine.Analytics; +using UnityEngine.Serialization; +using UnityEngine.UIElements; + +namespace UnityEditor.Audio.Analytics; + +class UIBuildAnalyticsEvent : IPostprocessBuildWithReport +{ + static bool s_EventRegistered; + const string k_EventName = "uiBuild"; + const string k_VendorKey = "unity.ui"; + const int k_MaxEventsPerHour = 60; + const int k_MaxNumberOfElements = 1000; + private const int k_EventVersion = 2; + + [Serializable] + internal class UIBuildEvent + { + [Serializable] + public struct UIAssetCounters + { + public int PanelSettings; + public int VisualTreeAsset; + public int StyleSheet; + public int ThemeStyleSheet; + } + + public string build_guid; + public string build_session_guid; + public int build_type; + public UIAssetCounters counters; + + public UIBuildEvent(string buildGuid, string buildSessionGuid, BuildType buildType, UIAssetCounters counters) + { + this.build_guid = buildGuid; + this.build_session_guid = buildSessionGuid; + this.build_type = (int)buildType; + this.counters = counters; + } + } + + public int callbackOrder { get; } + + public void OnPostprocessBuild(BuildReport report) + { + SendEvent(report); + } + + + static bool EnableAnalytics() + { + if (!EditorAnalytics.enabled) + return false; + + if (!s_EventRegistered) + { + AnalyticsResult result = EditorAnalytics.RegisterEventWithLimit(k_EventName, k_MaxEventsPerHour, k_MaxNumberOfElements, k_VendorKey, k_EventVersion); + if (result == AnalyticsResult.Ok) + s_EventRegistered = true; + } + + return s_EventRegistered; + } + + static void SendEvent(BuildReport report) + { + if ( report == null + || report.packedAssets.Length == 0) + { + return; + } + + if (!EnableAnalytics()) + return; + + UIBuildEvent.UIAssetCounters counters = default; + + var registeredVTAs = new HashSet(); + + for (var i = 0; i < report.packedAssets.Length; ++i) + { + var infos = report.packedAssets[i].GetContents(); + + for (var j = 0; j < infos.Length; ++j) + { + PackedAssetInfo info = infos[j]; + + // Skip any default/builtin resource + if (info.sourceAssetGUID == default) + continue; + + if (info.type == typeof(MonoBehaviour)) + { + // We want the actual user type, which is not given by PackedAssetInfo.type + Type type = AssetDatabase.GetMainAssetTypeFromGUID(info.sourceAssetGUID); + + if (type == null) + continue; + + if (type == typeof(VisualTreeAsset)) + { + // The same VisualTreeAsset may appear multiple times because it contains a StyleSheet subasset + // Which seems to cause it to appear twice in the BuildReport content list + // So make sure we only count unique GUIDs to avoid counting them twice + if (registeredVTAs.Add(info.sourceAssetGUID)) + { + counters.VisualTreeAsset++; + } + } + else if (type == typeof(StyleSheet)) + { + counters.StyleSheet++; + } + else if (type == typeof(ThemeStyleSheet)) + { + counters.ThemeStyleSheet++; + } + else if (type == typeof(PanelSettings)) + { + counters.PanelSettings++; + } + } + } + } + + string buildSessionGuid = EditorApplication.buildSessionGUID.ToString(); + + var buildEvent = new UIBuildEvent(report.summary.guid.ToString(), buildSessionGuid, report.summary.buildType, counters); + EditorAnalytics.SendEventWithLimit(k_EventName, buildEvent, k_EventVersion); + } +} diff --git a/Editor/Mono/Undo/Undo.bindings.cs b/Editor/Mono/Undo/Undo.bindings.cs index 2ac5ff4d9b..6a4d59b13d 100644 --- a/Editor/Mono/Undo/Undo.bindings.cs +++ b/Editor/Mono/Undo/Undo.bindings.cs @@ -64,9 +64,15 @@ public void Dispose() [NativeHeader("Editor/Src/Undo/ObjectUndo.h")] [NativeHeader("Editor/Mono/Undo/Undo.bindings.h")] [NativeHeader("Editor/Src/Undo/AssetUndo.h")] - public partial class Undo { + [StaticAccessor("UndoBindings", StaticAccessorType.DoubleColon)] + public static extern bool isProcessing + { + [NativeMethod("GetIsProcessing")] + get; + } + [StaticAccessor("UndoBindings", StaticAccessorType.DoubleColon)] private static extern void GetRecordsInternal(object undoRecords, out int undoCursorPos); @@ -105,7 +111,7 @@ public static void RegisterCompleteObjectUndo(Object[] objectsToUndo, string nam [NativeThrows] [StaticAccessor("UndoBindings", StaticAccessorType.DoubleColon)] - private static extern void RegisterCompleteObjectUndoMultiple([NotNull] Object identifier, Object[] objectsToUndo, string name, int namePriority); + private static extern void RegisterCompleteObjectUndoMultiple([NotNull] Object identifier, [Unmarshalled] Object[] objectsToUndo, string name, int namePriority); public static void SetTransformParent(Transform transform, Transform newParent, string name) { @@ -119,6 +125,9 @@ public static void SetTransformParent(Transform transform, Transform newParent, [StaticAccessor("UndoBindings", StaticAccessorType.DoubleColon)] public static extern void MoveGameObjectToScene([NotNull] GameObject go, Scene scene, string name); + [StaticAccessor("UndoBindings", StaticAccessorType.DoubleColon)] + public static extern void SetSiblingIndex([NotNull] Transform transform, int siblingIndex, string name); + // Register the state of a Unity Object so the user can later undo back to that state. public static void RegisterCreatedObjectUndo(Object objectToUndo, string name) { @@ -188,7 +197,7 @@ public static void RecordObjects(Object[] objectsToUndo, string name) [NativeThrows] [StaticAccessor("UndoBindings", StaticAccessorType.DoubleColon)] - private static extern void RecordObjectsInternal(Object[] objectToUndo, int size, string name); + private static extern void RecordObjectsInternal([Unmarshalled] Object[] objectToUndo, int size, string name); [StaticAccessor("GetUndoManager()", StaticAccessorType.Dot)] [NativeMethod("ClearUndoIdentifier")] diff --git a/Editor/Mono/UnityConnect/CloudProjectSettings.cs b/Editor/Mono/UnityConnect/CloudProjectSettings.cs index 5d98a54f7e..ae88a01cb4 100644 --- a/Editor/Mono/UnityConnect/CloudProjectSettings.cs +++ b/Editor/Mono/UnityConnect/CloudProjectSettings.cs @@ -3,6 +3,8 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Threading; +using System.Threading.Tasks; using UnityEditor.Connect; namespace UnityEditor @@ -45,6 +47,9 @@ public static void RefreshAccessToken(Action refresh) UnityConnect.instance.RefreshAccessToken(refresh); } + public static Task GetServiceTokenAsync(CancellationToken cancellationToken = default) + => ServiceToken.Instance.GetServiceTokenAsync(accessToken, cancellationToken); + /// /// This method shows the Unity login popup. /// diff --git a/Modules/UnityConnectEditor/Common/UnityConnectWebRequestException.cs b/Editor/Mono/UnityConnect/Network/UnityConnectWebRequestException.cs similarity index 97% rename from Modules/UnityConnectEditor/Common/UnityConnectWebRequestException.cs rename to Editor/Mono/UnityConnect/Network/UnityConnectWebRequestException.cs index 142d30783d..f1fd950a6d 100644 --- a/Modules/UnityConnectEditor/Common/UnityConnectWebRequestException.cs +++ b/Editor/Mono/UnityConnect/Network/UnityConnectWebRequestException.cs @@ -2,8 +2,6 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using UnityEngine; using System; using System.Collections.Generic; diff --git a/Editor/Mono/UnityConnect/Network/UnityConnectWebRequestUtils.cs b/Editor/Mono/UnityConnect/Network/UnityConnectWebRequestUtils.cs new file mode 100644 index 0000000000..68110b9b34 --- /dev/null +++ b/Editor/Mono/UnityConnect/Network/UnityConnectWebRequestUtils.cs @@ -0,0 +1,83 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.Threading; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.Networking; + +namespace UnityEditor.Connect +{ + internal static class UnityConnectWebRequestUtils + { + internal static UnityConnectWebRequestException CreateUnityWebRequestException(UnityWebRequest request, + string message) + => new(L10n.Tr(message)) + { + error = request.error, + method = request.method, + timeout = request.timeout, + url = request.url, + responseHeaders = request.GetResponseHeaders(), + responseCode = request.responseCode, + isHttpError = request.result == UnityWebRequest.Result.ProtocolError, + isNetworkError = request.result == UnityWebRequest.Result.ConnectionError + }; + + /// + /// Used to determine if the UnityWebRequest had an error or error code + /// + internal static bool IsRequestError(UnityWebRequest request) + { + if (!string.IsNullOrEmpty(request.error)) + { + return true; + } + + switch (request.result) + { + case UnityWebRequest.Result.ConnectionError: + case UnityWebRequest.Result.ProtocolError: + case UnityWebRequest.Result.DataProcessingError: + return true; + } + + if (request.responseCode is < 200 or >= 300) + { + return true; + } + + return false; + } + + internal static bool IsUnityWebRequestReadyForJsonExtract(UnityWebRequest unityWebRequest) + { + return !IsRequestError(unityWebRequest) + && !string.IsNullOrEmpty(unityWebRequest.downloadHandler.text); + } + + /// + /// Used to run a UnityWebRequest on the main thread in an awaitable manner, while handling CancellationToken + /// + internal static async Task SendWebRequestAsync( + UnityWebRequest unityWebRequest, + CancellationToken cancellationToken = default) + { + var webRequestTask = AsyncUtils.RunUnityWebRequestOnMainThread(unityWebRequest); + + while (!unityWebRequest.isDone) + { + if (cancellationToken.IsCancellationRequested) + { + unityWebRequest.Abort(); + cancellationToken.ThrowIfCancellationRequested(); + } + + await Task.Yield(); + } + + await webRequestTask; + } + } +} diff --git a/Editor/Mono/UnityConnect/ServiceToken/Caching/GenesisAndServiceTokenCaching.cs b/Editor/Mono/UnityConnect/ServiceToken/Caching/GenesisAndServiceTokenCaching.cs new file mode 100644 index 0000000000..9c4d480e98 --- /dev/null +++ b/Editor/Mono/UnityConnect/ServiceToken/Caching/GenesisAndServiceTokenCaching.cs @@ -0,0 +1,62 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; + +namespace UnityEditor.Connect +{ + class GenesisAndServiceTokenCaching : IGenesisAndServiceTokenCaching + { + internal const string CacheKey = "Editor.GatewayTokens.Cache"; + readonly TimeSpan m_RefreshGracePeriod = TimeSpan.FromMinutes(30); + + public Tokens LoadCache() + { + var serializedTokens = SessionState.GetString(CacheKey, string.Empty); + + if (string.IsNullOrEmpty(serializedTokens)) + { + return new Tokens(); + } + + var deserializedTokens = Json.Deserialize(serializedTokens) as Dictionary; + + if (deserializedTokens == null) + { + return new Tokens(); + } + + return new Tokens() + { + GenesisToken = deserializedTokens.GetValueOrDefault(nameof(Tokens.GenesisToken))?.ToString(), + GatewayToken = deserializedTokens.GetValueOrDefault(nameof(Tokens.GatewayToken))?.ToString() + }; + } + + public void SaveCache(Tokens tokens) + { + var serialized = Json.Serialize(tokens); + SessionState.SetString(CacheKey, serialized); + } + + public DateTime GetNextRefreshTime(string gatewayToken) + { + try + { + if (string.IsNullOrEmpty(gatewayToken)) + { + return new DateTime(); + } + + var jwt = JsonWebToken.Decode(gatewayToken); + return jwt.exp - m_RefreshGracePeriod; + } + catch + { + return new DateTime(); + } + } + } +} diff --git a/Editor/Mono/UnityConnect/ServiceToken/Caching/IGenesisAndServiceTokenCaching.cs b/Editor/Mono/UnityConnect/ServiceToken/Caching/IGenesisAndServiceTokenCaching.cs new file mode 100644 index 0000000000..0f78a91043 --- /dev/null +++ b/Editor/Mono/UnityConnect/ServiceToken/Caching/IGenesisAndServiceTokenCaching.cs @@ -0,0 +1,15 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; + +namespace UnityEditor.Connect +{ + interface IGenesisAndServiceTokenCaching + { + public Tokens LoadCache(); + public void SaveCache(Tokens tokens); + public DateTime GetNextRefreshTime(string gatewayToken); + } +} diff --git a/Editor/Mono/UnityConnect/ServiceToken/Caching/JsonWebToken.cs b/Editor/Mono/UnityConnect/ServiceToken/Caching/JsonWebToken.cs new file mode 100644 index 0000000000..6955766c12 --- /dev/null +++ b/Editor/Mono/UnityConnect/ServiceToken/Caching/JsonWebToken.cs @@ -0,0 +1,58 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using System.Text; + +namespace UnityEditor.Connect +{ + readonly struct JsonWebToken + { + static readonly char[] k_JwtSeparator = { '.' }; + static readonly DateTime k_UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + + public DateTime exp { get; } + + public JsonWebToken(long exp) + { + this.exp = k_UnixEpoch.AddSeconds(exp); + } + + public override string ToString() + { + return Json.Serialize(this); + } + + public static JsonWebToken Decode(string token) + { + var parts = token.Split(k_JwtSeparator); + if (parts.Length != 3) + { + throw new ArgumentException($"The authentication token is malformed or invalid. " + + $"JWT has an invalid number of sections. Token: '{token}'"); + } + + var payload = parts[1]; + var payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload)); + var deserialized = Json.Deserialize(payloadJson) as Dictionary; + return new JsonWebToken(Convert.ToInt64(deserialized.GetValueOrDefault(nameof(exp)))); + } + + static byte[] Base64UrlDecode(string input) + { + var output = input; + output = output.Replace('-', '+'); // 62nd char of encoding + output = output.Replace('_', '/'); // 63rd char of encoding + + var mod4 = input.Length % 4; + if (mod4 > 0) + { + output += new string('=', 4 - mod4); + } + + return Convert.FromBase64String(output); + } + } +} diff --git a/Editor/Mono/UnityConnect/ServiceToken/Caching/Tokens.cs b/Editor/Mono/UnityConnect/ServiceToken/Caching/Tokens.cs new file mode 100644 index 0000000000..1c983636fd --- /dev/null +++ b/Editor/Mono/UnityConnect/ServiceToken/Caching/Tokens.cs @@ -0,0 +1,18 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +namespace UnityEditor.Connect +{ + internal struct Tokens + { + public string GatewayToken; + public string GenesisToken; + + public Tokens() + { + GatewayToken = default; + GenesisToken = default; + } + } +} diff --git a/Editor/Mono/UnityConnect/ServiceToken/ConfigurationProvider/CloudEnvironmentConfigProvider.cs b/Editor/Mono/UnityConnect/ServiceToken/ConfigurationProvider/CloudEnvironmentConfigProvider.cs new file mode 100644 index 0000000000..e902c7587d --- /dev/null +++ b/Editor/Mono/UnityConnect/ServiceToken/ConfigurationProvider/CloudEnvironmentConfigProvider.cs @@ -0,0 +1,54 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using UnityEngine; + +namespace UnityEditor.Connect +{ + class CloudEnvironmentConfigProvider : ICloudEnvironmentConfigProvider + { + internal const string CloudEnvironmentArg = "-cloudEnvironment"; + internal const string StagingEnv = "staging"; + + public bool IsStaging() + { + return GetCloudEnvironment(Environment.GetCommandLineArgs()) == StagingEnv; + } + + internal string GetCloudEnvironment(string[] commandLineArgs) + { + string cloudEnvironmentField = null; + + foreach (var arg in commandLineArgs) + { + if (arg.StartsWith(CloudEnvironmentArg)) + { + cloudEnvironmentField = arg; + break; + } + } + + if (cloudEnvironmentField != null) + { + var cloudEnvironmentIndex = Array.IndexOf(commandLineArgs, cloudEnvironmentField); + + if (cloudEnvironmentField == CloudEnvironmentArg) + { + if (cloudEnvironmentIndex <= commandLineArgs.Length - 2) + { + return commandLineArgs[cloudEnvironmentIndex + 1]; + } + } + else if (cloudEnvironmentField.Contains('=')) + { + var value = cloudEnvironmentField.Substring(cloudEnvironmentField.IndexOf('=') + 1); + return !string.IsNullOrEmpty(value) ? value : null; + } + } + + return null; + } + } +} diff --git a/Editor/Mono/UnityConnect/ServiceToken/ConfigurationProvider/ICloudEnvironmentConfigProvider.cs b/Editor/Mono/UnityConnect/ServiceToken/ConfigurationProvider/ICloudEnvironmentConfigProvider.cs new file mode 100644 index 0000000000..d3552daed4 --- /dev/null +++ b/Editor/Mono/UnityConnect/ServiceToken/ConfigurationProvider/ICloudEnvironmentConfigProvider.cs @@ -0,0 +1,11 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +namespace UnityEditor.Connect +{ + interface ICloudEnvironmentConfigProvider + { + bool IsStaging(); + } +} diff --git a/Editor/Mono/UnityConnect/ServiceToken/ServiceToken.cs b/Editor/Mono/UnityConnect/ServiceToken/ServiceToken.cs new file mode 100644 index 0000000000..e2baa36a94 --- /dev/null +++ b/Editor/Mono/UnityConnect/ServiceToken/ServiceToken.cs @@ -0,0 +1,71 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Threading; +using System.Threading.Tasks; +using UnityEngine; + +namespace UnityEditor.Connect +{ + class ServiceToken + { + readonly ITokenExchange m_TokenExchange; + readonly IGenesisAndServiceTokenCaching m_GenesisAndServiceTokenCaching; + readonly DateTime m_DateTime; + + internal static ServiceToken Instance => k_LazyInstance.Value; + + static readonly Lazy k_LazyInstance = new Lazy(() => + { + var cloudEnvironmentConfigProvider = new CloudEnvironmentConfigProvider(); + var tokenExchange = new TokenExchange(cloudEnvironmentConfigProvider); + var tokenCaching = new GenesisAndServiceTokenCaching(); + return new ServiceToken(tokenExchange, tokenCaching); + }); + + internal ServiceToken( + ITokenExchange tokenExchange, + IGenesisAndServiceTokenCaching genesisAndServiceTokenCaching) + { + m_TokenExchange = tokenExchange; + m_GenesisAndServiceTokenCaching = genesisAndServiceTokenCaching; + m_DateTime = DateTime.UtcNow; + } + + public async Task GetServiceTokenAsync(string genesisToken, CancellationToken cancellationToken = default) + { + Tokens cachedTokens = new(); + await AsyncUtils.RunNextActionOnMainThread(() => cachedTokens = m_GenesisAndServiceTokenCaching.LoadCache()); + + var nextRefreshTime = m_GenesisAndServiceTokenCaching.GetNextRefreshTime(cachedTokens.GatewayToken); + + if (genesisToken != cachedTokens.GenesisToken || m_DateTime.ToUniversalTime() >= nextRefreshTime) + { + if (!string.IsNullOrEmpty(genesisToken)) + { + try + { + cachedTokens.GatewayToken = + await m_TokenExchange.GetServiceTokenAsync(genesisToken, cancellationToken); + } + catch + { + cachedTokens.GatewayToken = null; + throw; + } + } + else + { + cachedTokens.GatewayToken = null; + } + + cachedTokens.GenesisToken = genesisToken; + } + + await AsyncUtils.RunNextActionOnMainThread(() => m_GenesisAndServiceTokenCaching.SaveCache(cachedTokens)); + return cachedTokens.GatewayToken; + } + } +} diff --git a/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/ITokenExchange.cs b/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/ITokenExchange.cs new file mode 100644 index 0000000000..fd22ef6739 --- /dev/null +++ b/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/ITokenExchange.cs @@ -0,0 +1,14 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.Threading; +using System.Threading.Tasks; + +namespace UnityEditor.Connect +{ + interface ITokenExchange + { + Task GetServiceTokenAsync(string genesisToken, CancellationToken cancellationToken); + } +} diff --git a/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/Model/TokenExchangeRequest.cs b/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/Model/TokenExchangeRequest.cs new file mode 100644 index 0000000000..6088c26d9e --- /dev/null +++ b/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/Model/TokenExchangeRequest.cs @@ -0,0 +1,21 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; + +namespace UnityEditor.Connect +{ + [Serializable] + class TokenExchangeRequest + { + public string token; + + public TokenExchangeRequest() {} + + public TokenExchangeRequest(string token) + { + this.token = token; + } + } +} diff --git a/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/Model/TokenExchangeResponse.cs b/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/Model/TokenExchangeResponse.cs new file mode 100644 index 0000000000..af101c3e6b --- /dev/null +++ b/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/Model/TokenExchangeResponse.cs @@ -0,0 +1,14 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; + +namespace UnityEditor.Connect +{ + [Serializable] + class TokenExchangeResponse + { + public string token; + } +} diff --git a/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/TokenExchange.cs b/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/TokenExchange.cs new file mode 100644 index 0000000000..695a0f4c42 --- /dev/null +++ b/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/TokenExchange.cs @@ -0,0 +1,150 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.Networking; + +namespace UnityEditor.Connect +{ + class TokenExchange : ITokenExchange + { + const string k_RequestContentType = "application/json"; + const string k_StagingServicesGatewayTokenExchangeUrl = + "https://staging.services.unity.com/api/auth/v1/genesis-token-exchange/unity"; + const string k_ProductionServicesGatewayTokenExchangeUrl = + "https://services.unity.com/api/auth/v1/genesis-token-exchange/unity"; + + const string k_SerializationFailureMessage = + "Token Exchange failed due to an issue with serialization/deserialization. "; + const string k_WebRequestFailureMessage = + "Token Exchange failed due a failure with the web request."; + const string k_PayloadDeserializationFailureMessage = + k_SerializationFailureMessage + "Payload that failed to deserialize: "; + const string k_KeyMissingSerializationFailureMessage = + k_SerializationFailureMessage + "Deserialized response does not contain the key: "; + + readonly ICloudEnvironmentConfigProvider m_CloudEnvironmentConfigProvider; + + internal TokenExchange(ICloudEnvironmentConfigProvider cloudEnvironmentConfigProvider) + { + m_CloudEnvironmentConfigProvider = cloudEnvironmentConfigProvider; + } + + public async Task GetServiceTokenAsync( + string genesisToken, + CancellationToken cancellationToken = default) + { + var tokenExchangeRequest = new TokenExchangeRequest(genesisToken); + Dictionary deserializedResponse; + + var exchangeResult = await TokenExchangeRequestAsync(tokenExchangeRequest, cancellationToken); + + try + { + deserializedResponse = Json.Deserialize(exchangeResult.ResponseJson) as Dictionary; + } + catch (Exception exception) + { + throw new SerializationException(k_PayloadDeserializationFailureMessage + + $"'{exchangeResult.ResponseJson}'", exception); + } + + if (deserializedResponse is null) + { + throw new SerializationException(k_PayloadDeserializationFailureMessage + + $"'{exchangeResult.ResponseJson}'"); + } + + if (!TokenExchangeResponseContainsTokenKey(deserializedResponse)) + { + throw new SerializationException(k_KeyMissingSerializationFailureMessage + + $"'{nameof(TokenExchangeResponse.token)}'"); + } + + return deserializedResponse[nameof(TokenExchangeResponse.token)].ToString(); + } + + async Task TokenExchangeRequestAsync( + TokenExchangeRequest tokenExchangeRequest, + CancellationToken cancellationToken = default) + { + var jsonPayload = Json.Serialize(tokenExchangeRequest); + var postBytes = Encoding.UTF8.GetBytes(jsonPayload); + var endpoint = GetEndpoint(); + + using (var exchangeRequest = new UnityWebRequest(endpoint, UnityWebRequest.kHttpVerbPOST)) + { + exchangeRequest.uploadHandler = new UploadHandlerRaw(postBytes) {contentType = k_RequestContentType}; + exchangeRequest.downloadHandler = new DownloadHandlerBuffer(); + + await UnityConnectWebRequestUtils.SendWebRequestAsync(exchangeRequest, cancellationToken); + + VerifyTokenExchangeResponse(exchangeRequest); + + return new TokenExchangeResult( + exchangeRequest.result.ToString(), + exchangeRequest.error, + exchangeRequest.responseCode.ToString(), + exchangeRequest.downloadHandler.text); + } + } + + static void VerifyTokenExchangeResponse(UnityWebRequest exchangeRequest) + { + if (UnityConnectWebRequestUtils.IsUnityWebRequestReadyForJsonExtract(exchangeRequest)) + { + return; + } + + throw UnityConnectWebRequestUtils + .CreateUnityWebRequestException(exchangeRequest, k_WebRequestFailureMessage); + } + + string GetEndpoint() + { + string endpoint = k_ProductionServicesGatewayTokenExchangeUrl; + + try + { + if (m_CloudEnvironmentConfigProvider.IsStaging()) + { + endpoint = k_StagingServicesGatewayTokenExchangeUrl; + } + } + catch (Exception e) + { + Debug.LogError("Error while parsing the Unity build command" + + " line environment argument, defaulting environment to production for token" + + $" exchange. Details: '{e}'."); + } + + return endpoint; + } + + bool TokenExchangeResponseContainsTokenKey(Dictionary deserializedResponse) + => deserializedResponse.ContainsKey(nameof(TokenExchangeResponse.token)); + } + + struct TokenExchangeResult + { + public string Result { get; } + public string Error { get; } + public string ResponseCode { get; } + public string ResponseJson { get; } + + public TokenExchangeResult(string result, string error, string responseCode, string responseJson) + { + Result = result; + Error = error; + ResponseCode = responseCode; + ResponseJson = responseJson; + } + } +} diff --git a/Editor/Mono/UnityConnect/UnityConnect.bindings.cs b/Editor/Mono/UnityConnect/UnityConnect.bindings.cs index 514f537452..14137eca7a 100644 --- a/Editor/Mono/UnityConnect/UnityConnect.bindings.cs +++ b/Editor/Mono/UnityConnect/UnityConnect.bindings.cs @@ -366,19 +366,19 @@ public bool SetCOPPACompliance(int compliance) } // End for Javascript Only - [MenuItem("Window/Unity Connect/Clear Access Token", false, 1000, true)] + [MenuItem("Window/Unity Connect/Clear Access Token", false, 1000, true, secondaryPriority = 1)] public static void InvokeClearAccessTokenForTesting() { instance.ClearAccessToken(); } - [MenuItem("Window/Unity Connect/Computer GoesToSleep", false, 1000, true)] + [MenuItem("Window/Unity Connect/Computer GoesToSleep", false, 1000, true, secondaryPriority = 2)] public static void TestComputerGoesToSleep() { instance.ComputerGoesToSleep(); } - [MenuItem("Window/Unity Connect/Computer DidWakeUp", false, 1000, true)] + [MenuItem("Window/Unity Connect/Computer DidWakeUp", false, 1000, true, secondaryPriority = 3)] public static void TestComputerDidWakeUp() { instance.ComputerDidWakeUp(); diff --git a/Editor/Mono/UnityConnect/Utils/AsyncUtils.cs b/Editor/Mono/UnityConnect/Utils/AsyncUtils.cs new file mode 100644 index 0000000000..2e77d418e1 --- /dev/null +++ b/Editor/Mono/UnityConnect/Utils/AsyncUtils.cs @@ -0,0 +1,79 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.Networking; + +namespace UnityEditor.Connect +{ + internal static class AsyncUtils + { + /// + /// Used to run an action on the main thread of Unity + /// + /// Awaitable task that indicates when the action is completed + internal static Task RunNextActionOnMainThread( + Action action, + [CallerFilePath] string file = null, + [CallerMemberName] string caller = null, + [CallerLineNumber] int line = 0) + { + var taskCompletionSource = new TaskCompletionSource(); + EditorApplication.CallbackFunction callback = null; + callback = () => + { + EditorApplication.update -= callback; + try + { + action(); + taskCompletionSource.SetResult(true); + } + catch (Exception e) when (caller != null && file != null && line != 0) + { + taskCompletionSource.SetException(e); + throw new Exception($"Exception thrown from invocation made by '{file}'({line}) by {caller}", e); + } + }; + EditorApplication.update += callback; + return taskCompletionSource.Task; + } + + /// + /// Used to run a UnityWebRequest on the main thread of Unity + /// + /// Awaitable task that indicates when the web request is completed + internal static Task RunUnityWebRequestOnMainThread( + UnityWebRequest request, + [CallerFilePath] string file = null, + [CallerMemberName] string caller = null, + [CallerLineNumber] int line = 0) + { + var taskCompletionSource = new TaskCompletionSource(); + EditorApplication.update += Callback; + return taskCompletionSource.Task; + + void Callback() + { + EditorApplication.update -= Callback; + try + { + request.SendWebRequest().completed += RequestCompleted; + } + catch (Exception e) when (caller != null && file != null && line != 0) + { + taskCompletionSource.SetException(e); + throw new Exception($"Exception thrown from invocation made by '{file}'({line}) by {caller}", e); + } + } + + void RequestCompleted(AsyncOperation _) + { + taskCompletionSource.SetResult(true); + } + } + } +} diff --git a/Editor/Mono/Unsupported.bindings.cs b/Editor/Mono/Unsupported.bindings.cs index 6523eaf5c4..fac7aec0bc 100644 --- a/Editor/Mono/Unsupported.bindings.cs +++ b/Editor/Mono/Unsupported.bindings.cs @@ -88,6 +88,9 @@ public static bool IsDeveloperBuild() [FreeFunction("GetSceneTracker().FlushDirty")] public static extern void SceneTrackerFlushDirty(); + [FreeFunction("GetSceneTracker().TickHierarchyHasChanged")] + internal static extern void TickHierarchyHasChanged(); + [FreeFunction("GetScreenManager().SetAllowCursorHide")] public static extern void SetAllowCursorHide(bool allow); diff --git a/Editor/Mono/Unwrapping.bindings.cs b/Editor/Mono/Unwrapping.bindings.cs index eb66678f06..40e0f8cf59 100644 --- a/Editor/Mono/Unwrapping.bindings.cs +++ b/Editor/Mono/Unwrapping.bindings.cs @@ -44,6 +44,9 @@ public static Vector2[] GeneratePerTriangleUV(Mesh src) // Will generate per-triangle uv (3 uv pairs for each triangle) with provided settings public static Vector2[] GeneratePerTriangleUV(Mesh src, UnwrapParam settings) { + if (src == null) + throw new ArgumentNullException("src"); + return GeneratePerTriangleUVImpl(src, settings); } diff --git a/Editor/Mono/Utils/Paths.cs b/Editor/Mono/Utils/Paths.cs index 0fa7263ea7..9b7cbcf08b 100644 --- a/Editor/Mono/Utils/Paths.cs +++ b/Editor/Mono/Utils/Paths.cs @@ -13,7 +13,7 @@ namespace UnityEditor.Utils { internal static class Paths { - static char[] invalidFilenameChars; + internal static char[] invalidFilenameChars; static Paths() { diff --git a/Editor/Mono/Utils/Pram.cs b/Editor/Mono/Utils/Pram.cs index 135b15c240..2f346a6bf9 100644 --- a/Editor/Mono/Utils/Pram.cs +++ b/Editor/Mono/Utils/Pram.cs @@ -77,5 +77,11 @@ public Program AppDeploy(string provider, string applicationId, string environme public Program AppStartDetached(string provider, string applicationId, string environment, params string[] arguments) => CreateProgram(new[] {"app-start-detached", "--environment", environment, provider, applicationId, "--"}.Concat(arguments)); + + public Program DetectEnvironment(string provider) => + CreateProgram(new[] {"env-detect", provider }); + + public Program EnvironmentProperties(string provider, string environment) => + CreateProgram(new[] {"env-props", "--environment", environment, provider }); } } diff --git a/Editor/Mono/Utils/SearchUtils.cs b/Editor/Mono/Utils/SearchUtils.cs index 8912adb40c..b92f183618 100644 --- a/Editor/Mono/Utils/SearchUtils.cs +++ b/Editor/Mono/Utils/SearchUtils.cs @@ -6,6 +6,11 @@ namespace UnityEditor { + interface ISearchableContainer + { + string searchText { get; } + } + static class SearchUtils { internal static bool MatchSearch(string searchContext, string content) @@ -23,7 +28,7 @@ internal static bool MatchSearchGroups(string searchContext, string content) internal static bool MatchSearchGroups(string searchContext, string content, out int startIndex, out int endIndex) { startIndex = endIndex = -1; - if (searchContext == null || content == null) + if (searchContext == null || content == null || content.Length == 0) return false; if (searchContext == content) diff --git a/Editor/Mono/Utils/WebURLs.bindings.cs b/Editor/Mono/Utils/WebURLs.bindings.cs index 0b8b271d98..2ae1870a82 100644 --- a/Editor/Mono/Utils/WebURLs.bindings.cs +++ b/Editor/Mono/Utils/WebURLs.bindings.cs @@ -15,8 +15,8 @@ internal static class WebURLs public static extern string unityConnect { get; } [NativeProperty("kURLUnityForum", true, TargetType.Field)] public static extern string unityForum { get; } - [NativeProperty("kURLUnityAnswers", true, TargetType.Field)] - public static extern string unityAnswers { get; } + [NativeProperty("kURLUnityDiscussions", true, TargetType.Field)] + public static extern string unityDiscussions { get; } [NativeProperty("kURLUnityFeedback", true, TargetType.Field)] public static extern string unityFeedback { get; } [NativeProperty("kURLWhatsNewPage", true, TargetType.Field)] diff --git a/Editor/Mono/View.cs b/Editor/Mono/View.cs index caa520513b..4b9252a7f1 100644 --- a/Editor/Mono/View.cs +++ b/Editor/Mono/View.cs @@ -120,12 +120,9 @@ public Rect position // Override to resize subviews protected virtual void SetPosition(Rect newPos) { - m_Position = newPos; - } + if (!IsValidViewRect(newPos)) + throw new ArgumentException($"Invalid position: {newPos}"); - // Only set the position. - internal void SetPositionOnly(Rect newPos) - { m_Position = newPos; } @@ -239,5 +236,14 @@ virtual protected bool OnFocus() { return true; } + + internal static bool IsValidViewPosition(Vector2 p) => IsValidViewVector(p); + internal static bool IsValidViewSize(Vector2 s) => IsValidViewVector(s); + internal static bool IsValidViewRect(Rect r) => IsValidViewVector(r.position) && IsValidViewVector(r.size); + + static bool IsValidViewVector(Vector2 v) + { + return !float.IsNaN(v.x) && !float.IsNaN(v.y) && !float.IsInfinity(v.x) && !float.IsInfinity(v.y); + } } } //namespace diff --git a/Editor/Mono/WindowBackendManager.cs b/Editor/Mono/WindowBackendManager.cs index a7d194534f..fa05ce8af1 100644 --- a/Editor/Mono/WindowBackendManager.cs +++ b/Editor/Mono/WindowBackendManager.cs @@ -21,6 +21,8 @@ internal interface IWindowModel Action onGUIHandler { get; } IWindowBackend windowBackend { get; set; } + + bool resetPanelRenderingOnAssetChange { get; } } internal interface IEditorWindowModel : IWindowModel @@ -48,6 +50,7 @@ internal interface IWindowBackend void SizeChanged(); void EventInterestsChanged(); + void ResetPanelRenderingOnAssetChangeChanged(); } internal interface IEditorWindowBackend : IWindowBackend diff --git a/External/Bee/BeeDriver2/Bee.BeeDriver2.dll b/External/Bee/BeeDriver2/Bee.BeeDriver2.dll deleted file mode 100644 index 32fc1706bf..0000000000 Binary files a/External/Bee/BeeDriver2/Bee.BeeDriver2.dll and /dev/null differ diff --git a/External/Bee/BeeDriver2/Bee.BinLog.dll b/External/Bee/BeeDriver2/Bee.BinLog.dll deleted file mode 100644 index b8c566b39d..0000000000 Binary files a/External/Bee/BeeDriver2/Bee.BinLog.dll and /dev/null differ diff --git a/External/ExCSS/builds/builds/lib/net35/ExCSS.Unity.dll b/External/ExCSS/builds/builds/lib/net35/ExCSS.Unity.dll index 7d18e93f66..2211559843 100644 Binary files a/External/ExCSS/builds/builds/lib/net35/ExCSS.Unity.dll and b/External/ExCSS/builds/builds/lib/net35/ExCSS.Unity.dll differ diff --git a/External/JsonParsers/MiniJson/MiniJSON.cs b/External/JsonParsers/MiniJson/MiniJSON.cs index e7e5be0e31..1fc76c0a1c 100644 --- a/External/JsonParsers/MiniJson/MiniJSON.cs +++ b/External/JsonParsers/MiniJson/MiniJSON.cs @@ -322,17 +322,14 @@ object ParseNumber () { string number = NextWord; - if (number.IndexOf ('.') == -1) - { - long parsedInt; - Int64.TryParse (number, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out parsedInt); - return parsedInt; - } - - double parsedDouble; - Double.TryParse (number, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out parsedDouble); - return parsedDouble; - } + if (Int64.TryParse(number, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var parsedInt)) + { + return parsedInt; + } + + Double.TryParse(number, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var parsedDouble); + return parsedDouble; + } void EatWhitespace () { diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Builder.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Builder.cs index b9296f24b0..169d0b39fd 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Builder.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Builder.cs @@ -8,6 +8,16 @@ namespace Unity.UI.Builder { class Builder : BuilderPaneWindow, IBuilderViewportWindow, IHasCustomMenu { + static Builder() + { + EditorApplication.fileMenuSaved += () => + { + var builder = ActiveWindow; + if (builder != null && builder.document.hasUnsavedChanges) + builder.SaveChanges(); + }; + } + BuilderSelection m_Selection; BuilderToolbar m_Toolbar; @@ -223,6 +233,9 @@ public override void SaveChanges() public override void DiscardChanges() { + // Ensure stylesheet cache will be up to date. + UnityEngine.UIElements.StyleSheets.StyleSheetCache.ClearCaches(); + // Restore UXML and USS assets from backup document.RestoreAssetsFromBackup(); diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/BuilderExternalPackages.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/BuilderExternalPackages.cs index 0e0a810b33..6ccab145f4 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/BuilderExternalPackages.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/BuilderExternalPackages.cs @@ -11,7 +11,10 @@ public static bool is2DSpriteEditorInstalled { get { - return PackageInfo.GetAllRegisteredPackages().Any(x => x.name == "com.unity.2d.sprite" && x.version == "1.0.0"); + var packageInfo = PackageInfo.FindForPackageName("com.unity.2d.sprite"); + if (packageInfo != null) + return packageInfo.version == "1.0.0"; + return false; } } diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/BuilderPane.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/BuilderPane.cs index e6e090a9aa..a780401d81 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/BuilderPane.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/BuilderPane.cs @@ -22,6 +22,14 @@ internal class BuilderPane : VisualElement public new class UxmlTraits : VisualElement.UxmlTraits { + /// + /// Constructor. + /// + public UxmlTraits() + { + focusable.defaultValue = true; + } + UxmlStringAttributeDescription m_Title = new UxmlStringAttributeDescription { name = "title" }; public override IEnumerable uxmlChildElementsDescription diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/BuilderSelection.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/BuilderSelection.cs index ecda5947d9..845d657ad3 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/BuilderSelection.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/BuilderSelection.cs @@ -81,14 +81,14 @@ public BuilderSelectionType selectionType return BuilderSelectionType.StyleSheet; if (selectedElement.GetVisualElementAsset() == null) { - if (selectedElement.HasProperty(VisualTreeAsset.LinkedVEAInTemplatePropertyName)) + if (selectedElement.HasProperty(VisualTreeAsset.LinkedVEAInTemplatePropertyName) + && BuilderAssetUtilities.GetVisualElementRootTemplate(selectedElement) != null + && !BuilderAssetUtilities.HasDynamicallyCreatedTemplateAncestor(selectedElement)) { return BuilderSelectionType.ElementInTemplateInstance; } - else - { - return BuilderSelectionType.ElementInControlInstance; - } + + return BuilderSelectionType.ElementInControlInstance; } if (selectedElement.IsPartOfActiveVisualTreeAsset(m_PaneWindow.document)) return BuilderSelectionType.Element; @@ -133,6 +133,16 @@ public BuilderSelection(VisualElement root, BuilderPaneWindow paneWindow) m_LiveReloadTriggerMethod = ReflectionExtensions.GetStaticMethodByReflection(typeof(UIElementsUtility), "InMemoryAssetsHaveBeenChanged"); } + internal VisualElement GetFirstSelectedElement() + { + foreach (var element in m_Selection) + { + return element; + } + + return null; + } + public void AssignNotifiers(IEnumerable notifiers) { m_Notifiers.Clear(); @@ -239,6 +249,25 @@ public void NotifyOfHierarchyChange( if (m_Notifiers == null || m_Notifiers.Count == 0) return; + ForceVisualAssetUpdateWithoutSave(element, changeType); + + // This is so anyone interested can refresh their use of this UXML with + // the latest (unsaved to disk) changes. + EditorUtility.SetDirty(m_PaneWindow.document.visualTreeAsset); + m_LiveReloadTriggerMethod?.Invoke(null, null); + + foreach (var notifier in m_Notifiers) + if (notifier != source) + notifier.HierarchyChanged(element, changeType); + } + + internal void ForceVisualAssetUpdateWithoutSave( + VisualElement element = null, + BuilderHierarchyChangeType changeType = BuilderHierarchyChangeType.All) + { + if (m_Notifiers == null || m_Notifiers.Count == 0) + return; + VisualElementAsset vea = element?.GetVisualElementAsset(); if (vea != null && vea.ruleIndex >= 0 && changeType.HasFlag(BuilderHierarchyChangeType.InlineStyle)) { @@ -254,15 +283,6 @@ public void NotifyOfHierarchyChange( { m_PaneWindow.document.RefreshStyle(m_DocumentRootElement); } - - // This is so anyone interested can refresh their use of this UXML with - // the latest (unsaved to disk) changes. - EditorUtility.SetDirty(m_PaneWindow.document.visualTreeAsset); - m_LiveReloadTriggerMethod?.Invoke(null, null); - - foreach (var notifier in m_Notifiers) - if (notifier != source) - notifier.HierarchyChanged(element, changeType); } public void NotifyOfStylingChange(IBuilderSelectionNotifier source = null, List styles = null, BuilderStylingChangeType changeType = BuilderStylingChangeType.Default) diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Document/BuilderDocument.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Document/BuilderDocument.cs index 5cf9fda7a6..da2c33879e 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Document/BuilderDocument.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Document/BuilderDocument.cs @@ -4,6 +4,9 @@ using UnityEditor; using System; using System.IO; +using UnityEditor.UIElements.StyleSheets; +using UnityEngine.Serialization; +using Object = UnityEngine.Object; namespace Unity.UI.Builder { @@ -22,6 +25,17 @@ public enum CanvasTheme Custom } + [Serializable] + public struct UxmlURIToThemeStyleSheetURIEntry + { + public string UxmlURI; + public string ThemeStyleSheetURI; + public CanvasTheme CanvasTheme; + } + + [SerializeField] + List m_SavedBuilderUxmlToThemeStyleSheetList = new List(); + // // Serialized Data // @@ -133,8 +147,12 @@ public bool hasUnsavedChanges public CanvasTheme currentCanvasTheme => m_CurrentCanvasTheme; public ThemeStyleSheet currentCanvasThemeStyleSheet => m_CurrentCanvasThemeStyleSheetReference.asset; - public void ChangeDocumentTheme(VisualElement documentElement, CanvasTheme canvasTheme, ThemeStyleSheet themeSheet) + private ThemeStyleSheetManager m_ThemeManager; + + public void ChangeDocumentTheme(VisualElement documentElement, CanvasTheme canvasTheme, ThemeStyleSheet themeSheet, ThemeStyleSheetManager themeStyleSheetManager, bool isInit = false) { + if (themeStyleSheetManager != null && m_ThemeManager != themeStyleSheetManager) + m_ThemeManager = themeStyleSheetManager; m_CurrentCanvasTheme = canvasTheme; m_CurrentCanvasThemeStyleSheetReference = themeSheet; @@ -142,6 +160,45 @@ public void ChangeDocumentTheme(VisualElement documentElement, CanvasTheme canva { themeSheet.isDefaultStyleSheet = true; } + + // Only replace the entry on Load, not on Builder Init + if (!string.IsNullOrEmpty(activeOpenUXMLFile.uxmlPath) && !isInit) + { + var existingEntry = + m_SavedBuilderUxmlToThemeStyleSheetList.Find(entry => + { + var assetPath = Path.GetFileNameWithoutExtension(entry.UxmlURI); + var response = URIHelpers.ValidateAssetURL(assetPath, entry.UxmlURI); + if (response.resolvedQueryAsset is VisualTreeAsset vta) + { + return vta == activeOpenUXMLFile.visualTreeAsset; + } + + return false; + }); + + if (!string.IsNullOrEmpty(existingEntry.UxmlURI)) + { + m_SavedBuilderUxmlToThemeStyleSheetList.Remove(existingEntry); + } + + var newEntry = new UxmlURIToThemeStyleSheetURIEntry + { + UxmlURI = URIHelpers.MakeAssetUri(activeOpenUXMLFile.visualTreeAsset), + ThemeStyleSheetURI = URIHelpers.MakeAssetUri(themeSheet), + CanvasTheme = m_CurrentCanvasTheme + }; + + // Do not save entry if it is using the default theme + var projectDefaultTssAsset = m_ThemeManager.FindProjectDefaultRuntimeThemeAsset(); + + // If we find a Default Runtime Theme in the project, use that as the default theme + // Otherwise we use the built-in Default Runtime Theme + var defaultTssAsset = projectDefaultTssAsset == null ? m_ThemeManager.builtInDefaultRuntimeTheme : projectDefaultTssAsset; + if (themeSheet != defaultTssAsset) + m_SavedBuilderUxmlToThemeStyleSheetList.Add(newEntry); + } + RefreshStyle(documentElement); } @@ -293,12 +350,57 @@ public void NewDocument(VisualElement documentRootElement) internal bool SaveNewTemplateFileFromHierarchy(string newTemplatePath, string uxml) => activeOpenUXMLFile.SaveNewTemplateFileFromHierarchy(newTemplatePath, uxml); - public void LoadDocument(VisualTreeAsset visualTreeAsset, VisualElement documentElement) + public void LoadDocument(VisualTreeAsset visualTreeAsset, VisualElement documentElement, ThemeStyleSheetManager themeStyleSheetManager) { activeOpenUXMLFile.LoadDocument(visualTreeAsset, documentElement); + if (themeStyleSheetManager != null && m_ThemeManager != themeStyleSheetManager) + m_ThemeManager = themeStyleSheetManager; + ForceUpdateDocumentTheme(); SaveToDisk(); } + private void ForceUpdateDocumentTheme() + { + if (m_ThemeManager == null) + return; + + // Find the entry. If it exists and is custom, use it to set the theme. Otherwise, use the editor themes or the default runtime theme. + var forceToRuntimeTheme = false; + var existingEntry = + m_SavedBuilderUxmlToThemeStyleSheetList.Find(entry => + { + var assetPath = Path.GetFileNameWithoutExtension(entry.UxmlURI); + var response = URIHelpers.ValidateAssetURL(assetPath, entry.UxmlURI); + if (response.resolvedQueryAsset is VisualTreeAsset vta) + { + return vta == activeOpenUXMLFile.visualTreeAsset; + } + + return false; + }); + if (!string.IsNullOrEmpty(existingEntry.UxmlURI) && existingEntry.CanvasTheme == CanvasTheme.Custom) + { + m_CurrentCanvasTheme = existingEntry.CanvasTheme; + var themeSheetAssetPath = Path.GetFileNameWithoutExtension(existingEntry.ThemeStyleSheetURI); + var response = URIHelpers.ValidateAssetURL(themeSheetAssetPath, existingEntry.ThemeStyleSheetURI); + if (response.resolvedQueryAsset is ThemeStyleSheet themeStyleSheet) + m_CurrentCanvasThemeStyleSheetReference = + themeStyleSheet; + else + forceToRuntimeTheme = true; + } + else if (existingEntry.CanvasTheme == CanvasTheme.Custom) + forceToRuntimeTheme = true; + + if (!forceToRuntimeTheme) + return; + + m_CurrentCanvasTheme = CanvasTheme.Custom; + var projectDefaultTssAsset = m_ThemeManager.FindProjectDefaultRuntimeThemeAsset(); + var defaultTssAsset = projectDefaultTssAsset == null ? m_ThemeManager.builtInDefaultRuntimeTheme : projectDefaultTssAsset; + m_CurrentCanvasThemeStyleSheetReference = defaultTssAsset; + } + // // Circular Dependencies // @@ -424,7 +526,8 @@ public void OnBeforeSerialize() public void OnAfterDeserialize() => activeOpenUXMLFile.OnAfterDeserialize(); - void LoadFromDisk() + // internal because it's used in tests + internal void LoadFromDisk() { var path = BuilderConstants.builderDocumentDiskJsonFileAbsolutePath; @@ -446,6 +549,18 @@ void LoadFromDisk() public void SaveToDisk() { + // Before saving, clear entries with deleted UXMLs from ThemeStyleSheetList + var BuilderUxmlToThemeStyleSheetListCopy = new List(m_SavedBuilderUxmlToThemeStyleSheetList); + foreach (var entry in BuilderUxmlToThemeStyleSheetListCopy) + { + var assetPath = Path.GetFileNameWithoutExtension(entry.UxmlURI); + var response = URIHelpers.ValidateAssetURL(assetPath, entry.UxmlURI); + if (response.resolvedQueryAsset is not VisualTreeAsset) + { + m_SavedBuilderUxmlToThemeStyleSheetList.Remove(entry); + } + } + var json = EditorJsonUtility.ToJson(this, true); var folderPath = BuilderConstants.builderDocumentDiskJsonFolderAbsolutePath; diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Document/BuilderDocumentOpenUXML.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Document/BuilderDocumentOpenUXML.cs index 08ca0d21a9..fb0e579ba1 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Document/BuilderDocumentOpenUXML.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Document/BuilderDocumentOpenUXML.cs @@ -20,9 +20,6 @@ class BuilderDocumentOpenUXML [SerializeField] List m_OpenUSSFiles = new List(); - [SerializeField] - VisualTreeAsset m_VisualTreeAssetBackup; - [SerializeField] string m_OpenendVisualTreeAssetOldPath; @@ -50,11 +47,15 @@ class BuilderDocumentOpenUXML BuilderUXMLFileSettings m_FileSettings; BuilderDocument m_Document; VisualElement m_CurrentDocumentRootElement; + VisualTreeAsset m_VisualTreeAssetBackup; // // Getters // + // Used in tests + internal bool isBackupSet => m_VisualTreeAssetBackup != null; + public BuilderUXMLFileSettings fileSettings => m_FileSettings ?? (m_FileSettings = new BuilderUXMLFileSettings(visualTreeAsset)); List openUXMLFiles @@ -95,7 +96,7 @@ public BuilderDocumentSettings settings { get { - // If this uxmnl is being edited in place then use the parent document's settings + // If this uxml is being edited in place then use the parent document's settings if (isChildSubDocument && openSubDocumentParentSourceTemplateAssetIndex != -1) { m_Settings = openSubDocumentParent.settings; @@ -333,27 +334,6 @@ void RemoveStyleSheetsFromRootAsset(VisualElementAsset rootAsset) } } - // For the near to mid term, we have this code that cleans up any - // existing root element stylesheets. - void RemoveLegacyStyleSheetsFromRootAssets() - { - foreach (var asset in visualTreeAsset.visualElementAssets) - { - if (!visualTreeAsset.IsRootElement(asset)) - continue; // Not a root asset. - - RemoveStyleSheetsFromRootAsset(asset); - } - - foreach (var asset in visualTreeAsset.templateAssets) - { - if (!visualTreeAsset.IsRootElement(asset)) - continue; // Not a root asset. - - RemoveStyleSheetsFromRootAsset(asset); - } - } - public void AddStyleSheetsToAllRootElements(string newUssPath = null, int newUssIndex = 0) { var rootVEA = visualTreeAsset.GetRootUXMLElement(); @@ -392,8 +372,6 @@ public bool SaveNewDocument( { needsFullRefresh = false; - ClearUndo(); - // Re-use or ask the user for the UXML path. var newUxmlPath = uxmlPath; if (string.IsNullOrEmpty(newUxmlPath) || isSaveAs) @@ -409,6 +387,8 @@ public bool SaveNewDocument( return false; } } + + ClearUndo(); List savedUSSFiles = new List(); @@ -659,10 +639,6 @@ public void PostLoadDocumentStyleSheetCleanup() for (int i = 0; i < styleSheetsUsed.Count; ++i) AddStyleSheetToDocument(styleSheetsUsed[i], null); - // For the near to mid term, we have this code that cleans up any - // existing root element stylesheets. - RemoveLegacyStyleSheetsFromRootAssets(); - hasUnsavedChanges = false; } @@ -802,9 +778,14 @@ public void OnAfterDeserialize() public void OnAfterLoadFromDisk() { - // Very important we convert asset references to paths here after a restore. if (m_VisualTreeAsset != null) + { + // Very important we convert asset references to paths here after a restore. m_VisualTreeAsset.UpdateUsingEntries(); + + // Make sure we have a backup after loading from disk + m_VisualTreeAssetBackup = m_VisualTreeAsset.DeepCopy(); + } } // @@ -822,7 +803,8 @@ public void RestoreAssetsFromBackup() hasUnsavedChanges = false; } - void ClearBackups() + // internal because it's used in tests + internal void ClearBackups() { m_VisualTreeAssetBackup.Destroy(); m_VisualTreeAssetBackup = null; @@ -973,7 +955,11 @@ void ReloadStyleSheetElements(VisualElement documentRootElement) BuilderSharedStyles.AddSelectorElementsFromStyleSheet(documentRootElement, m_OpenUSSFiles); var parentIndex = openSubDocumentParentIndex; - while (parentIndex > -1) + + // Do not display styles from parent documents if this is a subdocument open in isolation. + bool isIsolationMode = isChildSubDocument && openSubDocumentParentSourceTemplateAssetIndex == -1; + + while (parentIndex > -1 && !isIsolationMode) { var parentUXML = openUXMLFiles[parentIndex]; var parentUSSFiles = parentUXML.openUSSFiles; diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Draggers/BuilderClassDragger.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Draggers/BuilderClassDragger.cs index f5fd0b17ff..506ac6dc2a 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Draggers/BuilderClassDragger.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Draggers/BuilderClassDragger.cs @@ -34,7 +34,23 @@ protected override void FillDragElement(VisualElement pill) protected override bool StartDrag(VisualElement target, Vector2 mousePosition, VisualElement pill) { m_ClassNameBeingDragged = target.GetProperty(BuilderConstants.ExplorerStyleClassPillClassNameVEPropertyName) as string; - return true; + // if a ChildSubDocument is open, make sure that style class is part of active stylesheet, otherwise refuse drag + if (!paneWindow.document.activeOpenUXMLFile.isChildSubDocument) + return true; + + if (target.IsParentSelector()) + return false; + + foreach (var openUSSFile in paneWindow.document.openUSSFiles) + { + var currentStyleSheet = openUSSFile.styleSheet; + if (currentStyleSheet.FindSelector(m_ClassNameBeingDragged) != null) + { + return true; + } + } + + return false; } protected override void PerformAction(VisualElement destination, DestinationPane pane, Vector2 localMousePosition, int index = -1) diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Draggers/BuilderDragger.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Draggers/BuilderDragger.cs index 7527583554..da2841d3a1 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Draggers/BuilderDragger.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Draggers/BuilderDragger.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using UnityEditor; @@ -25,7 +26,7 @@ protected enum DestinationPane // It's possible to have multiple BuilderDraggers on the same element. This ensures // a kind of capture without using the capture system and just between BuilderDraggers. - static BuilderDragger s_CurrentlyActiveBuilderDragger = null; + internal static BuilderDragger s_CurrentlyActiveBuilderDragger = null; Vector2 m_Start; bool m_Active; @@ -47,6 +48,8 @@ protected enum DestinationPane public VisualElement builderHierarchyRoot { get; set; } public VisualElement builderStylesheetRoot { get; set; } + public bool active => m_Active; + protected BuilderPaneWindow paneWindow { get { return m_PaneWindow; } } protected BuilderSelection selection { get { return m_Selection; } } protected VisualElement documentRootElement => m_Canvas; @@ -57,6 +60,8 @@ protected enum DestinationPane List activators { get; set; } ManipulatorActivationFilter m_CurrentActivator; + public event Action onEndDrag; + public BuilderDragger( BuilderPaneWindow paneWindow, VisualElement root, BuilderSelection selection, @@ -165,16 +170,24 @@ protected virtual VisualElement GetDefaultTargetElement() return m_Canvas.Query().Where(e => e.GetVisualTreeAsset() == m_PaneWindow.document.visualTreeAsset).First(); } + private StyleLength m_targetInitialMinWidth; + private StyleLength m_targetInitialMinHeight; + protected void FixElementSizeAndPosition(VisualElement target) { + m_targetInitialMinWidth = target.style.minWidth; + m_targetInitialMinHeight = target.style.minHeight; + target.style.minWidth = target.resolvedStyle.width; target.style.minHeight = target.resolvedStyle.height; } protected void UnfixElementSizeAndPosition(VisualElement target) { - target.style.minWidth = StyleKeyword.Null; - target.style.minHeight = StyleKeyword.Null; + target.style.minWidth = m_targetInitialMinWidth; + target.style.minHeight = m_targetInitialMinHeight; + + selection.ForceVisualAssetUpdateWithoutSave(target, BuilderHierarchyChangeType.InlineStyle); } public void RegisterCallbacksOnTarget(VisualElement target) @@ -391,7 +404,7 @@ void PerformDragInner(VisualElement target, Vector2 mousePosition) if (pickedElement == null) return; - + // Mirror final drag destination in the viewport using the placement indicator. m_PlacementIndicator?.Activate(pickedElement, index); @@ -418,6 +431,7 @@ void PerformDragInner(VisualElement target, Vector2 mousePosition) void EndDragInner() { EndDrag(); + onEndDrag?.Invoke(); m_LastRowHoverElement?.RemoveFromClassList(s_TreeItemHoverHoverClassName); m_LastRowHoverElement?.Query(className: BuilderConstants.ExplorerItemReorderZoneClassName).ForEach(e => e.RemoveFromClassList(s_TreeItemHoverWithDragBetweenElementsSupportClassName)); @@ -428,6 +442,8 @@ void EndDragInner() void OnMouseDown(MouseDownEvent evt) { + if (s_CurrentlyActiveBuilderDragger != null && s_CurrentlyActiveBuilderDragger != this) + return; var target = evt.currentTarget as VisualElement; if (m_WeStartedTheDrag && target.HasMouseCapture()) @@ -481,6 +497,7 @@ void OnMouseMove(MouseMoveEvent evt) else { target.ReleaseMouse(); + s_CurrentlyActiveBuilderDragger = null; } } diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Draggers/BuilderHierarchyDragger.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Draggers/BuilderHierarchyDragger.cs index 7d1d68f46f..aba5c1eafe 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Draggers/BuilderHierarchyDragger.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Draggers/BuilderHierarchyDragger.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using UnityEngine; using UnityEngine.UIElements; @@ -62,6 +63,12 @@ protected override void PerformAction(VisualElement destination, DestinationPane if (newParent == element || newParent.HasAncestor(element)) continue; + // When editing in context the new parent has to be null so it's inserted at the root of the active vta + if (newParent.IsActiveSubDocumentRoot(paneWindow.document)) + { + newParent = null; + } + BuilderAssetUtilities.ReparentElementInAsset( paneWindow.document, element, newParent, index, undo); @@ -80,6 +87,9 @@ protected override bool IsPickedElementValid(VisualElement element) if (element == null) return true; + if (element.IsActiveSubDocumentRoot(paneWindow.document)) + return true; + if (element.contentContainer == null) return false; @@ -134,7 +144,12 @@ protected override void ResetDragPreviewElement() protected override IEnumerable GetSelectedElements() { - return documentRootElement.FindSelectedElements(); + var selectedElements = selection.selection; + var sortedSelectedElementsInAsset = documentRootElement.FindSelectedElements(); + + sortedSelectedElementsInAsset.RemoveAll(x => !selectedElements.Contains(x)); + + return sortedSelectedElementsInAsset; } } } diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Draggers/BuilderStyleSheetsDragger.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Draggers/BuilderStyleSheetsDragger.cs index f81bcb5c88..bd82571fa6 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Draggers/BuilderStyleSheetsDragger.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Draggers/BuilderStyleSheetsDragger.cs @@ -15,8 +15,26 @@ public BuilderStyleSheetsDragger( protected override bool ExplorerCanStartDrag(VisualElement targetElement) { - bool readyForDrag = (targetElement.IsSelector() || targetElement.IsStyleSheet()) && !targetElement.IsParentSelector(); - return readyForDrag; + bool readyForDrag = targetElement.IsSelector() && !targetElement.IsParentSelector(); + if (readyForDrag) + return true; + + if (!targetElement.IsStyleSheet() || targetElement.IsParentSelector()) + return false; + if (!paneWindow.document.activeOpenUXMLFile.isChildSubDocument) + return true; + if (paneWindow.document.openUSSFiles.Count == 0) + return false; + + var styleSheet = targetElement.GetStyleSheet(); + foreach (var openUSSFile in paneWindow.document.openUSSFiles) + { + if (openUSSFile.styleSheet == styleSheet) + { + return true; + } + } + return false; } protected override string ExplorerGetDraggedPillText(VisualElement targetElement) diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Explorer/BuilderExplorer.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Explorer/BuilderExplorer.cs index fb7b99088f..ddad7d0e85 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Explorer/BuilderExplorer.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Explorer/BuilderExplorer.cs @@ -18,6 +18,7 @@ internal enum BuilderElementInfoVisibilityState TypeName = 1 << 0, ClassList = 1 << 1, StyleSheets = 1 << 2, + FullSelectorText = 1 << 3, All = ~0 } @@ -30,6 +31,7 @@ internal enum BuilderElementInfoVisibilityState protected ElementHierarchyView m_ElementHierarchyView; protected BuilderSelection m_Selection; bool m_SelectionMadeExternally; + [SerializeField] protected BuilderElementInfoVisibilityState m_ElementInfoVisibilityState; BuilderClassDragger m_ClassDragger; BuilderExplorerDragger m_ExplorerDragger; @@ -97,6 +99,21 @@ public BuilderExplorer( m_ShouldRebuildHierarchyOnStyleChange = true; } + internal void ChangeVisibilityState(BuilderElementInfoVisibilityState state) + { + m_ElementInfoVisibilityState ^= state; + m_ElementHierarchyView.elementInfoVisibilityState = m_ElementInfoVisibilityState; + SaveViewData(); + UpdateHierarchyAndSelection(m_ElementHierarchyView.hasUnsavedChanges); + } + + internal override void OnViewDataReady() + { + base.OnViewDataReady(); + OverwriteFromViewData(this, viewDataKey); + m_ElementHierarchyView.elementInfoVisibilityState = m_ElementInfoVisibilityState; + } + public void ClearHighlightOverlay() { m_ElementHierarchyView.ClearHighlightOverlay(); @@ -162,10 +179,10 @@ public void UpdateHierarchyAndSelection(bool hasUnsavedChanges) public virtual void HierarchyChanged(VisualElement element, BuilderHierarchyChangeType changeType) { if (element == null || - changeType.HasFlag(BuilderHierarchyChangeType.ChildrenAdded) || - changeType.HasFlag(BuilderHierarchyChangeType.ChildrenRemoved) || - changeType.HasFlag(BuilderHierarchyChangeType.Attributes) || - changeType.HasFlag(BuilderHierarchyChangeType.ClassList)) + (changeType & (BuilderHierarchyChangeType.ChildrenAdded | + BuilderHierarchyChangeType.ChildrenRemoved | + BuilderHierarchyChangeType.Attributes | + BuilderHierarchyChangeType.ClassList)) != 0) { UpdateHierarchyAndSelection(m_Selection.hasUnsavedChanges); m_ShouldRebuildHierarchyOnStyleChange = !m_Selection.hasUnsavedChanges; diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Explorer/BuilderExplorerItem.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Explorer/BuilderExplorerItem.cs index 94887486fb..a5f172d14d 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Explorer/BuilderExplorerItem.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Explorer/BuilderExplorerItem.cs @@ -1,5 +1,7 @@ +using System.Collections.Generic; using UnityEngine.UIElements; -using UnityEngine; +using UnityEditor.UIElements; +using UnityEditor; namespace Unity.UI.Builder { @@ -8,6 +10,8 @@ internal class BuilderExplorerItem : VisualElement VisualElement m_Container; VisualElement m_ReorderZoneAbove; VisualElement m_ReorderZoneBelow; + TextField m_RenameTextField; + internal List