diff --git a/Editor/IncrementalBuildPipeline/BeeBuildProgramCommon.Data/Data.cs b/Editor/IncrementalBuildPipeline/BeeBuildProgramCommon.Data/Data.cs index 53ea17e788..f4569abdd0 100644 --- a/Editor/IncrementalBuildPipeline/BeeBuildProgramCommon.Data/Data.cs +++ b/Editor/IncrementalBuildPipeline/BeeBuildProgramCommon.Data/Data.cs @@ -36,5 +36,6 @@ public class ConfigurationData public bool AdvancedLicense; public bool Batchmode; public bool EmitDataForBeeWhy; + public string NamedPipeOrUnixSocket; } } diff --git a/Editor/IncrementalBuildPipeline/PlayerBuildProgramLibrary.Data/Data.cs b/Editor/IncrementalBuildPipeline/PlayerBuildProgramLibrary.Data/Data.cs index 39cfeed30b..1a465f071b 100644 --- a/Editor/IncrementalBuildPipeline/PlayerBuildProgramLibrary.Data/Data.cs +++ b/Editor/IncrementalBuildPipeline/PlayerBuildProgramLibrary.Data/Data.cs @@ -66,6 +66,7 @@ public class Il2CppConfig public bool EnableDeepProfilingSupport; public bool EnableFullGenericSharing; public string Profile; + public string Defines; public string ConfigurationName; public bool GcWBarrierValidation; @@ -73,9 +74,13 @@ public class Il2CppConfig public string[] AdditionalCppFiles = new string[0]; public string[] AdditionalArgs = new string[0]; + public string CompilerFlags; + public string LinkerFlags; public string ExtraTypes; public bool CreateSymbolFiles; public bool AllowDebugging; + public string SysRootPath; + public string ToolChainPath; } public class Services diff --git a/Editor/IncrementalBuildPipeline/ScriptCompilationBuildProgram.Data/Data.cs b/Editor/IncrementalBuildPipeline/ScriptCompilationBuildProgram.Data/Data.cs index 283b502694..f8ce77bd32 100644 --- a/Editor/IncrementalBuildPipeline/ScriptCompilationBuildProgram.Data/Data.cs +++ b/Editor/IncrementalBuildPipeline/ScriptCompilationBuildProgram.Data/Data.cs @@ -14,7 +14,6 @@ public static class Constants public class ScriptCompilationData { public AssemblyData[] Assemblies; - public AssemblyData[] CodegenAssemblies; public string DotnetRuntimePath; public string DotnetRoslynPath; public string MovedFromExtractorPath; @@ -38,15 +37,18 @@ public class AssemblyData public int[] References = new int[0]; public bool AllowUnsafeCode; public string RuleSet; + public string AnalyzerConfigPath; public string LanguageVersion; public bool UseDeterministicCompilation; public bool SuppressCompilerWarnings; public string[] Analyzers = new string[0]; + public string[] AdditionalFiles = new string[0]; public string Asmdef; public string[] BclDirectories = new string[0]; public string[] CustomCompilerOptions = new string[0]; public int DebugIndex; public bool SkipCodeGen; + public string Path; } public class ScriptCompilationData_Out diff --git a/Editor/Mono/2D/Common/TexturePlatformSettingsView.cs b/Editor/Mono/2D/Common/TexturePlatformSettingsView.cs index 9432ed2b11..e46d1314a0 100644 --- a/Editor/Mono/2D/Common/TexturePlatformSettingsView.cs +++ b/Editor/Mono/2D/Common/TexturePlatformSettingsView.cs @@ -20,7 +20,7 @@ class Styles public readonly GUIContent compressionQualityLabel = EditorGUIUtility.TrTextContent("Compressor Quality"); public readonly GUIContent compressionQualitySliderLabel = EditorGUIUtility.TrTextContent("Compressor Quality", "Use the slider to adjust compression quality from 0 (Fastest) to 100 (Best)"); - public readonly int[] kMaxTextureSizeValues = { 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192 }; + public readonly int[] kMaxTextureSizeValues = { 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384 }; public readonly GUIContent[] kMaxTextureSizeStrings; public readonly GUIContent[] kTextureCompressionOptions = diff --git a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporterInspector.cs b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporterInspector.cs index a0d7556217..b1e65d7a46 100644 --- a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporterInspector.cs +++ b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporterInspector.cs @@ -412,12 +412,6 @@ protected override void Apply() base.Apply(); } - public void ApplyAndImportSpriteAtlas() - { - Apply(); - base.ApplyAndImport(); - } - protected void PackPreviewGUI() { EditorGUILayout.Space(); @@ -432,7 +426,7 @@ protected void PackPreviewGUI() { GUI.FocusControl(null); SpriteAtlasUtility.EnableV2Import(true); - ApplyAndImportSpriteAtlas(); + SaveChanges(); SpriteAtlasUtility.EnableV2Import(false); } } @@ -447,26 +441,6 @@ private bool IsValidAtlas() return true; } - public override void OnDisable() - { - - if (Unsupported.IsDestroyScriptableObject(this)) - { - if (spriteAtlasAsset && HasModified()) - { - if (EditorUtility.DisplayDialog("Unapplied import settings", "Unapplied import settings for \'" + m_AssetPath + "\'", "Apply", "Revert")) - { - ApplyAndImportSpriteAtlas(); - } - else - { - ResetValues(); - } - } - } - base.OnDisable(); - } - public override bool HasModified() { return base.HasModified() || m_ContentHash != GetInspectorHash(); @@ -535,6 +509,8 @@ public override void OnInspectorGUI() serializedAssetObject.ApplyModifiedProperties(); PackPreviewGUI(); } + + ApplyRevertGUI(); } private void HandleCommonSettingUI() diff --git a/Editor/Mono/ActiveEditorTracker.bindings.cs b/Editor/Mono/ActiveEditorTracker.bindings.cs index 7b28d1e9aa..c682934cd9 100644 --- a/Editor/Mono/ActiveEditorTracker.bindings.cs +++ b/Editor/Mono/ActiveEditorTracker.bindings.cs @@ -7,6 +7,7 @@ using System.Runtime.InteropServices; using UnityEngine; using UnityEngine.Bindings; +using UnityEngine.Scripting; using UnityObject = UnityEngine.Object; namespace UnityEditor @@ -15,6 +16,7 @@ namespace UnityEditor // Might want to add a manual dispose [NativeHeader("Editor/Src/Utility/ActiveEditorTracker.bindings.h")] [Serializable] + [RequiredByNativeCode] public sealed class ActiveEditorTracker { #pragma warning disable 649 @@ -113,6 +115,17 @@ public bool isLocked set { Internal_SetIsLocked(this, value); } } + [FreeFunction] + static extern bool Internal_HasUnsavedChanges(ActiveEditorTracker activeEditorTracker); + public bool hasUnsavedChanges => Internal_HasUnsavedChanges(this); + + [FreeFunction] + static extern void Internal_UnsavedChangesStateChanged(ActiveEditorTracker self, int editorInstance, bool value); + internal void UnsavedChangesStateChanged(Editor editor, bool value) + { + Internal_UnsavedChangesStateChanged(this, editor.GetInstanceID(), value); + } + [FreeFunction] static extern bool Internal_GetDelayFlushDirtyRebuild(); diff --git a/Editor/Mono/Animation/AnimationUtility.bindings.cs b/Editor/Mono/Animation/AnimationUtility.bindings.cs index f3996af8d1..c8b1bdcd94 100644 --- a/Editor/Mono/Animation/AnimationUtility.bindings.cs +++ b/Editor/Mono/Animation/AnimationUtility.bindings.cs @@ -168,6 +168,7 @@ internal static System.Type GetEditorCurveValueType(ScriptableObject scriptableO extern private static System.Type Internal_GetScriptableObjectEditorCurveValueType([NotNull] ScriptableObject scriptableObject, EditorCurveBinding binding); extern public static bool GetFloatValue([NotNull] GameObject root, EditorCurveBinding binding, out float data); + extern public static bool GetDiscreteIntValue([NotNull] GameObject root, EditorCurveBinding binding, out int data); public static bool GetObjectReferenceValue(GameObject root, EditorCurveBinding binding, out Object data) { data = Internal_GetObjectReferenceValue(root, binding); @@ -388,6 +389,7 @@ public static AnimationCurve GetEditorCurve(AnimationClip clip, string relativeP extern public static string CalculateTransformPath([NotNull] Transform targetTransform, Transform root); extern public static AnimationClipSettings GetAnimationClipSettings([NotNull] AnimationClip clip); + extern internal static void RebuildMecanimData([NotNull] AnimationClip clip); extern public static void SetAnimationClipSettings([NotNull] AnimationClip clip, AnimationClipSettings srcClipInfo); extern internal static void SetAnimationClipSettingsNoDirty([NotNull] AnimationClip clip, AnimationClipSettings srcClipInfo); diff --git a/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyDataSource.cs b/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyDataSource.cs index 738cf9fe1f..3424c023a9 100644 --- a/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyDataSource.cs +++ b/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyDataSource.cs @@ -75,7 +75,7 @@ private TreeViewItem AddGameObjectToHierarchy(GameObject gameObject, GameObject // Don't show for the root go if (curveBinding.path != "") { - TreeViewItem newNode = CreateNode(singleObjectBindings.ToArray(), node); + TreeViewItem newNode = CreateNode(singleObjectBindings.ToArray(), node, null); if (newNode != null) childNodes.Add(newNode); singleObjectBindings.Clear(); @@ -111,20 +111,6 @@ private TreeViewItem AddGameObjectToHierarchy(GameObject gameObject, GameObject } } - var animator = rootGameObject.GetComponent(); - if (animator != null) - { - //If the Animator has a human avatar, we need to check if the avatar's hierarchy matches that of the current GameObject. If they do not match, disable the node. - if (animator.avatarRoot != null && animator.isHuman) - { - if (animator.avatarRoot.Find(path) == null) - { - node.propertyPathMismatchWithHumanAvatar = true; - } - } - } - - if (showEntireHierarchy) { // Iterate over all child GOs @@ -137,6 +123,13 @@ private TreeViewItem AddGameObjectToHierarchy(GameObject gameObject, GameObject } } + // Remove Leaf nodes without properties. + if (childNodes.Count == 0) + { + node.parent = null; + return null; + } + TreeViewUtility.SetChildParentReferences(childNodes, node); return node; } @@ -191,17 +184,24 @@ private TreeViewItem AddAnimatableObjectToHierarchy(EditorCurveBinding[] curveBi List childNodes = new List(); List singlePropertyBindings = new List(); + SerializedObject so = null; for (int i = 0; i < curveBindings.Length; i++) { EditorCurveBinding curveBinding = curveBindings[i]; + if (curveBinding.isSerializeReferenceCurve) + { + var animatedObject = AnimationUtility.GetAnimatedObject(AddCurvesPopup.s_State.activeRootGameObject, curveBinding); + if (animatedObject != null && (so == null || so.targetObject != animatedObject)) + so = new SerializedObject(animatedObject); + } singlePropertyBindings.Add(curveBinding); // We expect curveBindings to come sorted by propertyname if (i == curveBindings.Length - 1 || AnimationWindowUtility.GetPropertyGroupName(curveBindings[i + 1].propertyName) != AnimationWindowUtility.GetPropertyGroupName(curveBinding.propertyName)) { - TreeViewItem newNode = CreateNode(singlePropertyBindings.ToArray(), node); + TreeViewItem newNode = CreateNode(singlePropertyBindings.ToArray(), node, so); if (newNode != null) childNodes.Add(newNode); singlePropertyBindings.Clear(); @@ -214,9 +214,9 @@ private TreeViewItem AddAnimatableObjectToHierarchy(EditorCurveBinding[] curveBi return node; } - private TreeViewItem CreateNode(EditorCurveBinding[] curveBindings, TreeViewItem parentNode) + private TreeViewItem CreateNode(EditorCurveBinding[] curveBindings, TreeViewItem parentNode, SerializedObject so) { - var node = new AddCurvesPopupPropertyNode(parentNode, curveBindings); + var node = new AddCurvesPopupPropertyNode(parentNode, curveBindings, AnimationWindowUtility.GetNicePropertyDisplayName(curveBindings[0], so)); // For RectTransform.position we only want .z if (AnimationWindowUtility.IsRectTransformPosition(node.curveBindings[0])) @@ -234,7 +234,6 @@ public void UpdateData() internal class AddCurvesPopupGameObjectNode : TreeViewItem { - internal bool propertyPathMismatchWithHumanAvatar = false; public AddCurvesPopupGameObjectNode(GameObject gameObject, TreeViewItem parent, string displayName) : base(gameObject.GetInstanceID(), parent != null ? parent.depth + 1 : -1, parent, displayName) { @@ -253,8 +252,8 @@ internal class AddCurvesPopupPropertyNode : TreeViewItem { public EditorCurveBinding[] curveBindings; - public AddCurvesPopupPropertyNode(TreeViewItem parent, EditorCurveBinding[] curveBindings) - : base(curveBindings[0].GetHashCode(), parent.depth + 1, parent, AnimationWindowUtility.NicifyPropertyGroupName(curveBindings[0].type, AnimationWindowUtility.GetPropertyGroupName(curveBindings[0].propertyName))) + public AddCurvesPopupPropertyNode(TreeViewItem parent, EditorCurveBinding[] curveBindings, string displayPath) + : base(curveBindings[0].GetHashCode(), parent.depth + 1, parent, displayPath) { this.curveBindings = curveBindings; } diff --git a/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyGUI.cs b/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyGUI.cs index 33897a16b6..b1b39169dc 100644 --- a/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyGUI.cs +++ b/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyGUI.cs @@ -20,38 +20,17 @@ internal class AddCurvesPopupHierarchyGUI : TreeViewGUI private GUIContent addPropertiesContent = EditorGUIUtility.TrTextContent("Add Properties"); private const float plusButtonWidth = 17; - static Texture2D warningIcon = (Texture2D)EditorGUIUtility.LoadRequired("Icons/ShortcutManager/alertDialog.png"); - public static GUIStyle warningIconStyle; - public AddCurvesPopupHierarchyGUI(TreeViewController treeView, EditorWindow owner) : base(treeView, true) { this.owner = owner; - warningIconStyle = new GUIStyle(); - warningIconStyle.margin = new RectOffset(15, 15, 15, 15); } public override void OnRowGUI(Rect rowRect, TreeViewItem node, int row, bool selected, bool focused) { - bool propertyPathMismatchWithHumanAvatar = false; - AddCurvesPopupGameObjectNode addCurvesPopupNode = node as AddCurvesPopupGameObjectNode; - if (addCurvesPopupNode != null) - { - propertyPathMismatchWithHumanAvatar = addCurvesPopupNode.propertyPathMismatchWithHumanAvatar; - } - - using (new EditorGUI.DisabledScope(propertyPathMismatchWithHumanAvatar)) - { - base.OnRowGUI(rowRect, node, row, selected, focused); - DoAddCurveButton(rowRect, node); - HandleContextMenu(rowRect, node); - } - - if (propertyPathMismatchWithHumanAvatar) - { - Rect iconRect = new Rect(rowRect.width - plusButtonWidth, rowRect.yMin, plusButtonWidth, buttonStyle.fixedHeight); - GUI.Label(iconRect, new GUIContent(warningIcon, "The Avatar definition does not match the property path. Please author using a hierarchy the Avatar was built with."), warningIconStyle); - } + base.OnRowGUI(rowRect, node, row, selected, focused); + DoAddCurveButton(rowRect, node); + HandleContextMenu(rowRect, node); } private void DoAddCurveButton(Rect rowRect, TreeViewItem node) diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationRecording.cs b/Editor/Mono/Animation/AnimationWindow/AnimationRecording.cs index 81c8f4cd83..eff217eca1 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationRecording.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationRecording.cs @@ -510,12 +510,6 @@ static void AddKey(IAnimationRecordingState state, EditorCurveBinding binding, T { if (state.currentFrame != 0) { - // case 1373924 - // In the case of a new curve, we also have to convert the previousValue to float for a discrete int - if(binding.isDiscreteCurve) - { - previousValue = UnityEngine.Animations.DiscreteEvaluationAttributeUtilities.ConvertDiscreteIntToFloat((int)previousValue); - } AnimationWindowUtility.AddKeyframeToCurve(curve, previousValue, type, AnimationKeyTime.Frame(0, clip.frameRate)); } } diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindow.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindow.cs index a0418795ad..2b5100aac3 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindow.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindow.cs @@ -224,7 +224,7 @@ void OnEnable() OnSelectionChange(); - Undo.undoRedoPerformed += UndoRedoPerformed; + Undo.undoRedoEvent += UndoRedoPerformed; } void OnDisable() @@ -232,7 +232,7 @@ void OnDisable() s_AnimationWindows.Remove(this); m_AnimEditor.OnDisable(); - Undo.undoRedoPerformed -= UndoRedoPerformed; + Undo.undoRedoEvent -= UndoRedoPerformed; } void OnDestroy() @@ -452,7 +452,7 @@ private bool ShouldUpdateSelection(AnimationWindowSelectionItem selectedItem) return (selectedItem.GetRefreshHash() != currentSelection.GetRefreshHash()); } - private void UndoRedoPerformed() + private void UndoRedoPerformed(in UndoRedoInfo info) { Repaint(); } diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowControl.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowControl.cs index 7f2caac0ce..9deb61f26e 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowControl.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowControl.cs @@ -131,6 +131,7 @@ 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"); @@ -417,13 +418,21 @@ public override bool StartPreview() AnimationMode.StartAnimationMode(GetAnimationModeDriver()); AnimationPropertyContextualMenu.Instance.SetResponder(this); Undo.postprocessModifications += PostprocessAnimationRecordingModifications; + PrefabUtility.allowRecordingPrefabPropertyOverridesFor += AllowRecordingPrefabPropertyOverridesFor; DestroyGraph(); CreateCandidateClip(); + //If a hierarchy was created and array reorder happen in the inspector prior + //to the preview being started we will need to ensure that the display name + //reflects the binding path on an array element. + state.UpdateCurvesDisplayName(); + IAnimationWindowPreview[] previewComponents = FetchPostProcessComponents(); m_UsesPostProcessComponents = previewComponents != null && previewComponents.Length > 0; if (previewComponents != null) { + // Animation preview affects inspector values, so make sure we ignore constrain proportions + ConstrainProportionsTransformScale.m_IsAnimationPreview = true; foreach (var component in previewComponents) { component.StartPreview(); @@ -435,8 +444,14 @@ public override bool StartPreview() public override void StopPreview() { + if (previewing) + OnExitingAnimationMode(); + StopPlayback(); StopRecording(); + + ConstrainProportionsTransformScale.m_IsAnimationPreview = false; + ClearCandidates(); DestroyGraph(); DestroyCandidateClip(); @@ -471,8 +486,6 @@ public override void StopPreview() m_UsesPostProcessComponents = false; } - - Undo.postprocessModifications -= PostprocessAnimationRecordingModifications; } public override bool canRecord @@ -757,12 +770,55 @@ private AnimationModeDriver GetCandidateDriver() return m_CandidateDriver; } + private bool AllowRecordingPrefabPropertyOverridesFor(UnityEngine.Object componentOrGameObject) + { + 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 + { + 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; + } + + 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) + { + if (obj != null) + EditorUtility.SetDirty(obj); + } + + m_ObjectsModifiedDuringAnimationMode.Clear(); + } + private UndoPropertyModification[] PostprocessAnimationRecordingModifications(UndoPropertyModification[] modifications) { - //Fix for case 751009: The animationMode can be changed outside the AnimationWindow, and this callback needs to be unregistered. + //Fix for case 751009: The animationMode can be changed outside the AnimationWindow, and callbacks needs to be unregistered. if (!AnimationMode.InAnimationMode(GetAnimationModeDriver())) { - Undo.postprocessModifications -= PostprocessAnimationRecordingModifications; + OnExitingAnimationMode(); return modifications; } @@ -771,13 +827,42 @@ private UndoPropertyModification[] PostprocessAnimationRecordingModifications(Un else if (previewing) modifications = RegisterCandidates(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; } + private void RefreshDisplayNamesOnArrayTopologicalChange(UndoPropertyModification[] modifications) + { + if (modifications.Length >= 2) + { + if (modifications[0].currentValue.propertyPath.EndsWith("]") && + modifications[0].currentValue.propertyPath.Contains(".Array.data[") && + modifications[1].currentValue.propertyPath.EndsWith("]") && + modifications[1].currentValue.propertyPath.Contains(".Array.data[")) + { + //Array reordering might affect curves display name + state.UpdateCurvesDisplayName(); + } + else if (modifications[0].currentValue.propertyPath.EndsWith(".Array.size") && + Convert.ToInt64(modifications[0].currentValue.value) < + Convert.ToInt64(modifications[0].previousValue.value)) + { + //Array shrinking might affect curves display name + state.UpdateCurvesDisplayName(); + } + } + } + private UndoPropertyModification[] ProcessAutoKey(UndoPropertyModification[] modifications) { BeginKeyModification(); diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowCurve.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowCurve.cs index f4bac4da2b..966a51b453 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowCurve.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowCurve.cs @@ -28,6 +28,7 @@ internal class AnimationWindowCurve : IComparable, IEquata public EditorCurveBinding binding { get { return m_Binding; } } public bool isPPtrCurve { get { return m_Binding.isPPtrCurve; } } public bool isDiscreteCurve { get { return m_Binding.isDiscreteCurve; } } + public bool isSerializeReferenceCurve{ get {return m_Binding.isSerializeReferenceCurve;}} public bool isPhantom { get { return m_Binding.isPhantom; } } public string propertyName { get { return m_Binding.propertyName; } } public string path { get { return m_Binding.path; } } @@ -45,6 +46,18 @@ 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; } } + private object defaultValue + { + get + { + if (isPPtrCurve) + return null; + if (isDiscreteCurve) + return 0; + return 0f; + } + } + public AnimationWindowSelectionItem selectionBinding { get { return m_SelectionBinding; } set { m_SelectionBinding = value; } } public AnimationWindowCurve(AnimationClip clip, EditorCurveBinding binding, System.Type valueType) @@ -228,7 +241,7 @@ public AnimationWindowKeyframe FindKeyAtTime(AnimationKeyTime keyTime) public object Evaluate(float time) { if (m_Keyframes.Count == 0) - return isPPtrCurve ? null : (object)0f; + return defaultValue; AnimationWindowKeyframe firstKey = m_Keyframes[0]; if (time <= firstKey.time) @@ -243,9 +256,9 @@ public object Evaluate(float time) { AnimationWindowKeyframe nextKey = m_Keyframes[i]; - if (key.time < time && nextKey.time >= time) + if (key.time <= time && nextKey.time > time) { - if (isPPtrCurve) + if (isPPtrCurve || isDiscreteCurve) { return key.value; } @@ -266,7 +279,7 @@ public object Evaluate(float time) } // Shouldn't happen... - return isPPtrCurve ? null : (object)0f; + return defaultValue; } public void AddKeyframe(AnimationWindowKeyframe key, AnimationKeyTime keyTime) diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyDataSource.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyDataSource.cs index 820a11e84c..79ff048efa 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyDataSource.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyDataSource.cs @@ -77,6 +77,7 @@ public List CreateTreeFromCurves() AnimationWindowCurve[] curves = state.allCurves.ToArray(); AnimationWindowHierarchyNode parentNode = (AnimationWindowHierarchyNode)m_RootItem; + SerializedObject so = null; for (int i = 0; i < curves.Length; i++) { @@ -87,6 +88,13 @@ public List CreateTreeFromCurves() AnimationWindowCurve nextCurve = i < curves.Length - 1 ? curves[i + 1] : null; + if (curve.isSerializeReferenceCurve && state.activeRootGameObject != null) + { + var animatedObject = AnimationUtility.GetAnimatedObject(state.activeRootGameObject, curve.binding); + if (animatedObject != null && (so == null || so.targetObject != animatedObject)) + so = new SerializedObject(animatedObject); + } + singlePropertyCurves.Add(curve); bool areSameGroup = nextCurve != null && AnimationWindowUtility.GetPropertyGroupName(nextCurve.propertyName) == AnimationWindowUtility.GetPropertyGroupName(curve.propertyName); @@ -97,9 +105,9 @@ public List CreateTreeFromCurves() if (i == curves.Length - 1 || !areSameGroup || !areSamePathAndType) { if (singlePropertyCurves.Count > 1) - nodes.Add(AddPropertyGroupToHierarchy(singlePropertyCurves.ToArray(), parentNode)); + nodes.Add(AddPropertyGroupToHierarchy(singlePropertyCurves.ToArray(), parentNode, so)); else - nodes.Add(AddPropertyToHierarchy(singlePropertyCurves[0], parentNode)); + nodes.Add(AddPropertyToHierarchy(singlePropertyCurves[0], parentNode, so)); singlePropertyCurves.Clear(); } } @@ -107,12 +115,12 @@ public List CreateTreeFromCurves() return nodes; } - private AnimationWindowHierarchyPropertyGroupNode AddPropertyGroupToHierarchy(AnimationWindowCurve[] curves, AnimationWindowHierarchyNode parentNode) + private AnimationWindowHierarchyPropertyGroupNode AddPropertyGroupToHierarchy(AnimationWindowCurve[] curves, AnimationWindowHierarchyNode parentNode, SerializedObject so) { List childNodes = new List(); System.Type animatableObjectType = curves[0].type; - AnimationWindowHierarchyPropertyGroupNode node = new AnimationWindowHierarchyPropertyGroupNode(animatableObjectType, 0, AnimationWindowUtility.GetPropertyGroupName(curves[0].propertyName), curves[0].path, parentNode); + AnimationWindowHierarchyPropertyGroupNode node = new AnimationWindowHierarchyPropertyGroupNode(animatableObjectType, 0, AnimationWindowUtility.GetPropertyGroupName(curves[0].propertyName), curves[0].path, parentNode, AnimationWindowUtility.GetNicePropertyGroupDisplayName(curves[0].binding, so)); node.icon = GetIcon(curves[0].binding); @@ -121,7 +129,7 @@ private AnimationWindowHierarchyPropertyGroupNode AddPropertyGroupToHierarchy(An foreach (AnimationWindowCurve curve in curves) { - AnimationWindowHierarchyPropertyNode childNode = AddPropertyToHierarchy(curve, node); + AnimationWindowHierarchyPropertyNode childNode = AddPropertyToHierarchy(curve, node, so); // For child nodes we do not want to display the type in front (It is already shown by the group node) childNode.displayName = AnimationWindowUtility.GetPropertyDisplayName(childNode.propertyName); childNodes.Add(childNode); @@ -131,9 +139,9 @@ private AnimationWindowHierarchyPropertyGroupNode AddPropertyGroupToHierarchy(An return node; } - private AnimationWindowHierarchyPropertyNode AddPropertyToHierarchy(AnimationWindowCurve curve, AnimationWindowHierarchyNode parentNode) + private AnimationWindowHierarchyPropertyNode AddPropertyToHierarchy(AnimationWindowCurve curve, AnimationWindowHierarchyNode parentNode, SerializedObject so) { - AnimationWindowHierarchyPropertyNode node = new AnimationWindowHierarchyPropertyNode(curve.type, 0, curve.propertyName, curve.path, parentNode, curve.binding, curve.isPPtrCurve); + AnimationWindowHierarchyPropertyNode node = new AnimationWindowHierarchyPropertyNode(curve.type, 0, curve.propertyName, curve.path, parentNode, curve.binding, curve.isPPtrCurve, AnimationWindowUtility.GetNicePropertyDisplayName(curve.binding, so)); if (parentNode.icon != null) node.icon = parentNode.icon; @@ -156,6 +164,34 @@ public Texture2D GetIcon(EditorCurveBinding curveBinding) return AssetPreview.GetMiniTypeThumbnail(curveBinding.type); } + public void UpdateSerializeReferenceCurvesArrayNiceDisplayName() + { + if (state.activeRootGameObject == null) + return; + + //This is required in the case that there might have been a topological change + //leading to a different display name(topological path) + SerializedObject so = null; + foreach (AnimationWindowHierarchyNode hierarchyNode in GetRows()) + { + if (hierarchyNode.curves != null) + { + foreach (var curve in hierarchyNode.curves) + { + if (curve.isSerializeReferenceCurve && hierarchyNode.displayName.Contains(".Array.data[")) + { + var animatedObject = AnimationUtility.GetAnimatedObject(state.activeRootGameObject, curve.binding); + if (animatedObject != null && (so == null || so.targetObject != animatedObject)) + so = new SerializedObject(animatedObject); + + hierarchyNode.displayName = AnimationWindowUtility.GetNicePropertyDisplayName(curve.binding, so); + } + } + } + } + + } + public void UpdateData() { m_TreeView.ReloadData(); diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyGUI.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyGUI.cs index af1655b959..0fbdbf0583 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyGUI.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyGUI.cs @@ -330,13 +330,10 @@ private void DoValueField(Rect rect, AnimationWindowHierarchyNode node, int row) // We do valuefields for dopelines that only have single curve AnimationWindowCurve curve = curves[0]; - object objectValue = CurveBindingUtility.GetCurrentValue(state, curve); - int intValue = 0; + object value = CurveBindingUtility.GetCurrentValue(state, curve); - if (objectValue is float) + if (!curve.isPPtrCurve) { - float value = (float)objectValue; - Rect valueFieldDragRect = new Rect(rect.xMax - k_ValueFieldOffsetFromRightSide - k_ValueFieldDragWidth, rect.y, k_ValueFieldDragWidth, rect.height); Rect valueFieldRect = new Rect(rect.xMax - k_ValueFieldOffsetFromRightSide, rect.y, k_ValueFieldWidth, rect.height); @@ -347,7 +344,7 @@ private void DoValueField(Rect rect, AnimationWindowHierarchyNode node, int row) if (curve.valueType == typeof(bool)) { - value = GUI.Toggle(valueFieldRect, m_HierarchyItemValueControlIDs[row], value != 0, GUIContent.none, EditorStyles.toggle) ? 1 : 0; + value = GUI.Toggle(valueFieldRect, m_HierarchyItemValueControlIDs[row], (float)value != 0, GUIContent.none, EditorStyles.toggle) ? 1f : 0f; } else { @@ -366,12 +363,11 @@ private void DoValueField(Rect rect, AnimationWindowHierarchyNode node, int row) if (curve.isDiscreteCurve) { - intValue = UnityEngine.Animations.DiscreteEvaluationAttributeUtilities.ConvertFloatToDiscreteInt(value); - intValue = EditorGUI.DoIntField(EditorGUI.s_RecycledEditor, + value = EditorGUI.DoIntField(EditorGUI.s_RecycledEditor, valueFieldRect, valueFieldDragRect, id, - intValue, + (int)value, EditorGUI.kIntFieldFormatString, m_AnimationSelectionTextField, true, @@ -388,7 +384,7 @@ private void DoValueField(Rect rect, AnimationWindowHierarchyNode node, int row) valueFieldRect, valueFieldDragRect, id, - value, + (float)value, "g5", m_AnimationSelectionTextField, true); @@ -397,32 +393,18 @@ private void DoValueField(Rect rect, AnimationWindowHierarchyNode node, int row) GUI.changed = true; Event.current.Use(); } + + if (float.IsInfinity((float)value) || float.IsNaN((float)value)) + value = 0f; } } - if (float.IsInfinity(value) || float.IsNaN(value)) - value = 0; - if (EditorGUI.EndChangeCheck()) { string undoLabel = "Edit Key"; AnimationKeyTime newAnimationKeyTime = AnimationKeyTime.Time(state.currentTime, curve.clip.frameRate); - - AnimationWindowKeyframe existingKeyframe = null; - foreach (AnimationWindowKeyframe keyframe in curve.m_Keyframes) - { - if (Mathf.Approximately(keyframe.time, state.currentTime)) - existingKeyframe = keyframe; - } - - if (curve.isDiscreteCurve) - value = UnityEngine.Animations.DiscreteEvaluationAttributeUtilities.ConvertDiscreteIntToFloat(intValue); - - if (existingKeyframe == null) - AnimationWindowUtility.AddKeyframeToCurve(curve, value, curve.valueType, newAnimationKeyTime); - else - existingKeyframe.value = value; + AnimationWindowUtility.AddKeyframeToCurve(curve, value, curve.valueType, newAnimationKeyTime); state.SaveCurve(curve.clip, curve, undoLabel); curvesChanged = true; @@ -431,7 +413,13 @@ private void DoValueField(Rect rect, AnimationWindowHierarchyNode node, int row) } if (curvesChanged) + { + //Fix for case 1382193: Stop recording any candidates if a property value field is modified + if (AnimationMode.IsRecordingCandidates()) + state.ClearCandidates(); + state.ResampleAnimation(); + } } private bool DoTreeViewButton(int id, Rect position, GUIContent content, GUIStyle style) diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyNode.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyNode.cs index 7ed4ca36a9..bcd960f940 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyNode.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyNode.cs @@ -33,8 +33,8 @@ public AnimationWindowHierarchyNode(int instanceID, int depth, TreeViewItem pare internal class AnimationWindowHierarchyPropertyGroupNode : AnimationWindowHierarchyNode { - public AnimationWindowHierarchyPropertyGroupNode(System.Type animatableObjectType, int setId, string propertyName, string path, TreeViewItem parent) - : base(AnimationWindowUtility.GetPropertyNodeID(setId, path, animatableObjectType, propertyName), parent != null ? parent.depth + 1 : -1, parent, animatableObjectType, AnimationWindowUtility.GetPropertyGroupName(propertyName), path, AnimationWindowUtility.GetNicePropertyGroupDisplayName(animatableObjectType, propertyName)) + public AnimationWindowHierarchyPropertyGroupNode(System.Type animatableObjectType, int setId, string propertyName, string path, TreeViewItem parent, string displayName) + : base(AnimationWindowUtility.GetPropertyNodeID(setId, path, animatableObjectType, propertyName), parent != null ? parent.depth + 1 : -1, parent, animatableObjectType, AnimationWindowUtility.GetPropertyGroupName(propertyName), path, displayName) {} } @@ -42,8 +42,8 @@ internal class AnimationWindowHierarchyPropertyNode : AnimationWindowHierarchyNo { public bool isPptrNode; - public AnimationWindowHierarchyPropertyNode(System.Type animatableObjectType, int setId, string propertyName, string path, TreeViewItem parent, EditorCurveBinding binding, bool isPptrNode) - : base(AnimationWindowUtility.GetPropertyNodeID(setId, path, animatableObjectType, propertyName), parent != null ? parent.depth + 1 : -1, parent, animatableObjectType, propertyName, path, AnimationWindowUtility.GetNicePropertyDisplayName(animatableObjectType, propertyName)) + public AnimationWindowHierarchyPropertyNode(System.Type animatableObjectType, int setId, string propertyName, string path, TreeViewItem parent, EditorCurveBinding binding, bool isPptrNode, string displayName) + : base(AnimationWindowUtility.GetPropertyNodeID(setId, path, animatableObjectType, propertyName), parent != null ? parent.depth + 1 : -1, parent, animatableObjectType, propertyName, path, displayName) { this.binding = binding; this.isPptrNode = isPptrNode; diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowKeyframe.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowKeyframe.cs index 2a310b8c35..cbdfa58b5e 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowKeyframe.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowKeyframe.cs @@ -2,6 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; using UnityEngine; using UnityEditor; @@ -104,7 +105,16 @@ public AnimationWindowKeyframe(AnimationWindowKeyframe key) public AnimationWindowKeyframe(AnimationWindowCurve curve, Keyframe key) { this.time = key.time; - this.value = key.value; + + if (curve.isDiscreteCurve) + { + this.value = UnityEngine.Animations.DiscreteEvaluationAttributeUtilities.ConvertFloatToDiscreteInt(key.value); + } + else + { + this.value = key.value; + } + this.curve = curve; this.m_InTangent = key.inTangent; this.m_OutTangent = key.outTangent; @@ -151,7 +161,20 @@ public int GetIndex() public Keyframe ToKeyframe() { - var keyframe = new Keyframe(time, (float)value, inTangent, outTangent); + float floatValue; + if (curve.isDiscreteCurve) + { + // 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)); + } + else + { + floatValue = (float)value; + } + + var keyframe = new Keyframe(time, floatValue, inTangent, outTangent); keyframe.tangentModeInternal = m_TangentMode; keyframe.weightedMode = weightedMode; diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowState.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowState.cs index cfa92e3ed0..1511a2bf16 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowState.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowState.cs @@ -365,7 +365,7 @@ public void OnEnable() { hideFlags = HideFlags.HideAndDontSave; AnimationUtility.onCurveWasModified += CurveWasModified; - Undo.undoRedoPerformed += UndoRedoPerformed; + Undo.undoRedoEvent += UndoRedoPerformed; AssemblyReloadEvents.beforeAssemblyReload += PurgeSelection; // NoOps... @@ -381,7 +381,7 @@ public void OnEnable() public void OnDisable() { AnimationUtility.onCurveWasModified -= CurveWasModified; - Undo.undoRedoPerformed -= UndoRedoPerformed; + Undo.undoRedoEvent -= UndoRedoPerformed; AssemblyReloadEvents.beforeAssemblyReload -= PurgeSelection; m_ControlInterface.OnDisable(); @@ -434,7 +434,7 @@ public RefreshType refresh } } - public void UndoRedoPerformed() + public void UndoRedoPerformed(in UndoRedoInfo info) { refresh = RefreshType.Everything; controlInterface.ResampleAnimation(); @@ -577,6 +577,12 @@ public void StartPreview() controlInterface.ResampleAnimation(); } + public void UpdateCurvesDisplayName() + { + if (hierarchyData != null) + hierarchyData.UpdateSerializeReferenceCurvesArrayNiceDisplayName(); + } + public void StopPreview() { controlInterface.StopPreview(); @@ -614,6 +620,11 @@ public void ResampleAnimation() controlInterface.ResampleAnimation(); } + public void ClearCandidates() + { + controlInterface.ClearCandidates(); + } + public bool ShouldShowCurve(AnimationWindowCurve curve) { if (filterBySelection && activeRootGameObject != null) @@ -844,7 +855,7 @@ public Bounds selectionBounds { AnimationWindowKeyframe key = keys[0]; float time = key.time; - float val = key.isPPtrCurve ? 0.0f : (float)key.value; + float val = key.isPPtrCurve || key.isDiscreteCurve ? 0.0f : (float)key.value; Bounds bounds = new Bounds(new Vector2(time, val), Vector2.zero); @@ -853,7 +864,7 @@ public Bounds selectionBounds key = keys[i]; time = key.time; - val = key.isPPtrCurve ? 0.0f : (float)key.value; + val = key.isPPtrCurve || key.isDiscreteCurve ? 0.0f : (float)key.value; bounds.Encapsulate(new Vector2(time, val)); } @@ -1050,7 +1061,7 @@ public void TransformSelectedKeys(Matrix4x4 matrix, bool flipX, bool flipY, bool if (snapshot.curve.animationIsEditable) { // Transform time value. - Vector3 v = new Vector3(liveEditKey.keySnapshot.time, liveEditKey.keySnapshot.isPPtrCurve ? 0f : (float)liveEditKey.keySnapshot.value, 0f); + Vector3 v = new Vector3(liveEditKey.keySnapshot.time, liveEditKey.keySnapshot.isPPtrCurve || liveEditKey.keySnapshot.isDiscreteCurve ? 0f : (float)liveEditKey.keySnapshot.value, 0f); v = matrix.MultiplyPoint3x4(v); liveEditKey.key.time = Mathf.Max((snapToFrame) ? SnapToFrame(v.x, snapshot.curve.clip.frameRate) : v.x, 0f); @@ -1071,7 +1082,7 @@ public void TransformSelectedKeys(Matrix4x4 matrix, bool flipX, bool flipY, bool liveEditKey.key.outWeight = liveEditKey.keySnapshot.inWeight; } - if (!liveEditKey.key.isPPtrCurve) + if (!liveEditKey.key.isPPtrCurve && !liveEditKey.key.isDiscreteCurve) { liveEditKey.key.value = v.y; diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowUtility.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowUtility.cs index b53d36c6c4..dfcbc37c23 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowUtility.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowUtility.cs @@ -266,6 +266,18 @@ public static AnimationWindowKeyframe AddKeyframeToCurve(AnimationWindowCurve cu keyframe.curve = curve; curve.AddKeyframe(keyframe, time); } + else if (curve.isDiscreteCurve) + { + Keyframe tempKey = new Keyframe(time.time, 0f); + AnimationUtility.SetKeyLeftTangentMode(ref tempKey, TangentMode.Constant); + AnimationUtility.SetKeyRightTangentMode(ref tempKey, TangentMode.Constant); + AnimationUtility.SetKeyBroken(ref tempKey, true); + + keyframe = new AnimationWindowKeyframe(curve, tempKey); + keyframe.value = Convert.ToInt32(value); + + curve.AddKeyframe(keyframe, time); + } else if (type == typeof(bool) || type == typeof(float) || type == typeof(int)) { Keyframe tempKey = new Keyframe(time.time, (float)value); @@ -281,11 +293,9 @@ public static AnimationWindowKeyframe AddKeyframeToCurve(AnimationWindowCurve cu // Create temporary curve to get proper tangents AnimationCurve animationCurve = curve.ToAnimationCurve(); - - if (animationCurve.length <= 1) { - TangentMode tangentMode = curve.isDiscreteCurve ? TangentMode.Constant : TangentMode.Linear; + TangentMode tangentMode = TangentMode.Linear; AnimationUtility.SetKeyLeftTangentMode(ref tempKey, tangentMode); AnimationUtility.SetKeyRightTangentMode(ref tempKey, tangentMode); } @@ -460,14 +470,17 @@ public static object GetCurrentValue(GameObject rootGameObject, EditorCurveBindi { if (curveBinding.isPPtrCurve) { - Object value; - AnimationUtility.GetObjectReferenceValue(rootGameObject, curveBinding, out value); + AnimationUtility.GetObjectReferenceValue(rootGameObject, curveBinding, out var value); + return value; + } + else if (curveBinding.isDiscreteCurve) + { + AnimationUtility.GetDiscreteIntValue(rootGameObject, curveBinding, out var value); return value; } else { - float value; - AnimationUtility.GetFloatValue(rootGameObject, curveBinding, out value); + AnimationUtility.GetFloatValue(rootGameObject, curveBinding, out var value); return value; } } @@ -617,7 +630,7 @@ public static PropertyModification[] SerializedPropertyToPropertyModifications(S var modification = new PropertyModification(); modification.target = targetObjects[j]; - modification.propertyPath = singleProperty.propertyPath; + modification.propertyPath = (singleProperty.isReferencingAManagedReferenceField ? singleProperty.managedReferencePropertyPath : singleProperty.propertyPath); modification.value = value; modification.objectReference = objectReference; modifications.Add(modification); @@ -635,6 +648,8 @@ public static PropertyModification[] SerializedPropertyToPropertyModifications(S value = propertyIter.floatValue.ToString(CultureInfo.InvariantCulture); else if (isInt) value = propertyIter.intValue.ToString(); + else if (isEnum) + value = propertyIter.enumValueIndex.ToString(); else // if (isBool) value = propertyIter.boolValue ? "1" : "0"; @@ -643,7 +658,7 @@ public static PropertyModification[] SerializedPropertyToPropertyModifications(S var modification = new PropertyModification(); modification.target = targetObjects[j]; - modification.propertyPath = propertyIter.propertyPath; + modification.propertyPath = (propertyIter.isReferencingAManagedReferenceField ? propertyIter.managedReferencePropertyPath : propertyIter.propertyPath); modification.value = value; modification.objectReference = objectReference; modifications.Add(modification); @@ -747,6 +762,8 @@ public static string GetPropertyDisplayName(string propertyName) propertyName = propertyName.Replace("localEulerAnglesRaw", k_RotationDisplayName); propertyName = propertyName.Replace("localEulerAngles", k_RotationDisplayName); propertyName = propertyName.Replace("m_Materials.Array.data", k_MaterialReferenceDisplayName); + if (propertyName.StartsWith("managedReferences[")) + propertyName = propertyName.Remove(0, propertyName.IndexOf('.')+1); propertyName = ObjectNames.NicifyVariableName(propertyName); propertyName = propertyName.Replace("m_", ""); @@ -766,6 +783,29 @@ public static bool ShouldPrefixWithTypeName(Type animatableObjectType, string pr return true; } + public static string GetNicePropertyDisplayName(EditorCurveBinding curveBinding, SerializedObject so) + { + if (curveBinding.isSerializeReferenceCurve) + { + if (so != null) + { + var displayName = curveBinding.propertyName; + var sp = so.FindFirstPropertyFromManagedReferencePath(displayName); + if (sp != null) + displayName = AnimationWindowUtility.GetPropertyDisplayName(AnimationWindowUtility.GetPropertyGroupName(sp.propertyPath)); + if (displayName != "") + return displayName; + } + else + { + return ObjectNames.NicifyVariableName(curveBinding.type.Name) + "." + curveBinding.propertyName; + } + + } + + return AnimationWindowUtility.GetNicePropertyDisplayName(curveBinding.type, AnimationWindowUtility.GetPropertyGroupName(curveBinding.propertyName)); + } + public static string GetNicePropertyDisplayName(Type animatableObjectType, string propertyName) { if (ShouldPrefixWithTypeName(animatableObjectType, propertyName)) @@ -774,6 +814,28 @@ public static string GetNicePropertyDisplayName(Type animatableObjectType, strin return GetPropertyDisplayName(propertyName); } + public static string GetNicePropertyGroupDisplayName(EditorCurveBinding curveBinding, SerializedObject so) + { + if (curveBinding.isSerializeReferenceCurve ) + { + if (so != null) + { + var displayName = curveBinding.propertyName; + var sp = so.FindFirstPropertyFromManagedReferencePath(displayName); + if (sp != null) + displayName = AnimationWindowUtility.GetPropertyDisplayName(AnimationWindowUtility.GetPropertyGroupName(sp.propertyPath)); + if (displayName != "") + return displayName; + } + else + { + return ObjectNames.NicifyVariableName(curveBinding.type.Name) + "." + curveBinding.propertyName; + } + } + + return NicifyPropertyGroupName(curveBinding.type, AnimationWindowUtility.GetPropertyGroupName(curveBinding.propertyName)); + } + public static string GetNicePropertyGroupDisplayName(Type animatableObjectType, string propertyGroupName) { if (ShouldPrefixWithTypeName(animatableObjectType, propertyGroupName)) diff --git a/Editor/Mono/Animation/AnimationWindow/CurveBindingUtility.cs b/Editor/Mono/Animation/AnimationWindow/CurveBindingUtility.cs index 21ee380a73..4d2baef4e2 100644 --- a/Editor/Mono/Animation/AnimationWindow/CurveBindingUtility.cs +++ b/Editor/Mono/Animation/AnimationWindow/CurveBindingUtility.cs @@ -4,6 +4,7 @@ using UnityEditor; using UnityEngine; +using UnityEngine.Animations; namespace UnityEditorInternal { @@ -37,6 +38,10 @@ public static object GetCurrentValue(GameObject rootGameObject, EditorCurveBindi // Cannot extract type of PPtrCurve. return null; } + else if (curveBinding.isDiscreteCurve) + { + return 0; + } else { // Cannot extract type of AnimationCurve. Default to float. diff --git a/Editor/Mono/Animation/AnimationWindow/CurveEditor.cs b/Editor/Mono/Animation/AnimationWindow/CurveEditor.cs index 6e8b33154e..81df882277 100644 --- a/Editor/Mono/Animation/AnimationWindow/CurveEditor.cs +++ b/Editor/Mono/Animation/AnimationWindow/CurveEditor.cs @@ -652,13 +652,13 @@ public CurveEditor(Rect rect, CurveWrapper[] curves, bool minimalGUI) : base(min public void OnEnable() { // Only add callback once. - Undo.undoRedoPerformed -= UndoRedoPerformed; - Undo.undoRedoPerformed += UndoRedoPerformed; + Undo.undoRedoEvent -= UndoRedoPerformed; + Undo.undoRedoEvent += UndoRedoPerformed; } public void OnDisable() { - Undo.undoRedoPerformed -= UndoRedoPerformed; + Undo.undoRedoEvent -= UndoRedoPerformed; if (m_PointRenderer != null) m_PointRenderer.FlushCache(); @@ -672,7 +672,7 @@ public void OnDestroy() ScriptableObject.DestroyImmediate(m_Selection); } - void UndoRedoPerformed() + void UndoRedoPerformed(in UndoRedoInfo info) { if (settings.undoRedoSelection) InvalidateSelectionBounds(); diff --git a/Editor/Mono/Animation/AnimationWindow/DopeSheetEditor.cs b/Editor/Mono/Animation/AnimationWindow/DopeSheetEditor.cs index 2ba63645a7..bcf4722b50 100644 --- a/Editor/Mono/Animation/AnimationWindow/DopeSheetEditor.cs +++ b/Editor/Mono/Animation/AnimationWindow/DopeSheetEditor.cs @@ -775,9 +775,7 @@ private void HandleDragAndDropToEmptyArea() { EditorCurveBinding? spriteBinding = CreateNewPptrDopeline(state.selection, typeof(Sprite)); if (spriteBinding != null) - { DoSpriteDropAfterGeneratingNewDopeline(state.activeAnimationClip, spriteBinding); - } } DragAndDrop.visualMode = DragAndDropVisualMode.Copy; @@ -794,8 +792,35 @@ private void DoSpriteDropAfterGeneratingNewDopeline(AnimationClip animationClip, // Create the new curve for our sprites AnimationWindowCurve newCurve = new AnimationWindowCurve(animationClip, (EditorCurveBinding)spriteBinding, typeof(Sprite)); - // And finally perform the drop onto the curve + // Perform the drop onto the curve PerformDragAndDrop(newCurve, 0f); + + // Assign the Sprite in the first keyframe to the SpriteRenderer's Sprite property + AssignSpriteToSpriteRenderer(newCurve); + } + + private void AssignSpriteToSpriteRenderer(AnimationWindowCurve curve) + { + var rootGameObject = state.selection.rootGameObject; + if (rootGameObject == null) + return; + + var hasValidCurve = curve.m_Keyframes.Count > 0 && curve.binding.type == typeof(SpriteRenderer); + if (!hasValidCurve) + return; + + var spriteRenderer = AnimationUtility.GetAnimatedObject(rootGameObject, curve.binding) as SpriteRenderer; + var hasValidSpriteRenderer = spriteRenderer != null && spriteRenderer.sprite == null; + if (!hasValidSpriteRenderer) + return; + + var keyframe = curve.m_Keyframes[0]; + var sprite = keyframe.value as Sprite; + if (sprite != null) + { + Undo.RecordObject(spriteRenderer, "Add Sprite"); + spriteRenderer.sprite = sprite; + } } private void HandleRectangleToolEvents() diff --git a/Editor/Mono/Animation/EditorCurveBinding.bindings.cs b/Editor/Mono/Animation/EditorCurveBinding.bindings.cs index d6ccd3d822..15e8f88f70 100644 --- a/Editor/Mono/Animation/EditorCurveBinding.bindings.cs +++ b/Editor/Mono/Animation/EditorCurveBinding.bindings.cs @@ -29,15 +29,24 @@ public struct EditorCurveBinding : IEquatable //is it a discrete curve private int m_isDiscreteCurve; + //is it a serialize reference curve + private int m_isSerializeReferenceCurve; + //is it placeholder curve private int m_isPhantom; + //is it a unknow curve: mean it needs to be processed further more to find out what is this curve + // This is necessary to support old user code using FloatCurve for non float type + private int m_isUnknowCurve; + // This is only used internally for deleting curves internal int m_ClassID; internal int m_ScriptInstanceID; public bool isPPtrCurve { get { return m_isPPtrCurve != 0; } } public bool isDiscreteCurve { get { return m_isDiscreteCurve != 0; } } + + public bool isSerializeReferenceCurve { get { return m_isSerializeReferenceCurve != 0; } } internal bool isPhantom { get { return m_isPhantom != 0; } set { m_isPhantom = value == true ? 1 : 0; } } public static bool operator==(EditorCurveBinding lhs, EditorCurveBinding rhs) @@ -49,7 +58,7 @@ public struct EditorCurveBinding : IEquatable return false; } - return lhs.m_isPPtrCurve == rhs.m_isPPtrCurve && lhs.m_isDiscreteCurve == rhs.m_isDiscreteCurve && lhs.path == rhs.path && lhs.type == rhs.type && lhs.propertyName == rhs.propertyName; + return lhs.m_isPPtrCurve == rhs.m_isPPtrCurve && lhs.m_isDiscreteCurve == rhs.m_isDiscreteCurve && lhs.m_isSerializeReferenceCurve == rhs.m_isSerializeReferenceCurve && lhs.path == rhs.path && lhs.type == rhs.type && lhs.propertyName == rhs.propertyName; } public static bool operator!=(EditorCurveBinding lhs, EditorCurveBinding rhs) @@ -93,6 +102,8 @@ static public EditorCurveBinding FloatCurve(string inPath, System.Type inType, s BaseCurve(inPath, inType, inPropertyName, out binding); binding.m_isPPtrCurve = 0; binding.m_isDiscreteCurve = 0; + binding.m_isSerializeReferenceCurve = 0; + binding.m_isUnknowCurve = 1; return binding; } @@ -103,6 +114,8 @@ static public EditorCurveBinding PPtrCurve(string inPath, System.Type inType, st BaseCurve(inPath, inType, inPropertyName, out binding); binding.m_isPPtrCurve = 1; binding.m_isDiscreteCurve = 1; + binding.m_isSerializeReferenceCurve = 0; + binding.m_isUnknowCurve = 0; return binding; } @@ -112,6 +125,20 @@ static public EditorCurveBinding DiscreteCurve(string inPath, System.Type inType BaseCurve(inPath, inType, inPropertyName, out binding); binding.m_isPPtrCurve = 0; binding.m_isDiscreteCurve = 1; + binding.m_isSerializeReferenceCurve = 0; + binding.m_isUnknowCurve = 0; + + return binding; + } + + static public EditorCurveBinding SerializeReferenceCurve(string inPath, System.Type inType, long refID, string inPropertyName, bool isPPtr, bool isDiscrete) + { + EditorCurveBinding binding; + BaseCurve(inPath, inType, $"managedReferences[{refID}].{inPropertyName}", out binding); + binding.m_isPPtrCurve = isPPtr ? 1 : 0; + binding.m_isDiscreteCurve = isDiscrete || isPPtr ? 1 : 0; + binding.m_isSerializeReferenceCurve = 1; + binding.m_isUnknowCurve = 0; return binding; } diff --git a/Editor/Mono/Animation/TransitionPreview.cs b/Editor/Mono/Animation/TransitionPreview.cs index 5695841ed9..1c2433fb7a 100644 --- a/Editor/Mono/Animation/TransitionPreview.cs +++ b/Editor/Mono/Animation/TransitionPreview.cs @@ -36,6 +36,7 @@ internal class TransitionPreview private bool m_MustResample = true; private bool m_MustSampleMotions = false; + private bool m_MustResetParameterInfoList = false; public bool mustResample { set { m_MustResample = value; } get { return m_MustResample; } } private float m_LastEvalTime = -1.0f; private bool m_IsResampling = false; @@ -398,6 +399,8 @@ private void ResampleTransition(AnimatorStateTransition transition, AvatarMask l public void SetTransition(AnimatorStateTransition transition, AnimatorState sourceState, AnimatorState destinationState, AnimatorControllerLayer srcLayer, Animator previewObject) { m_RefSrcState = sourceState; + m_MustResetParameterInfoList = m_RefDstState != destinationState; + m_RefDstState = destinationState; TransitionInfo info = new TransitionInfo(); info.Set(transition, sourceState, destinationState); @@ -589,7 +592,12 @@ private void Init(Animator scenePreviewObject, Motion motion) CreateController(); - CreateParameterInfoList(); + if(m_ParameterInfoList == null || m_MustResetParameterInfoList) + { + m_MustResetParameterInfoList = false; + CreateParameterInfoList(); + } + } public void DoTransitionPreview() diff --git a/Editor/Mono/Annotation/SceneRenderModeWindow.cs b/Editor/Mono/Annotation/SceneRenderModeWindow.cs index cde408f01a..22e7108dfd 100644 --- a/Editor/Mono/Annotation/SceneRenderModeWindow.cs +++ b/Editor/Mono/Annotation/SceneRenderModeWindow.cs @@ -317,6 +317,8 @@ public static GUIContent GetGUIContent(DrawCameraMode drawCameraMode) internal static SceneView.CameraMode GetBuiltinCameraMode(DrawCameraMode drawMode) { + if (drawMode == DrawCameraMode.Normal) + drawMode = DrawCameraMode.Textured; return Styles.sBuiltinCameraModes.Single(mode => mode.drawMode == drawMode); } diff --git a/Editor/Mono/Annotation/SceneViewCameraWindow.cs b/Editor/Mono/Annotation/SceneViewCameraWindow.cs index 1c6d9f87dd..9cdb8a8d16 100644 --- a/Editor/Mono/Annotation/SceneViewCameraWindow.cs +++ b/Editor/Mono/Annotation/SceneViewCameraWindow.cs @@ -42,6 +42,8 @@ static Styles() GUIContent m_SceneCameraLabel = EditorGUIUtility.TrTextContent("Scene Camera"); GUIContent m_NavigationLabel = EditorGUIUtility.TrTextContent("Navigation"); + readonly string k_ClippingPlaneWarning = L10n.Tr("Using extreme values between the near and far planes may cause rendering issues. In general, to get better precision move the Near plane as far as possible."); + const int kFieldCount = 13; const int kWindowWidth = 290; const int kContentPadding = 4; @@ -132,6 +134,8 @@ void Draw() EditorGUI.s_NearAndFarLabels[1], ref near, ref far, EditorGUI.kNearFarLabelsWidth); settings.SetClipPlanes(near, far); + if(far/near > 10000000 || near < 0.0001f) + EditorGUILayout.HelpBox(k_ClippingPlaneWarning,MessageType.Warning); } settings.occlusionCulling = EditorGUILayout.Toggle(m_OcclusionCulling, settings.occlusionCulling); diff --git a/Editor/Mono/AssemblyHelper.cs b/Editor/Mono/AssemblyHelper.cs index 3aa7261772..cd9fd2c351 100644 --- a/Editor/Mono/AssemblyHelper.cs +++ b/Editor/Mono/AssemblyHelper.cs @@ -18,6 +18,8 @@ using UnityEngine.Scripting; using Debug = UnityEngine.Debug; using Unity.Profiling; +using UnityEditor.AssetImporters; +using UnityEngine.Scripting.APIUpdating; namespace UnityEditor { @@ -254,59 +256,35 @@ public static string[] GetDefaultAssemblySearchPaths() return searchPaths.ToArray(); } - public static void ExtractAllClassesThatAreUserExtendedScripts(string path, out string[] classNamesArray, out string[] classNameSpacesArray, out string[] originalClassNameSpacesArray) + [RequiredByNativeCode] + public static void ExtractAllClassesThatAreUserExtendedScripts(string path, out string[] classNamesArray, out string[] classNameSpacesArray, out string[] movedFromNamespacesArray) { - List classNames = new List(); - List nameSpaces = new List(); - List originalNamespaces = new List(); - var readerParameters = new ReaderParameters(); - - // this will resolve any types in assemblies within the same directory as the type's assembly - // or any folder which contains a currently available precompiled dll - var assemblyResolver = new DefaultAssemblyResolver(); - var searchPaths = GetDefaultAssemblySearchPaths(); + var typesDerivedFromMonoBehaviour = TypeCache.GetTypesDerivedFrom(); + var typesDerivedFromScriptableObject = TypeCache.GetTypesDerivedFrom(); + var typesDerivedFromScriptedImporter = TypeCache.GetTypesDerivedFrom(); - foreach (var asmpath in searchPaths) - assemblyResolver.AddSearchDirectory(asmpath); + var fullPath = Path.GetFullPath(path); + IEnumerable userTypes = typesDerivedFromMonoBehaviour.Where(x => Path.GetFullPath(x.Assembly.Location) == fullPath); + userTypes = userTypes + .Concat(typesDerivedFromScriptableObject.Where(x => Path.GetFullPath(x.Assembly.Location) == fullPath)) + .Concat(typesDerivedFromScriptedImporter.Where(x => Path.GetFullPath(x.Assembly.Location) == fullPath)).ToList(); - assemblyResolver.AddSearchDirectory(Path.GetDirectoryName(path)); - readerParameters.AssemblyResolver = assemblyResolver; + List classNames = new List(userTypes.Count()); + List nameSpaces = new List(userTypes.Count()); + List originalNamespaces = new List(userTypes.Count()); - AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(path, readerParameters); - foreach (ModuleDefinition module in assembly.Modules) + foreach (var userType in userTypes) { - foreach (TypeDefinition type in module.Types) - { - TypeReference baseType = type.BaseType; - - try - { - if (IsTypeAUserExtendedScript(baseType)) - { - classNames.Add(type.Name); - nameSpaces.Add(type.Namespace); + classNames.Add(userType.Name); + nameSpaces.Add(userType.Namespace); - var originalNamespace = string.Empty; - var attribute = type.CustomAttributes.SingleOrDefault(a => a.AttributeType.FullName == typeof(UnityEngine.Scripting.APIUpdating.MovedFromAttribute).FullName); - - if (attribute != null) - { - originalNamespace = (string)attribute.ConstructorArguments[0].Value; - } - - originalNamespaces.Add(originalNamespace); - } - } - catch (Exception) - { - Debug.LogError("Failed to extract " + type.FullName + " class of base type " + baseType.FullName + " when inspecting " + path); - } - } + var movedFromAttribute = userType.GetCustomAttribute(); + originalNamespaces.Add(movedFromAttribute?.data.nameSpace); } classNamesArray = classNames.ToArray(); classNameSpacesArray = nameSpaces.ToArray(); - originalClassNameSpacesArray = originalNamespaces.ToArray(); + movedFromNamespacesArray = originalNamespaces.ToArray(); } /// Extract information about all types in the specified assembly, searchDirs might be used to resolve dependencies. diff --git a/Editor/Mono/AssemblyValidation.cs b/Editor/Mono/AssemblyValidation.cs index 0ea315cf59..41841e9827 100644 --- a/Editor/Mono/AssemblyValidation.cs +++ b/Editor/Mono/AssemblyValidation.cs @@ -380,12 +380,10 @@ public static void ResolveAndSetupReferences(int index, Capacity = assemblyReferences.Length }; - var assemblyVersionValidation = PlayerSettings.assemblyVersionValidation; - bool isReferencingUnityAssemblies = false; foreach (var reference in assemblyReferences) { - if (!isReferencingUnityAssemblies && (Utility.FastStartsWith(reference.Name, "UnityEngine.", "unityengine.") || Utility.FastStartsWith(reference.Name, "UnityEditor.", "unityeditor."))) + if (!isReferencingUnityAssemblies && (Utility.FastStartsWith(reference.Name, "UnityEngine", "unityengine") || Utility.FastStartsWith(reference.Name, "UnityEditor", "unityeditor"))) { isReferencingUnityAssemblies = true; } @@ -399,21 +397,8 @@ public static void ResolveAndSetupReferences(int index, errors[index].Add(ErrorFlags.ReferenceHasErrors, $"{reference.Name} references itself."); } - if (assemblyVersionValidation && assemblyDefinitionNameToIndex.TryGetValue(referenceAssemblyDefinition.Name.Name, - out int referenceAssemblyDefinitionIndex)) + if (assemblyDefinitionNameToIndex.TryGetValue(referenceAssemblyDefinition.Name.Name, out int referenceAssemblyDefinitionIndex)) { - bool isSigned = IsSigned(reference); - if (isSigned) - { - var definition = assemblyDefinitions[referenceAssemblyDefinitionIndex]; - - if (definition.Name.Version.ToString() != reference.Version.ToString() && !IsInSameFolder(assemblyDefinition, referenceAssemblyDefinition)) - { - errors[index].Add(ErrorFlags.UnresolvableReference, - $"{assemblyDefinition.Name.Name} references strong named {reference.Name} Assembly references: {reference.Version} Found in project: {definition.Name.Version}.\nAssembly Version Validation can be disabled in Player Settings \"Assembly Version Validation\""); - } - } - referenceIndieces.Add(referenceAssemblyDefinitionIndex); } } diff --git a/Editor/Mono/AssetModificationProcessor.cs b/Editor/Mono/AssetModificationProcessor.cs index d029b39a47..7a58f06380 100644 --- a/Editor/Mono/AssetModificationProcessor.cs +++ b/Editor/Mono/AssetModificationProcessor.cs @@ -97,7 +97,7 @@ static System.Collections.Generic.IEnumerable AssetModificationProc #pragma warning restore 0618 [RequiredByNativeCode] - static void OnWillCreateAsset(string path) + internal static void OnWillCreateAsset(string path) { foreach (var assetModificationProcessorClass in AssetModificationProcessors) { diff --git a/Editor/Mono/AssetPipeline/AssetImportContext.bindings.cs b/Editor/Mono/AssetPipeline/AssetImportContext.bindings.cs index 0c4a34890e..92f7fe34f0 100644 --- a/Editor/Mono/AssetPipeline/AssetImportContext.bindings.cs +++ b/Editor/Mono/AssetPipeline/AssetImportContext.bindings.cs @@ -164,6 +164,11 @@ public void DependsOnCustomDependency(string dependency) throw new ArgumentNullException("dependency", "Cannot add custom dependency on an empty custom dependency."); } + if (string.CompareOrdinal(dependency,"srp/default-shader") == 0 && assetPath.EndsWith(".shader", StringComparison.OrdinalIgnoreCase)) + { + throw new Exception($"A shader '{assetPath}' cannot depend on the 'srp/default-shader' custom dependency because this operation is unsupported."); + } + DependsOnCustomDependencyInternal(dependency); } diff --git a/Editor/Mono/AssetPipeline/TextureImporter.bindings.cs b/Editor/Mono/AssetPipeline/TextureImporter.bindings.cs index 608b4c6e90..75c8e32f37 100644 --- a/Editor/Mono/AssetPipeline/TextureImporter.bindings.cs +++ b/Editor/Mono/AssetPipeline/TextureImporter.bindings.cs @@ -374,7 +374,8 @@ public extern TextureWrapMode wrapMode public extern SecondarySpriteTexture[] secondarySpriteTextures { get; set; } - public extern string spritePackingTag { get; set; } + [Obsolete("Support for packing sprites through spritePackingTag has been removed. Please use SpriteAtlas instead.")] + public string spritePackingTag { get { return ""; } set { } } // The number of pixels in one unit. Note: The C++ side still uses the name pixelsToUnits which is misleading, // but has not been changed yet to minimize merge conflicts. @@ -467,5 +468,8 @@ public void SetTextureSettings(TextureImporterSettings src) // This is for remapping Sprite that are renamed. extern internal bool GetNameFromInternalIDMap(long id, ref string name); + + [NativeName("GetSpriteMetaDatas")] + internal extern SpriteMetaData[] GetSpriteMetaDatas(); } } diff --git a/Editor/Mono/AssetPostprocessor.cs b/Editor/Mono/AssetPostprocessor.cs index f8038d8a5b..79416847e8 100644 --- a/Editor/Mono/AssetPostprocessor.cs +++ b/Editor/Mono/AssetPostprocessor.cs @@ -12,6 +12,7 @@ using UnityEditor.AssetImporters; using Object = UnityEngine.Object; using UnityEditor.Profiling; +using UnityEditor.Callbacks; namespace UnityEditor { @@ -95,6 +96,58 @@ public void LogError(string warning) } + class OnPostprocessAllAssetsCallbackCollection : OrderedCallbackCollection + { + public class MethodInfoCallback : Callback + { + public MethodInfo Method { get; } + + public override Type classType => Method.DeclaringType; + + public bool MethodDomainReload { get; } + + public override string name => classType.FullName; + + public MethodInfoCallback(MethodInfo method, bool methodDomainReload) + { + Method = method; + MethodDomainReload = methodDomainReload; + } + + public override IEnumerable GetCustomAttributes() => Method.GetCustomAttributes(); + } + + public override string name => "OnPostprocessAllAssets"; + + public override List GetCallbacks() + { + var methodArgTypes = new Type[] { typeof(string).MakeArrayType(), typeof(string).MakeArrayType(), typeof(string).MakeArrayType(), typeof(string).MakeArrayType() }; + var methodDomainReloadParamArgTypes = new Type[] { methodArgTypes[0], methodArgTypes[1], methodArgTypes[2], methodArgTypes[3], typeof(bool) }; + const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; + + var callbacks = new List(); + foreach (var assetPostprocessorClass in TypeCache.GetTypesDerivedFrom()) + { + var method = assetPostprocessorClass.GetMethod("OnPostprocessAllAssets", flags, null, methodArgTypes, null); + if (method != null) + { + callbacks.Add(new MethodInfoCallback(method, false)); + } + else + { + // OnPostprocessAllAssets with didDomainReload parameter + method = assetPostprocessorClass.GetMethod("OnPostprocessAllAssets", flags, null, methodDomainReloadParamArgTypes, null); + if (method != null) + { + callbacks.Add(new MethodInfoCallback(method, true)); + } + } + } + + return callbacks; + } + } + internal class AssetPostprocessingInternal { // What is it: @@ -189,6 +242,9 @@ internal class AssetPostprocessingInternal static Dictionary s_StaticPostprocessorMethodsByImporterType; static Dictionary s_DynamicPostprocessorMethodsByImporterType; + // Internal for debugging purposes. We can generate dependency graphs to help understand issues. + internal static OnPostprocessAllAssetsCallbackCollection s_OnPostprocessAllAssetsCallbacks = new OnPostprocessAllAssetsCallbackCollection(); + static AssetPostprocessingInternal() { s_StaticPostprocessorMethodsByImporterType = new Dictionary(); @@ -238,28 +294,23 @@ static void LogPostProcessorMissingDefaultConstructor(Type type) static void PostprocessAllAssets(string[] importedAssets, string[] addedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromPathAssets, bool didDomainReload) { object[] args = { importedAssets, deletedAssets, movedAssets, movedFromPathAssets }; - object[] argsWithDidDomainReload = { importedAssets, deletedAssets, movedAssets, movedFromPathAssets, didDomainReload}; - foreach (var assetPostprocessorClass in GetCachedAssetPostprocessorClasses()) - { - const string methodName = "OnPostprocessAllAssets"; - MethodInfo method = assetPostprocessorClass.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { typeof(string).MakeArrayType(), typeof(string).MakeArrayType(), typeof(string).MakeArrayType(), typeof(string).MakeArrayType() }, null); + var containsNoAssets = importedAssets.Length == 0 && addedAssets.Length == 0 && deletedAssets.Length == 0 && movedAssets.Length == 0 && movedFromPathAssets.Length == 0; - if (method != null) + foreach (OnPostprocessAllAssetsCallbackCollection.MethodInfoCallback assetPostProcessor in s_OnPostprocessAllAssetsCallbacks.sortedCallbacks) + { + if (assetPostProcessor.MethodDomainReload) { - if (importedAssets.Length != 0 || addedAssets.Length != 0 || deletedAssets.Length != 0 || movedAssets.Length != 0 || movedFromPathAssets.Length != 0) - using (new EditorPerformanceMarker($"{assetPostprocessorClass.Name}.{methodName}", assetPostprocessorClass).Auto()) - InvokeMethod(method, args); + using (new EditorPerformanceMarker($"{assetPostProcessor.classType.Name}.OnPostprocessAllAssets", assetPostProcessor.classType).Auto()) + InvokeMethod(assetPostProcessor.Method, argsWithDidDomainReload); } else { - // OnPostprocessAllAssets with didDomainReload parameter - method = assetPostprocessorClass.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { typeof(string).MakeArrayType(), typeof(string).MakeArrayType(), typeof(string).MakeArrayType(), typeof(string).MakeArrayType(), typeof(bool)}, null); - if (method != null) - { - using (new EditorPerformanceMarker($"{assetPostprocessorClass.Name}.{methodName}", assetPostprocessorClass).Auto()) - InvokeMethod(method, argsWithDidDomainReload); - } + if (containsNoAssets) + continue; + + using (new EditorPerformanceMarker($"{assetPostProcessor.classType.Name}.OnPostprocessAllAssets", assetPostProcessor.classType).Auto()) + InvokeMethod(assetPostProcessor.Method, args); } } diff --git a/Editor/Mono/AssetStore/AssetStoreContext.cs b/Editor/Mono/AssetStore/AssetStoreContext.cs index 0909c63e8b..b20682794b 100644 --- a/Editor/Mono/AssetStore/AssetStoreContext.cs +++ b/Editor/Mono/AssetStore/AssetStoreContext.cs @@ -53,9 +53,10 @@ public string GetAuthToken() return UnityEditorInternal.InternalEditorUtility.GetAuthToken(); } + [Obsolete("GetLicenseFlags is no longer supported", error: true)] public int[] GetLicenseFlags() { - return UnityEditorInternal.InternalEditorUtility.GetLicenseFlags(); + return new int[] {}; } public string GetString(string key) diff --git a/Editor/Mono/AssetStore/AssetStoreWindow.cs b/Editor/Mono/AssetStore/AssetStoreWindow.cs index b61cedfa30..f0533b3eaa 100644 --- a/Editor/Mono/AssetStore/AssetStoreWindow.cs +++ b/Editor/Mono/AssetStore/AssetStoreWindow.cs @@ -5,6 +5,8 @@ using UnityEngine; using UnityEngine.UIElements; using UnityEditor.Connect; +using UnityEditor.PackageManager.UI; +using UnityEditor.UIElements; namespace UnityEditor { @@ -47,7 +49,7 @@ public void OnEnable() { var root = windowResource.CloneTree(); - var lightStyleSheet = EditorGUIUtility.Load(EditorUIService.instance.GetUIToolkitDefaultCommonLightStyleSheetPath()) as StyleSheet; + var lightStyleSheet = EditorGUIUtility.Load(UIElementsEditorUtility.s_DefaultCommonLightStyleSheetPath) as StyleSheet; var assetStoreStyleSheet = EditorGUIUtility.Load("StyleSheets/AssetStore/AssetStoreWindow.uss") as StyleSheet; var styleSheet = CreateInstance(); styleSheet.isDefaultStyleSheet = true; @@ -93,7 +95,7 @@ private void OnVisitWebsiteButtonClicked() private void OnLaunchPackageManagerButtonClicked() { - EditorUIService.instance.PackageManagerOpen(); + PackageManagerWindow.OpenPackageManager(null); } private void SetMinMaxSizes() diff --git a/Editor/Mono/Audio/Effects/AudioMixerEffectPlugin.cs b/Editor/Mono/Audio/Effects/AudioMixerEffectPlugin.cs index c2d159ed8b..7e16941054 100644 --- a/Editor/Mono/Audio/Effects/AudioMixerEffectPlugin.cs +++ b/Editor/Mono/Audio/Effects/AudioMixerEffectPlugin.cs @@ -4,25 +4,71 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using UnityEngine; -using UnityEditor; -using UnityEditor.Audio; namespace UnityEditor.Audio { public class AudioMixerEffectPlugin : IAudioEffectPlugin { + internal AudioMixerController m_Controller; + internal AudioMixerEffectController m_Effect; + internal MixerParameterDefinition[] m_ParamDefs; + + static bool s_ParameterChangeUndoIsRecorded; + static bool s_ParameterChangeUndoGroupNameIsSet; + + static readonly Dictionary k_UpdatedParameterMap = new Dictionary(); + + readonly HashSet m_VerifiedParameters = new HashSet(); + public override bool SetFloatParameter(string name, float value) { + if (!HasParameter(name)) + { + return false; + } + + GetFloatParameter(name, out var previousValue); + + if (Mathf.Approximately(value, previousValue)) + { + return true; + } + + if (k_UpdatedParameterMap.Count == 0 && !s_ParameterChangeUndoIsRecorded) + { + Undo.RecordObject(m_Controller.TargetSnapshot, $"Change {name}"); + } + + k_UpdatedParameterMap[name] = value; + + if (k_UpdatedParameterMap.Count > 1 && !s_ParameterChangeUndoGroupNameIsSet) + { + Undo.SetCurrentGroupName("Change Effect Parameters"); + s_ParameterChangeUndoGroupNameIsSet = true; + } + m_Effect.SetValueForParameter(m_Controller, m_Controller.TargetSnapshot, name, value); + return true; } public override bool GetFloatParameter(string name, out float value) { + if (!HasParameter(name)) + { + value = default; + + return false; + } + + if (k_UpdatedParameterMap.TryGetValue(name, out value)) + { + return true; + } + value = m_Effect.GetValueForParameter(m_Controller, m_Controller.TargetSnapshot, name); + return true; } @@ -38,6 +84,7 @@ public override bool GetFloatParameterInfo(string name, out float minRange, out return true; } } + minRange = 0.0f; maxRange = 1.0f; defaultValue = 0.5f; @@ -60,8 +107,32 @@ public override bool IsPluginEditableAndEnabled() return AudioMixerController.EditingTargetSnapshot() && !m_Effect.bypass; } - internal AudioMixerController m_Controller; - internal AudioMixerEffectController m_Effect; - internal MixerParameterDefinition[] m_ParamDefs; + // Call this after each interaction that changes effect parameters. + // NOTE: It is assumed that parameter changes can only happen on a single effect at a time. + internal static void OnParameterChangesDone() + { + k_UpdatedParameterMap.Clear(); + s_ParameterChangeUndoIsRecorded = false; + s_ParameterChangeUndoGroupNameIsSet = false; + } + + bool HasParameter(string name) + { + if (m_VerifiedParameters.Contains(name)) + { + return true; + } + + for (var i = 0; i < m_ParamDefs.Length; ++i) + { + if (m_ParamDefs[i].name.Equals(name)) + { + m_VerifiedParameters.Add(name); + return true; + } + } + + return false; + } } } diff --git a/Editor/Mono/Audio/Effects/DuckVolumeGUI.cs b/Editor/Mono/Audio/Effects/DuckVolumeGUI.cs index de297bec76..dfcf768afe 100644 --- a/Editor/Mono/Audio/Effects/DuckVolumeGUI.cs +++ b/Editor/Mono/Audio/Effects/DuckVolumeGUI.cs @@ -4,12 +4,14 @@ //#define DEBUG_PLOT_GAIN +using System; using System.Globalization; +using UnityEditor.Audio; using UnityEngine; namespace UnityEditor { - internal class DuckVolumeGUI : IAudioEffectPluginGUI + class DuckVolumeGUI : IAudioEffectPluginGUI { public static string kThresholdName = "Threshold"; public static string kRatioName = "Ratio"; @@ -68,7 +70,7 @@ public enum DragType MakeupGain, } - private static DragType dragtype = DragType.None; + static DragType dragtype = DragType.None; protected static Color ScaleAlpha(Color col, float blend) { @@ -104,6 +106,7 @@ static float EvaluateDuckingVolume(float x, float ratio, float threshold, float } else if (t > 0.0f) gain = duckThreshold + duckGradient * t; + return (2.0f * (gain + duckMakeupGain - dbMin) / dbRange) - 1.0f; } @@ -117,10 +120,14 @@ static bool CurveDisplay(IAudioEffectPlugin plugin, Rect r0, ref float threshold const float thresholdActiveWidth = 10f; float vuWidth = 10f; - float minThreshold, maxThreshold, defThreshold; plugin.GetFloatParameterInfo(kThresholdName, out minThreshold, out maxThreshold, out defThreshold); - float minRatio, maxRatio, defRatio; plugin.GetFloatParameterInfo(kRatioName, out minRatio, out maxRatio, out defRatio); - float minMakeupGain, maxMakeupGain, defMakeupGain; plugin.GetFloatParameterInfo(kMakeupGainName, out minMakeupGain, out maxMakeupGain, out defMakeupGain); - float minKnee, maxKnee, defKnee; plugin.GetFloatParameterInfo(kKneeName, out minKnee, out maxKnee, out defKnee); + float minThreshold, maxThreshold, defThreshold; + plugin.GetFloatParameterInfo(kThresholdName, out minThreshold, out maxThreshold, out defThreshold); + float minRatio, maxRatio, defRatio; + plugin.GetFloatParameterInfo(kRatioName, out minRatio, out maxRatio, out defRatio); + float minMakeupGain, maxMakeupGain, defMakeupGain; + plugin.GetFloatParameterInfo(kMakeupGainName, out minMakeupGain, out maxMakeupGain, out defMakeupGain); + float minKnee, maxKnee, defKnee; + plugin.GetFloatParameterInfo(kKneeName, out minKnee, out maxKnee, out defKnee); float dbRange = 100.0f, dbMin = -80.0f; float thresholdPosX = r.width * (threshold - dbMin) / dbRange; @@ -142,6 +149,7 @@ static bool CurveDisplay(IAudioEffectPlugin plugin, Rect r0, ref float threshold else dragtype = DragType.ThresholdAndKnee; } + break; case EventType.MouseUp: @@ -151,7 +159,9 @@ static bool CurveDisplay(IAudioEffectPlugin plugin, Rect r0, ref float threshold GUIUtility.hotControl = 0; EditorGUIUtility.SetWantsMouseJumping(0); evt.Use(); + AudioMixerEffectPlugin.OnParameterChangesDone(); } + break; case EventType.MouseDrag: @@ -178,6 +188,7 @@ static bool CurveDisplay(IAudioEffectPlugin plugin, Rect r0, ref float threshold modifiedValue = true; evt.Use(); } + break; } @@ -222,6 +233,7 @@ static bool CurveDisplay(IAudioEffectPlugin plugin, Rect r0, ref float threshold } else if (t > 0.0f) gain = duckThreshold + duckGradient * t; + return (2.0f * (gain + duckMakeupGain - dbMin) / dbRange) - 1.0f; } ); @@ -243,6 +255,7 @@ static bool CurveDisplay(IAudioEffectPlugin plugin, Rect r0, ref float threshold } else if (t > 0.0f) gain = duckThreshold + duckGradient * t; + return (2.0f * (gain + duckMakeupGain - dbMin) / dbRange) - 1.0f; }, Color.white @@ -261,17 +274,17 @@ static bool CurveDisplay(IAudioEffectPlugin plugin, Rect r0, ref float threshold if (dragtype == DragType.Ratio) { - float aspect = (float)r.height / (float)r.width; + float aspect = r.height / r.width; Handles.DrawAAPolyLine(2.0f, - new Color[] { Color.black, Color.black }, - new Vector3[] + new[] { Color.black, Color.black }, + new[] { new Vector3(r.x + thresholdPosX + r.width, r.y + thresholdPosY - aspect * r.width, 0.0f), new Vector3(r.x + thresholdPosX - r.width, r.y + thresholdPosY + aspect * r.width, 0.0f) }); Handles.DrawAAPolyLine(3.0f, - new Color[] { Color.white, Color.white }, - new Vector3[] + new[] { Color.white, Color.white }, + new[] { new Vector3(r.x + thresholdPosX + r.width, r.y + thresholdPosY - aspect * duckGradient * r.width, 0.0f), new Vector3(r.x + thresholdPosX - r.width, r.y + thresholdPosY + aspect * duckGradient * r.width, 0.0f) @@ -309,20 +322,21 @@ static bool CurveDisplay(IAudioEffectPlugin plugin, Rect r0, ref float threshold public override bool OnGUI(IAudioEffectPlugin plugin) { - float blend = plugin.IsPluginEditableAndEnabled() ? 1.0f : 0.5f; + var blend = plugin.IsPluginEditableAndEnabled() ? 1.0f : 0.5f; - float threshold, ratio, makeupGain, attackTime, releaseTime, knee; - plugin.GetFloatParameter(kThresholdName, out threshold); - plugin.GetFloatParameter(kRatioName, out ratio); - plugin.GetFloatParameter(kMakeupGainName, out makeupGain); - plugin.GetFloatParameter(kAttackTimeName, out attackTime); - plugin.GetFloatParameter(kReleaseTimeName, out releaseTime); - plugin.GetFloatParameter(kKneeName, out knee); + plugin.GetFloatParameter(kThresholdName, out var threshold); + plugin.GetFloatParameter(kRatioName, out var ratio); + plugin.GetFloatParameter(kMakeupGainName, out var makeupGain); + plugin.GetFloatParameter(kAttackTimeName, out var attackTime); + plugin.GetFloatParameter(kReleaseTimeName, out var releaseTime); + plugin.GetFloatParameter(kKneeName, out var knee); - float[] metering; plugin.GetFloatBuffer("Metering", out metering, 2); + plugin.GetFloatBuffer("Metering", out var metering, 2); GUILayout.Space(5f); - Rect r = GUILayoutUtility.GetRect(200, 160, GUILayout.ExpandWidth(true)); + + var r = GUILayoutUtility.GetRect(200, 160, GUILayout.ExpandWidth(true)); + if (CurveDisplay(plugin, r, ref threshold, ref ratio, ref makeupGain, ref attackTime, ref releaseTime, ref knee, metering[0], metering[1], blend)) { plugin.SetFloatParameter(kThresholdName, threshold); diff --git a/Editor/Mono/Audio/Effects/ParamEQGUI.cs b/Editor/Mono/Audio/Effects/ParamEQGUI.cs index 9eb719d14d..32eebe6f29 100644 --- a/Editor/Mono/Audio/Effects/ParamEQGUI.cs +++ b/Editor/Mono/Audio/Effects/ParamEQGUI.cs @@ -2,14 +2,14 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License -using UnityEngine; -using UnityEditor; using System; using System.Globalization; +using UnityEditor.Audio; +using UnityEngine; namespace UnityEditor { - internal class ParamEqGUI : IAudioEffectPluginGUI + class ParamEqGUI : IAudioEffectPluginGUI { public static string kCenterFreqName = "Center freq"; public static string kOctaveRangeName = "Octave range"; @@ -48,7 +48,7 @@ public static GUIStyle BuildGUIStyleForLabel(Color color, int fontSize, bool wra public static GUIStyle textStyle10 = BuildGUIStyleForLabel(Color.grey, 10, false, FontStyle.Normal, TextAnchor.MiddleCenter); - private static void DrawFrequencyTickMarks(Rect r, float samplerate, bool logScale, Color col) + static void DrawFrequencyTickMarks(Rect r, float samplerate, bool logScale, Color col) { textStyle10.normal.textColor = col; float px = r.x, w = 60.0f; @@ -71,7 +71,7 @@ protected static Color ScaleAlpha(Color col, float blend) } // Maps from normalized frequency to real frequency - private static double MapNormalizedFrequency(double f, double sr, bool useLogScale, bool forward) + static double MapNormalizedFrequency(double f, double sr, bool useLogScale, bool forward) { double maxFreq = 0.5 * sr; if (useLogScale) @@ -81,6 +81,7 @@ private static double MapNormalizedFrequency(double f, double sr, bool useLogSca return lowestFreq * Math.Pow(maxFreq / lowestFreq, f); return Math.Log(f / lowestFreq) / Math.Log(maxFreq / lowestFreq); } + return (forward) ? (f * maxFreq) : (f / maxFreq); } @@ -91,9 +92,12 @@ static bool ParamEqualizerCurveEditor(IAudioEffectPlugin plugin, Rect r, ref flo r = AudioCurveRendering.BeginCurveFrame(r); - float minCenterFreq, maxCenterFreq, defCenterFreq; plugin.GetFloatParameterInfo(kCenterFreqName, out minCenterFreq, out maxCenterFreq, out defCenterFreq); - float minOctaveRange, maxOctaveRange, defOctaveRange; plugin.GetFloatParameterInfo(kOctaveRangeName, out minOctaveRange, out maxOctaveRange, out defOctaveRange); - float minGain, maxGain, defGain; plugin.GetFloatParameterInfo(kFrequencyGainName, out minGain, out maxGain, out defGain); + float minCenterFreq, maxCenterFreq, defCenterFreq; + plugin.GetFloatParameterInfo(kCenterFreqName, out minCenterFreq, out maxCenterFreq, out defCenterFreq); + float minOctaveRange, maxOctaveRange, defOctaveRange; + plugin.GetFloatParameterInfo(kOctaveRangeName, out minOctaveRange, out maxOctaveRange, out defOctaveRange); + float minGain, maxGain, defGain; + plugin.GetFloatParameterInfo(kFrequencyGainName, out minGain, out maxGain, out defGain); bool modifiedValue = false; switch (evt.GetTypeForControl(controlID)) @@ -105,15 +109,19 @@ static bool ParamEqualizerCurveEditor(IAudioEffectPlugin plugin, Rect r, ref flo EditorGUIUtility.SetWantsMouseJumping(1); evt.Use(); } + break; case EventType.MouseUp: + // Signal if (GUIUtility.hotControl == controlID && evt.button == 0) { GUIUtility.hotControl = 0; EditorGUIUtility.SetWantsMouseJumping(0); evt.Use(); + AudioMixerEffectPlugin.OnParameterChangesDone(); } + break; case EventType.MouseDrag: @@ -128,6 +136,7 @@ static bool ParamEqualizerCurveEditor(IAudioEffectPlugin plugin, Rect r, ref flo modifiedValue = true; evt.Use(); } + break; } @@ -145,17 +154,17 @@ static bool ParamEqualizerCurveEditor(IAudioEffectPlugin plugin, Rect r, ref flo double Q = 1.0 / bandwidth; double A = gain; double alpha = Math.Sin(w0) / (2.0 * Q); - double b0 = 1.0 + alpha * A; + double b0 = 1.0 + alpha * A; double b1 = -2.0 * Math.Cos(w0); - double b2 = 1.0 - alpha * A; - double a0 = 1.0 + alpha / A; + double b2 = 1.0 - alpha * A; + double a0 = 1.0 + alpha / A; double a1 = -2.0 * Math.Cos(w0); - double a2 = 1.0 - alpha / A; + double a2 = 1.0 - alpha / A; AudioCurveRendering.DrawCurve( r, delegate(float x) { - double f = MapNormalizedFrequency((double)x, plugin.GetSampleRate(), useLogScale, true); + double f = MapNormalizedFrequency(x, plugin.GetSampleRate(), useLogScale, true); ComplexD w = ComplexD.Exp(wm * f); ComplexD n = w * (w * b2 + b1) + b0; ComplexD d = w * (w * a2 + a1) + a0; @@ -176,15 +185,16 @@ static bool ParamEqualizerCurveEditor(IAudioEffectPlugin plugin, Rect r, ref flo public override bool OnGUI(IAudioEffectPlugin plugin) { - float blend = plugin.IsPluginEditableAndEnabled() ? 1.0f : 0.5f; + var blend = plugin.IsPluginEditableAndEnabled() ? 1.0f : 0.5f; - float centerFreq, octaveRange, frequencyGain; - plugin.GetFloatParameter(kCenterFreqName, out centerFreq); - plugin.GetFloatParameter(kOctaveRangeName, out octaveRange); - plugin.GetFloatParameter(kFrequencyGainName, out frequencyGain); + plugin.GetFloatParameter(kCenterFreqName, out var centerFreq); + plugin.GetFloatParameter(kOctaveRangeName, out var octaveRange); + plugin.GetFloatParameter(kFrequencyGainName, out var frequencyGain); GUILayout.Space(5f); - Rect r = GUILayoutUtility.GetRect(200, 100, GUILayout.ExpandWidth(true)); + + var r = GUILayoutUtility.GetRect(200, 100, GUILayout.ExpandWidth(true)); + if (ParamEqualizerCurveEditor(plugin, r, ref centerFreq, ref octaveRange, ref frequencyGain, blend)) { plugin.SetFloatParameter(kCenterFreqName, centerFreq); diff --git a/Editor/Mono/Audio/Mixer/GUI/AudioMixerGroupTreeView.cs b/Editor/Mono/Audio/Mixer/GUI/AudioMixerGroupTreeView.cs index 63c39688e2..cd6ce63f34 100644 --- a/Editor/Mono/Audio/Mixer/GUI/AudioMixerGroupTreeView.cs +++ b/Editor/Mono/Audio/Mixer/GUI/AudioMixerGroupTreeView.cs @@ -684,7 +684,7 @@ public void EndRenaming() m_AudioGroupTree.EndNameEditing(true); } - public void OnUndoRedoPerformed() + public void OnUndoRedoPerformed(in UndoRedoInfo info) { ReloadTree(); if (m_Controller != null) diff --git a/Editor/Mono/Audio/Mixer/GUI/AudioMixerGroupViewList.cs b/Editor/Mono/Audio/Mixer/GUI/AudioMixerGroupViewList.cs index 4f62c30298..303fe4a939 100644 --- a/Editor/Mono/Audio/Mixer/GUI/AudioMixerGroupViewList.cs +++ b/Editor/Mono/Audio/Mixer/GUI/AudioMixerGroupViewList.cs @@ -38,7 +38,7 @@ public void OnMixerControllerChanged(AudioMixerController controller) RecreateListControl(); } - public void OnUndoRedoPerformed() + public void OnUndoRedoPerformed(in UndoRedoInfo info) { RecreateListControl(); } diff --git a/Editor/Mono/Audio/Mixer/GUI/AudioMixerSnapshotView.cs b/Editor/Mono/Audio/Mixer/GUI/AudioMixerSnapshotView.cs index fad4fc269b..c15493f81e 100644 --- a/Editor/Mono/Audio/Mixer/GUI/AudioMixerSnapshotView.cs +++ b/Editor/Mono/Audio/Mixer/GUI/AudioMixerSnapshotView.cs @@ -322,7 +322,7 @@ static void Delete(object userData) } } - public void OnUndoRedoPerformed() + public void OnUndoRedoPerformed(in UndoRedoInfo info) { LoadFromBackend(); } diff --git a/Editor/Mono/Audio/Mixer/GUI/AudioMixerWindow.cs b/Editor/Mono/Audio/Mixer/GUI/AudioMixerWindow.cs index d5fa9b3a62..199bacfb82 100644 --- a/Editor/Mono/Audio/Mixer/GUI/AudioMixerWindow.cs +++ b/Editor/Mono/Audio/Mixer/GUI/AudioMixerWindow.cs @@ -267,7 +267,7 @@ public void OnEnable() s_Instance = this; - Undo.undoRedoPerformed += UndoRedoPerformed; + Undo.undoRedoEvent += UndoRedoPerformed; EditorApplication.pauseStateChanged += OnPauseStateChanged; EditorApplication.playModeStateChanged += OnPlayModeStateChanged; } @@ -276,7 +276,7 @@ public void OnDisable() { EditorApplication.pauseStateChanged -= OnPauseStateChanged; EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; - Undo.undoRedoPerformed -= UndoRedoPerformed; + Undo.undoRedoEvent -= UndoRedoPerformed; } void OnPauseStateChanged(PauseState state) @@ -314,7 +314,7 @@ void EndRenaming() m_MixersTree.EndRenaming(); } - public void UndoRedoPerformed() + public void UndoRedoPerformed(in UndoRedoInfo info) { if (m_Controller == null) return; @@ -325,16 +325,16 @@ public void UndoRedoPerformed() m_Controller.OnUnitySelectionChanged(); if (m_GroupTree != null) - m_GroupTree.OnUndoRedoPerformed(); + m_GroupTree.OnUndoRedoPerformed(info); if (m_GroupViews != null) - m_GroupViews.OnUndoRedoPerformed(); + m_GroupViews.OnUndoRedoPerformed(info); if (m_SnapshotListView != null) - m_SnapshotListView.OnUndoRedoPerformed(); + m_SnapshotListView.OnUndoRedoPerformed(info); if (m_MixersTree != null) - m_MixersTree.OnUndoRedoPerformed(); + m_MixersTree.OnUndoRedoPerformed(info); AudioMixerUtility.RepaintAudioMixerAndInspectors(); } diff --git a/Editor/Mono/Audio/Mixer/GUI/AudioMixersTreeView.cs b/Editor/Mono/Audio/Mixer/GUI/AudioMixersTreeView.cs index 95826a6727..c76b2cc774 100644 --- a/Editor/Mono/Audio/Mixer/GUI/AudioMixersTreeView.cs +++ b/Editor/Mono/Audio/Mixer/GUI/AudioMixersTreeView.cs @@ -657,7 +657,7 @@ public void EndRenaming() m_TreeView.EndNameEditing(true); } - public void OnUndoRedoPerformed() + public void OnUndoRedoPerformed(in UndoRedoInfo info) { ReloadTree(); } diff --git a/Editor/Mono/BuildPipeline.bindings.cs b/Editor/Mono/BuildPipeline.bindings.cs index d647247697..91304f909a 100644 --- a/Editor/Mono/BuildPipeline.bindings.cs +++ b/Editor/Mono/BuildPipeline.bindings.cs @@ -686,36 +686,6 @@ internal static string GetBuildTargetGroupName(BuildTarget target) [FreeFunction] internal static extern bool SupportsReflectionEmit(BuildTarget target); - internal static string[] GetReferencingPlayerAssembliesForDLL(string dllPath, string assembliesOutputPath) - { - DefaultAssemblyResolver resolverRoot = new DefaultAssemblyResolver(); - resolverRoot.AddSearchDirectory(Path.GetDirectoryName(dllPath)); - AssemblyDefinition assemblyRoot = AssemblyDefinition.ReadAssembly(dllPath, new ReaderParameters { AssemblyResolver = resolverRoot }); - - string[] assemblyPaths = BuildPipeline.GetManagedPlayerDllPaths(assembliesOutputPath); - List referencingAssemblies = new List(); - - // determine whether there is an assembly that is referencing the assembly path - foreach (string assemblyPath in assemblyPaths) - { - DefaultAssemblyResolver resolver = new DefaultAssemblyResolver(); - resolver.AddSearchDirectory(Path.GetDirectoryName(assemblyPath)); - AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(assemblyPath, new ReaderParameters { AssemblyResolver = resolver }); - - foreach (AssemblyNameReference anr in assembly.MainModule.AssemblyReferences) - { - if (anr.FullName == assemblyRoot.Name.FullName) - { - referencingAssemblies.Add(assemblyPath); - } - } - } - - return referencingAssemblies.ToArray(); - } - - internal static extern string[] GetManagedPlayerDllPaths(string assembliesOutputPath); - [RequiredByNativeCode] public static PlayerConnectionInitiateMode GetPlayerConnectionInitiateMode(BuildTarget targetPlatform, BuildOptions buildOptions) { @@ -729,6 +699,10 @@ public static PlayerConnectionInitiateMode GetPlayerConnectionInitiateMode(Build private static bool DoesBuildTargetSupportPlayerConnectionPlayerToEditor(BuildTarget targetPlatform) { return + targetPlatform == BuildTarget.StandaloneOSX || + targetPlatform == BuildTarget.StandaloneWindows || + targetPlatform == BuildTarget.StandaloneWindows64 || + targetPlatform == BuildTarget.StandaloneLinux64 || // 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 24af76cfc5..28518df3e3 100644 --- a/Editor/Mono/BuildPipeline/AssemblyStripper.cs +++ b/Editor/Mono/BuildPipeline/AssemblyStripper.cs @@ -157,7 +157,7 @@ private static bool RunAssemblyLinker(IEnumerable args, out string @out, var argString = args.Aggregate((buff, s) => buff + " " + s); var responseFile = Path.Combine(workingDirectory, "response.rsp"); File.WriteAllText(responseFile, argString); - Console.WriteLine("Invoking UnityLinker with response file. response.rsp contents: " + argString); + UnityLogWriter.WriteStringToUnityLog($"Invoking UnityLinker with response file. response.rsp contents: {argString}\n"); Runner.RunNetCoreProgram(linkerPath, $"@{CommandLineFormatter.PrepareFileName(responseFile)}", workingDirectory, null, null); @out = ""; diff --git a/Editor/Mono/BuildPipeline/BuildUtils.cs b/Editor/Mono/BuildPipeline/BuildUtils.cs index 93b59f1d92..77bd6668e0 100644 --- a/Editor/Mono/BuildPipeline/BuildUtils.cs +++ b/Editor/Mono/BuildPipeline/BuildUtils.cs @@ -107,7 +107,7 @@ private static void RunProgram(Program p, string exe, string args, string workin p.WaitForExit(); stopwatch.Stop(); - Console.WriteLine("{0} exited after {1} ms.", exe, stopwatch.ElapsedMilliseconds); + UnityLogWriter.WriteStringToUnityLog($"{exe} exited after {stopwatch.ElapsedMilliseconds} ms.\n"); var messages = new List(); if (parser != null) diff --git a/Editor/Mono/BuildPipeline/DesktopStandaloneBuildWindowExtension.cs b/Editor/Mono/BuildPipeline/DesktopStandaloneBuildWindowExtension.cs index 62798b8f9f..0f56110de4 100644 --- a/Editor/Mono/BuildPipeline/DesktopStandaloneBuildWindowExtension.cs +++ b/Editor/Mono/BuildPipeline/DesktopStandaloneBuildWindowExtension.cs @@ -18,15 +18,17 @@ internal abstract class DesktopStandaloneBuildWindowExtension : DefaultBuildWind protected bool m_HasMonoPlayers; protected bool m_HasIl2CppPlayers; + protected bool m_HasCoreCLRPlayers; protected bool m_HasServerPlayers; protected bool m_IsRunningOnHostPlatform; - public DesktopStandaloneBuildWindowExtension(bool hasMonoPlayers, bool hasIl2CppPlayers, bool hasServerPlayers) + public DesktopStandaloneBuildWindowExtension(bool hasMonoPlayers, bool hasIl2CppPlayers, bool hasCoreCLRPlayers, bool hasServerPlayers) { SetupStandaloneSubtargets(); m_IsRunningOnHostPlatform = Application.platform == GetHostPlatform(); m_HasIl2CppPlayers = hasIl2CppPlayers; + m_HasCoreCLRPlayers = hasCoreCLRPlayers; m_HasMonoPlayers = hasMonoPlayers; m_HasServerPlayers = hasServerPlayers; } @@ -210,7 +212,7 @@ protected virtual string GetCannotBuildPlayerInCurrentSetupError() if (namedBuildTarget == NamedBuildTarget.Server) { if(!m_HasServerPlayers) - return $"Dedicated Server support for {GetHostPlatformName()} is not installed"; + return $"Dedicated Server support for {GetHostPlatformName()} is not installed."; if (PlayerSettings.GetScriptingBackend(namedBuildTarget) == ScriptingImplementation.IL2CPP && !m_IsRunningOnHostPlatform) return string.Format("{0} IL2CPP player can only be built on {0}.", GetHostPlatformName()); @@ -218,19 +220,36 @@ protected virtual string GetCannotBuildPlayerInCurrentSetupError() return null; } - if (PlayerSettings.GetScriptingBackend(namedBuildTarget) != ScriptingImplementation.IL2CPP) + switch(PlayerSettings.GetScriptingBackend(namedBuildTarget)) { - if (!m_HasMonoPlayers) - return "Currently selected scripting backend (Mono) is not installed."; + case ScriptingImplementation.Mono2x: + { + if (!m_HasMonoPlayers) + return "Currently selected scripting backend (Mono) is not installed."; + break; + } + #pragma warning disable 618 + case ScriptingImplementation.CoreCLR: + { + if (!m_HasCoreCLRPlayers) + return $"Currently selected scripting backend (CoreCLR) is not {(Unsupported.IsSourceBuild() ? "installed" : "supported")}."; + break; + } + case ScriptingImplementation.IL2CPP: + { + if (!m_IsRunningOnHostPlatform) + return string.Format("{0} IL2CPP player can only be built on {0}.", GetHostPlatformName()); + if (!m_HasIl2CppPlayers) + return "Currently selected scripting backend (IL2CPP) is not installed."; + break; + } + default: + { + return $"Unknown scripting backend: {PlayerSettings.GetScriptingBackend(namedBuildTarget)}"; + } } - else - { - if (!m_IsRunningOnHostPlatform) - return string.Format("{0} IL2CPP player can only be built on {0}.", GetHostPlatformName()); - if (!m_HasIl2CppPlayers) - return "Currently selected scripting backend (IL2CPP) is not installed."; // Note: error should match UWP player error message for consistency. - } + return null; } diff --git a/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs b/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs index 4e9887ba76..7f7c6ac294 100644 --- a/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs +++ b/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs @@ -2,7 +2,9 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; using UnityEditor; +using UnityEditor.Build; using UnityEditor.Modules; using UnityEditorInternal; using UnityEngine; @@ -25,11 +27,7 @@ protected virtual string GetVariationName(BuildPostProcessArgs args) } protected bool GetServer(BuildPostProcessArgs args) => - (args.target == BuildTarget.StandaloneWindows || - args.target == BuildTarget.StandaloneWindows64 || - args.target == BuildTarget.StandaloneOSX || - args.target == BuildTarget.StandaloneLinux64) && - (StandaloneBuildSubtarget)args.subtarget == StandaloneBuildSubtarget.Server; + GetNamedBuildTarget(args) == NamedBuildTarget.Server; protected string GetVariationFolder(BuildPostProcessArgs args) => $"{args.playerPackage}/Variations/{GetVariationName(args)}"; @@ -41,7 +39,7 @@ public override void UpdateBootConfig(BuildTarget target, BootConfigData config, config.AddKey("single-instance"); if (!PlayerSettings.useFlipModelSwapchain) config.AddKey("force-d3d11-bitblt-mode"); - if (IL2CPPUtils.UseIl2CppCodegenWithMonoBackend(BuildPipeline.GetBuildTargetGroup(target))) + if (IL2CPPUtils.UseIl2CppCodegenWithMonoBackend(NamedBuildTarget.FromActiveSettings(target))) config.Set("mono-codegen", "il2cpp"); if ((options & BuildOptions.EnableCodeCoverage) != 0) config.Set("enableCodeCoverage", "1"); @@ -56,27 +54,43 @@ public override void LaunchPlayer(BuildLaunchPlayerArgs args) readonly bool m_HasMonoPlayers; readonly bool m_HasIl2CppPlayers; + readonly bool m_HasCoreCLRPlayers; + readonly bool m_HasServerMonoPlayers; + readonly bool m_HasServerIl2CppPlayers; + readonly bool m_HasServerCoreCLRPlayers; - protected DesktopStandalonePostProcessor(bool hasMonoPlayers, bool hasIl2CppPlayers) + protected DesktopStandalonePostProcessor(bool hasMonoPlayers, bool hasIl2CppPlayers, bool hasCoreCLRPlayers, bool hasServerMonoPlayers, bool hasServerIl2CppPlayers, bool hasServerCoreCLRPlayers) { m_HasMonoPlayers = hasMonoPlayers; m_HasIl2CppPlayers = hasIl2CppPlayers; + m_HasCoreCLRPlayers = hasCoreCLRPlayers; + m_HasServerMonoPlayers = hasServerMonoPlayers; + m_HasServerIl2CppPlayers = hasServerIl2CppPlayers; + m_HasServerCoreCLRPlayers = hasServerCoreCLRPlayers; } public override string PrepareForBuild(BuildOptions options, BuildTarget target) { - if (!m_HasMonoPlayers) - { - var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(target); - if (PlayerSettings.GetScriptingBackend(buildTargetGroup) != ScriptingImplementation.IL2CPP) - return "Currently selected scripting backend (Mono) is not installed."; - } + var namedBuildTarget = NamedBuildTarget.FromActiveSettings(target); + var isServer = namedBuildTarget == NamedBuildTarget.Server; - if (!m_HasIl2CppPlayers) + switch (PlayerSettings.GetScriptingBackend(namedBuildTarget)) { - var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(target); - if (PlayerSettings.GetScriptingBackend(buildTargetGroup) == ScriptingImplementation.IL2CPP) - return "Currently selected scripting backend (IL2CPP) is not installed."; + case ScriptingImplementation.Mono2x: + if ((!isServer && !m_HasMonoPlayers) || (isServer && !m_HasServerMonoPlayers)) + return "Currently selected scripting backend (Mono) is not installed."; + break; + case ScriptingImplementation.IL2CPP: + if ((!isServer && !m_HasIl2CppPlayers) || (isServer && !m_HasServerIl2CppPlayers)) + return "Currently selected scripting backend (IL2CPP) is not installed."; + break; + #pragma warning disable 618 + case ScriptingImplementation.CoreCLR: + if ((!isServer && !m_HasCoreCLRPlayers) || (isServer && !m_HasServerCoreCLRPlayers)) + return "Currently selected scripting backend (CoreCLR) is not installed."; + break; + default: + return $"Unknown scripting backend: {PlayerSettings.GetScriptingBackend(namedBuildTarget)}"; } return base.PrepareForBuild(options, target); diff --git a/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs b/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs index e834c5bc91..2f892ed3c0 100644 --- a/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs +++ b/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Text; using System.Runtime.InteropServices; +using NiceIO; using UnityEditor; using UnityEditor.Build; using UnityEditor.Build.Player; @@ -34,6 +35,10 @@ public abstract class Sysroot public abstract string TargetPlatform { get; } public abstract string TargetArch { get; } public abstract IEnumerable GetIl2CppArguments(); + public abstract string GetSysrootPath(); + public abstract string GetToolchainPath(); + public abstract string GetIl2CppCompilerFlags(); + public abstract string GetIl2CppLinkerFlags(); } } @@ -111,6 +116,10 @@ private static bool GetTargetPlatformAndArchFromBuildTarget(BuildTarget target, targetPlatform = "webgl"; targetArch = ""; return true; + case BuildTarget.EmbeddedLinux: + targetPlatform = "embeddedlinux"; + targetArch = ""; + return true; } targetPlatform = null; @@ -212,7 +221,7 @@ public static string HostTargetTuple(BuildTarget buildTarget) host = $"{_hostPlatform}-{_hostArch}"; break; } - string target = $"{targetPlatform}-{targetArch}"; + string target = String.IsNullOrEmpty(targetArch) ? targetPlatform : $"{targetPlatform}-{targetArch}"; return host == target ? target : $"{host}-{target}"; } } @@ -275,21 +284,21 @@ internal static IIl2CppPlatformProvider PlatformProviderForNotModularPlatform(Bu internal static IL2CPPBuilder RunIl2Cpp(string tempFolder, string stagingAreaData, IIl2CppPlatformProvider platformProvider, Action modifyOutputBeforeCompile, RuntimeClassRegistry runtimeClassRegistry) { - var builder = new IL2CPPBuilder(tempFolder, stagingAreaData, platformProvider, modifyOutputBeforeCompile, runtimeClassRegistry, IL2CPPUtils.UseIl2CppCodegenWithMonoBackend(BuildPipeline.GetBuildTargetGroup(platformProvider.target))); + var builder = new IL2CPPBuilder(tempFolder, stagingAreaData, platformProvider, modifyOutputBeforeCompile, runtimeClassRegistry, IL2CPPUtils.UseIl2CppCodegenWithMonoBackend(platformProvider.namedBuildTarget)); builder.Run(); return builder; } internal static IL2CPPBuilder RunIl2Cpp(string stagingAreaData, IIl2CppPlatformProvider platformProvider, Action modifyOutputBeforeCompile, RuntimeClassRegistry runtimeClassRegistry) { - var builder = new IL2CPPBuilder(stagingAreaData, stagingAreaData, platformProvider, modifyOutputBeforeCompile, runtimeClassRegistry, IL2CPPUtils.UseIl2CppCodegenWithMonoBackend(BuildPipeline.GetBuildTargetGroup(platformProvider.target))); + var builder = new IL2CPPBuilder(stagingAreaData, stagingAreaData, platformProvider, modifyOutputBeforeCompile, runtimeClassRegistry, IL2CPPUtils.UseIl2CppCodegenWithMonoBackend(platformProvider.namedBuildTarget)); builder.Run(); return builder; } internal static IL2CPPBuilder RunCompileAndLink(string tempFolder, string stagingAreaData, IIl2CppPlatformProvider platformProvider, Action modifyOutputBeforeCompile, RuntimeClassRegistry runtimeClassRegistry, string il2cppBuildCacheSource) { - var builder = new IL2CPPBuilder(tempFolder, stagingAreaData, platformProvider, modifyOutputBeforeCompile, runtimeClassRegistry, IL2CPPUtils.UseIl2CppCodegenWithMonoBackend(BuildPipeline.GetBuildTargetGroup(platformProvider.target))); + var builder = new IL2CPPBuilder(tempFolder, stagingAreaData, platformProvider, modifyOutputBeforeCompile, runtimeClassRegistry, IL2CPPUtils.UseIl2CppCodegenWithMonoBackend(platformProvider.namedBuildTarget)); builder.RunCompileAndLink(il2cppBuildCacheSource); return builder; } @@ -338,18 +347,18 @@ internal static string ApiCompatibilityLevelToDotNetProfileArgument(ApiCompatibi } } - internal static bool UseIl2CppCodegenWithMonoBackend(BuildTargetGroup targetGroup) + internal static bool UseIl2CppCodegenWithMonoBackend(NamedBuildTarget namedBuildTarget) { return EditorApplication.useLibmonoBackendForIl2cpp && - PlayerSettings.GetScriptingBackend(targetGroup) == ScriptingImplementation.IL2CPP; + PlayerSettings.GetScriptingBackend(namedBuildTarget) == ScriptingImplementation.IL2CPP; } - internal static bool EnableIL2CPPDebugger(IIl2CppPlatformProvider provider, BuildTargetGroup targetGroup) + internal static bool EnableIL2CPPDebugger(IIl2CppPlatformProvider provider) { if (!provider.allowDebugging || !provider.development) return false; - switch (PlayerSettings.GetApiCompatibilityLevel(targetGroup)) + switch (PlayerSettings.GetApiCompatibilityLevel(provider.namedBuildTarget)) { case ApiCompatibilityLevel.NET_Unity_4_8: case ApiCompatibilityLevel.NET_Standard: @@ -360,10 +369,9 @@ internal static bool EnableIL2CPPDebugger(IIl2CppPlatformProvider provider, Buil } } - internal static string[] GetBuilderDefinedDefines(IIl2CppPlatformProvider il2cppPlatformProvider, BuildTargetGroup buildTargetGroup) + internal static string[] GetBuilderDefinedDefines(BuildTarget target, ApiCompatibilityLevel apiCompatibilityLevel, bool enableIl2CppDebugger) { List defines = new List(); - var apiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(buildTargetGroup); switch (apiCompatibilityLevel) { @@ -381,8 +389,6 @@ internal static string[] GetBuilderDefinedDefines(IIl2CppPlatformProvider il2cpp throw new InvalidOperationException($"IL2CPP doesn't support building with {apiCompatibilityLevel} API compatibility level!"); } - - var target = il2cppPlatformProvider.target; if (target == BuildTarget.StandaloneWindows || target == BuildTarget.StandaloneWindows64 || target == BuildTarget.XboxOne || target == BuildTarget.WSAPlayer) { @@ -398,7 +404,7 @@ internal static string[] GetBuilderDefinedDefines(IIl2CppPlatformProvider il2cpp } } - if (EnableIL2CPPDebugger(il2cppPlatformProvider, buildTargetGroup)) + if (enableIl2CppDebugger) defines.Add("IL2CPP_MONO_DEBUGGER=1"); if (BuildPipeline.IsFeatureSupported("ENABLE_SCRIPTING_GC_WBARRIERS", target)) @@ -424,21 +430,21 @@ internal static string[] GetBuilderDefinedDefines(IIl2CppPlatformProvider il2cpp return defines.ToArray(); } - internal static string[] GetDebuggerIL2CPPArguments(IIl2CppPlatformProvider il2cppPlatformProvider, BuildTargetGroup buildTargetGroup) + internal static string[] GetDebuggerIL2CPPArguments(IIl2CppPlatformProvider il2cppPlatformProvider) { var arguments = new List(); - if (EnableIL2CPPDebugger(il2cppPlatformProvider, buildTargetGroup)) + if (EnableIL2CPPDebugger(il2cppPlatformProvider)) arguments.Add("--enable-debugger"); return arguments.ToArray(); } - internal static string[] GetBuildingIL2CPPArguments(IIl2CppPlatformProvider il2cppPlatformProvider, BuildTargetGroup buildTargetGroup) + internal static string[] GetBuildingIL2CPPArguments(IIl2CppPlatformProvider il2cppPlatformProvider) { // When changing this function, don't forget to change GetBuilderDefinedDefines! var arguments = new List(); - var apiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(buildTargetGroup); + var apiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(il2cppPlatformProvider.namedBuildTarget); if (BuildPipeline.IsFeatureSupported("ENABLE_SCRIPTING_GC_WBARRIERS", il2cppPlatformProvider.target)) { @@ -466,43 +472,114 @@ internal static string[] GetBuildingIL2CPPArguments(IIl2CppPlatformProvider il2c } internal static string GetIl2CppFolder() + { + return GetIl2CppFolder(out var _); + } + + internal static bool UsingDevelopmentBuild() + { + GetIl2CppFolder(out var isDevelopmentLocation); + return isDevelopmentLocation; + } + + static string GetIl2CppFolder(out bool isDevelopmentLocation) { var pathOverride = System.Environment.GetEnvironmentVariable("UNITY_IL2CPP_PATH"); if (!string.IsNullOrEmpty(pathOverride)) + { + isDevelopmentLocation = true; return pathOverride; + } pathOverride = Debug.GetDiagnosticSwitch("VMIl2CppPath").value as string; if (!string.IsNullOrEmpty(pathOverride)) + { + isDevelopmentLocation = true; return pathOverride; + } + isDevelopmentLocation = false; return Path.GetFullPath(Path.Combine( EditorApplication.applicationContentsPath, "il2cpp")); } - internal static string GetIl2CppBeeSettingsFolder() + static string GetBCLExtensionsFolder() { - return $"{GetIl2CppFolder()}/build/BeeSettings/offline"; - } + var il2CppFolder = GetIl2CppFolder(out var isDevelopmentLocation); + if (isDevelopmentLocation) + return Path.Combine(il2CppFolder, "build", "tests", "BCLExtensions", "net471"); - internal static string GetExePath(string executableFileName) - { - var platform = Application.platform; - var deployDirectory = $"{IL2CPPUtils.GetIl2CppFolder()}/build/deploy"; - var il2cppPath = $"{deployDirectory}/{executableFileName}{(platform == RuntimePlatform.WindowsEditor ? ".exe" : "")}"; - if (!File.Exists(il2cppPath)) - il2cppPath = $"{deployDirectory}/{ExpectedTargetFrameworkDirectoryNameWhenUsingCustomBuild(platform)}/{BinaryDirectoryForPlatform(platform)}/{executableFileName}{(platform == RuntimePlatform.WindowsEditor ? ".exe" : "")}"; - return il2cppPath; + return Paths.Combine(il2CppFolder, "BCLExtensions"); } - internal static string GetTundraFolder() + internal static IEnumerable GetBCLExtensionLibraries() { - return $"{GetIl2CppFolder()}/external/bee/tundra"; + var bclExtensionsFolder = GetBCLExtensionsFolder(); + return new string[] + { + Path.Combine(bclExtensionsFolder, "System.Runtime.WindowsRuntime.dll"), + Path.Combine(bclExtensionsFolder, "System.Runtime.WindowsRuntime.UI.Xaml.dll"), + }; } - internal static string GetReapiCacheClientFolder() + internal static string GetExePath(string toolName) { - return $"{GetIl2CppFolder()}/external/bee/reapi-cache-client"; + var platform = Application.platform; + var il2CppFolder = GetIl2CppFolder(out var isDevelopmentLocation); + var expectedToolExecutableName = $"{toolName}{(platform == RuntimePlatform.WindowsEditor ? ".exe" : "")}"; + + if (isDevelopmentLocation) + { + // Locating the correct development build to use is a little tricky. Complications come from + // 1) We don't know if the Debug or Release build is desired. To overcome this we will pick whichever was modified most recently + // 2) We don't know if the published or non-published build is desired. Again, we'll use whichever was modified most recently + // 3) Published builds for all platforms may or may not be built. We need to make sure not to pick a build for a different platform + + // Note that this logic will intentionally avoid checking for an expected TFM. This is a dev build. Using w/e is newest is probably + // the most robust and maintainable approach. + + var toolBinDirectory = Path.Combine(il2CppFolder, toolName, "bin").ToNPath(); + var candidates = toolBinDirectory.Files($"*{expectedToolExecutableName}", recurse: true) + .OrderByDescending(f => f.GetLastWriteTimeUtc()) + .ToArray(); + + if (candidates.Length == 0) + throw new InvalidOperationException($"{toolName} does not appear to be built in {il2CppFolder}"); + + var expectedPublishDirectoryName = BinaryDirectoryForPlatform(platform).ToNPath(); + + foreach (var candidate in candidates) + { + // Examples : + // 1) il2cpp/bin/Release//il2cpp.exe + // 2) il2cpp/bin/Debug//il2cpp.exe + if (candidate.Parent.Parent.Parent.FileName == "bin") + { + // Found a non-published build + return candidate.ToString(); + } + + // Examples : + // 1) il2cpp/bin/Release///publish/il2cpp.exe + // 2) il2cpp/bin/Debug///publish/il2cpp.exe + if (candidate.Parent.FileName == "publish" && candidate.Parent.Parent.FileName == expectedPublishDirectoryName) + { + // found a published build + return candidate.ToString(); + } + + // There is a 3rd path structure that we will ignore + // Examples : + // 1) il2cpp/bin/Release///il2cpp.exe + // 2) il2cpp/bin/Debug///il2cpp.exe + } + + throw new InvalidOperationException($"Could not determine which of the {candidates.Length} of {toolName} to use. The expected TFM or expected directory layout may have changed and this logic may need to be updated"); + } + + var deployDirectory = $"{il2CppFolder}/build/deploy"; + return $"{deployDirectory}/{expectedToolExecutableName}"; } internal static string GetAdditionalArguments() @@ -539,14 +616,6 @@ private static string BinaryDirectoryForPlatform(RuntimePlatform platform) return "osx-arm64"; return "osx-x64"; } - - private static string ExpectedTargetFrameworkDirectoryNameWhenUsingCustomBuild(RuntimePlatform platform) - { - var arch = RuntimeInformation.ProcessArchitecture.ToString().ToLower(); - if (platform == RuntimePlatform.OSXEditor && arch == "arm64") - return "net6.0"; - return "net5.0"; - } } internal class IL2CPPBuilder @@ -611,9 +680,7 @@ public void Run() // Make all assemblies in Staging/Managed writable for stripping. ClearReadOnlyFlagOnAllFilesNonRecursively(managedDir); - var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(m_PlatformProvider.target); - - var managedStrippingLevel = PlayerSettings.GetManagedStrippingLevel(buildTargetGroup); + var managedStrippingLevel = PlayerSettings.GetManagedStrippingLevel(m_PlatformProvider.namedBuildTarget); // IL2CPP does not support a managed stripping level of disabled. If the player settings // do try this (which should not be possible from the editor), use Low instead. @@ -667,23 +734,22 @@ public void RunCompileAndLink(string il2cppBuildCacheSource) Directory.CreateDirectory(buildCacheDirectory); var buildCacheNativeOutputFile = Path.Combine(GetNativeOutputRelativeDirectory(buildCacheDirectory), m_PlatformProvider.nativeLibraryFileName); - var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(m_PlatformProvider.target); - var compilerConfiguration = PlayerSettings.GetIl2CppCompilerConfiguration(buildTargetGroup); + var compilerConfiguration = PlayerSettings.GetIl2CppCompilerConfiguration(m_PlatformProvider.namedBuildTarget); var arguments = Il2CppNativeCodeBuilderUtils.AddBuilderArguments(il2CppNativeCodeBuilder, buildCacheNativeOutputFile, m_PlatformProvider.includePaths, m_PlatformProvider.libraryPaths, compilerConfiguration).ToList(); var additionalArgs = IL2CPPUtils.GetAdditionalArguments(); if (!string.IsNullOrEmpty(additionalArgs)) arguments.Add(additionalArgs); - foreach (var buildingArgument in IL2CPPUtils.GetBuildingIL2CPPArguments(m_PlatformProvider, buildTargetGroup)) + foreach (var buildingArgument in IL2CPPUtils.GetBuildingIL2CPPArguments(m_PlatformProvider)) { if (!arguments.Contains(buildingArgument)) arguments.Add(buildingArgument); } arguments.Add($"--generatedcppdir={CommandLineFormatter.PrepareFileName(GetCppOutputDirectory(il2cppBuildCacheSource))}"); - arguments.Add($"--dotnetprofile=\"{IL2CPPUtils.ApiCompatibilityLevelToDotNetProfileArgument(PlayerSettings.GetApiCompatibilityLevel(buildTargetGroup), m_PlatformProvider.target)}\""); - arguments.AddRange(IL2CPPUtils.GetDebuggerIL2CPPArguments(m_PlatformProvider, buildTargetGroup)); + arguments.Add($"--dotnetprofile=\"{IL2CPPUtils.ApiCompatibilityLevelToDotNetProfileArgument(PlayerSettings.GetApiCompatibilityLevel(m_PlatformProvider.namedBuildTarget), m_PlatformProvider.target)}\""); + arguments.AddRange(IL2CPPUtils.GetDebuggerIL2CPPArguments(m_PlatformProvider)); Action setupStartInfo = il2CppNativeCodeBuilder.SetupStartInfo; RunIl2CppWithArguments(arguments, setupStartInfo); @@ -729,12 +795,10 @@ private void ConvertPlayerDlltoCpp(Il2CppBuildPipelineData data) if (m_BuildForMonoRuntime) arguments.Add("--mono-runtime"); - var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(m_PlatformProvider.target); - var apiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(buildTargetGroup); + var apiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(m_PlatformProvider.namedBuildTarget); arguments.Add(string.Format("--dotnetprofile=\"{0}\"", IL2CPPUtils.ApiCompatibilityLevelToDotNetProfileArgument(apiCompatibilityLevel, m_PlatformProvider.target))); - var namedBuildTarget = NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup); - var il2cppCodeGeneration = PlayerSettings.GetIl2CppCodeGeneration(namedBuildTarget); + var il2cppCodeGeneration = PlayerSettings.GetIl2CppCodeGeneration(m_PlatformProvider.namedBuildTarget); if (il2cppCodeGeneration == Il2CppCodeGeneration.OptimizeSize) arguments.Add("--generics-option=EnableFullSharing"); @@ -742,7 +806,7 @@ private void ConvertPlayerDlltoCpp(Il2CppBuildPipelineData data) if (il2CppNativeCodeBuilder != null) { var buildCacheNativeOutputFile = Path.Combine(GetNativeOutputRelativeDirectory(m_PlatformProvider.il2cppBuildCacheDirectory), m_PlatformProvider.nativeLibraryFileName); - var compilerConfiguration = PlayerSettings.GetIl2CppCompilerConfiguration(buildTargetGroup); + var compilerConfiguration = PlayerSettings.GetIl2CppCompilerConfiguration(m_PlatformProvider.namedBuildTarget); Il2CppNativeCodeBuilderUtils.ClearAndPrepareCacheDirectory(il2CppNativeCodeBuilder); arguments.AddRange(Il2CppNativeCodeBuilderUtils.AddBuilderArguments(il2CppNativeCodeBuilder, buildCacheNativeOutputFile, m_PlatformProvider.includePaths, m_PlatformProvider.libraryPaths, compilerConfiguration)); } @@ -755,8 +819,8 @@ private void ConvertPlayerDlltoCpp(Il2CppBuildPipelineData data) foreach (var additionalCppFile in Directory.GetFiles(GetAdditionalCppFilesDirectory(m_PlatformProvider.il2cppBuildCacheDirectory))) arguments.Add($"--additional-cpp={CommandLineFormatter.PrepareFileName(GetShortPathName(Path.GetFullPath(additionalCppFile)))}"); - arguments.AddRange(IL2CPPUtils.GetDebuggerIL2CPPArguments(m_PlatformProvider, buildTargetGroup)); - foreach (var buildingArgument in IL2CPPUtils.GetBuildingIL2CPPArguments(m_PlatformProvider, buildTargetGroup)) + arguments.AddRange(IL2CPPUtils.GetDebuggerIL2CPPArguments(m_PlatformProvider)); + foreach (var buildingArgument in IL2CPPUtils.GetBuildingIL2CPPArguments(m_PlatformProvider)) { if (!arguments.Contains(buildingArgument)) arguments.Add(buildingArgument); @@ -818,7 +882,7 @@ private void RunIl2CppWithArguments(List arguments, Action current + arg + " "); - Console.WriteLine("Invoking il2cpp with arguments: " + args); + UnityLogWriter.WriteStringToUnityLog($"Invoking il2cpp with arguments: {args}\n"); Runner.RunNetCoreProgram(GetIl2CppExe(), args, m_PlatformProvider.il2cppBuildCacheDirectory, il2cppOutputParser, setupStartInfo); // Copy IL2CPP outputs to StagingArea @@ -845,37 +909,12 @@ public static string GetIl2CppExe() { return IL2CPPUtils.GetExePath("il2cpp"); } - - private string GetIl2CppTundraExe() - { - if (Application.platform == RuntimePlatform.OSXEditor) - return $"{IL2CPPUtils.GetTundraFolder()}/tundra-mac-x64/tundra2"; - if (Application.platform == RuntimePlatform.LinuxEditor) - return $"{IL2CPPUtils.GetTundraFolder()}/tundra-linux-x64/tundra2"; - - return $"{IL2CPPUtils.GetTundraFolder()}/tundra-win-x64/tundra2.exe"; - } - - private string GetIl2CppReapiCacheClientExe() - { - if (Application.platform == RuntimePlatform.OSXEditor) - return $"{IL2CPPUtils.GetReapiCacheClientFolder()}/tundra-mac-x64/tundra2"; - if (Application.platform == RuntimePlatform.LinuxEditor) - return $"{IL2CPPUtils.GetReapiCacheClientFolder()}/tundra-linux-x64/tundra2"; - - return $"{IL2CPPUtils.GetReapiCacheClientFolder()}/tundra-win-x64/tundra2.exe"; - } - - private string GetMonoBleedingEdgeExe() - { - var path = Path.Combine(MonoInstallationFinder.GetMonoInstallation("MonoBleedingEdge"), "bin"); - return Path.Combine(path, "mono"); - } } internal interface IIl2CppPlatformProvider { BuildTarget target { get; } + NamedBuildTarget namedBuildTarget { get; } bool emitNullChecks { get; } bool enableStackTraces { get; } bool enableArrayBoundsCheck { get; } @@ -907,6 +946,7 @@ public BaseIl2CppPlatformProvider(BuildTarget target, string libraryFolder, Buil string baselibLibraryDirectory) { this.target = target; + this.namedBuildTarget = NamedBuildTarget.FromActiveSettings(target); this.libraryFolder = libraryFolder; this.buildReport = buildReport; _baselibLibraryDirectory = baselibLibraryDirectory; @@ -914,6 +954,8 @@ public BaseIl2CppPlatformProvider(BuildTarget target, string libraryFolder, Buil public virtual BuildTarget target { get; private set; } + public virtual NamedBuildTarget namedBuildTarget { get; private set; } + public virtual string libraryFolder { get; private set; } public virtual bool emitNullChecks diff --git a/Editor/Mono/BuildPipeline/NamedBuildTarget.cs b/Editor/Mono/BuildPipeline/NamedBuildTarget.cs index 82dfeec30a..6130437d89 100644 --- a/Editor/Mono/BuildPipeline/NamedBuildTarget.cs +++ b/Editor/Mono/BuildPipeline/NamedBuildTarget.cs @@ -118,6 +118,22 @@ public static NamedBuildTarget FromBuildTargetGroup(BuildTargetGroup buildTarget throw new ArgumentException($"There is no a valid NamedBuildTarget for BuildTargetGroup '{buildTargetGroup}'"); } + // TODO: We shouldn't be assuming that the namedBuildTarget can be extracted from the + // active settings. This should be passed through the callstack instead when building. + // We will need to use BuildTargetSelection (BuildTarget + Subtarget) that is in the cpp side. + // For now this fixes an issue where Dedicated Server compiles with the Standalone settings. + internal static NamedBuildTarget FromActiveSettings(BuildTarget target) + { + var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(target); + + if (buildTargetGroup == BuildTargetGroup.Standalone && (StandaloneBuildSubtarget)EditorUserBuildSettings.GetActiveSubtargetFor(target) == StandaloneBuildSubtarget.Server) + { + return NamedBuildTarget.Server; + } + + return NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup); + } + public static bool operator==(NamedBuildTarget lhs, NamedBuildTarget rhs) { return lhs.Equals(rhs); diff --git a/Editor/Mono/BuildPipeline/RuntimeClassMetadata.cs b/Editor/Mono/BuildPipeline/RuntimeClassMetadata.cs index 965b55325b..08b7c66406 100644 --- a/Editor/Mono/BuildPipeline/RuntimeClassMetadata.cs +++ b/Editor/Mono/BuildPipeline/RuntimeClassMetadata.cs @@ -5,8 +5,10 @@ using System.Collections.Generic; using System; using System.Linq; +using System.Runtime.InteropServices; using UnityEditor.Compilation; using UnityEditor.Scripting.ScriptCompilation; +using UnityEngine.Scripting; namespace UnityEditor { @@ -17,11 +19,11 @@ namespace UnityEditor internal class RuntimeClassRegistry { protected Dictionary> serializedClassesPerAssembly = new Dictionary>(); - protected Dictionary m_UsedTypesPerUserAssembly = new Dictionary(); + protected Dictionary> m_UsedTypesPerUserAssembly = new Dictionary>(); protected Dictionary> classScenes = new Dictionary>(); protected UnityType objectUnityType = null; - public Dictionary UsedTypePerUserAssembly + public Dictionary> UsedTypePerUserAssembly { get { return m_UsedTypesPerUserAssembly; } } @@ -49,7 +51,21 @@ public void AddNativeClassID(int ID) public void SetUsedTypesInUserAssembly(string[] typeNames, string assemblyName) { - m_UsedTypesPerUserAssembly[assemblyName] = typeNames; + if (!m_UsedTypesPerUserAssembly.TryGetValue(assemblyName, out HashSet types)) + m_UsedTypesPerUserAssembly[assemblyName] = types = new HashSet(); + + foreach (var typeName in typeNames) + types.Add(typeName); + } + + [RequiredByNativeCode] + public void SetSerializedTypesInUserAssembly(string[] typeNames, string assemblyName) + { + if (!serializedClassesPerAssembly.TryGetValue(assemblyName, out HashSet types)) + serializedClassesPerAssembly[assemblyName] = types = new HashSet(); + + foreach (var typeName in typeNames) + types.Add(typeName); } public bool IsDLLUsed(string dll) @@ -68,6 +84,20 @@ public bool IsDLLUsed(string dll) return m_UsedTypesPerUserAssembly.ContainsKey(dll); } + protected void AddUsedClass(string assemblyName, string className) + { + if (string.IsNullOrEmpty(assemblyName)) + throw new ArgumentException(nameof(assemblyName)); + + if (string.IsNullOrEmpty(className)) + throw new ArgumentException(nameof(className)); + + if (!m_UsedTypesPerUserAssembly.TryGetValue(assemblyName, out HashSet types)) + m_UsedTypesPerUserAssembly[assemblyName] = types = new HashSet(); + + types.Add(className); + } + protected void AddSerializedClass(string assemblyName, string className) { if (string.IsNullOrEmpty(assemblyName)) @@ -115,7 +145,7 @@ public Dictionary GetAllManagedTypesInScenes() items.Add("UnityEngine.dll", engineModuleTypes.ToArray()); foreach (var userAssembly in m_UsedTypesPerUserAssembly) - items.Add(userAssembly.Key, userAssembly.Value); + items.Add(userAssembly.Key, userAssembly.Value.ToArray()); return items; } @@ -133,6 +163,24 @@ public IEnumerable> GetAllSerializedClassesAsStri } } + [RequiredByNativeCode] + public MethodDescription[] GetAllMethodsToPreserve() + { + return m_MethodsToPreserve.ToArray(); + } + + [RequiredByNativeCode] + public string[] GetAllSerializedClassesAssemblies() + { + return serializedClassesPerAssembly.Keys.ToArray(); + } + + [RequiredByNativeCode] + public string[] GetAllSerializedClassesForAssembly(string assembly) + { + return serializedClassesPerAssembly[assembly].ToArray(); + } + public static RuntimeClassRegistry Create() { return new RuntimeClassRegistry(); @@ -155,6 +203,8 @@ public void SetSceneClasses(int[] nativeClassIDs, string scene) } } + // Needs to stay in sync with MethodDescription in Editor/Src/BuildPipeline/BuildSerialization.h + [StructLayout(LayoutKind.Sequential)] internal class MethodDescription { public string assembly; @@ -165,12 +215,24 @@ internal class MethodDescription internal List m_MethodsToPreserve = new List(); //invoked by native code - internal void AddMethodToPreserve(string assembly, string @namespace, string klassName, string methodName) + [RequiredByNativeCode] + public void AddMethodToPreserve(string assembly, string ns, string klassName, string methodName) + { + m_MethodsToPreserve.Add(new MethodDescription() + { + assembly = assembly, + fullTypeName = ns + (ns.Length > 0 ? "." : "") + klassName, + methodName = methodName + }); + } + + [RequiredByNativeCode] + public void AddMethodToPreserveWithFullTypeName(string assembly, string fullTypeName, string methodName) { m_MethodsToPreserve.Add(new MethodDescription() { assembly = assembly, - fullTypeName = @namespace + (@namespace.Length > 0 ? "." : "") + klassName, + fullTypeName = fullTypeName, methodName = methodName }); } diff --git a/Editor/Mono/BuildPipeline/UnityLinker/UnityLinkerArgumentValueProvider.cs b/Editor/Mono/BuildPipeline/UnityLinker/UnityLinkerArgumentValueProvider.cs index b86479ee5b..80a98e4c92 100644 --- a/Editor/Mono/BuildPipeline/UnityLinker/UnityLinkerArgumentValueProvider.cs +++ b/Editor/Mono/BuildPipeline/UnityLinker/UnityLinkerArgumentValueProvider.cs @@ -20,7 +20,7 @@ public string Runtime { get { - var backend = PlayerSettings.GetScriptingBackend(m_RunInformation.buildTargetGroup); + var backend = PlayerSettings.GetScriptingBackend(m_RunInformation.namedBuildTarget); switch (backend) { case ScriptingImplementation.IL2CPP: @@ -33,7 +33,7 @@ public string Runtime } } - public string Profile => IL2CPPUtils.ApiCompatibilityLevelToDotNetProfileArgument(PlayerSettings.GetApiCompatibilityLevel(m_RunInformation.buildTargetGroup), m_RunInformation.target); + public string Profile => IL2CPPUtils.ApiCompatibilityLevelToDotNetProfileArgument(PlayerSettings.GetApiCompatibilityLevel(m_RunInformation.namedBuildTarget), m_RunInformation.target); public string RuleSet { diff --git a/Editor/Mono/BuildPipeline/UnityLinker/UnityLinkerRunInformation.cs b/Editor/Mono/BuildPipeline/UnityLinker/UnityLinkerRunInformation.cs index 0a5fdf6aab..998255a8d1 100644 --- a/Editor/Mono/BuildPipeline/UnityLinker/UnityLinkerRunInformation.cs +++ b/Editor/Mono/BuildPipeline/UnityLinker/UnityLinkerRunInformation.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using UnityEditor; +using UnityEditor.Build; using UnityEditor.Build.Reporting; using UnityEditor.UnityLinker; @@ -17,6 +18,7 @@ class UnityLinkerRunInformation public readonly string managedAssemblyFolderPath; public readonly BuildTarget target; public readonly BuildTargetGroup buildTargetGroup; + public readonly NamedBuildTarget namedBuildTarget; public readonly BaseUnityLinkerPlatformProvider platformProvider; public readonly RuntimeClassRegistry rcr; public readonly ManagedStrippingLevel managedStrippingLevel; @@ -43,8 +45,9 @@ public UnityLinkerRunInformation(string managedAssemblyFolderPath, pipelineData = new UnityLinkerBuildPipelineData(target, managedAssemblyFolderPath); buildTargetGroup = BuildPipeline.GetBuildTargetGroup(buildTarget); + namedBuildTarget = NamedBuildTarget.FromActiveSettings(buildTarget); argumentProvider = new UnityLinkerArgumentValueProvider(this); - isMonoBackend = PlayerSettings.GetScriptingBackend(buildTargetGroup) == ScriptingImplementation.Mono2x; + isMonoBackend = PlayerSettings.GetScriptingBackend(namedBuildTarget) == ScriptingImplementation.Mono2x; engineStrippingSupported = (platformProvider?.supportsEngineStripping ?? false) && !isMonoBackend; performEngineStripping = rcr != null && PlayerSettings.stripEngineCode && engineStrippingSupported; } diff --git a/Editor/Mono/BuildPlayerWindow.cs b/Editor/Mono/BuildPlayerWindow.cs index fda2e95fef..c560f60427 100644 --- a/Editor/Mono/BuildPlayerWindow.cs +++ b/Editor/Mono/BuildPlayerWindow.cs @@ -28,7 +28,8 @@ public partial class BuildPlayerWindow : EditorWindow class Styles { public GUIContent invalidColorSpaceMessage = EditorGUIUtility.TrTextContent("In order to build a player, go to 'Player Settings...' to resolve the incompatibility between the Color Space and the current settings.", EditorGUIUtility.GetHelpIcon(MessageType.Warning)); - public GUIContent invalidLightmapEncodingMessage = EditorGUIUtility.TrTextContent("In order to build a player, go to 'Player Settings...' to resolve the incompatibility between the selected Lightmap Encoding and the current settings.", EditorGUIUtility.GetHelpIcon(MessageType.Warning)); + public GUIContent invalidLightmapEncodingMessage = EditorGUIUtility.TrTextContent("In order to build a player, go to 'Player Settings...' to resolve the incompatibility between the Lightmap Encoding value you have selected and the current settings.", EditorGUIUtility.GetHelpIcon(MessageType.Warning)); + public GUIContent invalidHDRCubemapEncodingMessage = EditorGUIUtility.TrTextContent("In order to build a player, go to 'Player Settings...' to resolve the incompatibility between the HDR Cubemap Encoding value you have selected and the current settings.", EditorGUIUtility.GetHelpIcon(MessageType.Warning)); public GUIContent invalidVirtualTexturingSettingMessage = EditorGUIUtility.TrTextContent("Cannot build player because Virtual Texturing is enabled, but the target platform or graphics API does not support Virtual Texturing. Go to Player Settings to resolve the incompatibility.", EditorGUIUtility.GetHelpIcon(MessageType.Warning)); public GUIContent compilingMessage = EditorGUIUtility.TrTextContent("Cannot build player while editor is importing assets or compiling scripts.", EditorGUIUtility.GetHelpIcon(MessageType.Warning)); public GUIStyle title = EditorStyles.boldLabel; @@ -71,6 +72,7 @@ public GUIContent GetDownloadErrorForTarget(BuildTarget target) public GUIContent buildWithDeepProfilerDisabled = EditorGUIUtility.TrTextContent("Deep Profiling", "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"); @@ -587,34 +589,42 @@ static bool IsColorSpaceValid(BuildPlatform platform) } } + static bool IsHDRCubemapEncodingValid(BuildPlatform platform) + { + var encoding = PlayerSettings.GetHDRCubemapEncodingQualityForPlatformGroup(platform.namedBuildTarget.ToBuildTargetGroup()); + return IsGITextureEncodingValid(platform, encoding == HDRCubemapEncodingQuality.Low); + } + static bool IsLightmapEncodingValid(BuildPlatform platform) { - if (PlayerSettings.GetLightmapEncodingQualityForPlatformGroup(platform.namedBuildTarget.ToBuildTargetGroup()) != LightmapEncodingQuality.Low) - { - var hasMinGraphicsAPI = true; + var encoding = PlayerSettings.GetLightmapEncodingQualityForPlatformGroup(platform.namedBuildTarget.ToBuildTargetGroup()); + return IsGITextureEncodingValid(platform, encoding == LightmapEncodingQuality.Low); + } - if (platform.namedBuildTarget == NamedBuildTarget.iOS) - { - var apis = PlayerSettings.GetGraphicsAPIs(BuildTarget.iOS); - hasMinGraphicsAPI = apis.Contains(GraphicsDeviceType.Metal) && !apis.Contains(GraphicsDeviceType.OpenGLES3) && !apis.Contains(GraphicsDeviceType.OpenGLES2); - } - else if (platform.namedBuildTarget == NamedBuildTarget.tvOS) - { - var apis = PlayerSettings.GetGraphicsAPIs(BuildTarget.tvOS); - hasMinGraphicsAPI = apis.Contains(GraphicsDeviceType.Metal) && !apis.Contains(GraphicsDeviceType.OpenGLES3) && !apis.Contains(GraphicsDeviceType.OpenGLES2); - } - else if (platform.namedBuildTarget == NamedBuildTarget.Android) - { - var apis = PlayerSettings.GetGraphicsAPIs(BuildTarget.Android); - hasMinGraphicsAPI = (apis.Contains(GraphicsDeviceType.Vulkan) || apis.Contains(GraphicsDeviceType.OpenGLES3)) && !apis.Contains(GraphicsDeviceType.OpenGLES2); - } + static bool IsGITextureEncodingValid(BuildPlatform platform, bool isLowQuality) + { + if (isLowQuality) + return true; - return hasMinGraphicsAPI; + var hasMinGraphicsAPI = true; + + if (platform.namedBuildTarget == NamedBuildTarget.iOS) + { + var apis = PlayerSettings.GetGraphicsAPIs(BuildTarget.iOS); + hasMinGraphicsAPI = apis.Contains(GraphicsDeviceType.Metal) && !apis.Contains(GraphicsDeviceType.OpenGLES3) && !apis.Contains(GraphicsDeviceType.OpenGLES2); } - else + else if (platform.namedBuildTarget == NamedBuildTarget.tvOS) { - return true; + var apis = PlayerSettings.GetGraphicsAPIs(BuildTarget.tvOS); + hasMinGraphicsAPI = apis.Contains(GraphicsDeviceType.Metal) && !apis.Contains(GraphicsDeviceType.OpenGLES3) && !apis.Contains(GraphicsDeviceType.OpenGLES2); + } + else if (platform.namedBuildTarget == NamedBuildTarget.Android) + { + var apis = PlayerSettings.GetGraphicsAPIs(BuildTarget.Android); + hasMinGraphicsAPI = (apis.Contains(GraphicsDeviceType.Vulkan) || apis.Contains(GraphicsDeviceType.OpenGLES3)) && !apis.Contains(GraphicsDeviceType.OpenGLES2); } + + return hasMinGraphicsAPI; } static bool IsVirtualTexturingSettingsValid(BuildPlatform platform) @@ -929,11 +939,17 @@ void ShowBuildTargetSettings() { EditorUserBuildSettings.waitForManagedDebugger = EditorGUILayout.Toggle(styles.waitForManagedDebugger, EditorUserBuildSettings.waitForManagedDebugger); } + + bool shouldDrawManagedDebuggerFixedPort = buildWindowExtension != null ? buildWindowExtension.ShouldDrawManagedDebuggerFixedPort() : false; + if (EditorUserBuildSettings.allowDebugging && shouldDrawManagedDebuggerFixedPort) + { + EditorUserBuildSettings.managedDebuggerFixedPort = EditorGUILayout.IntField(styles.managedDebuggerFixedPort, EditorUserBuildSettings.managedDebuggerFixedPort); + } } if (EditorUserBuildSettings.allowDebugging && PlayerSettings.GetScriptingBackend(namedBuildTarget) == ScriptingImplementation.IL2CPP) { - var apiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(namedBuildTarget.ToBuildTargetGroup()); + var apiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(namedBuildTarget); bool isDebuggerUsable = apiCompatibilityLevel == ApiCompatibilityLevel.NET_4_6 || apiCompatibilityLevel == ApiCompatibilityLevel.NET_Standard_2_0 || apiCompatibilityLevel == ApiCompatibilityLevel.NET_Unity_4_8 || apiCompatibilityLevel == ApiCompatibilityLevel.NET_Standard; @@ -1068,23 +1084,32 @@ private static void GUIBuildButtons(IBuildWindowExtension buildWindowExtension, buildWindowExtension.ShowPlatformBuildWarnings(); // Disable the 'Build' and 'Build And Run' buttons when the project setup doesn't satisfy the platform requirements - if (!IsColorSpaceValid(platform) && enableBuildButton && enableBuildAndRunButton) - { - enableBuildAndRunButton = false; - enableBuildButton = false; - EditorGUILayout.HelpBox(styles.invalidColorSpaceMessage); - } - else if (!IsLightmapEncodingValid(platform) && enableBuildButton && enableBuildAndRunButton) + if (enableBuildButton && enableBuildAndRunButton) { - enableBuildAndRunButton = false; - enableBuildButton = false; - EditorGUILayout.HelpBox(styles.invalidLightmapEncodingMessage); - } - else if (!IsVirtualTexturingSettingsValid(platform) && enableBuildButton && enableBuildAndRunButton) - { - enableBuildAndRunButton = false; - enableBuildButton = false; - EditorGUILayout.HelpBox(styles.invalidVirtualTexturingSettingMessage); + if (!IsColorSpaceValid(platform)) + { + enableBuildAndRunButton = false; + enableBuildButton = false; + EditorGUILayout.HelpBox(styles.invalidColorSpaceMessage); + } + else if (!IsLightmapEncodingValid(platform)) + { + enableBuildAndRunButton = false; + enableBuildButton = false; + EditorGUILayout.HelpBox(styles.invalidLightmapEncodingMessage); + } + else if (!IsHDRCubemapEncodingValid(platform)) + { + enableBuildAndRunButton = false; + enableBuildButton = false; + EditorGUILayout.HelpBox(styles.invalidHDRCubemapEncodingMessage); + } + else if (!IsVirtualTexturingSettingsValid(platform)) + { + enableBuildAndRunButton = false; + enableBuildButton = false; + EditorGUILayout.HelpBox(styles.invalidVirtualTexturingSettingMessage); + } } if (EditorApplication.isCompiling || EditorApplication.isUpdating) @@ -1154,6 +1179,12 @@ private static void GUIBuildButtons(IBuildWindowExtension buildWindowExtension, CallBuildMethods(askForBuildLocation, BuildOptions.ShowBuiltPlayer | BuildOptions.CleanBuildCache); }); + menu.AddItem(new GUIContent("Force skip data build"), false, + () => + { + CallBuildMethods(askForBuildLocation, + BuildOptions.ShowBuiltPlayer | BuildOptions.BuildScriptsOnly); + }); menu.DropDown(buildRect); } else if (GUI.Button(buildRect, buildButton, EditorStyles.dropDownToggleButton)) diff --git a/Editor/Mono/BuildPlayerWindowBuildMethods.cs b/Editor/Mono/BuildPlayerWindowBuildMethods.cs index 594dbd4dfe..eb36afe539 100644 --- a/Editor/Mono/BuildPlayerWindowBuildMethods.cs +++ b/Editor/Mono/BuildPlayerWindowBuildMethods.cs @@ -170,7 +170,7 @@ public static void BuildPlayer(BuildPlayerOptions options) bool locationPathExistedBeforeBuild = System.IO.Directory.Exists(options.locationPathName); // Trigger build. // Note: report will be null, if delayToAfterScriptReload = true - var report = BuildPipeline.BuildPlayerInternalNoCheck(options.scenes, options.locationPathName, PostprocessBuildPlayer.GetStreamingAssetsBundleManifestPath(), options.targetGroup, options.target, options.subtarget, options.options, options.extraScriptingDefines, delayToAfterScriptReload); + var report = BuildPipeline.BuildPlayerInternalNoCheck(options.scenes, options.locationPathName, options.assetBundleManifestPath, options.targetGroup, options.target, options.subtarget, options.options, options.extraScriptingDefines, delayToAfterScriptReload); if (report != null ) @@ -308,7 +308,7 @@ internal static BuildPlayerOptions GetBuildPlayerOptionsInternal(bool askForBuil options.subtarget = subtarget; options.targetGroup = buildTargetGroup; options.locationPathName = EditorUserBuildSettings.GetBuildLocation(buildTarget); - options.assetBundleManifestPath = null; + options.assetBundleManifestPath = PostprocessBuildPlayer.GetStreamingAssetsBundleManifestPath(); // Build a list of scenes that are enabled ArrayList scenesList = new ArrayList(); diff --git a/Editor/Mono/BuildTargetConverter.cs b/Editor/Mono/BuildTargetConverter.cs index 0c9674a106..1bc2a4e4eb 100644 --- a/Editor/Mono/BuildTargetConverter.cs +++ b/Editor/Mono/BuildTargetConverter.cs @@ -22,6 +22,8 @@ internal static class BuildTargetConverter return RuntimePlatform.PS5; case BuildTarget.StandaloneLinux64: return RuntimePlatform.LinuxPlayer; + case BuildTarget.CloudRendering: + return RuntimePlatform.LinuxPlayer; case BuildTarget.StandaloneOSX: return RuntimePlatform.OSXPlayer; case BuildTarget.StandaloneWindows: diff --git a/Editor/Mono/Clipboard/ClipboardContextMenu.cs b/Editor/Mono/Clipboard/ClipboardContextMenu.cs index 9d2a16e164..c61316f7ed 100644 --- a/Editor/Mono/Clipboard/ClipboardContextMenu.cs +++ b/Editor/Mono/Clipboard/ClipboardContextMenu.cs @@ -145,10 +145,21 @@ internal static void SetupPropertyCopyPaste(SerializedProperty property, Generic p => p.hash128Value = Clipboard.hash128Value); break; case SerializedPropertyType.Generic: - SetupAction(property, menu, evt, - Clipboard.SetSerializedProperty, - p => Clipboard.HasSerializedProperty(), - Clipboard.GetSerializedProperty); + if (property.type == "MinMaxGradient") + { + SetupMinMaxGradient(property, menu, evt); + } + else if (property.type == "MinMaxCurve") + { + SetupMinMaxCurve(property, menu, evt); + } + else + { + SetupAction(property, menu, evt, + Clipboard.SetSerializedProperty, + p => Clipboard.HasSerializedProperty(), + Clipboard.GetSerializedProperty); + } break; case SerializedPropertyType.Integer: SetupAction(property, menu, evt, @@ -183,6 +194,14 @@ internal static void SetupPropertyCopyPaste(SerializedProperty property, Generic static readonly GUIContent kCopyQuatContent = EditorGUIUtility.TrTextContent("Copy Quaternion"); static readonly GUIContent kCopyPathContent = EditorGUIUtility.TrTextContent("Copy Path"); static readonly GUIContent kCopyGuidContent = EditorGUIUtility.TrTextContent("Copy GUID"); + static readonly GUIContent kPasteMinColorContent = EditorGUIUtility.TrTextContent("Paste Min Color"); + static readonly GUIContent kPasteMaxColorContent = EditorGUIUtility.TrTextContent("Paste Max Color"); + static readonly GUIContent kPasteMinGradientContent = EditorGUIUtility.TrTextContent("Paste Min Gradient"); + static readonly GUIContent kPasteMaxGradientContent = EditorGUIUtility.TrTextContent("Paste Max Gradient"); + static readonly GUIContent kPasteMinScalarContent = EditorGUIUtility.TrTextContent("Paste Min Scalar"); + static readonly GUIContent kPasteMaxScalarContent = EditorGUIUtility.TrTextContent("Paste Max Scalar"); + static readonly GUIContent kPasteMinCurveContent = EditorGUIUtility.TrTextContent("Paste Min Curve"); + static readonly GUIContent kPasteMaxCurveContent = EditorGUIUtility.TrTextContent("Paste Max Curve"); static void AddSeparator(GenericMenu menu) { @@ -220,6 +239,8 @@ static void SetupAction(SerializedProperty property, GenericMenu menu, Event evt var prop = (SerializedProperty)o; pasteFunc(prop); prop.serializedObject.ApplyModifiedProperties(); + // Constrain proportions scale widget might need extra recalculation, notify if a paste + ConstrainProportionsTransformScale.NotifyPropertyPasted(prop.propertyPath); }, property); } else @@ -308,6 +329,237 @@ static void SetupQuaternion(SerializedProperty property, GenericMenu menu, Event } } + static void SetupMinMaxGradient(SerializedProperty property, GenericMenu menu, Event evt) + { + var canCopy = !property.hasMultipleDifferentValues; + var canPasteWhole = GUI.enabled && Clipboard.HasSerializedProperty(); + + if (menu != null) + { + AddSeparator(menu); + + var copyContent = overrideCopyContent ?? kCopyContent; + if (canCopy) + menu.AddItem(copyContent, false, o => Clipboard.SetSerializedProperty((SerializedProperty)o), property); + else + menu.AddDisabledItem(copyContent); + + var pasteContent = overridePasteContent ?? kPasteContent; + + if (canPasteWhole) + { + menu.AddItem(pasteContent, false, + delegate (object o) + { + var prop = (SerializedProperty)o; + Clipboard.GetSerializedProperty(prop); + prop.serializedObject.ApplyModifiedProperties(); + }, property); + } + else if (GUI.enabled && Clipboard.hasColor) + { + MinMaxGradientState state = (MinMaxGradientState)property.FindPropertyRelative("minMaxState").intValue; + if (state == MinMaxGradientState.k_Color) + { + AddPasteColorItem(property.FindPropertyRelative("maxColor"), menu, pasteContent); + } + else if (state == MinMaxGradientState.k_RandomBetweenTwoColors) + { + // Allow the user to choose whether to paste the color on their clipboard to either the max or min + AddPasteColorItem(property.FindPropertyRelative("maxColor"), menu, kPasteMaxColorContent); + AddPasteColorItem(property.FindPropertyRelative("minColor"), menu, kPasteMinColorContent); + } + else + { + menu.AddDisabledItem(pasteContent); + } + } + else if (GUI.enabled && Clipboard.hasGradient) + { + MinMaxGradientState state = (MinMaxGradientState)property.FindPropertyRelative("minMaxState").intValue; + if (state == MinMaxGradientState.k_Gradient || state == MinMaxGradientState.k_RandomColor) + { + AddPasteGradientItem(property.FindPropertyRelative("maxGradient"), menu, pasteContent); + } + else if (state == MinMaxGradientState.k_RandomBetweenTwoGradients) + { + // Allow the user to choose whether to paste the gradient on their clipboard to either the max or min + AddPasteGradientItem(property.FindPropertyRelative("maxGradient"), menu, kPasteMaxGradientContent); + AddPasteGradientItem(property.FindPropertyRelative("minGradient"), menu, kPasteMinGradientContent); + } + else + { + menu.AddDisabledItem(pasteContent); + } + } + else + { + menu.AddDisabledItem(pasteContent); + } + } + if (evt != null) + { + if (canCopy && evt.commandName == EventCommandNames.Copy) + { + if (evt.type == EventType.ValidateCommand) + evt.Use(); + if (evt.type == EventType.ExecuteCommand) + { + Clipboard.SetSerializedProperty(property); + evt.Use(); + } + } + if (canPasteWhole && evt.commandName == EventCommandNames.Paste) + { + if (evt.type == EventType.ValidateCommand) + evt.Use(); + if (evt.type == EventType.ExecuteCommand) + { + Clipboard.GetSerializedProperty(property); + property.serializedObject.ApplyModifiedProperties(); + evt.Use(); + } + } + } + } + static void SetupMinMaxCurve(SerializedProperty property, GenericMenu menu, Event evt) + { + var canCopy = !property.hasMultipleDifferentValues; + var canPasteWhole = GUI.enabled && Clipboard.HasSerializedProperty(); + + if (menu != null) + { + AddSeparator(menu); + + var copyContent = overrideCopyContent ?? kCopyContent; + if (canCopy) + menu.AddItem(copyContent, false, o => Clipboard.SetSerializedProperty((SerializedProperty)o), property); + else + menu.AddDisabledItem(copyContent); + + var pasteContent = overridePasteContent ?? kPasteContent; + + if (canPasteWhole) + { + menu.AddItem(pasteContent, false, + delegate (object o) + { + var prop = (SerializedProperty)o; + Clipboard.GetSerializedProperty(prop); + prop.serializedObject.ApplyModifiedProperties(); + }, property); + } + else if (GUI.enabled && Clipboard.hasFloat) + { + MinMaxCurveState state = (MinMaxCurveState)property.FindPropertyRelative("minMaxState").intValue; + if (state == MinMaxCurveState.k_Scalar) + { + AddPasteFloatItem(property.FindPropertyRelative("scalar"), menu, pasteContent); + } + else if (state == MinMaxCurveState.k_TwoScalars) + { + // Allow the user to choose whether to paste the float on their clipboard to either the min or max + AddPasteFloatItem(property.FindPropertyRelative("minScalar"), menu, kPasteMinScalarContent); + AddPasteFloatItem(property.FindPropertyRelative("scalar"), menu, kPasteMaxScalarContent); + } + else + { + menu.AddDisabledItem(pasteContent); + } + } + else if (GUI.enabled && Clipboard.hasAnimationCurve) + { + MinMaxCurveState state = (MinMaxCurveState)property.FindPropertyRelative("minMaxState").intValue; + if (state == MinMaxCurveState.k_Curve) + { + AddPasteCurveItem(property.FindPropertyRelative("maxCurve"), menu, pasteContent); + } + else if (state == MinMaxCurveState.k_TwoCurves) + { + // Allow the user to choose whether to paste the color on their clipboard to either the max or min + AddPasteCurveItem(property.FindPropertyRelative("maxCurve"), menu, kPasteMaxCurveContent); + AddPasteCurveItem(property.FindPropertyRelative("minCurve"), menu, kPasteMinCurveContent); + } + else + { + menu.AddDisabledItem(pasteContent); + } + } + else + { + menu.AddDisabledItem(pasteContent); + } + } + if (evt != null) + { + if (canCopy && evt.commandName == EventCommandNames.Copy) + { + if (evt.type == EventType.ValidateCommand) + evt.Use(); + if (evt.type == EventType.ExecuteCommand) + { + Clipboard.SetSerializedProperty(property); + evt.Use(); + } + } + if (canPasteWhole && evt.commandName == EventCommandNames.Paste) + { + if (evt.type == EventType.ValidateCommand) + evt.Use(); + if (evt.type == EventType.ExecuteCommand) + { + Clipboard.GetSerializedProperty(property); + property.serializedObject.ApplyModifiedProperties(); + evt.Use(); + } + } + } + } + + static void AddPasteColorItem(SerializedProperty property, GenericMenu menu, GUIContent menuItemContent) + { + menu.AddItem(menuItemContent, false, + delegate (object o) + { + var prop = (SerializedProperty)o; + prop.colorValue = Clipboard.colorValue; + prop.serializedObject.ApplyModifiedProperties(); + }, property); + } + + static void AddPasteGradientItem(SerializedProperty property, GenericMenu menu, GUIContent menuItemContent) + { + menu.AddItem(menuItemContent, false, + delegate (object o) + { + var prop = (SerializedProperty)o; + prop.gradientValue = Clipboard.gradientValue; + prop.serializedObject.ApplyModifiedProperties(); + UnityEditorInternal.GradientPreviewCache.ClearCache(); + }, property); + } + static void AddPasteFloatItem(SerializedProperty property, GenericMenu menu, GUIContent menuItemContent) + { + menu.AddItem(menuItemContent, false, + delegate (object o) + { + var prop = (SerializedProperty)o; + prop.floatValue = Clipboard.floatValue; + prop.serializedObject.ApplyModifiedProperties(); + }, property); + } + static void AddPasteCurveItem(SerializedProperty property, GenericMenu menu, GUIContent menuItemContent) + { + menu.AddItem(menuItemContent, false, + delegate (object o) + { + var prop = (SerializedProperty)o; + prop.animationCurveValue = Clipboard.animationCurveValue; + prop.serializedObject.ApplyModifiedProperties(); + UnityEditorInternal.AnimationCurvePreviewCache.ClearCache(); + }, property); + } + static void PasteObjectReference(SerializedProperty p) { if (Clipboard.hasObject) @@ -464,7 +716,7 @@ static void PasteTransformScaleMenu(MenuCommand command) tr.localScale = Clipboard.vector3Value; } - [MenuItem("CONTEXT/Transform/Copy/Position (World)", false, 100101)] // ReSharper disable once UnusedMember.Local + [MenuItem("CONTEXT/Transform/Copy/World Transform", false, 100150)] // ReSharper disable once UnusedMember.Local static void CopyTransformWorldPlacementMenu(MenuCommand command) { var tr = command.context as Transform; @@ -473,14 +725,14 @@ static void CopyTransformWorldPlacementMenu(MenuCommand command) Clipboard.SetCustomValue(new TransformWorldPlacement(tr)); } - [MenuItem("CONTEXT/Transform/Paste/Position (World)", true)] // ReSharper disable once UnusedMember.Local + [MenuItem("CONTEXT/Transform/Paste/World Transform", true)] // ReSharper disable once UnusedMember.Local static bool PasteTransformWorldPlacementMenuValidate(MenuCommand command) { var tr = command.context as Transform; return tr != null && IsUserModifiable(tr) && Clipboard.HasCustomValue(); } - [MenuItem("CONTEXT/Transform/Paste/Position (World)", false, 100101)] // ReSharper disable once UnusedMember.Local + [MenuItem("CONTEXT/Transform/Paste/World Transform", false, 100150)] // ReSharper disable once UnusedMember.Local static void PasteTransformWorldPlacementMenu(MenuCommand command) { var tr = command.context as Transform; diff --git a/Editor/Mono/CutCopyPasteUtility.cs b/Editor/Mono/ClipboardUtility.cs similarity index 56% rename from Editor/Mono/CutCopyPasteUtility.cs rename to Editor/Mono/ClipboardUtility.cs index b26df32afe..5ee7bb7159 100644 --- a/Editor/Mono/CutCopyPasteUtility.cs +++ b/Editor/Mono/ClipboardUtility.cs @@ -2,15 +2,87 @@ // 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; namespace UnityEditor { - internal static class CutCopyPasteUtility + public static class ClipboardUtility { + public static Func canCopyGameObject; + public static Func canCutGameObject; + public static Func canDuplicateGameObject; + public static event Action copyingGameObjects; + public static event Action cuttingGameObjects; + public static event Action duplicatingGameObjects; + public static event Action duplicatedGameObjects; + public static event Action rejectedGameObjects; + public static event Action pastedGameObjects; + + private static void FilterSelection(Func filter) + { + if (filter == null) + return; + + // If we dont have any filters we really should just leave right now + Delegate[] invocationList = filter.GetInvocationList(); + int invocationListLength = invocationList.Length; + + if (invocationListLength == 0) + return; + + // Make our tracking objects + int selectedGameObjectsLength = Selection.gameObjects.Length; + Queue pass = new Queue(selectedGameObjectsLength); + Queue fail = new Queue(selectedGameObjectsLength); + + // Iterate through our selected game objects, checking each + for (int i = 0; i < selectedGameObjectsLength; i++) + { + GameObject currentGameObject = Selection.gameObjects[i]; + + bool shouldInclude = true; + for (int j = 0; j < invocationListLength; j++) + { + Func func = (Func)invocationList[j]; + + // Passed, next check + if (func.Invoke(currentGameObject)) + continue; + + // Failed, add to failures + if (fail.Contains(currentGameObject)) + continue; + + fail.Enqueue(currentGameObject); + shouldInclude = false; + break; + } + + // Passed everything add to passed gameobjects + if (shouldInclude && !pass.Contains(currentGameObject)) + { + pass.Enqueue(currentGameObject); + } + } + + // Update the selection with the filtered GameObjects + Selection.objects = pass.ToArray(); + + // Notify after filter, for that off chance someone wants to subscribe for logging OR be able to reintroduce + // into the selection an object at their control. + if (rejectedGameObjects != null && fail.Count > 0) + { + rejectedGameObjects.Invoke(fail.ToArray()); + } + } + internal static void CutGO() { + FilterSelection(canCutGameObject); + cuttingGameObjects?.Invoke(Selection.gameObjects); CutBoard.CutGO(); RepaintHierarchyWindowsAfterPaste(); } @@ -18,6 +90,8 @@ internal static void CutGO() internal static void CopyGO() { CutBoard.Reset(); + FilterSelection(canCopyGameObject); + copyingGameObjects?.Invoke(Selection.gameObjects); Unsupported.CopyGameObjectsToPasteboard(); } @@ -35,6 +109,7 @@ internal static void PasteGO(Transform fallbackParent) if (customParentIsSelected) Selection.activeTransform.SetParent(fallbackParent, true); } + pastedGameObjects?.Invoke(Selection.gameObjects); } internal static void PasteGOAsChild() @@ -62,17 +137,22 @@ internal static void PasteGOAsChild() if (pasteToSubScene) { if (subScene.handle != 0) + { CutBoard.PasteToScene(subScene, selected[0]); + pastedGameObjects?.Invoke(Selection.gameObjects); + } } else if (!isSubScene) { CutBoard.PasteAsChildren(selected[0]); + pastedGameObjects?.Invoke(Selection.gameObjects); } } // paste after copy else if (pasteToSubScene || !isSubScene) { Unsupported.PasteGameObjectsFromPasteboard(selected[0], pasteToSubScene ? subScene.handle : 0); + pastedGameObjects?.Invoke(Selection.gameObjects); } } RepaintHierarchyWindowsAfterPaste(); @@ -81,10 +161,13 @@ internal static void PasteGOAsChild() internal static void DuplicateGO(Transform fallbackParent) { CutBoard.Reset(); + FilterSelection(canDuplicateGameObject); bool customParentIsSelected = GetIsCustomParentSelected(fallbackParent); + duplicatingGameObjects?.Invoke(Selection.gameObjects); Unsupported.DuplicateGameObjectsUsingPasteboard(); if (customParentIsSelected) Selection.activeTransform.SetParent(fallbackParent, true); + duplicatedGameObjects?.Invoke(Selection.gameObjects); } internal static bool CanPasteAsChild() diff --git a/Editor/Mono/CodeEditor/CodeEditor.cs b/Editor/Mono/CodeEditor/CodeEditor.cs index d8fb9ffc77..ba3c86d927 100644 --- a/Editor/Mono/CodeEditor/CodeEditor.cs +++ b/Editor/Mono/CodeEditor/CodeEditor.cs @@ -31,7 +31,29 @@ public struct Installation private Installation m_CurrentInstallation; internal const string SystemDefaultPath = ""; - public IExternalCodeEditor CurrentCodeEditor => m_CurrentEditor; + public IExternalCodeEditor CurrentCodeEditor { + get + { + if(m_CurrentEditor == m_DefaultEditor && !IsCurrentEditorPathExplicitlySet) + { + // try to resolve first found visual studio installation and enable it + try + { + var vs = m_ExternalCodeEditors.FirstOrDefault(e => e.GetType().Name == "VisualStudioEditor"); + var installs = vs?.Installations; + if (installs != null && installs.Length > 0) + { + SetCodeEditor(installs[0].Path); + } + } + catch(Exception ex) + { + Debug.LogWarning($"Can't locate Visual Studio installation: {ex.Message}"); + } + } + return m_CurrentEditor; + } + } public Installation CurrentInstallation => m_CurrentInstallation; [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] @@ -39,6 +61,7 @@ public struct Installation [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] public static string CurrentEditorInstallation => CurrentEditorPath; public static string CurrentEditorPath => EditorPrefs.GetString("kScriptsDefaultApp", ""); + private static bool IsCurrentEditorPathExplicitlySet => EditorPrefs.HasKey("kScriptsDefaultApp"); public static CodeEditor Editor { get; } = new CodeEditor(); @@ -191,7 +214,10 @@ public static void Register(IExternalCodeEditor externalCodeEditor) if (Editor.m_ExternalCodeEditors.Select(editor => editor.GetType()).Any(editorType => editorType == externalCodeEditor.GetType())) return; Editor.m_ExternalCodeEditors.Add(externalCodeEditor); - CodeEditor.Editor.SetCodeEditor(Editor.m_CurrentInstallation.Path); + if (IsCurrentEditorPathExplicitlySet) + { + CodeEditor.Editor.SetCodeEditor(Editor.m_CurrentInstallation.Path); + } } public static void Unregister(IExternalCodeEditor externalCodeEditor) diff --git a/Editor/Mono/CodeEditor/DefaultExternalCodeEditor.cs b/Editor/Mono/CodeEditor/DefaultExternalCodeEditor.cs index f8fa2cf6ca..9dcb83225e 100644 --- a/Editor/Mono/CodeEditor/DefaultExternalCodeEditor.cs +++ b/Editor/Mono/CodeEditor/DefaultExternalCodeEditor.cs @@ -135,6 +135,12 @@ public bool OpenProject(string path, int line, int column) } string applicationPath = CodeEditor.CurrentEditorPath.Trim(); + + if (!string.IsNullOrEmpty(applicationPath) && !File.Exists(applicationPath)) + { + UnityEngine.Debug.LogWarning($"External Code Editor application path does not exist ({applicationPath})! Please select a different application"); + } + if (applicationPath == CodeEditor.SystemDefaultPath) { return InternalEditorUtility.OpenFileAtLineExternal(path, -1, -1); diff --git a/Editor/Mono/CompilationPipeline.bindings.cs b/Editor/Mono/CompilationPipeline.bindings.cs index 4eeefa9cc9..134b372e48 100644 --- a/Editor/Mono/CompilationPipeline.bindings.cs +++ b/Editor/Mono/CompilationPipeline.bindings.cs @@ -11,10 +11,8 @@ namespace UnityEditor.Compilation enum CompilationSetupErrors // Keep in sync with enum CompilationSetupErrors::Flags in ScriptCompilationPipeline.h { None = 0, - CyclicReferences = 1 << 0, // set when CyclicAssemblyReferenceException is thrown - LoadError = 1 << 1, // set when AssemblyDefinitionException is thrown - PrecompiledAssemblyError = 1 << 2, // set when PrecompiledAssemblyException is thrown - All = CyclicReferences | LoadError | PrecompiledAssemblyError, + LoadError = 1 << 0, // set when AssemblyDefinitionException is thrown + All = LoadError, }; [NativeHeader("Editor/Src/ScriptCompilation/ScriptCompilationPipeline.h")] diff --git a/Editor/Mono/ComponentUtility.bindings.cs b/Editor/Mono/ComponentUtility.bindings.cs index 3306d3a144..f5edb0ef2a 100644 --- a/Editor/Mono/ComponentUtility.bindings.cs +++ b/Editor/Mono/ComponentUtility.bindings.cs @@ -17,19 +17,19 @@ public sealed partial class ComponentUtility { public static bool MoveComponentUp(Component component) { - return MoveComponentUp(new[] { component }, false); + return MoveComponentUp_Internal(new[] { component }, false, false); } [FreeFunction] - static extern bool MoveComponentUp(UnityObject[] context, bool validateOnly); + static extern bool MoveComponentUp_Internal(UnityObject[] context, bool validateOnly, bool showDialog); public static bool MoveComponentDown(Component component) { - return MoveComponentDown(new UnityObject[] { component }, false); + return MoveComponentDown_Internal(new UnityObject[] { component }, false, false); } [FreeFunction] - static extern bool MoveComponentDown(UnityObject[] context, bool validateOnly); + static extern bool MoveComponentDown_Internal(UnityObject[] context, bool validateOnly, bool showDialog); public static bool CopyComponent(Component component) { diff --git a/Editor/Mono/ContainerWindow.cs b/Editor/Mono/ContainerWindow.cs index a153eaca59..1570d7b02a 100644 --- a/Editor/Mono/ContainerWindow.cs +++ b/Editor/Mono/ContainerWindow.cs @@ -195,8 +195,7 @@ public void Show(ShowMode showMode, bool loadPosition, bool displayImmediately, s_MainWindow = this; // Fit window to screen - needs to be done after bringing the window live - position = FitWindowRectToScreen(m_PixelRect, true, useMousePos); - rootView.position = new Rect(0, 0, GUIUtility.RoundToPixelGrid(m_PixelRect.width), GUIUtility.RoundToPixelGrid(m_PixelRect.height)); + FitWindowToScreen(useMousePos); rootView.Reflow(); @@ -213,6 +212,13 @@ public void Show(ShowMode showMode, bool loadPosition, bool displayImmediately, } } + internal void FitWindowToScreen(bool useMousePos) + { + position = FitWindowRectToScreen(m_PixelRect, true, useMousePos); + if (rootView) + rootView.position = new Rect(0, 0, GUIUtility.RoundToPixelGrid(m_PixelRect.width), GUIUtility.RoundToPixelGrid(m_PixelRect.height)); + } + public void OnEnable() { if (m_RootView) @@ -284,7 +290,7 @@ internal bool InternalRequestCloseAllExcept(EditorWindow editorWindow) return true; } - private static bool PrivateRequestClose(List allUnsaved) + internal static bool PrivateRequestClose(List allUnsaved) { Debug.Assert(allUnsaved.Count > 0); diff --git a/Editor/Mono/Display/EditorFullscreenController.cs b/Editor/Mono/Display/EditorFullscreenController.cs index 30498d4654..6c87f12a5c 100644 --- a/Editor/Mono/Display/EditorFullscreenController.cs +++ b/Editor/Mono/Display/EditorFullscreenController.cs @@ -41,6 +41,7 @@ internal class EditorFullscreenController : ScriptableSingleton m_AvailableWindowTypes; private Dictionary m_DisplaySettings; @@ -266,7 +267,7 @@ private void OnEnable() EditorApplication.globalEventHandler += HandleToggleFullscreenKeyShortcut; UpdateDisplayNamesAndIds(); - GetInstalledBuildTargetData(); + m_buildTargetDataInitialized = false; if (m_profiles == null) { @@ -354,6 +355,9 @@ private void UpdateDisplayNamesAndIds() private void GetInstalledBuildTargetData() { + if (m_buildTargetDataInitialized) + return; + var buildTargetData = EnumDataUtility.GetCachedEnumData(typeof(BuildTarget)); var installedBuildTargetCount = (from BuildTarget target in buildTargetData.values @@ -385,6 +389,8 @@ private void GetInstalledBuildTargetData() ++j; } } + + m_buildTargetDataInitialized = true; } private EditorDisplayFullscreenSetting GetSettingForDisplay(int displayIndex) @@ -1025,6 +1031,8 @@ private static bool DrawPreferenceProfileGUI(EditorDisplaySettingsProfile profil 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) diff --git a/Editor/Mono/DragAndDrop.bindings.cs b/Editor/Mono/DragAndDrop.bindings.cs index a4856d0aca..810bf40a23 100644 --- a/Editor/Mono/DragAndDrop.bindings.cs +++ b/Editor/Mono/DragAndDrop.bindings.cs @@ -100,6 +100,26 @@ internal static void RemoveDropHandler(int dropDstId, Delegate handler) } } + internal static DragAndDropVisualMode DropOnProjectBrowserWindow(int dragUponInstanceId, string dropUponPath, bool perform) + { + return Drop(DragAndDropWindowTarget.projectBrowser, dragUponInstanceId, dropUponPath, perform); + } + + internal static DragAndDropVisualMode DropOnSceneWindow(UnityEngine.Object dropUpon, Vector3 worldPosition, Vector2 viewportPosition, Transform parentForDraggedObjects, bool perform) + { + return Drop(DragAndDropWindowTarget.sceneView, dropUpon, worldPosition, viewportPosition, parentForDraggedObjects, perform); + } + + internal static DragAndDropVisualMode DropOnInspectorWindow(UnityEngine.Object[] targets, bool perform) + { + return Drop(DragAndDropWindowTarget.inspector, targets, perform); + } + + internal static DragAndDropVisualMode DropOnHierarchyWindow(int dropTargetInstanceID, HierarchyDropFlags dropMode, Transform parentForDraggedObjects, bool perform) + { + return Drop(DragAndDropWindowTarget.hierarchy, dropTargetInstanceID, dropMode, parentForDraggedObjects, perform); + } + internal static DragAndDropVisualMode Drop(int dropDstId, params object[] args) { SavedGUIState guiState = SavedGUIState.Create(); @@ -246,8 +266,12 @@ public static void SetGenericData(string type, object data) public static extern DragAndDropVisualMode visualMode { get; set; } public static extern void AcceptDrag(); - [NativeMethod("PrepareStartDrag")] private static extern void PrepareStartDrag_Internal(); - [NativeMethod("StartDrag")] private static extern void StartDrag_Internal(string title); + [NativeMethod("PrepareStartDrag")] + private static extern void PrepareStartDrag_Internal(); + [NativeMethod("Cleanup")] + internal static extern void Cleanup(); + [NativeMethod("StartDrag")] + private static extern void StartDrag_Internal(string title); public delegate DragAndDropVisualMode ProjectBrowserDropHandler(int dragInstanceId, string dropUponPath, bool perform); public delegate DragAndDropVisualMode SceneDropHandler(UnityEngine.Object dropUpon, Vector3 worldPosition, Vector2 viewportPosition, Transform parentForDraggedObjects, bool perform); diff --git a/Editor/Mono/DrivenRectTransformUndo.cs b/Editor/Mono/DrivenRectTransformUndo.cs index 816771f88a..19c2030978 100644 --- a/Editor/Mono/DrivenRectTransformUndo.cs +++ b/Editor/Mono/DrivenRectTransformUndo.cs @@ -14,12 +14,17 @@ static DrivenRectTransformUndo() { Undo.willFlushUndoRecord += ForceUpdateCanvases; // After undo or redo performed, the 'driven values' & 'driven properties mask' need to be updated. - Undo.undoRedoPerformed += ForceUpdateCanvases; + Undo.undoRedoEvent += OnUndoRedoPerformed; } static void ForceUpdateCanvases() { Canvas.ForceUpdateCanvases(); } + + static void OnUndoRedoPerformed(in UndoRedoInfo info) + { + Canvas.ForceUpdateCanvases(); + } } } diff --git a/Editor/Mono/EditorApplication.bindings.cs b/Editor/Mono/EditorApplication.bindings.cs index f84c9037ef..d653fd9100 100644 --- a/Editor/Mono/EditorApplication.bindings.cs +++ b/Editor/Mono/EditorApplication.bindings.cs @@ -159,8 +159,43 @@ internal static extern bool useLibmonoBackendForIl2cpp [StaticAccessor("GetApplication()", StaticAccessorType.Dot)] internal static extern bool CanReloadAssemblies(); + private static extern bool ExecuteMenuItemInternal(string menuItemPath, bool logErrorOnUnfoundItem); + // Invokes the menu item in the specified path. - public static extern bool ExecuteMenuItem(string menuItemPath); + public static bool ExecuteMenuItem(string menuItemPath) + { + var isDefaultMode = ModeService.currentId == ModeService.k_DefaultModeId; + var result = ExecuteMenuItemInternal(menuItemPath, isDefaultMode); + if (result) + return result; + + if (!isDefaultMode) + { + var menuItems = TypeCache.GetMethodsWithAttribute(); + foreach (var item in menuItems) + { + MenuItem itemData = (MenuItem)item.GetCustomAttributes(typeof(MenuItem), false)[0]; + if (!itemData.validate && itemData.menuItem == menuItemPath) + { + if (item.GetParameters().Length == 0) + { + item.Invoke(null, new object[0]); + return true; + } + else if (item.GetParameters()[0].ParameterType == typeof(MenuCommand)) + { + item.Invoke(null, new[] { new MenuCommand(null) }); + return true; + } + break; + } + } + + Debug.LogError($"ExecuteMenuItem failed because there is no menu named '{menuItemPath}'"); + } + + return false; + } // Validates the menu item in the specific path internal static extern bool ValidateMenuItem(string menuItemPath); diff --git a/Editor/Mono/EditorApplication.cs b/Editor/Mono/EditorApplication.cs index 9c02817ab5..a7592073b0 100644 --- a/Editor/Mono/EditorApplication.cs +++ b/Editor/Mono/EditorApplication.cs @@ -514,10 +514,17 @@ static void Internal_RestoreLastOpenedScenes() // Open requested scene if any if (!string.IsNullOrEmpty(lastOpenedScene)) { - if (EditorSceneManager.CanOpenScene()) - EditorSceneManager.OpenScene(lastOpenedScene, OpenSceneMode.Single); - else - InstantiateDefaultScene(); + try + { + if (EditorSceneManager.CanOpenScene()) + EditorSceneManager.OpenScene(lastOpenedScene, OpenSceneMode.Single); + else + InstantiateDefaultScene(); + } + catch (Exception e) + { + Debug.LogWarning($"Error while opening specified scene \"{lastOpenedScene}\":\n {e.Message}"); + } // Regardless of the operation outcome, reset last opened scene so that we don't // force it next time around diff --git a/Editor/Mono/EditorGUI.cs b/Editor/Mono/EditorGUI.cs index 02715ebfb3..4e98ec840d 100644 --- a/Editor/Mono/EditorGUI.cs +++ b/Editor/Mono/EditorGUI.cs @@ -113,7 +113,7 @@ private enum DragCandidateState internal static string kDoubleFieldFormatString = UINumericFieldsUtils.k_DoubleFieldFormatString; internal static string kIntFieldFormatString = UINumericFieldsUtils.k_IntFieldFormatString; internal static int ms_IndentLevel = 0; - private const float kIndentPerLevel = 15; + internal const float kIndentPerLevel = 15; internal const int kControlVerticalSpacingLegacy = 2; internal const int kDefaultSpacing = 6; internal static readonly SVC kControlVerticalSpacing = new SVC("--theme-control-vertical-spacing", 2.0f); @@ -141,6 +141,8 @@ private enum DragCandidateState private static readonly float[] s_Vector4Floats = {0, 0, 0, 0}; private static readonly GUIContent[] s_XYZWLabels = {EditorGUIUtility.TextContent("X"), EditorGUIUtility.TextContent("Y"), EditorGUIUtility.TextContent("Z"), EditorGUIUtility.TextContent("W")}; + private const float kQuaternionFloatPrecision = 1e-6f; + private static readonly GUIContent[] s_WHLabels = {EditorGUIUtility.TextContent("W"), EditorGUIUtility.TextContent("H")}; private static readonly GUIContent s_CenterLabel = EditorGUIUtility.TrTextContent("Center"); @@ -165,6 +167,9 @@ private enum DragCandidateState internal static Color k_OverrideMarginColorSelected = new Color(239f / 255f, 239f / 255f, 239f / 239f, 1f); internal static Color k_OverrideMarginColorNotApplicable = new Color(1f / 255f, 153f / 255f, 235f / 255f, 0.35f); + internal static Color k_LiveModifiedMarginLightThemeColor = new Color(183f / 255f, 60f / 255f, 21f / 255f, 1f); + internal static Color k_LiveModifiedMarginDarkThemeColor = new Color(255f / 255f, 165f / 255f, 60f / 255f, 1f); + private const int kInspTitlebarSpacing = 4; private static readonly GUIContent s_PropertyFieldTempContent = new GUIContent(); private static GUIContent s_IconDropDown; @@ -243,21 +248,20 @@ public static void FocusTextInControl(string name) internal static void ClearStacks() { - s_PropertyCount = 0; s_EnabledStack.Clear(); + s_IsInsideListStack.Clear(); + GUI.isInsideList = false; s_ChangedStack.Clear(); s_PropertyStack.Clear(); + MaterialProperty.ClearStack(); ScriptAttributeUtility.s_DrawerStack.Clear(); s_FoldoutHeaderGroupActive = 0; } - // Property counting is required by ReorderableList. Element rendering callbacks can change and use - // different number of properties to represent an element each frame. We need a way to be able to track - // if the property count changed from the last frame so we can recache those elements. - internal static int s_PropertyCount = 0; private static readonly Stack s_PropertyStack = new Stack(); private static readonly Stack s_EnabledStack = new Stack(); + private static readonly Stack<(bool insideList, int depth)> s_IsInsideListStack = new Stack<(bool insideList, int depth)>(); // @TODO: API soon to be deprecated but still in a grace period; documentation states that users // are encouraged to use EditorGUI.DisabledScope instead. Uncomment next line when appropriate. @@ -314,6 +318,28 @@ public void Dispose() } } + internal class DisabledGuiViewInputScope : GUI.Scope + { + GUIView m_View; + bool m_WasDisabled; + + public DisabledGuiViewInputScope(GUIView view, bool disabled) + { + m_View = view; + if (m_View != null) + { + m_WasDisabled = view.disableInputEvents; + m_View.disableInputEvents = disabled; + } + } + + protected override void CloseScope() + { + if (m_View != null) + m_View.disableInputEvents = m_WasDisabled; + } + } + internal struct LabelHighlightScope : IDisposable { bool m_Disposed; @@ -365,6 +391,28 @@ internal static void EndDisabled() GUI.enabled = s_EnabledStack.Pop(); } + internal static void BeginIsInsideList(int depth) + { + s_IsInsideListStack.Push((GUI.isInsideList, depth)); + GUI.isInsideList = true; + } + + internal static int GetInsideListDepth() + { + if (s_IsInsideListStack.Count > 0) + return s_IsInsideListStack.Peek().depth; + return -1; + } + + internal static void EndIsInsideList() + { + // Stack might have been cleared with ClearStack(), check before pop. + if (s_IsInsideListStack.Count > 0) + GUI.isInsideList = s_IsInsideListStack.Pop().insideList; + else + GUI.isInsideList = false; + } + private static readonly Stack s_ChangedStack = new Stack(); public class ChangeCheckScope : GUI.Scope @@ -1604,7 +1652,7 @@ internal static bool DoObjectMouseInteraction(bool foldout, Rect interactionRect if (interactionRect.Contains(evt.mousePosition)) { - DragAndDrop.visualMode = DragAndDrop.Drop(DragAndDropWindowTarget.inspector, targetObjs, false); + DragAndDrop.visualMode = DragAndDrop.DropOnInspectorWindow(targetObjs, false); Event.current.Use(); } break; @@ -1612,7 +1660,7 @@ internal static bool DoObjectMouseInteraction(bool foldout, Rect interactionRect case EventType.DragPerform: if (interactionRect.Contains(evt.mousePosition)) { - DragAndDrop.visualMode = DragAndDrop.Drop(DragAndDropWindowTarget.inspector, targetObjs, true); + DragAndDrop.visualMode = DragAndDrop.DropOnInspectorWindow(targetObjs, true); DragAndDrop.AcceptDrag(); Event.current.Use(); } @@ -2939,37 +2987,33 @@ internal static GenericMenu FillPropertyContextMenu(SerializedProperty property, pm.AddItem(EditorGUIUtility.TrTextContent("Duplicate Array Element"), false, (a) => { - // Reorderable - if (PropertyHandler.s_reorderableLists.ContainsKey(ReorderableListWrapper.GetPropertyIdentifier(parentArrayProperty))) - { - ReorderableListWrapper list = PropertyHandler.s_reorderableLists[ReorderableListWrapper.GetPropertyIdentifier(parentArrayProperty)]; + var list = ReorderableList.GetReorderableListFromSerializedProperty(parentArrayProperty); - // 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 - // ReorderableLists multi-selection features - if (list != null && list.m_ReorderableList.selectedIndices.Count > 0) + // 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 + // ReorderableLists multi-selection features + if (list != null && list.selectedIndices.Count > 0) + { + for (int i = list.selectedIndices.Count - 1; i >= 0; i--) { - for (int i = list.m_ReorderableList.selectedIndices.Count - 1; i >= 0; i--) - { - if (list.m_ReorderableList.selectedIndices[i] >= parentArrayProperty.arraySize) continue; + if (list.selectedIndices[i] >= parentArrayProperty.arraySize) continue; - SerializedProperty resolvedProperty = parentArrayProperty.GetArrayElementAtIndex(list.m_ReorderableList.selectedIndices[i]); - if (resolvedProperty != null) - { - if (!TargetChoiceHandler.DuplicateArrayElement(resolvedProperty)) continue; - } + SerializedProperty resolvedProperty = parentArrayProperty.GetArrayElementAtIndex(list.selectedIndices[i]); + if (resolvedProperty != null) + { + if (!TargetChoiceHandler.DuplicateArrayElement(resolvedProperty)) continue; + } - for (int j = i; j < list.m_ReorderableList.selectedIndices.Count; j++) - { - list.m_ReorderableList.m_Selection[j]++; - } + for (int j = i; j < list.selectedIndices.Count; j++) + { + list.m_Selection[j]++; } + } - if (list.m_ReorderableList.onChangedCallback != null) - list.m_ReorderableList.onChangedCallback(list.m_ReorderableList); + if (list.onChangedCallback != null) + list.onChangedCallback(list); - ReorderableList.ClearExistingListCaches(); - } + ReorderableList.InvalidateExistingListCaches(); } else // Non reorderable { @@ -2986,36 +3030,39 @@ internal static GenericMenu FillPropertyContextMenu(SerializedProperty property, TargetChoiceHandler.DuplicateArrayElement(a); } } + + if(list != null) + { + if (list.onChangedCallback != null) list.onChangedCallback(list); + ReorderableList.InvalidateExistingListCaches(); + } EditorGUIUtility.editingTextField = false; }, propertyWithPath); pm.AddItem(EditorGUIUtility.TrTextContent("Delete Array Element"), false, (a) => { - // Reorderable - if (PropertyHandler.s_reorderableLists.ContainsKey(ReorderableListWrapper.GetPropertyIdentifier(parentArrayProperty))) - { - ReorderableListWrapper list = PropertyHandler.s_reorderableLists[ReorderableListWrapper.GetPropertyIdentifier(parentArrayProperty)]; + var list = ReorderableList.GetReorderableListFromSerializedProperty(parentArrayProperty); - // 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 - // ReorderableLists multi-selection features - if (list != null && list.m_ReorderableList.selectedIndices.Count > 0) + // 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 + // ReorderableLists multi-selection features + if (list != null && list.selectedIndices.Count > 0) + { + foreach (var selected in list.selectedIndices.Reverse()) { - foreach (var selected in list.m_ReorderableList.selectedIndices.Reverse()) - { - if (selected >= parentArrayProperty.arraySize) continue; + if (selected >= parentArrayProperty.arraySize) continue; - SerializedProperty resolvedProperty = parentArrayProperty.GetArrayElementAtIndex(selected); - if (resolvedProperty != null) - { - if (!TargetChoiceHandler.DeleteArrayElement(resolvedProperty)) continue; - } + SerializedProperty resolvedProperty = parentArrayProperty.GetArrayElementAtIndex(selected); + if (resolvedProperty != null) + { + if (!TargetChoiceHandler.DeleteArrayElement(resolvedProperty)) continue; } - - list.m_ReorderableList.m_Selection.Clear(); - if (list.m_ReorderableList.onChangedCallback != null) - list.m_ReorderableList.onChangedCallback(list.m_ReorderableList); - ReorderableList.ClearExistingListCaches(); } + + list.m_Selection.Clear(); + + if (list.onChangedCallback != null) + list.onChangedCallback(list); + ReorderableList.InvalidateExistingListCaches(); } else // Non reorderable { @@ -3032,6 +3079,13 @@ internal static GenericMenu FillPropertyContextMenu(SerializedProperty property, TargetChoiceHandler.DeleteArrayElement(a); } } + + if(list != null) + { + list.m_Selection.Clear(); + if (list.onChangedCallback != null) list.onChangedCallback(list); + ReorderableList.InvalidateExistingListCaches(); + } EditorGUIUtility.editingTextField = false; }, propertyWithPath); } @@ -3996,7 +4050,7 @@ internal static Enum EnumFlagsField(Rect position, GUIContent label, Enum enumVa var enumData = EnumDataUtility.GetCachedEnumData(enumType, !includeObsolete); if (!enumData.serializable) - // this is the same message used in ScriptPopupMenus.cpp + // this is the same message used in SerializedPropertyEnumHelper.cpp throw new NotSupportedException(string.Format("Unsupported enum base type for {0}", enumType.Name)); var id = GUIUtility.GetControlID(s_EnumFlagsField, FocusType.Keyboard, position); @@ -4019,7 +4073,7 @@ internal static int EnumFlagsField(Rect position, GUIContent label, int enumValu var enumData = EnumDataUtility.GetCachedEnumData(enumType, !includeObsolete); if (!enumData.serializable) - // this is the same message used in ScriptPopupMenus.cpp + // this is the same message used in SerializedPropertyEnumHelper.cpp throw new NotSupportedException(string.Format("Unsupported enum base type for {0}", enumType.Name)); var id = GUIUtility.GetControlID(s_EnumFlagsField, FocusType.Keyboard, position); @@ -4373,12 +4427,9 @@ internal static Vector3 LinkedVector3Field(Rect position, GUIContent label, GUI { GUIContent copy = label; Rect fullLabelRect = position; - // If SerializedProperty is passed, make sure to call begin and end for property - // Since we have two separate properties to handle, make sure fields are not overlaping each other - // If only localScale property has override, make sure constrainProportionsScale property is in the layer behind - if (proportionalScaleProperty != null && property.prefabOverride && !proportionalScale) - label = BeginPropertyInternal(fullLabelRect, label, proportionalScaleProperty); + if(proportionalScaleProperty != null) + BeginPropertyInternal(fullLabelRect, label, proportionalScaleProperty); if (property != null) label = BeginPropertyInternal(position, label, property); @@ -4392,34 +4443,19 @@ internal static Vector3 LinkedVector3Field(Rect position, GUIContent label, GUI float toggleOffset = toggleRect.width + kDefaultSpacing; toggleRect.x -= toggleOffset; toggle.x -= toggleOffset; - BeginChangeCheck(); Styles.linkButton.alignment = TextAnchor.MiddleCenter; - if (proportionalScaleProperty != null) - { - // If proportional scaling is enabled, make sure to use full scale rect, to be able revert all axis and state at the same time, - // otherwise rect size should match label size - if (!property.prefabOverride && !proportionalScale) - fullLabelRect.xMax = toggleRect.xMin; + // In case we have a background overlay, make sure Constrain proportions toggle won't be affected + Color currentColor = GUI.backgroundColor; + GUI.backgroundColor = Color.white; - // Make sure proportional scale property is used only if enabled, otherwise use localScale's one. - if (!property.prefabOverride || proportionalScale) - label = BeginPropertyInternal(fullLabelRect, label, proportionalScale? proportionalScaleProperty : property); + bool previousProportionalScale = proportionalScale; + proportionalScale = GUI.Toggle(toggleRect, proportionalScale, toggleContent, Styles.linkButton); - BeginChangeCheck(); - bool previousProportionalScale = proportionalScale; - proportionalScale = GUI.Toggle(toggleRect, proportionalScale, toggleContent, Styles.linkButton); - if (previousProportionalScale != proportionalScale) - proportionalScaleProperty.boolValue = proportionalScale; - EndChangeCheck(); + if (proportionalScaleProperty != null && previousProportionalScale != proportionalScale) + proportionalScaleProperty.boolValue = proportionalScale; - if (proportionalScale) - EndProperty(); - } - else - { - proportionalScale = GUI.Toggle(toggleRect, proportionalScale, toggleContent, Styles.linkButton); - } + GUI.backgroundColor = currentColor; position.x += toggle.x + kDefaultSpacing; position.width -= toggle.x + kDefaultSpacing; @@ -4430,7 +4466,7 @@ internal static Vector3 LinkedVector3Field(Rect position, GUIContent label, GUI if (property != null) EndProperty(); - if (proportionalScaleProperty != null && property.prefabOverride && !proportionalScale) + if(proportionalScaleProperty != null) EndProperty(); return newValue; @@ -4459,8 +4495,7 @@ static Vector3 LinkedVector3Field(Rect position, Vector3 value, bool proportiona valueAfterChangeCheck.z = s_Vector3Floats[2]; } - return proportionalScale && valueAfterChangeCheck != value ? - ConstrainProportionsTransformScale.DoScaleProportions(valueAfterChangeCheck, value, initialScale, ref axisModified) : valueAfterChangeCheck; + return proportionalScale? ConstrainProportionsTransformScale.DoScaleProportions(valueAfterChangeCheck, value, ConstrainProportionsTransformScale.m_IsAnimationPreview? value : initialScale, ref axisModified) : valueAfterChangeCheck; } // Make an X, Y field - not public (use PropertyField instead) @@ -4485,6 +4520,24 @@ private static void Vector3Field(Rect position, SerializedProperty property, GUI MultiPropertyFieldInternal(position, s_XYZLabels, cur, PropertyVisibility.All); } + // Make an X, Y and Z field for Quaternions - not public (use PropertyField instead) + private static void QuaternionEulerField(Rect position, SerializedProperty property, GUIContent label) + { + int id = GUIUtility.GetControlID(s_FoldoutHash, FocusType.Keyboard, position); + position = MultiFieldPrefixLabel(position, id, label, 3); + position.height = kSingleLineHeight; + Vector3 eulerValue = property.quaternionValue.eulerAngles; + s_Vector3Floats[0] = Mathf.Floor(eulerValue.x / kQuaternionFloatPrecision) * kQuaternionFloatPrecision; + s_Vector3Floats[1] = Mathf.Floor(eulerValue.y / kQuaternionFloatPrecision) * kQuaternionFloatPrecision; + s_Vector3Floats[2] = Mathf.Floor(eulerValue.z / kQuaternionFloatPrecision) * kQuaternionFloatPrecision; + BeginChangeCheck(); + MultiFloatFieldInternal(position, s_XYZLabels, s_Vector3Floats); + if (EndChangeCheck()) + { + property.quaternionValue = Quaternion.Euler(s_Vector3Floats[0], s_Vector3Floats[1], s_Vector3Floats[2]); + } + } + // Make an X, Y, Z and W field - not public (use PropertyField instead) static void Vector4Field(Rect position, SerializedProperty property, GUIContent label) { @@ -4927,9 +4980,12 @@ static void LockingMultiFloatFieldInternal(Rect position, bool locked, uint mixe bool hasMixedValues = mixedValues != 0; uint multiselectionLock = ConstrainProportionsTransformScale.GetMultiSelectionLockedFields(property?.serializedObject?.targetObjects); - if (initialValues == null) + if (initialValues == null || ConstrainProportionsTransformScale.m_IsAnimationPreview) initialValues = values; + // In animation preview mode we scale using last valid value instead of initialScale, make sure we won't lock fields on Vector3.zero + bool forceEnableFields = ConstrainProportionsTransformScale.ShouldForceEnablePropertyFields(initialValues); + bool mixedValueState = EditorGUI.showMixedValue; for (int i = 0; i < initialValues.Length; i++) @@ -4943,7 +4999,7 @@ static void LockingMultiFloatFieldInternal(Rect position, bool locked, uint mixe if (locked) { // If initial field value is 0, it must be locked not to break proportions - GUI.enabled = !Mathf.Approximately(initialValues[i], 0) && (property != null && property.serializedObject.targetObjectsCount > 1 ? !ConstrainProportionsTransformScale.IsBit(multiselectionLock, i) : true); + GUI.enabled = forceEnableFields || !Mathf.Approximately(initialValues[i], 0) && (property != null && property.serializedObject.targetObjectsCount > 1 ? !ConstrainProportionsTransformScale.IsBit(multiselectionLock, i) : true); } else { @@ -6499,7 +6555,6 @@ public static GUIContent BeginProperty(Rect totalPosition, GUIContent label, Ser // Create a Property wrapper, useful for making regular GUI controls work with [[SerializedProperty]]. internal static GUIContent BeginPropertyInternal(Rect totalPosition, GUIContent label, SerializedProperty property) { - s_PropertyCount++; if (s_PendingPropertyKeyboardHandling != null) { DoPropertyFieldKeyboardHandling(s_PendingPropertyKeyboardHandling); @@ -6558,6 +6613,17 @@ internal static GUIContent BeginPropertyInternal(Rect totalPosition, GUIContent } } + var isLiveModified = property.isLiveModified; + if (isLiveModified) + EditorGUIUtility.SetBoldDefaultFont(isLiveModified); + + if (Event.current.type == EventType.Repaint && isLiveModified) + { + Rect highlightRect = totalPosition; + highlightRect.xMin += EditorGUI.indent; + DrawLiveModifiedBackground(highlightRect, false); + } + s_PropertyStack.Push(new PropertyGUIData(property, totalPosition, wasBoldDefaultFont, GUI.enabled, GUI.backgroundColor)); EditorGUIUtility.BeginPropertyCallback(totalPosition, property); @@ -6596,7 +6662,7 @@ internal static GUIContent BeginPropertyInternal(Rect totalPosition, GUIContent if ( (DrivenPropertyManagerInternal.IsDriving(driver, target, propertyPath)) || - ((target is Transform || property.propertyType == SerializedPropertyType.Color) && DrivenPropertyManagerInternal.IsDrivingPartial(driver, target, propertyPath))) + ((target is Transform || target is ParticleSystem || property.propertyType == SerializedPropertyType.Color) && DrivenPropertyManagerInternal.IsDrivingPartial(driver, target, propertyPath))) { GUI.enabled = false; if (isCollectingTooltips) @@ -6701,6 +6767,18 @@ internal static void DrawMarginLineForRect(Rect position, Color color) Graphics.DrawTexture(position, EditorGUIUtility.whiteTexture, new Rect(), 0, 0, 0, 0, color, guiTextureClipVerticallyMaterial); } + internal static void DrawLiveModifiedBackground(Rect position, bool fixupRectForHeadersAndBackgrounds = false) + { + if (fixupRectForHeadersAndBackgrounds) + { + // Tweaks to match the specifics of how the horizontal lines between components are drawn. + position.yMin += 2; + position.yMax += 1; + } + + DrawMarginLineForRect(position, EditorGUIUtility.isProSkin? k_LiveModifiedMarginDarkThemeColor : k_LiveModifiedMarginLightThemeColor); + } + private static SerializedProperty s_PendingPropertyKeyboardHandling = null; private static SerializedProperty s_PendingPropertyDelete = null; @@ -6955,7 +7033,7 @@ internal static void StreamTexture(Texture texture, Material mat, float mipLevel } // This will return appriopriate material to use with the texture according to its usage mode - internal static Material GetMaterialForSpecialTexture(Texture t, Material defaultMat = null, bool normals2Linear = false, bool useVTMaterialWhenPossible = true) + internal static Material GetMaterialForSpecialTexture(Texture t, Material defaultMat = null, bool manualTex2Linear = false, bool useVTMaterialWhenPossible = true) { bool useVT = useVTMaterialWhenPossible && UseVTMaterial(t); @@ -6970,14 +7048,20 @@ internal static Material GetMaterialForSpecialTexture(Texture t, Material defaul return lightmapDoubleLDRMaterial; else if (usage == TextureUsageMode.BakedLightmapFullHDR) return lightmapFullHDRMaterial; - else if (usage == TextureUsageMode.NormalmapDXT5nm || (usage == TextureUsageMode.NormalmapPlain && format == TextureFormat.BC5)) + else if (TextureUtil.IsNormalMapUsageMode(usage)) { var normalMat = useVT ? normalmapVTMaterial : normalmapMaterial; - normalMat.SetFloat("_ManualTex2Linear", normals2Linear ? 1.0f : 0.0f); + normalMat.SetFloat("_IsPlainNormalmap", usage == TextureUsageMode.NormalmapPlain && format != TextureFormat.BC5 ? 1.0f : 0.0f); + normalMat.SetFloat("_ManualTex2Linear", manualTex2Linear ? 1.0f : 0.0f); return normalMat; } else if (TextureUtil.IsAlphaOnlyTextureFormat(format)) - return useVT ? alphaVTMaterial : alphaMaterial; + { + var alphaOnlyMat = useVT ? alphaVTMaterial : alphaMaterial; + alphaOnlyMat.SetFloat("_ManualTex2Linear", manualTex2Linear ? 1.0f : 0.0f); + return alphaOnlyMat; + } + return defaultMat; } @@ -7052,16 +7136,32 @@ internal static Material guiTextureClipVerticallyMaterial internal static void SetExpandedRecurse(SerializedProperty property, bool expanded) { SerializedProperty search = property.Copy(); - search.isExpanded = expanded; - int depth = search.depth; - while (search.NextVisible(true) && search.depth > depth) + HashSet visited = null; + bool visitChild; + do { - if (search.hasVisibleChildren) + visitChild = true; + if (search.propertyType == SerializedPropertyType.ManagedReference) + { + // Managed reference objects can form a cyclical graph, so need to track visited objects + if (visited == null) + visited = new HashSet(); + + long refId = search.managedReferenceId; + if (!visited.Add(refId)) + { + visitChild = false; + continue; + } + } + + if (depth == search.depth || search.hasVisibleChildren) { search.isExpanded = expanded; } } + while (search.NextVisible(visitChild) && search.depth > depth); } // Get the height needed for a ::ref::PropertyField control, not including its children and not taking custom PropertyDrawers into account. @@ -7076,7 +7176,7 @@ internal static float GetSinglePropertyHeight(SerializedProperty property, GUICo public static float GetPropertyHeight(SerializedPropertyType type, GUIContent label) { if (type == SerializedPropertyType.Vector3 || type == SerializedPropertyType.Vector2 || type == SerializedPropertyType.Vector4 || - type == SerializedPropertyType.Vector3Int || type == SerializedPropertyType.Vector2Int) + type == SerializedPropertyType.Vector3Int || type == SerializedPropertyType.Vector2Int || type == SerializedPropertyType.Quaternion) { return (!LabelHasContent(label) || EditorGUIUtility.wideMode ? 0f : kStructHeaderLineHeight + kVerticalSpacingMultiField) + kSingleLineHeight; @@ -7400,6 +7500,10 @@ internal static bool DefaultPropertyField(Rect position, SerializedProperty prop } } } + else if (type == SerializedPropertyType.Quaternion) + { + QuaternionEulerField(position, property, label); + } // Handle Foldout else { @@ -8606,6 +8710,7 @@ internal static class EnumNamesCache static Dictionary s_SerializedPropertyEnumLocalizedGUIContents = new Dictionary(); static Dictionary s_IsEnumTypeUsingFlagsAttribute = new Dictionary(); static Dictionary s_SerializedPropertyEnumDisplayNames = new Dictionary(); + static Dictionary s_SerializedPropertyEnumNames = new Dictionary(); internal static GUIContent[] GetEnumTypeLocalizedGUIContents(Type enumType, EnumData enumData) { @@ -8652,6 +8757,12 @@ internal static string[] GetEnumDisplayNames(SerializedProperty property) Type enumType; ScriptAttributeUtility.GetFieldInfoFromProperty(property, out enumType); + // For native properties, like Camera.m_ClearFlags, the enumType returned will be null. + // So we can't use it for the dict key below. Need to early out. + // Case: 1388694 + if (enumType == null) + return property.enumDisplayNames; + string[] result; if (!s_SerializedPropertyEnumDisplayNames.TryGetValue(enumType, out result)) { @@ -8661,6 +8772,20 @@ internal static string[] GetEnumDisplayNames(SerializedProperty property) return result; } + internal static string[] GetEnumNames(SerializedProperty property) + { + Type enumType; + ScriptAttributeUtility.GetFieldInfoFromProperty(property, out enumType); + + string[] result; + if (!s_SerializedPropertyEnumNames.TryGetValue(enumType, out result)) + { + result = property.enumNames; + s_SerializedPropertyEnumNames.Add(enumType, result); + } + return result; + } + internal static bool IsEnumTypeUsingFlagsAttribute(Type type) { bool result; @@ -10944,6 +11069,7 @@ static Rect GetTabRect(Rect rect, int tabIndex, int tabCount, out GUIStyle tabSt { 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"; diff --git a/Editor/Mono/EditorGUIUtility.cs b/Editor/Mono/EditorGUIUtility.cs index a427ee65c4..061998144d 100644 --- a/Editor/Mono/EditorGUIUtility.cs +++ b/Editor/Mono/EditorGUIUtility.cs @@ -408,6 +408,7 @@ internal class EditorLockTracker /// [SerializeField, HideInInspector] bool m_IsLocked; + PingData m_Ping = new PingData(); internal virtual bool isLocked { @@ -439,19 +440,55 @@ internal virtual void AddItemsToMenu(GenericMenu menu, bool disabled = false) } } - internal void ShowButton(Rect position, GUIStyle lockButtonStyle, bool disabled = false) + internal virtual void PingIcon() + { + m_Ping.isPinging = true; + + if (m_Ping.m_PingStyle == null) + { + m_Ping.m_PingStyle = new GUIStyle("TV Ping"); + + // The default padding is too high for such a small icon and causes the animation to become offset to the left. + m_Ping.m_PingStyle.padding = new RectOffset(8, 0, 0, 0); + } + } + + internal virtual void StopPingIcon() + { + m_Ping.isPinging = false; + } + + internal bool ShowButton(Rect position, GUIStyle lockButtonStyle, bool disabled = false) { using (new EditorGUI.DisabledScope(disabled)) { EditorGUI.BeginChangeCheck(); bool newLock = GUI.Toggle(position, isLocked, GUIContent.none, lockButtonStyle); + if (m_Ping.isPinging && Event.current.type == EventType.Layout) + { + m_Ping.m_ContentRect = position; + m_Ping.m_ContentRect.width *= 2f; + m_Ping.m_AvailableWidth = GUIView.current.position.width; + + m_Ping.m_ContentDraw = r => + { + GUI.Toggle(r, newLock, GUIContent.none, lockButtonStyle); + }; + } + + m_Ping.HandlePing(); + if (EditorGUI.EndChangeCheck()) { if (newLock != isLocked) + { FlipLocked(); + m_Ping.isPinging = false; + } } } + return m_Ping.isPinging; } void FlipLocked() diff --git a/Editor/Mono/EditorHandles/Button.cs b/Editor/Mono/EditorHandles/Button.cs index 321f7fc0f6..ec224a36ed 100644 --- a/Editor/Mono/EditorHandles/Button.cs +++ b/Editor/Mono/EditorHandles/Button.cs @@ -10,6 +10,22 @@ namespace UnityEditorInternal { internal class Button { + internal static bool Do(int id, Vector3 position, Quaternion direction, float size, float pickSize, Handles.CapFunction capFunction, bool useLayoutAndMouseMove) + { + if(!useLayoutAndMouseMove) + { + Event evt = Event.current; + if(evt.type == EventType.Layout || evt.type == EventType.MouseMove) + { + //Consuming event id + evt.GetTypeForControl(id); + return false; + } + } + + return Do(id, position, direction, size, pickSize, capFunction); + } + public static bool Do(int id, Vector3 position, Quaternion direction, float size, float pickSize, Handles.CapFunction capFunction) { Event evt = Event.current; diff --git a/Editor/Mono/EditorHandles/FreeMove.cs b/Editor/Mono/EditorHandles/FreeMove.cs index 2aff41d253..c19e283e72 100644 --- a/Editor/Mono/EditorHandles/FreeMove.cs +++ b/Editor/Mono/EditorHandles/FreeMove.cs @@ -55,7 +55,6 @@ public static Vector3 Do(int id, Vector3 position, float size, Vector3 snap, Han if (GUIUtility.hotControl == id) { bool rayDrag = EditorGUI.actionKey && evt.shift; - if (rayDrag) { if (HandleUtility.ignoreRaySnapObjects == null) @@ -64,7 +63,7 @@ public static Vector3 Do(int id, Vector3 position, float size, Vector3 snap, Han if (HandleUtility.PlaceObject(evt.mousePosition, out Vector3 point, out Vector3 normal)) { float offset = 0; - if (Tools.pivotMode == PivotMode.Center) + if (Tools.pivotMode == PivotMode.Center && !Tools.vertexDragging) { float geomOffset = HandleUtility.CalcRayPlaceOffset(HandleUtility.ignoreRaySnapObjects, normal); if (geomOffset != Mathf.Infinity) @@ -100,7 +99,7 @@ public static Vector3 Do(int id, Vector3 position, float size, Vector3 snap, Han position = Handles.inverseMatrix.MultiplyPoint(near); } } - + else if (EditorSnapSettings.incrementalSnapActive && !evt.shift) { Vector3 delta = position - s_StartPosition; diff --git a/Editor/Mono/EditorHandles/PositionHandle.cs b/Editor/Mono/EditorHandles/PositionHandle.cs index 34268079b7..15fbff9304 100644 --- a/Editor/Mono/EditorHandles/PositionHandle.cs +++ b/Editor/Mono/EditorHandles/PositionHandle.cs @@ -9,8 +9,14 @@ namespace UnityEditor { public sealed partial class Handles { - internal struct PositionHandleIds + /// + /// Set of IDs corresponding to the handles of Handles.PositionHandle. + /// + public struct PositionHandleIds { + /// + /// Default set of IDs to pass to Handles.PositionHandle. + /// public static PositionHandleIds @default { get @@ -29,7 +35,7 @@ public static PositionHandleIds @default public readonly int x, y, z, xy, yz, xz, xyz; - public int this[int index] + internal int this[int index] { get { @@ -47,7 +53,7 @@ public int this[int index] } } - public bool Has(int id) + internal bool Has(int id) { return x == id || y == id @@ -58,7 +64,7 @@ public bool Has(int id) || xyz == id; } - public PositionHandleIds(int x, int y, int z, int xy, int xz, int yz, int xyz) + internal PositionHandleIds(int x, int y, int z, int xy, int xz, int yz, int xyz) { this.x = x; this.y = y; @@ -184,7 +190,9 @@ internal static Vector3 DoPositionHandle(PositionHandleIds ids, Vector3 position { case EventType.KeyDown: // Holding 'V' turns on the FreeMove transform gizmo and enables vertex snapping. - if (evt.keyCode == KeyCode.V && !currentlyDragging) + if (evt.keyCode == Tools.vertexDraggingShortcutEvent.keyCode + && evt.modifiers == Tools.vertexDraggingShortcutEvent.modifiers + && !currentlyDragging) { s_FreeMoveMode = true; } @@ -197,13 +205,19 @@ internal static Vector3 DoPositionHandle(PositionHandleIds ids, Vector3 position // implementation is a fair bit simpler this way. // Basic idea: Leave this call above the 'if' statement. position = DoPositionHandle_Internal(ids, position, rotation, PositionHandleParam.DefaultHandle); - if (evt.keyCode == KeyCode.V && !evt.shift && !currentlyDragging) + if (evt.keyCode == Tools.vertexDraggingShortcutEvent.keyCode + && evt.modifiers == Tools.vertexDraggingShortcutEvent.modifiers + && !evt.shift && !currentlyDragging) { s_FreeMoveMode = false; } return position; case EventType.Layout: + if (Tools.vertexDragging && !s_FreeMoveMode) + { + s_FreeMoveMode = true; + } if (!currentlyDragging && !Tools.vertexDragging) { s_FreeMoveMode = evt.shift; diff --git a/Editor/Mono/EditorHandles/RotationHandle.cs b/Editor/Mono/EditorHandles/RotationHandle.cs index 6704592a2e..4a1173da2e 100644 --- a/Editor/Mono/EditorHandles/RotationHandle.cs +++ b/Editor/Mono/EditorHandles/RotationHandle.cs @@ -10,8 +10,14 @@ namespace UnityEditor { public sealed partial class Handles { - internal struct RotationHandleIds + /// + /// Set of IDs corresponding to the handles of Handles.RotationHandle. + /// + public struct RotationHandleIds { + /// + /// Default set of IDs to pass to Handles.RotationHandle. + /// public static RotationHandleIds @default { get @@ -28,7 +34,7 @@ public static RotationHandleIds @default public readonly int x, y, z, cameraAxis, xyz; - public int this[int index] + internal int this[int index] { get { @@ -44,7 +50,7 @@ public int this[int index] } } - public bool Has(int id) + internal bool Has(int id) { return x == id || y == id @@ -53,7 +59,7 @@ public bool Has(int id) || xyz == id; } - public RotationHandleIds(int x, int y, int z, int cameraAxis, int xyz) + internal RotationHandleIds(int x, int y, int z, int cameraAxis, int xyz) { this.x = x; this.y = y; diff --git a/Editor/Mono/EditorHandles/SliderScale.cs b/Editor/Mono/EditorHandles/SliderScale.cs index cdf8fb0b2e..b30ea06703 100644 --- a/Editor/Mono/EditorHandles/SliderScale.cs +++ b/Editor/Mono/EditorHandles/SliderScale.cs @@ -81,7 +81,7 @@ internal static float DoAxis(int id, float scale, Vector3 position, Vector3 dire break; case EventType.MouseUp: - if (GUIUtility.hotControl == id && (evt.button == 0 || evt.button == 2)) + if (GUIUtility.hotControl == id) { GUIUtility.hotControl = 0; evt.Use(); @@ -169,7 +169,7 @@ public static float DoCenter(int id, float value, Vector3 position, Quaternion r break; case EventType.MouseUp: - if (GUIUtility.hotControl == id && (evt.button == 0 || evt.button == 2)) + if (GUIUtility.hotControl == id) { GUIUtility.hotControl = 0; Tools.UnlockHandlePosition(); diff --git a/Editor/Mono/EditorHandles/VertexSnapping.cs b/Editor/Mono/EditorHandles/VertexSnapping.cs index c51b3f74d7..230dba08c2 100644 --- a/Editor/Mono/EditorHandles/VertexSnapping.cs +++ b/Editor/Mono/EditorHandles/VertexSnapping.cs @@ -9,9 +9,11 @@ namespace UnityEditor { internal class VertexSnapping { + internal const string k_VertexSnappingShortcut = "Scene View/Vertex Snapping"; + internal const string k_VertexSnappingToggleShortcut = "Scene View/Toggle Vertex Snapping"; private static Vector3 s_VertexSnappingOffset = Vector3.zero; - [Shortcut("Scene View/Toggle Vertex Snapping", typeof(SceneView), KeyCode.V, ShortcutModifiers.Shift)] + [Shortcut(k_VertexSnappingToggleShortcut, typeof(SceneView), KeyCode.V, ShortcutModifiers.Shift)] private static void ToggleVertexSnappingViaShortcut() { int id = GUIUtility.hotControl; @@ -21,7 +23,7 @@ private static void ToggleVertexSnappingViaShortcut() EnableVertexSnapping(id); } - [ClutchShortcut("Scene View/Vertex Snapping", typeof(SceneView), KeyCode.V)] + [ClutchShortcut(k_VertexSnappingShortcut, typeof(SceneView), KeyCode.V)] private static void ToggleVertexSnappingViaClutchShortcut(ShortcutArguments arguments) { int id = GUIUtility.hotControl; @@ -62,7 +64,7 @@ public static void HandleMouseMove(int id) private static void EnableVertexSnapping(int id) { Tools.vertexDragging = true; - if (GUIUtility.hotControl == id) + if (GUIUtility.hotControl != 0 && GUIUtility.hotControl == id) { Tools.handleOffset = s_VertexSnappingOffset; } @@ -73,6 +75,14 @@ private static void EnableVertexSnapping(int id) } } + //Used in tests + internal static void DisableVertexSnapping_Internal() + { + Tools.vertexDragging = false; + Tools.handleOffset = Vector3.zero; + s_VertexSnappingOffset = Vector3.zero; + } + private static void DisableVertexSnapping(int id) { Tools.vertexDragging = false; @@ -85,6 +95,16 @@ private static void DisableVertexSnapping(int id) private static void UpdateVertexSnappingOffset() { Event evt = Event.current; + + // If pressing ctrl/cmd key while using vertex snapping, + // then the pivot should be used to snap on vertices + if(EditorGUI.actionKey &&! evt.shift) + { + Tools.InvalidateHandlePosition(); + Tools.handleOffset = Vector3.zero; + return; + } + Tools.vertexDragging = true; Vector3 nearestVertex; Transform[] selection = Selection.GetTransforms(SelectionMode.Deep | SelectionMode.ExcludePrefab | SelectionMode.Editable); diff --git a/Editor/Mono/EditorMode/ModeService.cs b/Editor/Mono/EditorMode/ModeService.cs index c1b50b0b61..89384e9293 100644 --- a/Editor/Mono/EditorMode/ModeService.cs +++ b/Editor/Mono/EditorMode/ModeService.cs @@ -546,6 +546,17 @@ static private IList GetContextMenu(string menuId) return contextMenus[menuId] as IList; } + internal static bool HasDynamicLayout(int modeIndex = -1) + { + var layoutData = GetModeDataSection(modeIndex == -1 ? currentIndex : modeIndex, ModeDescriptor.LayoutKey) as JSONObject; + return layoutData != null; + } + + internal static JSONObject GetDynamicLayout(int modeIndex = -1) + { + return GetModeDataSection(modeIndex == -1 ? currentIndex : modeIndex, ModeDescriptor.LayoutKey) as JSONObject; + } + private static void BuildContextMenu(IList menus, string menuId, GenericMenu contextMenu, string prefix = "") { if (menus == null) @@ -644,7 +655,11 @@ private static void OnModeChangeLayouts(ModeChangedArgs args) if (HasCapability(ModeCapability.LayoutSwitching, true)) { - WindowLayout.SaveCurrentLayoutPerMode(GetModeId(args.prevIndex)); + var dynamicLayout = GetDynamicLayout(args.prevIndex); + if (dynamicLayout == null || Convert.ToBoolean(dynamicLayout["restore_saved_layout"])) + { + WindowLayout.SaveCurrentLayoutPerMode(GetModeId(args.prevIndex)); + } try { diff --git a/Editor/Mono/EditorSceneManager.bindings.cs b/Editor/Mono/EditorSceneManager.bindings.cs index e13a85b2de..a8d1249749 100644 --- a/Editor/Mono/EditorSceneManager.bindings.cs +++ b/Editor/Mono/EditorSceneManager.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 System; using System.Collections.Generic; using System.Linq; using UnityEngine; @@ -33,13 +34,6 @@ public sealed partial class EditorSceneManager : SceneManager [NativeMethod("IsReloading")] public extern static bool IsReloading(Scene scene); - public extern static int loadedSceneCount - { - [StaticAccessor("GetSceneManager()", StaticAccessorType.Dot)] - [NativeMethod("GetLoadedSceneCount")] - get; - } - public extern static int loadedRootSceneCount { [StaticAccessor("GetSceneManager()", StaticAccessorType.Dot)] diff --git a/Editor/Mono/EditorSceneManager.bindings.deprecated.cs b/Editor/Mono/EditorSceneManager.bindings.deprecated.cs new file mode 100644 index 0000000000..c6e3faa8ec --- /dev/null +++ b/Editor/Mono/EditorSceneManager.bindings.deprecated.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 + +using System; +using UnityEngine.SceneManagement; + +namespace UnityEditor.SceneManagement +{ + public sealed partial class EditorSceneManager : SceneManager + { + [Obsolete("EditorSceneManager.loadedSceneCount has been deprecated. Please use SceneManager.loadedSceneCount (UnityUpgradable) -> [UnityEngine] UnityEngine.SceneManagement.SceneManager.loadedSceneCount")] + new public static int loadedSceneCount + { + get => SceneManager.loadedSceneCount; + } + } +} diff --git a/Editor/Mono/EditorSerializationUtility.bindings.cs b/Editor/Mono/EditorSerializationUtility.bindings.cs index a48de00d4f..30399e0c87 100644 --- a/Editor/Mono/EditorSerializationUtility.bindings.cs +++ b/Editor/Mono/EditorSerializationUtility.bindings.cs @@ -3,6 +3,8 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using UnityEngine; +using UnityEngine.Serialization; using UnityEngine.Bindings; using UnityObject = UnityEngine.Object; using RefId = System.Int64; @@ -51,49 +53,33 @@ public int CompareTo(ManagedReferenceMissingType other) public sealed class SerializationUtility { // Must match the same declarations in "Runtime/Serialize/ReferenceId.h" + [System.Obsolete("Use Serialization.ManagedReferenceUtility.RefIdUnknown instead. (UnityUpgradable) -> [UnityEngine] UnityEngine.Serialization.ManagedReferenceUtility.RefIdUnknown", false)] public const RefId RefIdUnknown = -1; + [System.Obsolete("Use Serialization.ManagedReferenceUtility.RefIdNull instead. (UnityUpgradable) -> [UnityEngine] UnityEngine.Serialization.ManagedReferenceUtility.RefIdNull", false)] public const RefId RefIdNull = -2; - [NativeMethod("SetManagedReferenceIdForObject")] - static extern bool SetManagedReferenceIdForObjectInternal(UnityObject obj, object scriptObj, RefId refId); - + [System.Obsolete("Use Serialization.ManagedReferenceUtility.SetManagedReferenceIdForObject instead. (UnityUpgradable) -> [UnityEngine] UnityEngine.Serialization.ManagedReferenceUtility.SetManagedReferenceIdForObject(*)", false)] public static bool SetManagedReferenceIdForObject(UnityObject obj, object scriptObj, RefId refId) { - if (scriptObj == null) - return refId == RefIdNull; // There is no need to explicitly register RefIdNull - - var valueType = scriptObj.GetType(); - if (valueType == typeof(UnityObject) || valueType.IsSubclassOf(typeof(UnityObject))) - { - throw new System.InvalidOperationException( - $"Cannot assign an object deriving from UnityEngine.Object to a managed reference. This is not supported."); - } - - return SetManagedReferenceIdForObjectInternal(obj, scriptObj, refId); + return ManagedReferenceUtility.SetManagedReferenceIdForObject(obj, scriptObj, refId); } - [NativeMethod("GetManagedReferenceIdForObject")] - static extern RefId GetManagedReferenceIdForObjectInternal(UnityObject obj, object scriptObj); - + [System.Obsolete("Use Serialization.ManagedReferenceUtility::GetManagedReferenceIdForObject instead. (UnityUpgradable) -> [UnityEngine] UnityEngine.Serialization.ManagedReferenceUtility.GetManagedReferenceIdForObject(*)", false)] public static RefId GetManagedReferenceIdForObject(UnityObject obj, object scriptObj) { - return GetManagedReferenceIdForObjectInternal(obj, scriptObj); + return ManagedReferenceUtility.GetManagedReferenceIdForObject(obj, scriptObj); } - [NativeMethod("GetManagedReference")] - static extern object GetManagedReferenceInternal(UnityObject obj, RefId id); - + [System.Obsolete("Use Serialization.ManagedReferenceUtility::GetManagedReference instead. (UnityUpgradable) -> [UnityEngine] UnityEngine.Serialization.ManagedReferenceUtility.GetManagedReference(*)", false)] public static object GetManagedReference(UnityObject obj, RefId id) { - return GetManagedReferenceInternal(obj, id); + return ManagedReferenceUtility.GetManagedReference(obj, id); } - [NativeMethod("GetManagedReferenceIds")] - static extern RefId[] GetManagedReferenceIdsForObjectInternal(UnityObject obj); - + [System.Obsolete("Use Serialization.ManagedReferenceUtility::GetManagedReferencesIds instead. (UnityUpgradable) -> [UnityEngine] UnityEngine.Serialization.ManagedReferenceUtility.GetManagedReferenceIds(*)", false)] public static RefId[] GetManagedReferenceIds(UnityObject obj) { - return GetManagedReferenceIdsForObjectInternal(obj); + return ManagedReferenceUtility.GetManagedReferenceIds(obj); } [NativeMethod("HasManagedReferencesWithMissingTypes")] diff --git a/Editor/Mono/EditorSettings.bindings.cs b/Editor/Mono/EditorSettings.bindings.cs index 5689447634..15a342512b 100644 --- a/Editor/Mono/EditorSettings.bindings.cs +++ b/Editor/Mono/EditorSettings.bindings.cs @@ -305,5 +305,8 @@ public enum NamingScheme public static extern NamingScheme gameObjectNamingScheme { get; set; } [StaticAccessor("GetEditorSettings()", StaticAccessorType.Dot)] public static extern bool assetNamingUsesSpace { get; set; } + + [StaticAccessor("GetEditorSettings()", StaticAccessorType.Dot)] + internal static extern bool inspectorUseIMGUIDefaultInspector { get; set; } } } diff --git a/Editor/Mono/EditorUIService.cs b/Editor/Mono/EditorUIService.cs deleted file mode 100644 index 31f7ffc440..0000000000 --- a/Editor/Mono/EditorUIService.cs +++ /dev/null @@ -1,75 +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 UnityEditor.ShortcutManagement; -using UnityEditor.UIElements; -using UnityEditorInternal; -using UnityEngine; -using UnityEngine.UIElements; - -namespace UnityEditor -{ - internal interface IEditorUIService - { - // Editor window - IWindowBackend GetDefaultWindowBackend(IWindowModel model); - - // Toolbar - Type GetDefaultToolbarType(); - void AddSubToolbar(SubToolbar subToolbar); - - // Inspector - IEditorElement CreateEditorElement(int editorIndex, IPropertyView iw, string title); - IEditorElement CreateCulledEditorElement(int editorIndex, IPropertyView iw, string title); - - // PackageManagerUI - void PackageManagerOpen(); - - // ShortcutManager - IShortcutManagerWindowView CreateShortcutManagerWindowView(IShortcutManagerWindowViewController viewController, IKeyBindingStateProvider bindingStateProvider); - - // Progress - void ProgressWindowShowDetails(bool shouldReposition); - void ProgressWindowHideDetails(); - bool ProgressWindowCanHideDetails(); - - // UIToolkit - void AddDefaultEditorStyleSheets(VisualElement ve); - - VisualElement CreateUnityEventItem(); - void BindUnityEventItem(VisualElement item, UnityEventDrawer.PropertyData propertyData, Func createMenuCallback, Func formatSelectedValueCallback, Func getArgumentCallback); - BindableElement CreateObjectField(); - void SetObjectField(VisualElement element, UnityEngine.Object value, Type type, string label); - ListView CreateListViewBinding(SerializedObject serializedObject); - VisualElement CreateDropdownField(string[] options, Func getSelectedChoiceCallback); - VisualElement CreatePropertyField(SerializedProperty serializedProperty, string label); - BindableElement CreateFloatField(string name, Func onValidateValue = null, bool isDelayed = false); - BindableElement CreateDoubleField(string name, Func onValidateValue = null, bool isDelayed = false); - BindableElement CreateIntField(string name, Func onValidateValue = null, bool isDelayed = false); - BindableElement CreateLongField(string name, Func onValidateValue = null, bool isDelayed = false); - BindableElement CreateVector2Field(string name, Func onValidateValue); - BindableElement CreateVector2IntField(string name, Func onValidateValue); - BindableElement CreateVector3Field(string name, Func onValidateValue); - BindableElement CreateVector3IntField(string name, Func onValidateValue); - BindableElement CreateVector4Field(string name, Func onValidateValue); - BindableElement CreateTextField(string name = null, bool isMultiLine = false, bool isDelayed = false); - BindableElement CreateColorField(string name, bool showAlpha, bool hdr); - BindableElement CreateGradientField(string name, bool hdr, ColorSpace colorSpace); - - string GetUIToolkitDefaultCommonDarkStyleSheetPath(); - string GetUIToolkitDefaultCommonLightStyleSheetPath(); - - StyleSheet GetUIToolkitDefaultCommonDarkStyleSheet(); - StyleSheet GetUIToolkitDefaultCommonLightStyleSheet(); - StyleSheet CompileStyleSheetContent(string styleSheetContent, bool disableValidation, bool reportErrors); - } - - internal static class EditorUIService - { - public static IEditorUIService instance { get; set; } - - public static bool disableInspectorElementThrottling { get; set; } = false; - } -} diff --git a/Editor/Mono/EditorUserBuildSettings.bindings.cs b/Editor/Mono/EditorUserBuildSettings.bindings.cs index b52dd43bd7..9d51c22bc9 100644 --- a/Editor/Mono/EditorUserBuildSettings.bindings.cs +++ b/Editor/Mono/EditorUserBuildSettings.bindings.cs @@ -16,6 +16,9 @@ public enum StandaloneBuildSubtarget { Player = 0, Server = 1, + // *undocumented* + [System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)] + NoSubtarget = -1, } namespace Build @@ -304,12 +307,28 @@ internal struct SwitchShaderCompilerConfig internal bool triggerGraphicsDebuggersConfigUpdate; } + // *undocumented* + [NativeType(Header = "Editor/Src/EditorUserBuildSettings.h")] + public enum SwitchRomCompressionType + { + None = 0, + Lz4 = 1, + } + + [NativeType(Header = "Editor/Src/EditorUserBuildSettings.h")] public enum EmbeddedLinuxArchitecture { + [UnityEngine.InspectorName("Arm64")] Arm64 = 0, + + [UnityEngine.InspectorName("Arm32")] Arm32 = 1, + + [UnityEngine.InspectorName("X64")] X64 = 2, + + [UnityEngine.InspectorName("X86")] X86 = 3, } @@ -318,6 +337,7 @@ public enum EmbeddedLinuxArchitecture public partial class EditorUserBuildSettings : Object { private const string kSettingWaitForManagedDebugger = "WaitForManagedDebugger"; + private const string kSettingManagedDebuggerFixedPort = "ManagedDebuggerFixedPort"; private EditorUserBuildSettings() {} internal static extern AppleBuildAndRunType appleBuildAndRunType { get; set; } @@ -347,6 +367,13 @@ public static extern EmbeddedLinuxArchitecture selectedEmbeddedLinuxArchitecture set; } + // Embedded Linux remote device information + public static extern bool remoteDeviceInfo { get; set; } + public static extern string remoteDeviceAddress { get; set; } + public static extern string remoteDeviceUsername { get; set; } + public static extern string remoteDeviceExports { get; set; } + public static extern string pathOnRemoteDevice { get; set; } + // The currently selected target for a standalone build. public static extern BuildTarget selectedStandaloneTarget { @@ -684,10 +711,10 @@ public static string GetPlatformSettings(string platformName, string name) public static extern bool development { get; set; } [Obsolete("Use PlayerSettings.SetIl2CppCodeGeneration and PlayerSettings.GetIl2CppCodeGeneration instead.", true)] - public static Build.Il2CppCodeGeneration il2CppCodeGeneratione + public static Build.Il2CppCodeGeneration il2CppCodeGeneration { get { return Build.Il2CppCodeGeneration.OptimizeSpeed; } - set { Debug.LogWarning("EditorUserBuildSettings.il2CppCodeGeneratione is obsolete. Please use PlayerSettings.SetIl2CppCodeGeneration and PlayerSettings.GetIl2CppCodeGeneration instead." ); } + set { Debug.LogWarning("EditorUserBuildSettings.il2CppCodeGeneration is obsolete. Please use PlayerSettings.SetIl2CppCodeGeneration and PlayerSettings.GetIl2CppCodeGeneration instead." ); } } [Obsolete("Building with pre-built Engine option is no longer supported.", true)] @@ -758,6 +785,45 @@ public static extern bool switchCreateRomFile set; } + public static extern bool switchEnableRomCompression + { + [NativeMethod("GetEnableRomCompressionForSwitch")] + get; + [NativeMethod("SetEnableRomCompressionForSwitch")] + set; + } + + public static extern bool switchSaveADF + { + [NativeMethod("GetSaveADFForSwitch")] + get; + [NativeMethod("SetSaveADFForSwitch")] + set; + } + + public static extern SwitchRomCompressionType switchRomCompressionType + { + [NativeMethod("GetRomCompressionTypeForSwitch")] + get; + [NativeMethod("SetRomCompressionTypeForSwitch")] + set; + } + + public static extern int switchRomCompressionLevel + { + [NativeMethod("GetRomCompressionLevelForSwitch")] + get; + [NativeMethod("SetRomCompressionLevelForSwitch")] + set; + } + + public static extern string switchRomCompressionConfig + { + [NativeMethod("GetRomCompressionConfigForSwitch")] + get; + [NativeMethod("SetRomCompressionConfigForSwitch")] + set; + } // Enable linkage of NVN Graphics Debugger for Nintendo Switch. public static extern bool switchNVNGraphicsDebugger @@ -877,5 +943,24 @@ public static bool waitForManagedDebugger SetPlatformSettings("Editor", kSettingWaitForManagedDebugger, value.ToString().ToLower()); } } + + public static int managedDebuggerFixedPort + { + get + { + if (Int32.TryParse(GetPlatformSettings("Editor", kSettingManagedDebuggerFixedPort), out int value)) { + if (0 < value && value <= 65535) + { + return value; + } + } + return 0; + } + + set + { + SetPlatformSettings("Editor", kSettingManagedDebuggerFixedPort, value.ToString().ToLower()); + } + } } } diff --git a/Editor/Mono/EditorUtility.bindings.cs b/Editor/Mono/EditorUtility.bindings.cs index da35c60e93..2f50f701e5 100644 --- a/Editor/Mono/EditorUtility.bindings.cs +++ b/Editor/Mono/EditorUtility.bindings.cs @@ -10,11 +10,13 @@ using UnityEngine.Scripting; using System.Collections.Generic; using System.Linq; +using static UnityEditor.EditorGUI; namespace UnityEditor { [NativeHeader("Editor/Mono/EditorUtility.bindings.h")] [NativeHeader("Editor/Mono/MonoEditorUtility.h")] + [NativeHeader("Editor/Src/AssetPipeline/UnityExtensions.h")] [NativeHeader("Runtime/Shaders/ShaderImpl/ShaderUtilities.h")] partial class EditorUtility { @@ -32,7 +34,18 @@ public static string OpenFilePanelWithFilters(string title, string directory, st public static extern void RevealInFinder(string path); [FreeFunction("DisplayDialog")] - public static extern bool DisplayDialog(string title, string message, string ok, [DefaultValue("\"\"")] string cancel); + static extern bool DoDisplayDialog(string title, string message, string ok, [DefaultValue("\"\"")] string cancel); + + public static bool DisplayDialog(string title, string message, string ok, [DefaultValue("\"\"")] string cancel) + { + using (new DisabledGuiViewInputScope(GUIView.current, true)) + { + return DoDisplayDialog(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) @@ -42,6 +55,9 @@ 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); + [FreeFunction("RunOpenFolderPanel")] public static extern string OpenFolderPanel(string title, string folder, string defaultName); @@ -52,6 +68,10 @@ public static bool DisplayDialog(string title, string message, string ok) [FreeFunction("WarnPrefab")] public static extern bool WarnPrefab(Object target, string title, string warning, string okButton); + [StaticAccessor("UnityExtensions::Get()", StaticAccessorType.Dot)] + [NativeMethod("IsInitialized")] + public extern static bool IsUnityExtensionsInitialized(); + public static extern bool IsPersistent(Object target); public static extern string SaveFilePanel(string title, string directory, string defaultName, string extension); public static extern int NaturalCompare(string a, string b); diff --git a/Editor/Mono/EditorUtility.cs b/Editor/Mono/EditorUtility.cs index 00b7f51972..a5b7a3fc5b 100644 --- a/Editor/Mono/EditorUtility.cs +++ b/Editor/Mono/EditorUtility.cs @@ -581,6 +581,10 @@ internal static void DisplayObjectContextMenu(Rect position, Object[] context, i } } } + else if (context != null && context.Length == 1 && context[0] is Material) + { + MaterialEditor.AddAdditionalMaterialMenuItems(pm); + } pm.ObjectContextDropDown(position, context, contextUserData); diff --git a/Editor/Mono/EditorWindow.cs b/Editor/Mono/EditorWindow.cs index 9ba3e9b413..53ea658316 100644 --- a/Editor/Mono/EditorWindow.cs +++ b/Editor/Mono/EditorWindow.cs @@ -1132,6 +1132,13 @@ internal void SetDisplayViewSize(int displayId, Vector2 targetSize) m_Parent.SetDisplayViewSize(displayId, targetSize); } + internal Vector2 GetDisplayViewSize(int displayId) + { + if (m_Parent != null) + return m_Parent.GetDisplayViewSize(displayId); + return new Vector2(640, 480); + } + internal void SetPlayModeView(bool value) { m_IsPlayModeView = value; @@ -1297,7 +1304,7 @@ private static VisualElement CreateRoot() renderHints = RenderHints.ClipWithScissors }; root.pseudoStates |= PseudoStates.Root; - EditorUIService.instance.AddDefaultEditorStyleSheets(root); + UIElementsEditorUtility.AddDefaultEditorStyleSheets(root); root.style.overflow = UnityEngine.UIElements.Overflow.Hidden; return root; } @@ -1327,8 +1334,7 @@ public bool active } } - - [Shortcut("Overlays/Toggle All Overlays", typeof(OverlayShortcutContext), KeyCode.BackQuote)] + [Shortcut("Overlays/Toggle All Overlays", typeof(OverlayShortcutContext), KeyCode.BackQuote, ShortcutModifiers.Shift)] static void ToggleAllOverlays(ShortcutArguments args) { if (!(args.context is OverlayShortcutContext context)) @@ -1344,7 +1350,7 @@ static void HideOverlay(ShortcutArguments args) context.editorWindow.overlayCanvas.HideHoveredOverlay(); } - [Shortcut("Overlays/Show Overlay Menu", typeof(OverlayShortcutContext), KeyCode.Space)] + [Shortcut("Overlays/Show Overlay Menu", typeof(OverlayShortcutContext), KeyCode.BackQuote)] static void ShowOverlayMenu(ShortcutArguments args) { if(args.context is OverlayShortcutContext context) @@ -1365,6 +1371,15 @@ public bool TryGetOverlay(string id, out Overlay match) match = null; return false; } + + internal void OnBackingScaleFactorChangedInternal() + { + if(overlayCanvas != null) + overlayCanvas.Rebuild(); + OnBackingScaleFactorChanged(); + } + + protected virtual void OnBackingScaleFactorChanged() { } } [AttributeUsage(AttributeTargets.Class)] diff --git a/Editor/Mono/FileUtil.bindings.cs b/Editor/Mono/FileUtil.bindings.cs index 8136718e36..7ae78577ec 100644 --- a/Editor/Mono/FileUtil.bindings.cs +++ b/Editor/Mono/FileUtil.bindings.cs @@ -17,8 +17,16 @@ namespace UnityEditor public partial class FileUtil { // Deletes a file or a directory given a path. - [FreeFunction] - public static extern bool DeleteFileOrDirectory(string path); + public static bool DeleteFileOrDirectory(string path) + { + if (path is null) throw new ArgumentNullException("path"); + if (path == string.Empty) throw new ArgumentException("path", "The path cannot be empty."); + + return DeleteFileOrDirectoryInternal(path); + } + + [FreeFunction("DeleteFileOrDirectory")] + private static extern bool DeleteFileOrDirectoryInternal(string path); [FreeFunction("IsPathCreated")] private static extern bool PathExists(string path); diff --git a/Editor/Mono/FileUtil.cs b/Editor/Mono/FileUtil.cs index 87e0188043..95c3136dd0 100644 --- a/Editor/Mono/FileUtil.cs +++ b/Editor/Mono/FileUtil.cs @@ -210,6 +210,25 @@ internal static void UnityFileCopy(string from, string to, bool overwrite) File.Copy(NiceWinPath(from), NiceWinPath(to), overwrite); } + + internal static bool ReadFileContentBinary(string assetPath, out byte[] fileContent, out string errorMessage) + { + fileContent = null; + errorMessage = null; + string absolutePath = FileUtil.PathToAbsolutePath(assetPath); + try + { + fileContent = File.ReadAllBytes(absolutePath); + } + catch (Exception e) + { + fileContent = null; + errorMessage = e.Message; + } + + return fileContent != null; + } + internal static string NiceWinPath(string unityPath) { // IO functions do not like mixing of \ and / slashes, esp. for windows network paths (\\path) diff --git a/Editor/Mono/GI/Lightmapping.bindings.cs b/Editor/Mono/GI/Lightmapping.bindings.cs index 1335da13b8..4fb17a7da7 100644 --- a/Editor/Mono/GI/Lightmapping.bindings.cs +++ b/Editor/Mono/GI/Lightmapping.bindings.cs @@ -4,6 +4,7 @@ using System; using UnityEngine; +using UnityEngine.SceneManagement; using UnityEditor.SceneManagement; using UnityEngine.Bindings; using UnityEngine.Scripting; @@ -457,6 +458,11 @@ internal static void GetEnvironmentSamples(out Vector4[] outDirections, out Vect [FreeFunction] internal static extern void OnUpdateLightmapEncoding(BuildTargetGroup target); + // Called when the user changes the HDR Cubemap Encoding option, + // will reimport HDR cubemaps with the new encoding. + [FreeFunction] + internal static extern void OnUpdateHDRCubemapEncoding(BuildTargetGroup target); + // Called when the user changes the Lightmap streaming settings: [FreeFunction] internal static extern void OnUpdateLightmapStreaming(BuildTargetGroup target); @@ -568,7 +574,7 @@ public static void BakeMultipleScenes(string[] paths) EditorSceneManager.SceneOpenedCallback BakeOnAllOpen = null; BakeOnAllOpen = (UnityEngine.SceneManagement.Scene scene, SceneManagement.OpenSceneMode loadSceneMode) => { - if (EditorSceneManager.loadedSceneCount == paths.Length) + if (SceneManager.loadedSceneCount == paths.Length) { BakeAsync(); Lightmapping.bakeCompleted += OnBakeFinish; @@ -675,10 +681,36 @@ public sealed partial class Lightmapping public static extern bool probesIgnoreDirectEnvironment { get; set; } [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] - public static extern void SetCustomBakeInputs(Vector4[] inputData, int sampleCount); + private unsafe static extern void SetCustomBakeInputs([Span("inputDataLength", isReadOnly:true)]Vector4* inputData, int inputDataLength, int sampleCount); + + public static void SetCustomBakeInputs(Vector4[] inputData, int sampleCount) + { + SetCustomBakeInputs(inputData.AsSpan(), sampleCount); + } + public static unsafe void SetCustomBakeInputs(ReadOnlySpan inputData, int sampleCount) + { + fixed(Vector4* inputDataPtr = inputData) + { + SetCustomBakeInputs(inputDataPtr, inputData.Length, sampleCount); + } + } + + + [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] + private static unsafe extern bool GetCustomBakeResultsCopy([Span("resultsLength")]Vector4* results, int resultsLength); + public static unsafe bool GetCustomBakeResults(Span results) + { + fixed (Vector4* resultsPtr = results) { + return GetCustomBakeResultsCopy(resultsPtr, results.Length); + } + } + public static bool GetCustomBakeResults([Out] Vector4[] results) + { + return GetCustomBakeResults(results.AsSpan()); + } [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] - public static extern bool GetCustomBakeResults([Out] Vector4[] results); + public static extern ReadOnlySpan GetCustomBakeResultsNoCopy(); [Obsolete("UnityEditor.Experimental.Lightmapping.extractAmbientOcclusion is obsolete, use Lightmapping.lightingSettings.extractAO instead. ", false)] public static bool extractAmbientOcclusion @@ -715,6 +747,30 @@ public unsafe static bool GetAdditionalBakedProbes(int id, NativeArray outBakedProbeSH, Span outBakedProbeValidity, Span outBakedProbeOctahedralDepth) + { + const int octahedralDepthMapTexelCount = 64; // 8*8 + + int numEntries = outBakedProbeSH.Length; + + if (outBakedProbeOctahedralDepth.Length != numEntries * octahedralDepthMapTexelCount) + { + Debug.LogError("Octahedral array must provide " + numEntries * octahedralDepthMapTexelCount + " floats."); + return false; + } + + if (outBakedProbeValidity.Length != numEntries) + { + Debug.LogError("All output arrays must have equal size."); + return false; + } + fixed (void* shPtr = outBakedProbeSH) + fixed (void* validityPtr = outBakedProbeValidity) + fixed (void* octahedralDepthPtr = outBakedProbeOctahedralDepth) + { + return GetAdditionalBakedProbes(id, shPtr, validityPtr, octahedralDepthPtr, outBakedProbeSH.Length); + } + } public unsafe static bool GetAdditionalBakedProbes(int id, NativeArray outBakedProbeSH, NativeArray outBakedProbeValidity, NativeArray outBakedProbeOctahedralDepth) { if (outBakedProbeSH == null || !outBakedProbeSH.IsCreated || @@ -749,7 +805,19 @@ public unsafe static bool GetAdditionalBakedProbes(int id, NativeArray positions) + { + fixed(Vector3* positionsPtr = positions) + { + SetAdditionalBakedProbes(id, positionsPtr, positions.Length); + } + } [FreeFunction] public static extern void SetLightDirty(Light light); diff --git a/Editor/Mono/GUI/AppStatusBar.cs b/Editor/Mono/GUI/AppStatusBar.cs index 3ce7f068ee..67cea790e6 100644 --- a/Editor/Mono/GUI/AppStatusBar.cs +++ b/Editor/Mono/GUI/AppStatusBar.cs @@ -205,11 +205,11 @@ private void DrawRefreshStatus() } else { - var canHide = EditorUIService.instance.ProgressWindowCanHideDetails(); + var canHide = ProgressWindow.canHideDetails; if (GUILayout.Button(canHide ? Styles.progressHideIcon : Styles.progressIcon, Styles.statusIcon)) { if (canHide) - EditorUIService.instance.ProgressWindowHideDetails(); + ProgressWindow.HideDetails(); else Progress.ShowDetails(false); } diff --git a/Editor/Mono/GUI/ColorMutator.cs b/Editor/Mono/GUI/ColorMutator.cs index 76c9bfa28e..8ddf00633e 100644 --- a/Editor/Mono/GUI/ColorMutator.cs +++ b/Editor/Mono/GUI/ColorMutator.cs @@ -46,11 +46,9 @@ internal static void DecomposeHdrColor(Color linearColorHdr, out Color32 baseLin } } - public Color originalColor - { - get { return m_OriginalColor; } - } + public Color originalColor => m_OriginalColor; [SerializeField] private Color m_OriginalColor; + [SerializeField] private Color m_HDRBaseColor; public Color32 color { @@ -121,6 +119,7 @@ public void SetColorChannelHdr(RgbaChannel channel, float value) if (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); } @@ -157,6 +156,7 @@ public void SetColorChannel(HsvChannel channel, float value) m_ColorHdr[(int)RgbaChannel.R] = newColor.r; m_ColorHdr[(int)RgbaChannel.G] = newColor.g; m_ColorHdr[(int)RgbaChannel.B] = newColor.b; + m_HDRBaseColor = new Color(m_ColorHdr[0], m_ColorHdr[1], m_ColorHdr[2], m_ColorHdr[3]); } public float exposureValue @@ -167,7 +167,7 @@ public float exposureValue if (m_ExposureValue == value) return; m_ExposureValue = value; - var newRgbFloat = (Color)color * Mathf.Pow(2f, m_ExposureValue); + 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; @@ -178,6 +178,7 @@ public float exposureValue public ColorMutator(Color originalColor) { m_OriginalColor = originalColor; + m_HDRBaseColor = originalColor; Reset(); } @@ -190,6 +191,7 @@ public void Reset() m_ColorHdr[(int)RgbaChannel.G] = m_OriginalColor.g; m_ColorHdr[(int)RgbaChannel.B] = m_OriginalColor.b; 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]; diff --git a/Editor/Mono/GUI/EditorApplicationLayout.cs b/Editor/Mono/GUI/EditorApplicationLayout.cs index e284d0ee04..ea770e9a8b 100644 --- a/Editor/Mono/GUI/EditorApplicationLayout.cs +++ b/Editor/Mono/GUI/EditorApplicationLayout.cs @@ -134,11 +134,6 @@ static internal void InitPlaymodeLayout() playModeView.m_Parent.SetAsStartView(); playModeView.m_Parent.SetAsLastPlayModeView(); - if (playModeView.maximized) - { - playModeView.m_Parent.Focus(); - } - if (playModeView is IGameViewOnPlayMenuUser) { if (((IGameViewOnPlayMenuUser)playModeView).playFocused) @@ -155,11 +150,13 @@ static internal void FinalizePlaymodeLayout() { foreach (var playModeView in m_PlayModeViewList) { - if (playModeView != null && playModeView.enterPlayModeBehavior == PlayModeView.EnterPlayModeBehavior.PlayMaximized) + if (playModeView != null) { if (m_MaximizePending) WindowLayout.MaximizePresent(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(); } } diff --git a/Editor/Mono/GUI/GradientEditor.cs b/Editor/Mono/GUI/GradientEditor.cs index 0f036f273d..aea7c9bd30 100644 --- a/Editor/Mono/GUI/GradientEditor.cs +++ b/Editor/Mono/GUI/GradientEditor.cs @@ -28,6 +28,19 @@ static GUIStyle GetStyle(string name) GUISkin s = (GUISkin)EditorGUIUtility.LoadRequired("GradientEditor.GUISkin"); return s.GetStyle(name); } + + public readonly GUIContent[] modeTexts = + { + EditorGUIUtility.TrTextContent("Blend (Classic)"), + EditorGUIUtility.TrTextContent("Blend (Perceptual)"), + EditorGUIUtility.TrTextContent("Fixed") + }; + public readonly int[] modeValues = + { + (int)GradientMode.Blend, + (int)GradientMode.PerceptualBlend, + (int)GradientMode.Fixed + }; } static Styles s_Styles; static Texture2D s_BackgroundTexture; @@ -155,7 +168,7 @@ public void OnGUI(Rect position) float gradientTextureHeight = position.height - 2 * swatchHeight - editSectionHeight - modeHeight; position.height = modeHeight; - m_GradientMode = (GradientMode)EditorGUI.EnumPopup(position, s_Styles.modeText, m_GradientMode); + m_GradientMode = (GradientMode)EditorGUI.IntPopup(position, s_Styles.modeText, (int)m_GradientMode, s_Styles.modeTexts, s_Styles.modeValues); if (m_GradientMode != m_Gradient.mode) AssignBack(); @@ -471,6 +484,7 @@ void AssignBack() m_Gradient.colorKeys = colorKeys; m_Gradient.alphaKeys = alphaKeys; m_Gradient.mode = m_GradientMode; + m_Gradient.colorSpace = m_ColorSpace; GUI.changed = true; } diff --git a/Editor/Mono/GUI/GradientPicker.cs b/Editor/Mono/GUI/GradientPicker.cs index cf13ddf013..3919ba7850 100644 --- a/Editor/Mono/GUI/GradientPicker.cs +++ b/Editor/Mono/GUI/GradientPicker.cs @@ -43,7 +43,7 @@ internal class GradientPicker : EditorWindow // Static methods public static void Show(Gradient newGradient, bool hdr, ColorSpace colorSpace = ColorSpace.Gamma) { - Show(newGradient, hdr, ColorSpace.Gamma, null, GUIView.current); + Show(newGradient, hdr, colorSpace, null, GUIView.current); } public static void Show(Gradient newGradient, bool hdr, System.Action onGradientChanged) @@ -53,7 +53,7 @@ public static void Show(Gradient newGradient, bool hdr, System.Action public static void Show(Gradient newGradient, bool hdr, ColorSpace colorSpace, System.Action onGradientChanged) { - Show(newGradient, hdr, ColorSpace.Gamma, onGradientChanged, null); + Show(newGradient, hdr, colorSpace, onGradientChanged, null); } private static void Show(Gradient newGradient, bool hdr, ColorSpace colorSpace, System.Action onGradientChanged, GUIView currentView) @@ -81,7 +81,7 @@ static void PrepareShow(bool hdr, ColorSpace colorSpace) s_GradientPicker.minSize = minSize; s_GradientPicker.maxSize = maxSize; s_GradientPicker.wantsMouseMove = true; - Undo.undoRedoPerformed += s_GradientPicker.OnUndoPerformed; + Undo.undoRedoEvent += s_GradientPicker.OnUndoPerformed; } s_GradientPicker.ShowAuxWindow(); // Use this if auto close on lost focus is wanted. @@ -286,10 +286,10 @@ public static void RepaintWindow() private void UnregisterEvents() { - Undo.undoRedoPerformed -= this.OnUndoPerformed; + Undo.undoRedoEvent -= this.OnUndoPerformed; } - private void OnUndoPerformed() + private void OnUndoPerformed(in UndoRedoInfo info) { this.Init(this.m_Gradient, this.m_HDR, this.m_ColorSpace); } diff --git a/Editor/Mono/GUI/LazyLoadReferenceField.cs b/Editor/Mono/GUI/LazyLoadReferenceField.cs index d94d2c43f7..31fed6affb 100644 --- a/Editor/Mono/GUI/LazyLoadReferenceField.cs +++ b/Editor/Mono/GUI/LazyLoadReferenceField.cs @@ -35,10 +35,10 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) { ScriptAttributeUtility.GetFieldInfoFromProperty(property, out var fieldType); - var objectField = EditorUIService.instance.CreateObjectField(); + var objectField = new UnityEditor.UIElements.ObjectField(property.displayName); var genericType = fieldType.GetGenericArguments()[0]; - - EditorUIService.instance.SetObjectField(objectField, property.objectReferenceValue, genericType, property.displayName); + objectField.objectType = genericType; + objectField.value = property.objectReferenceValue; objectField.bindingPath = property.propertyPath; return objectField; diff --git a/Editor/Mono/GUI/ObjectField.cs b/Editor/Mono/GUI/ObjectField.cs index c0c5faed64..0e160ecae6 100644 --- a/Editor/Mono/GUI/ObjectField.cs +++ b/Editor/Mono/GUI/ObjectField.cs @@ -161,12 +161,17 @@ static Object DoObjectField(Rect position, Rect dropRect, int id, Object obj, Ob return DoObjectField(position, dropRect, id, obj, objBeingEdited, objType, null, property, validator, allowSceneObjects, style, EditorStyles.objectFieldButton); } + static Object DoObjectField(Rect position, Rect dropRect, int id, Object obj, Object objBeingEdited, System.Type objType, System.Type additionalType, SerializedProperty property, ObjectFieldValidator validator, bool allowSceneObjects, GUIStyle style, Action onObjectSelectorClosed, Action onObjectSelectedUpdated = null) + { + return DoObjectField(position, dropRect, id, obj, objBeingEdited, objType, additionalType, property, validator, allowSceneObjects, style, EditorStyles.objectFieldButton, onObjectSelectorClosed, onObjectSelectedUpdated); + } + static Object DoObjectField(Rect position, Rect dropRect, int id, Object obj, Object objBeingEdited, System.Type objType, System.Type additionalType, SerializedProperty property, ObjectFieldValidator validator, bool allowSceneObjects, GUIStyle style) { return DoObjectField(position, dropRect, id, obj, objBeingEdited, objType, additionalType, property, validator, allowSceneObjects, style, EditorStyles.objectFieldButton); } - static Object DoObjectField(Rect position, Rect dropRect, int id, Object obj, Object objBeingEdited, System.Type objType, System.Type additionalType, SerializedProperty property, ObjectFieldValidator validator, bool allowSceneObjects, GUIStyle style, GUIStyle buttonStyle) + static Object DoObjectField(Rect position, Rect dropRect, int id, Object obj, Object objBeingEdited, System.Type objType, System.Type additionalType, SerializedProperty property, ObjectFieldValidator validator, bool allowSceneObjects, GUIStyle style, GUIStyle buttonStyle, Action onObjectSelectorClosed = null, Action onObjectSelectedUpdated = null) { if (validator == null) validator = ValidateObjectFieldAssignment; @@ -278,9 +283,9 @@ static Object DoObjectField(Rect position, Rect dropRect, int id, Object obj, Ob GUIUtility.keyboardControl = id; var types = additionalType == null ? new Type[] {objType} : new Type[] { objType, additionalType }; if (property != null) - ObjectSelector.get.Show(types, property, allowSceneObjects); + ObjectSelector.get.Show(types, property, allowSceneObjects, onObjectSelectorClosed: onObjectSelectorClosed, onObjectSelectedUpdated: onObjectSelectedUpdated); else - ObjectSelector.get.Show(obj, types, objBeingEdited, allowSceneObjects); + ObjectSelector.get.Show(obj, types, objBeingEdited, allowSceneObjects, onObjectSelectorClosed: onObjectSelectorClosed, onObjectSelectedUpdated: onObjectSelectedUpdated); ObjectSelector.get.objectSelectorID = id; evt.Use(); @@ -364,8 +369,8 @@ static Object DoObjectField(Rect position, Rect dropRect, int id, Object obj, Ob var parentArrayProperty = property.serializedObject.FindProperty(parentArrayPropertyPath); bool isReorderableList = PropertyHandler.s_reorderableLists.ContainsKey(ReorderableListWrapper.GetPropertyIdentifier(parentArrayProperty)); - // If it's an element of an non-orderable array, remove that element from the array - if (!isReorderableList) + // If it's an element of an non-orderable array and it is displayed inside a list, remove that element from the array (cases 1379541 & 1335322) + if (!isReorderableList && GUI.isInsideList && GetInsideListDepth() == parentArrayProperty.depth) TargetChoiceHandler.DeleteArrayElement(property); else property.objectReferenceValue = null; diff --git a/Editor/Mono/GUI/PackageExport.cs b/Editor/Mono/GUI/PackageExport.cs index 7b761009f0..af6f7d45d6 100644 --- a/Editor/Mono/GUI/PackageExport.cs +++ b/Editor/Mono/GUI/PackageExport.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using UnityEditor.IMGUI.Controls; using UnityEngine; @@ -189,6 +190,14 @@ void BottomArea() if (GUILayout.Button(EditorGUIUtility.TrTextContent("Export..."))) { + string invalidChars = EditorUtility.GetInvalidFilenameChars(); + var selectedItemWithInvalidChar = m_ExportPackageItems.FirstOrDefault(item => Path.GetFileNameWithoutExtension(item.assetPath).IndexOfAny(invalidChars.ToCharArray()) != -1 && item.enabledStatus > 0); + 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(); + return; + } + Export(); GUIUtility.ExitGUI(); } diff --git a/Editor/Mono/GUI/PackageImportTreeView.cs b/Editor/Mono/GUI/PackageImportTreeView.cs index ac7a8f04f9..5c48ee85c4 100644 --- a/Editor/Mono/GUI/PackageImportTreeView.cs +++ b/Editor/Mono/GUI/PackageImportTreeView.cs @@ -21,7 +21,7 @@ internal class PackageImportTreeView static readonly bool s_UseFoldouts = true; public enum EnabledState { - NotSet = -1, + Disabled = -1, None = 0, All = 1, Mixed = 2 @@ -71,6 +71,11 @@ void RecursiveComputeEnabledStateForFolders(PackageImportTreeViewItem pitem, Has RecursiveComputeEnabledStateForFolders(child as PackageImportTreeViewItem, done); } } + else if (pitem.item.isFolder) + { + // since the folder is empty, we don't need to set the enabled check depending of it's children + done.Add(pitem); + } // Now do logic if (!done.Contains(pitem)) @@ -107,6 +112,10 @@ bool ItemShouldBeConsideredForEnabledCheck(PackageImportTreeViewItem pitem) if (pitem.item == null) return true; + // item is disabled + if (pitem.enableState == EnabledState.Disabled) + return false; + var item = pitem.item; // Its a package asset or its changed if (item.projectAsset || !(item.isFolder || item.assetChanged)) @@ -123,7 +132,8 @@ EnabledState GetFolderChildrenEnabledState(PackageImportTreeViewItem folder) if (!folder.hasChildren) return EnabledState.None; - EnabledState amount = EnabledState.NotSet; + // item is disabled if none of its children can be considered for enabled check + EnabledState amount = EnabledState.Disabled; int i = 0; for (; i < folder.children.Count; ++i) @@ -154,9 +164,6 @@ EnabledState GetFolderChildrenEnabledState(PackageImportTreeViewItem folder) } } - if (amount == EnabledState.NotSet) - return EnabledState.None; - return amount; } @@ -345,6 +352,7 @@ override public void OnRowGUI(Rect rowRect, TreeViewItem tvItem, int row, bool s selectionStyle.Draw(rowRect, false, false, true, focused); bool validItem = (item != null); + bool isDisabled = (item != null) ? item.enabledStatus == (int)EnabledState.Disabled : false; bool isFolder = (item != null) ? item.isFolder : true; bool assetChanged = (item != null) ? item.assetChanged : false; bool pathConflict = (item != null) ? item.pathConflict : false; @@ -372,7 +380,7 @@ override public void OnRowGUI(Rect rowRect, TreeViewItem tvItem, int row, bool s DoPreviewPopup(pitem, rowRect); // 4. Warning about file/GUID clashing. - if (repainting && validItem) + if (!isDisabled && repainting && validItem) { if (pathConflict) { @@ -392,7 +400,7 @@ override public void OnRowGUI(Rect rowRect, TreeViewItem tvItem, int row, bool s } // 5. Optional badge ("New") - if (repainting && validItem && !(exists || pathConflict)) + if (!isDisabled && repainting && validItem && !(exists || pathConflict)) { // FIXME: Need to enable tooltips here. Texture badge = Constants.badgeNew.image; @@ -401,7 +409,7 @@ override public void OnRowGUI(Rect rowRect, TreeViewItem tvItem, int row, bool s } // 7. Show what stuff has changed - if (repainting && validItem && (exists || pathConflict) && assetChanged) + if (!isDisabled && repainting && validItem && (exists || pathConflict) && assetChanged) { if (PackageImportWizard.instance.IsProjectSettingStep) { @@ -429,9 +437,14 @@ static void Toggle(ImportPackageItem[] items, PackageImportTreeViewItem pitem, R if (setMixed) style = EditorStyles.toggleMixed; + if (isFolder && (pitem.enableState == EnabledState.Disabled)) + GUI.enabled = false; + bool newEnabled = GUI.Toggle(toggleRect, enabled, GUIContent.none, style); if (newEnabled != enabled) pitem.enableState = newEnabled ? EnabledState.All : EnabledState.None; + + GUI.enabled = true; } void DoToggle(PackageImportTreeViewItem pitem, Rect toggleRect) diff --git a/Editor/Mono/GUI/PragmaFixingWindow.cs b/Editor/Mono/GUI/PragmaFixingWindow.cs deleted file mode 100644 index 796ec721bb..0000000000 --- a/Editor/Mono/GUI/PragmaFixingWindow.cs +++ /dev/null @@ -1,98 +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 UnityEditorInternal; -using UnityEditor.Scripting; - -namespace UnityEditor -{ - internal class PragmaFixingWindow : EditorWindow - { - public static void ShowWindow(string[] paths) - { - PragmaFixingWindow win = EditorWindow.GetWindow(true); - win.SetPaths(paths); - win.ShowModal(); - } - - class Styles - { - public GUIStyle selected = "OL SelectedRow"; - public GUIStyle box = "OL Box"; - public GUIStyle button = "LargeButton"; - } - - static Styles s_Styles = null; - - ListViewState m_LV = new ListViewState(); - string[] m_Paths; - - public PragmaFixingWindow() - { - titleContent = EditorGUIUtility.TrTextContent("Unity - #pragma fixing"); - } - - public void SetPaths(string[] paths) - { - m_Paths = paths; - m_LV.totalRows = paths.Length; - } - - void OnGUI() - { - if (s_Styles == null) - { - s_Styles = new Styles(); - minSize = new Vector2(450, 300); - position = new Rect(position.x, position.y, minSize.x, minSize.y); - } - - GUILayout.Space(10); - GUILayout.Label("#pragma implicit and #pragma downcast need to be added to following files\nfor backwards compatibility"); - GUILayout.Space(10); - - GUILayout.BeginHorizontal(); - GUILayout.Space(10); - foreach (ListViewElement el in ListViewGUILayout.ListView(m_LV, s_Styles.box)) - { - if (el.row == m_LV.row && Event.current.type == EventType.Repaint) - s_Styles.selected.Draw(el.position, false, false, false, false); - - GUILayout.Label(m_Paths[el.row]); - } - GUILayout.Space(10); - GUILayout.EndHorizontal(); - GUILayout.Space(10); - - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - if (GUILayout.Button("Fix now", s_Styles.button)) - { - Close(); - PragmaFixing30.FixFiles(m_Paths); - // bugfix (377429): do not call AssetDatabase.Refresh here as that screws up project upgrading. - // When this script is invoked from Application::InitializeProject, the assets will be refreshed anyway. - GUIUtility.ExitGUI(); - } - - if (GUILayout.Button("Ignore", s_Styles.button)) - { - Close(); - GUIUtility.ExitGUI(); - } - - if (GUILayout.Button("Quit", s_Styles.button)) - { - EditorApplication.Exit(0); - GUIUtility.ExitGUI(); - } - - GUILayout.Space(10); - GUILayout.EndHorizontal(); - - GUILayout.Space(10); - } - } -} diff --git a/Editor/Mono/GUI/RenameOverlay.cs b/Editor/Mono/GUI/RenameOverlay.cs index 8b775288f4..85b8ab136e 100644 --- a/Editor/Mono/GUI/RenameOverlay.cs +++ b/Editor/Mono/GUI/RenameOverlay.cs @@ -95,8 +95,8 @@ void BeginRenameInternalCallback() m_IsWaitingForDelay = false; - Undo.undoRedoPerformed -= UndoRedoWasPerformed; - Undo.undoRedoPerformed += UndoRedoWasPerformed; + Undo.undoRedoEvent -= UndoRedoWasPerformed; + Undo.undoRedoEvent += UndoRedoWasPerformed; } public void EndRename(bool acceptChanges) @@ -105,7 +105,7 @@ public void EndRename(bool acceptChanges) if (!m_IsRenaming) return; - Undo.undoRedoPerformed -= UndoRedoWasPerformed; + Undo.undoRedoEvent -= UndoRedoWasPerformed; EditorApplication.update -= BeginRenameInternalCallback; RemoveMessage(); @@ -137,12 +137,12 @@ public void Clear() m_UserData = 0; m_IsWaitingForDelay = false; m_OriginalEventType = EventType.Ignore; - Undo.undoRedoPerformed -= UndoRedoWasPerformed; + Undo.undoRedoEvent -= UndoRedoWasPerformed; // m_IsRenamingFilename = false; // Only clear temp data used for renaming not state that we want to persist } - void UndoRedoWasPerformed() + void UndoRedoWasPerformed(in UndoRedoInfo info) { // If undo/redo was performed then close the rename overlay as it does not support undo/redo // We need to delay the EndRename until next OnGUI as clients poll the state of the rename overlay state there @@ -243,7 +243,8 @@ public bool OnGUI(GUIStyle textFieldStyle) } // Detect if we clicked outside the text field (must be before text field below which steals keyboard control) - if (m_OriginalEventType == EventType.MouseDown && !m_EditFieldRect.Contains(Event.current.mousePosition)) + // To workaround that, we assume that an used mousedown event means a click outside. + if (m_OriginalEventType == EventType.MouseDown && (Event.current.type == EventType.Used || !m_EditFieldRect.Contains(Event.current.mousePosition))) { EndRename(true); return false; diff --git a/Editor/Mono/GUI/ReorderableList.cs b/Editor/Mono/GUI/ReorderableList.cs index edc5547b5e..8aa4dd0f13 100644 --- a/Editor/Mono/GUI/ReorderableList.cs +++ b/Editor/Mono/GUI/ReorderableList.cs @@ -78,31 +78,37 @@ public class ReorderableList public bool displayAdd; public bool displayRemove; - bool scheduleRemove; + bool m_scheduleRemove; internal bool m_IsEditable; internal bool m_HasPropertyDrawer; + internal int m_CacheCount = 0; // This one has to be internal so that we can execute multiple tests in one frame private int id = -1; - internal class PropertyCacheEntry + bool m_ScheduleGUIChanged = false; + internal struct PropertyCacheEntry { public SerializedProperty property; public float height; public float offset; - public int lastControlCount; + public int controlCount; - public PropertyCacheEntry(SerializedProperty property, float height, float offset) + public bool Set(SerializedProperty property, float height, float offset) { + bool heightChange = this.height != height; + this.property = property; this.height = height; this.offset = offset; - lastControlCount = 0; + + // Schedule recaching if height is changing. Otherwise we might mishandle animated GUI controls + return heightChange; } } - List m_PropertyCache = new List(); + internal bool m_PropertyCacheValid = false; + PropertyCacheEntry[] m_PropertyCache = new PropertyCacheEntry[0]; static List m_OutdatedProperties = new List(); - internal bool IsCacheClear => m_PropertyCache.Count == 0; static string GetParentListPath(string propertyPath) { @@ -127,7 +133,7 @@ bool CheckForChildInvalidation() { if (m_OutdatedProperties.BinarySearch(m_PropertyPath) >= 0) { - ClearCache(); + InvalidateCache(); m_OutdatedProperties = m_OutdatedProperties.Where(e => !e.Equals(m_PropertyPath)).ToList(); return true; } @@ -201,7 +207,7 @@ public void DrawFooter(Rect rect, ReorderableList list) DoAddButton(list); list.onChangedCallback?.Invoke(list); - list.ClearCacheRecursive(); + list.InvalidateCacheRecursive(); } } } @@ -211,7 +217,7 @@ public void DrawFooter(Rect rect, ReorderableList list) || (list.onCanRemoveCallback != null && !list.onCanRemoveCallback(list)) || list.isOverMaxMultiEditLimit)) { - if (GUI.Button(removeRect, iconToolbarMinus, preButton) || GUI.enabled && list.scheduleRemove) + if (GUI.Button(removeRect, iconToolbarMinus, preButton) || GUI.enabled && list.m_scheduleRemove) { if (list.onRemoveCallback == null) { @@ -221,13 +227,13 @@ public void DrawFooter(Rect rect, ReorderableList list) list.onRemoveCallback(list); list.onChangedCallback?.Invoke(list); - list.ClearCacheRecursive(); + list.InvalidateCacheRecursive(); GUI.changed = true; } } } - list.scheduleRemove = false; + list.m_scheduleRemove = false; } // default add button behavior @@ -273,7 +279,7 @@ internal void DoAddButton(ReorderableList list, Object value) Debug.LogError("Cannot add element of type Null."); } Undo.SetCurrentGroupName(undoAdd); - list.ClearCache(); + list.InvalidateCache(); } public void DoAddButton(ReorderableList list) @@ -317,7 +323,7 @@ public void DoRemoveButton(ReorderableList list) } list.index = Mathf.Clamp(lastDeletedIndex - 1, 0, list.count - 1); Undo.SetCurrentGroupName(undoRemove); - list.ClearCache(); + list.InvalidateCache(); } // draw the default header background @@ -376,8 +382,12 @@ public void DrawElement(Rect rect, SerializedProperty element, System.Object lis float oldLabelWidth = EditorGUIUtility.labelWidth; EditorGUIUtility.labelWidth = FieldLabelSize(rect, prop); - var handler = ScriptAttributeUtility.GetHandler(prop); - handler.OnGUI(rect, prop, null, true); + try + { + var handler = ScriptAttributeUtility.GetHandler(prop); + handler.OnGUI(rect, prop, null, true); + } + catch (ObjectDisposedException) { } if (Event.current.type == EventType.ContextClick && rect.Contains(Event.current.mousePosition)) Event.current.Use(); EditorGUIUtility.labelWidth = oldLabelWidth; @@ -411,12 +421,25 @@ public static Defaults defaultBehaviours } static List> s_Instances = new List>(); - internal static void ClearExistingListCaches() => s_Instances.ForEach(list => + internal static void InvalidateExistingListCaches() => s_Instances.ForEach(list => { if (!list.TryGetTarget(out ReorderableList reorderableList)) return; - reorderableList.ClearCache(); + reorderableList.InvalidateCache(); }); + public static ReorderableList GetReorderableListFromSerializedProperty(SerializedProperty prop) + { + var id = ReorderableListWrapper.GetPropertyIdentifier(prop); + ReorderableList list = null; + s_Instances?.FirstOrDefault(i => + { + ReorderableList list2 = null; + if (!(i?.TryGetTarget(out list2) ?? false)) return false; + return ReorderableListWrapper.GetPropertyIdentifier(list2?.serializedProperty).Equals(id); + })?.TryGetTarget(out list); + return list; + } + // constructors public ReorderableList(IList elements, Type elementType) { @@ -509,90 +532,84 @@ public bool draggable set { m_Draggable = value; } } + void TryOverrideElementHeightWithPropertyDrawer(SerializedProperty property, ref float height) + { + if (m_HasPropertyDrawer) + { + try + { + height = ScriptAttributeUtility.GetHandler(property).GetHeight(property, null, true); + } + catch (ObjectDisposedException) + { + // Sometimes we find properties that no longer exist so we don't cache them + height = int.MinValue; + m_Count--; + } + } + } + internal void CacheIfNeeded() { - if (isOverMaxMultiEditLimit) return; + // Don't allow recaching multiple times in one frame as we won't be able to handle animated foldouts + if (isOverMaxMultiEditLimit || m_PropertyCacheValid) return; + m_PropertyCacheValid = true; + m_CacheCount++; - m_PropertyCache.Capacity = Mathf.Max(m_PropertyCache.Capacity, m_Count - 1); - while (m_Count > m_PropertyCache.Count) + Array.Resize(ref m_PropertyCache, count); + + SerializedProperty property = null; + float height = 0; + float offset = 0; + + if (m_Count > 0) { - float offset; if (m_Elements != null) { - SerializedProperty property; - if (m_PropertyCache.Count == 0 && count != 0) - { - property = m_Elements.GetArrayElementAtIndex(0); - offset = 0; - } - else - { - PropertyCacheEntry lastEntry = m_PropertyCache.Last(); + property = m_Elements.GetArrayElementAtIndex(0); + TryOverrideElementHeightWithPropertyDrawer(property, ref height); + } - property = lastEntry.property.Copy(); - property.Next(false); - offset = lastEntry.offset + lastEntry.height; - } + height = elementHeightCallback?.Invoke(0) ?? elementHeight; + m_ScheduleGUIChanged |= m_PropertyCache[0].Set(property, height + Defaults.ElementPadding(height), offset); + } - float height = elementHeight; - if (elementHeightCallback != null) - { - height = elementHeightCallback(m_PropertyCache.Count); - } - else if (m_HasPropertyDrawer) - { - try - { - height = ScriptAttributeUtility.GetHandler(property).GetHeight(property, null, true); - } - catch - { - // Sometimes we find properties that no longer exist so we don't cache them - height = int.MinValue; - m_Count--; - } - } + for (int i = 1; i < m_Count; i++) + { + PropertyCacheEntry lastEntry = m_PropertyCache[i - 1]; - if (height > int.MinValue) m_PropertyCache.Add(new PropertyCacheEntry(property, height + Defaults.ElementPadding(height), offset)); - } - else - { - if (m_PropertyCache.Count == 0) - { - offset = 0; - } - else - { - PropertyCacheEntry lastEntry = m_PropertyCache.Last(); - offset = lastEntry.offset + lastEntry.height; - } + property = null; + height = elementHeightCallback?.Invoke(i) ?? elementHeight; + offset = lastEntry.offset + lastEntry.height; - float height = elementHeight; - if (elementHeightCallback != null) - { - height = elementHeightCallback(m_PropertyCache.Count); - } + if (m_Elements != null) + { + property = lastEntry.property.Copy(); + property.Next(false); - m_PropertyCache.Add(new PropertyCacheEntry(null, height + Defaults.ElementPadding(height), offset)); + TryOverrideElementHeightWithPropertyDrawer(property, ref height); } + + if (height > int.MinValue) m_ScheduleGUIChanged |= m_PropertyCache[i].Set(property, height + Defaults.ElementPadding(height), offset); } } - internal void ClearCache() + internal void InvalidateCache() { - m_PropertyCache.Clear(); + m_CacheCount = 0; + m_PropertyCacheValid = false; } - internal void ClearCacheRecursive() + internal void InvalidateCacheRecursive() { if (m_Elements != null) { - ClearCache(); - PropertyHandler.ClearListCacheIncludingChildren(m_Elements.propertyPath); + InvalidateCache(); + PropertyHandler.InvalidateListCacheIncludingChildren(m_Elements); } else { - ClearCache(); + InvalidateCache(); } } @@ -664,7 +681,7 @@ private float GetElementYOffset(int index) private float GetElementYOffset(int index, int skipIndex) { - CacheIfNeeded(); + if (m_PropertyCache.Length <= index) return 0; float skipOffset = 0; if (skipIndex >= 0 && skipIndex < index) @@ -677,7 +694,7 @@ private float GetElementYOffset(int index, int skipIndex) private float GetElementHeight(int index) { - CacheIfNeeded(); + if (m_PropertyCache.Length <= index) return 0; return m_PropertyCache[index].height; } @@ -686,7 +703,7 @@ private Rect GetRowRect(int index, Rect listRect) return new Rect(listRect.x, listRect.y + GetElementYOffset(index), listRect.width, GetElementHeight(index)); } - bool isOverMaxMultiEditLimit => m_Elements != null && smallerArraySize > m_Elements.serializedObject.maxArraySizeForMultiEditing && m_Elements.serializedObject.isEditingMultipleObjects; + bool isOverMaxMultiEditLimit => m_Elements != null && m_SmallerArraySize > m_Elements.serializedObject.maxArraySizeForMultiEditing && m_Elements.serializedObject.isEditingMultipleObjects; public int count { @@ -694,17 +711,18 @@ public int count { if (m_Elements != null) { - smallerArraySize = m_Elements.minArraySize; + m_SmallerArraySize = m_Elements.minArraySize; - if (isOverMaxMultiEditLimit) return 0; + if (isOverMaxMultiEditLimit) return m_Count = 0; - return smallerArraySize; + return m_Count = m_SmallerArraySize; } - return m_ElementList != null ? m_ElementList.Count : 0; + return m_Count = m_ElementList != null ? m_ElementList.Count : 0; } } + // Using count getter will automatically cache results here for quick reference; int m_Count; - int smallerArraySize; + int m_SmallerArraySize; public void DoLayoutList() //TODO: better API? { @@ -754,18 +772,27 @@ public float GetHeight() return totalHeight; } + float lastHeight = -1; private float GetListElementHeight() { + float height; float listElementPadding = kListElementBottomPadding + listElementTopPadding; - m_Count = count; - if (m_Count == 0 || isOverMaxMultiEditLimit) + if (m_CacheCount == 0) CacheIfNeeded(); + + if (m_Count <= 0 || isOverMaxMultiEditLimit) + height = elementHeight * (isOverMaxMultiEditLimit ? 2 : 1) + listElementPadding; + else + height = GetElementYOffset(m_Count - 1) + GetElementHeight(m_Count - 1) + listElementPadding; + + if(height != lastHeight) { - return elementHeight * (isOverMaxMultiEditLimit ? 2 : 1) + listElementPadding; + lastHeight = height; + InvalidateCache(); + height = GetListElementHeight(); } - CacheIfNeeded(); - return GetElementYOffset(m_Count - 1) + GetElementHeight(m_Count - 1) + listElementPadding; + return height; } int recursionCounter = 0; @@ -776,10 +803,10 @@ private void DoListElements(Rect listRect, Rect visibleRect) { // Recalculate cache values in case their height changed due to window resize lastRect = listRect; - ClearCacheRecursive(); + InvalidateCacheRecursive(); } - CacheIfNeeded(); + if (m_CacheCount == 0) CacheIfNeeded(); var prevIndent = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; @@ -806,6 +833,9 @@ private void DoListElements(Rect listRect, Rect visibleRect) bool handlingInput = Event.current.type == EventType.MouseDown; + // Cache element count so we don't try to draw elements that don't exist + _ = count; + if ((m_Elements != null && m_Elements.isArray || m_ElementList != null) && m_Count > 0 && !isOverMaxMultiEditLimit) { EditorGUI.BeginChangeCheck(); @@ -912,8 +942,6 @@ private void DoListElements(Rect listRect, Rect visibleRect) if (visibleRect.y > GetElementYOffset(i) + GetElementHeight(i)) continue; if (visibleRect.y + visibleRect.height < GetElementYOffset(i > 0 ? i - 1 : i)) break; - int initialProperties = EditorGUI.s_PropertyCount; - bool activeElement = m_Selection.Any(id => id == i); bool focusedElement = (activeElement && HasKeyboardControl()); @@ -929,13 +957,15 @@ private void DoListElements(Rect listRect, Rect visibleRect) defaultBehaviours.DrawElementDraggingHandle(elementRect, i, activeElement, focusedElement, m_Draggable); elementContentRect = GetContentRect(elementRect); + int initialControlCount = GUIUtility.s_ControlCount; // do the callback for the element if (drawElementCallback == null) { if (m_Elements != null) { - s_Defaults.DrawElement(elementContentRect, m_PropertyCache[i].property, null, activeElement, focusedElement, m_Draggable, m_IsEditable); + if (i < m_PropertyCache.Length && m_PropertyCache[i].property.isValid) + s_Defaults.DrawElement(elementContentRect, m_PropertyCache[i].property, null, activeElement, focusedElement, m_Draggable, m_IsEditable); } else defaultBehaviours.DrawElement(elementContentRect, null, m_ElementList[i], activeElement, focusedElement, m_Draggable, m_IsEditable); @@ -953,16 +983,13 @@ private void DoListElements(Rect listRect, Rect visibleRect) // Element drawing could be changed from distant properties or controls // so if we detect any change in the way the property is drawn, clear cache - int currentControlCount = EditorGUI.s_PropertyCount - initialProperties; - if (m_PropertyCache[i].lastControlCount > 1 && currentControlCount > 1 - && m_PropertyCache[i].lastControlCount != currentControlCount) + int currentControlCount = GUIUtility.s_ControlCount - initialControlCount; + if (i < m_PropertyCache.Length && Event.current.type == EventType.Repaint && m_PropertyCache[i].controlCount != currentControlCount || m_ScheduleGUIChanged) { - ClearCacheRecursive(); - CacheIfNeeded(); - InspectorWindow.RepaintAllInspectors(); - if ((m_Count = count) >= i) break; + InvalidateCache(); + m_PropertyCache[i].controlCount = currentControlCount; + m_ScheduleGUIChanged = false; } - m_PropertyCache[i].lastControlCount = currentControlCount; } } @@ -971,7 +998,7 @@ private void DoListElements(Rect listRect, Rect visibleRect) if (EditorGUI.EndChangeCheck()) { - ClearCacheRecursive(); + InvalidateCacheRecursive(); } } else @@ -1012,6 +1039,8 @@ private void DoListElements(Rect listRect, Rect visibleRect) recursionCounter++; DoListElements(listRect, visibleRect); } + + m_CacheCount = 0; } private void DoListHeader(Rect headerRect) @@ -1019,7 +1048,7 @@ private void DoListHeader(Rect headerRect) // Ensure there's proper Prefab and context menu handling for the list as a whole. // This ensures a deleted element in the list is displayed as an override and can // be handled by the user via the context menu. Case 1292522 - if (m_Elements != null) + if (m_Elements != null && m_DisplayHeader) EditorGUI.BeginProperty(headerRect, GUIContent.none, m_Elements); recursionCounter = 0; @@ -1039,7 +1068,7 @@ private void DoListHeader(Rect headerRect) else if (m_DisplayHeader) defaultBehaviours.DrawHeader(headerRect, m_SerializedObject, m_Elements, m_ElementList); - if (m_Elements != null) + if (m_Elements != null && m_DisplayHeader) EditorGUI.EndProperty(); } @@ -1109,7 +1138,7 @@ private void DoDraggingAndSelection(Rect listRect) if (Application.platform != RuntimePlatform.OSXEditor && evt.keyCode == KeyCode.Delete || Application.platform == RuntimePlatform.OSXEditor && evt.keyCode == KeyCode.Backspace && evt.modifiers.HasFlag(EventModifiers.Command)) { - scheduleRemove = true; + m_scheduleRemove = true; InvalidateParentCaches(m_PropertyPath); evt.Use(); } @@ -1333,6 +1362,7 @@ private void DoDraggingAndSelection(Rect listRect) onMouseUpCallback?.Invoke(this); } } + catch (ObjectDisposedException) { } finally { // It's quite possible a call to EndGUI was made in one of our callbacks diff --git a/Editor/Mono/GUI/Splitter.cs b/Editor/Mono/GUI/Splitter.cs index bb11754eee..af4da4bc3b 100644 --- a/Editor/Mono/GUI/Splitter.cs +++ b/Editor/Mono/GUI/Splitter.cs @@ -178,6 +178,9 @@ public void NormalizeRelativeSizes() public void RealToRelativeSizes() { + if (relativeSizes.Length == 0) + return; + float check = 1.0f; // try to avoid rounding issues float total = 0; int k; @@ -186,13 +189,23 @@ public void RealToRelativeSizes() for (k = 0; k < realSizes.Length; k++) total += realSizes[k]; - for (k = 0; k < realSizes.Length; k++) + if (total > 0.001f) { - relativeSizes[k] = realSizes[k] / total; - check -= relativeSizes[k]; - } - if (relativeSizes.Length > 0) + for (k = 0; k < realSizes.Length; k++) + { + relativeSizes[k] = realSizes[k] / total; + check -= relativeSizes[k]; + } + relativeSizes[relativeSizes.Length - 1] += check; + } + else + { + // Distribute evenly + float relativeSize = 1f / relativeSizes.Length; + for (k = 0; k < relativeSizes.Length; k++) + relativeSizes[k] = relativeSize; + } } public void RelativeToRealSizes(float totalSpace) diff --git a/Editor/Mono/GUI/Toolbar.cs b/Editor/Mono/GUI/Toolbar.cs index e5882f33b4..1df88833c6 100644 --- a/Editor/Mono/GUI/Toolbar.cs +++ b/Editor/Mono/GUI/Toolbar.cs @@ -77,21 +77,24 @@ internal static string lastLoadedLayoutName protected override void OnEnable() { base.OnEnable(); - EditorApplication.modifierKeysChanged += Repaint; - get = this; + m_EventInterests.wantsLessLayoutEvents = true; + CreateContents(); + } - m_MainToolbarVisual = (MainToolbarVisual)Activator.CreateInstance(EditorUIService.instance.GetDefaultToolbarType()); - + void CreateContents() + { + m_MainToolbarVisual = (MainToolbarVisual)Activator.CreateInstance(typeof(DefaultMainToolbar)); + m_Root?.RemoveFromHierarchy(); m_Root = CreateRoot(); - if (windowBackend.visualTree is VisualElement visualTree) + + if (windowBackend?.visualTree is VisualElement visualTree) { visualTree.Add(m_Root); m_Root.Add(m_MainToolbarVisual.root); } - m_EventInterests.wantsLessLayoutEvents = true; RepaintToolbar(); } @@ -126,11 +129,16 @@ static VisualElement CreateRoot() renderHints = RenderHints.ClipWithScissors }; root.pseudoStates |= PseudoStates.Root; - EditorUIService.instance.AddDefaultEditorStyleSheets(root); + UIElementsEditorUtility.AddDefaultEditorStyleSheets(root); root.style.overflow = Overflow.Hidden; return root; } + protected override void OnBackingScaleFactorChanged() + { + CreateContents(); + } + internal static void RepaintToolbar() { if (get != null) @@ -150,7 +158,7 @@ public Rect GetToolbarPosition() // @todo Remove when collab updates internal static void AddSubToolbar(SubToolbar subToolbar) { - EditorUIService.instance.AddSubToolbar(subToolbar); + MainToolbarImguiContainer.AddDeprecatedSubToolbar(subToolbar); } // Repaints all views, called from C++ when playmode entering is aborted diff --git a/Editor/Mono/GUI/Tools/EditorTool.cs b/Editor/Mono/GUI/Tools/EditorTool.cs index 04fedd38b1..74d08793c1 100644 --- a/Editor/Mono/GUI/Tools/EditorTool.cs +++ b/Editor/Mono/GUI/Tools/EditorTool.cs @@ -17,6 +17,8 @@ public interface IDrawSelectedHandles public abstract class EditorTool : ScriptableObject, IEditor { + bool m_Active; + [HideInInspector] [SerializeField] internal UnityObject[] m_Targets; @@ -55,6 +57,27 @@ public virtual bool gridSnapEnabled get { return false; } } + internal void Activate() + { + if(m_Active + // Prevent to reenable the tool if this is not the active one anymore + // Can happen when entering playmode due to the delayCall in EditorToolManager.OnEnable + || this != EditorToolManager.activeTool) + return; + + OnActivated(); + m_Active = true; + } + + internal void Deactivate() + { + if(!m_Active) + return; + + OnWillBeDeactivated(); + m_Active = false; + } + public virtual void OnActivated() {} public virtual void OnWillBeDeactivated() {} diff --git a/Editor/Mono/GUI/Tools/EditorToolCache.cs b/Editor/Mono/GUI/Tools/EditorToolCache.cs index f93de7524b..0e9458f3ff 100644 --- a/Editor/Mono/GUI/Tools/EditorToolCache.cs +++ b/Editor/Mono/GUI/Tools/EditorToolCache.cs @@ -247,7 +247,6 @@ internal IEnumerable GetEditorsForTargetType(Type target) void CollectEditorsForTracker(EditorToolContext ctx, ActiveEditorTracker tracker, List editors) { - var trackerEditors = tracker.activeEditors; for (int i = 0, c = trackerEditors.Length; i < c; i++) @@ -255,7 +254,7 @@ void CollectEditorsForTracker(EditorToolContext ctx, ActiveEditorTracker tracker var editor = trackerEditors[i]; var target = editor != null ? editor.target : null; - if (target == null) + if (target == null || EditorUtility.IsPersistent(target)) return; var eligible = GetEditorsForTargetType(editor.target.GetType()); diff --git a/Editor/Mono/GUI/Tools/EditorToolContext.cs b/Editor/Mono/GUI/Tools/EditorToolContext.cs index ac0fd57160..5382033c23 100644 --- a/Editor/Mono/GUI/Tools/EditorToolContext.cs +++ b/Editor/Mono/GUI/Tools/EditorToolContext.cs @@ -31,6 +31,8 @@ public override VisualElement CreateInspectorGUI() public abstract class EditorToolContext : ScriptableObject, IEditor { + bool m_Active; + [HideInInspector] [SerializeField] internal UnityObject[] m_Targets; @@ -45,6 +47,27 @@ public abstract class EditorToolContext : ScriptableObject, IEditor public UnityObject target => m_Target == null ? Selection.activeObject : m_Target; + internal void Activate() + { + if(m_Active + // Prevent to reenable the context if this is not the active one anymore + // Can happen when entering playmode due to the delayCall in EditorToolManager.OnEnable + || this != EditorToolManager.activeToolContext) + return; + + OnActivated(); + m_Active = true; + } + + internal void Deactivate() + { + if(!m_Active) + return; + + OnWillBeDeactivated(); + m_Active = false; + } + public virtual void OnActivated() {} public virtual void OnWillBeDeactivated() {} diff --git a/Editor/Mono/GUI/Tools/EditorToolManager.cs b/Editor/Mono/GUI/Tools/EditorToolManager.cs index 3e47adb2a6..5f35842af4 100644 --- a/Editor/Mono/GUI/Tools/EditorToolManager.cs +++ b/Editor/Mono/GUI/Tools/EditorToolManager.cs @@ -58,9 +58,8 @@ internal static EditorToolContext activeToolContext { instance.m_ActiveToolContext = GetSingleton(); ToolManager.ActiveContextDidChange(); - instance.m_ActiveToolContext.OnActivated(); + instance.m_ActiveToolContext.Activate(); } - return instance.m_ActiveToolContext; } @@ -83,11 +82,12 @@ internal static EditorToolContext activeToolContext // Make sure to get the active tool enum prior to setting the context, otherwise we'll be comparing // apples to oranges. Ie, the transform tools will be different despite being the same `Tool` enum value. var tool = Tools.current; + var wasAdditionalContextTool = tool == Tool.Custom && additionalContextToolTypesCache.Contains(activeTool.GetType()); var prev = instance.m_ActiveToolContext; if (prev != null) { - prev.OnWillBeDeactivated(); + prev.Deactivate(); if (!(prev is GameObjectToolContext)) instance.m_LastCustomContext = prev.GetType(); @@ -96,7 +96,7 @@ internal static EditorToolContext activeToolContext ToolManager.ActiveContextWillChange(); instance.m_ActiveToolContext = ctx; - ctx.OnActivated(); + ctx.Activate(); instance.RebuildAvailableTools(); @@ -118,6 +118,15 @@ internal static EditorToolContext activeToolContext // NoneTool for us. activeTool = resolved; } + // If the previous tool was an additional tool from the context, return to the Previous Persistent Tool + // when moving to that new context + else if(wasAdditionalContextTool) + { + var isAdditionalContextTool = instance.m_ActiveToolContext.GetAdditionalToolTypes().Contains(activeTool.GetType()); + + if(!isAdditionalContextTool) + RestorePreviousPersistentTool(); + } ToolManager.ActiveContextDidChange(); @@ -154,7 +163,7 @@ internal static EditorTool activeTool if (previous != null) { - previous.OnWillBeDeactivated(); + previous.Deactivate(); var prev = EditorToolUtility.GetEnumWithEditorTool(previous, activeToolContext); if (prev != Tool.View && prev != Tool.None && !EditorToolUtility.IsComponentTool(previous.GetType())) @@ -169,8 +178,7 @@ internal static EditorTool activeTool } instance.m_ActiveTool = tool; - - instance.m_ActiveTool.OnActivated(); + instance.m_ActiveTool.Activate(); ToolManager.ActiveToolDidChange(); @@ -269,24 +277,24 @@ void SaveComponentTool() void OnEnable() { - Undo.undoRedoPerformed += UndoRedoPerformed; + Undo.undoRedoEvent += UndoRedoPerformed; ActiveEditorTracker.editorTrackerRebuilt += TrackerRebuilt; Selection.selectedObjectWasDestroyed += SelectedObjectWasDestroyed; AssemblyReloadEvents.beforeAssemblyReload += BeforeAssemblyReload; ToolManager.activeContextChanged += ActiveContextChanged; - if (activeTool != null) - EditorApplication.delayCall += activeTool.OnActivated; + if(activeTool != null) + EditorApplication.delayCall += activeTool.Activate; if(activeToolContext != null) { - EditorApplication.delayCall += activeToolContext.OnActivated; + EditorApplication.delayCall += activeToolContext.Activate; ActiveContextChanged(); } } void OnDisable() { - Undo.undoRedoPerformed -= UndoRedoPerformed; + Undo.undoRedoEvent -= UndoRedoPerformed; ActiveEditorTracker.editorTrackerRebuilt -= TrackerRebuilt; Selection.selectedObjectWasDestroyed -= SelectedObjectWasDestroyed; AssemblyReloadEvents.beforeAssemblyReload -= BeforeAssemblyReload; @@ -296,10 +304,10 @@ void OnDisable() void BeforeAssemblyReload() { if (m_ActiveTool != null) - m_ActiveTool.OnWillBeDeactivated(); + m_ActiveTool.Deactivate(); if (m_ActiveToolContext != null) - m_ActiveToolContext.OnWillBeDeactivated(); + m_ActiveToolContext.Deactivate(); } void ActiveContextChanged() @@ -347,7 +355,7 @@ void SelectedObjectWasDestroyed(int id) } } - void UndoRedoPerformed() + void UndoRedoPerformed(in UndoRedoInfo info) { RestoreCustomEditorTool(); } @@ -374,7 +382,7 @@ void ClearCustomEditorTools() foreach (var customEditorTool in m_ComponentTools) { if (customEditorTool.editor == m_ActiveTool) - m_ActiveTool.OnWillBeDeactivated(); + m_ActiveTool.Deactivate(); DestroyImmediate(customEditorTool.editor); } @@ -386,7 +394,7 @@ void ClearComponentContexts() foreach (var context in m_ComponentContexts) { if (context.GetEditor() == m_ActiveToolContext) - m_ActiveToolContext.OnWillBeDeactivated(); + m_ActiveToolContext.Deactivate(); DestroyImmediate(context.editor); } @@ -465,6 +473,28 @@ public static void RestorePreviousPersistentTool() activeTool = instance.lastManipulationTool; } + // Used by tests - EditModeAndPlayModeTests/EditorTools/EscKeyTests + internal static bool TryPopToolState() + { + if(Tools.viewToolActive) + return false; + + if(!EditorToolUtility.IsBuiltinOverride(activeTool)) + { + RestorePreviousPersistentTool(); + return true; + } + + if(ToolManager.activeContextType != typeof(GameObjectToolContext)) + { + //if is in a Manipulation or additional tool leaves the current context to return to GameObject Context + ToolManager.SetActiveContext(); + return true; + } + + return false; + } + internal static void OnToolGUI(EditorWindow window) { activeToolContext.OnToolGUI(window); @@ -480,21 +510,8 @@ internal static void OnToolGUI(EditorWindow window) } var evt = Event.current; - if(evt.type == EventType.KeyDown && evt.keyCode == KeyCode.Escape) - { - if(!Tools.viewToolActive - && !EditorToolUtility.IsManipulationTool(EditorToolUtility.GetEnumWithEditorTool(current, instance.m_ActiveToolContext)) - && additionalContextToolTypesCache.All(toolType => toolType != current.GetType())) - { - RestorePreviousPersistentTool(); - evt.Use(); - } - else - { - //if is in a Manipulation or additional tool leaves the current context to return to GameObject Context - ToolManager.SetActiveContext(); - } - } + if (evt.type == EventType.KeyDown && evt.keyCode == KeyCode.Escape && TryPopToolState()) + evt.Use(); } static bool IsCustomEditorTool(EditorTool tool) diff --git a/Editor/Mono/GUI/Tools/EditorToolUtility.cs b/Editor/Mono/GUI/Tools/EditorToolUtility.cs index d4bac72de5..c7b74a0836 100644 --- a/Editor/Mono/GUI/Tools/EditorToolUtility.cs +++ b/Editor/Mono/GUI/Tools/EditorToolUtility.cs @@ -186,6 +186,22 @@ internal static bool IsManipulationTool(Tool tool) || tool == Tool.Transform; } + // In the current context, is this tool considered a built-in tool? + // Built-in tools are the first category of tools in the toolbar, and are always available while their parent + // context is active. + internal static bool IsBuiltinOverride(EditorTool tool) + { + if (tool == null) + return false; + if (IsManipulationTool(GetEnumWithEditorTool(tool))) + return true; + var type = tool.GetType(); + foreach(var extra in EditorToolManager.activeToolContext.GetAdditionalToolTypes()) + if (type == extra) + return true; + return false; + } + internal static bool IsComponentTool(Type type) { return s_ToolCache.GetTargetType(type) != null; @@ -224,17 +240,22 @@ internal static GUIContent GetIcon(Type editorToolType, bool forceReload = false res = new GUIContent() { tooltip = GetToolName(editorToolType) }; - // First check for the tool type itself - if ((res.image = EditorGUIUtility.FindTexture(editorToolType)) != null) + // First check if the tool as an icon attribute + var iconPath = EditorGUIUtility.GetIconPathFromAttribute(editorToolType); + if(!string.IsNullOrEmpty(iconPath) && (res.image = EditorGUIUtility.IconContent(iconPath).image)) + goto ReturnToolbarIcon; + + // Second check for the tool type itself + if(( res.image = EditorGUIUtility.FindTexture(editorToolType) ) != null) goto ReturnToolbarIcon; // If it's a custom editor tool, try to get an icon for the tool's target type var attrib = GetEditorToolAttribute(editorToolType); - if (attrib?.targetType != null && (res.image = AssetPreview.GetMiniTypeThumbnailFromType(attrib.targetType)) != null) + if(attrib?.targetType != null && ( res.image = AssetPreview.GetMiniTypeThumbnailFromType(attrib.targetType) ) != null) goto ReturnToolbarIcon; // And finally fall back to the default Custom Tool icon - res.image = EditorGUIUtility.LoadIconRequired("CustomTool"); + res.image = EditorGUIUtility.IconContent("CustomTool").image; ReturnToolbarIcon: if (string.IsNullOrEmpty(res.tooltip)) diff --git a/Editor/Mono/GUI/Tools/ToolManager.cs b/Editor/Mono/GUI/Tools/ToolManager.cs index 34abb68e45..991df48130 100644 --- a/Editor/Mono/GUI/Tools/ToolManager.cs +++ b/Editor/Mono/GUI/Tools/ToolManager.cs @@ -131,6 +131,18 @@ public static bool IsActiveTool(EditorTool tool) return EditorToolManager.activeTool == tool; } + public static void RefreshAvailableTools() + { + foreach (var obj in SceneView.sceneViews) + { + if (!(obj is SceneView scene)) + continue; + var overlay = scene.overlayCanvas?.overlays.FirstOrDefault(x => x is TransformToolsOverlayToolBar); + if(overlay != null) + overlay.RebuildContent(); + } + } + public static bool IsActiveContext(EditorToolContext context) { return EditorToolManager.activeToolContext == context; diff --git a/Editor/Mono/GUI/Tools/Tools.cs b/Editor/Mono/GUI/Tools/Tools.cs index f10283999a..a324df9cb1 100644 --- a/Editor/Mono/GUI/Tools/Tools.cs +++ b/Editor/Mono/GUI/Tools/Tools.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Linq; using Unity.Profiling; using UnityEditor.ShortcutManagement; using UnityEngine; @@ -77,7 +78,13 @@ public static Tool current get { return EditorToolUtility.GetEnumWithEditorTool(EditorToolManager.GetActiveTool()); } set { - EditorToolManager.activeTool = EditorToolUtility.GetEditorToolWithEnum(value); + var tool = EditorToolUtility.GetEditorToolWithEnum(value); + + //In case the new tool is leading to an incorrect tool type, return and leave the current tool as it is. + if(value != Tool.None && tool is NoneTool) + return; + + EditorToolManager.activeTool = tool; ShortcutManager.RegisterTag(value); } } @@ -346,6 +353,25 @@ public static bool hidden internal static bool vertexDragging; + static Event m_VertexDraggingShortcutEvent; + internal static Event vertexDraggingShortcutEvent + { + get + { + if(m_VertexDraggingShortcutEvent == null) + { + var vertexSnappingBinding = ShortcutManager.instance.GetShortcutBinding(VertexSnapping.k_VertexSnappingShortcut); + if(Enumerable.Count(vertexSnappingBinding.keyCombinationSequence) == 0) + m_VertexDraggingShortcutEvent = new Event(); + else + m_VertexDraggingShortcutEvent = vertexSnappingBinding.keyCombinationSequence.First().ToKeyboardEvent(); + } + + return m_VertexDraggingShortcutEvent; + } + set => m_VertexDraggingShortcutEvent = value; + } + static Vector3 s_LockHandlePosition; static bool s_LockHandlePositionActive = false; @@ -406,7 +432,10 @@ void OnEnable() visibleLayers = layerSettings.visibleLayersValue; lockedLayers = layerSettings.lockedLayersValue; Selection.selectionChanged += OnSelectionChange; - Undo.undoRedoPerformed += OnSelectionChange; + Undo.undoRedoEvent += OnUndoRedo; + + ShortcutManager.instance.activeProfileChanged += args => vertexDraggingShortcutEvent = null; + ShortcutManager.instance.shortcutBindingChanged += args => vertexDraggingShortcutEvent = null; EditorToolManager.activeToolChanged += (previous, active) => { @@ -422,7 +451,7 @@ void OnEnable() void OnDisable() { Selection.selectionChanged -= OnSelectionChange; - Undo.undoRedoPerformed -= OnSelectionChange; + Undo.undoRedoEvent -= OnUndoRedo; } internal static void OnSelectionChange() @@ -432,6 +461,11 @@ internal static void OnSelectionChange() localHandleOffset = Vector3.zero; } + internal static void OnUndoRedo(in UndoRedoInfo info) + { + OnSelectionChange(); + } + internal static void ResetGlobalHandleRotation() { get.m_GlobalHandleRotation = Quaternion.identity; diff --git a/Editor/Mono/GUI/TreeView/AssetOrGameObjectTreeViewDragging.cs b/Editor/Mono/GUI/TreeView/AssetOrGameObjectTreeViewDragging.cs index 4d0553c188..38722d2e08 100644 --- a/Editor/Mono/GUI/TreeView/AssetOrGameObjectTreeViewDragging.cs +++ b/Editor/Mono/GUI/TreeView/AssetOrGameObjectTreeViewDragging.cs @@ -56,7 +56,7 @@ public override void StartDrag(TreeViewItem draggedItem, List draggedItemID public override DragAndDropVisualMode DoDrag(TreeViewItem parentItem, TreeViewItem targetItem, bool perform, DropPosition dropPos) { var dragToInstanceId = parentItem?.id ?? 0; - return DragAndDrop.Drop(DragAndDropWindowTarget.projectBrowser, dragToInstanceId, AssetDatabase.GetAssetPath(dragToInstanceId), perform); + return DragAndDrop.DropOnProjectBrowserWindow(dragToInstanceId, AssetDatabase.GetAssetPath(dragToInstanceId), perform); } } @@ -164,7 +164,7 @@ public override DragAndDropVisualMode DoDrag(TreeViewItem parentItem, TreeViewIt if (parentForDraggedObjectsOutsideItems != null) { // Use specific parent for DragAndDropForwarding - return DragAndDrop.Drop(DragAndDropWindowTarget.hierarchy, 0, option, parentForDraggedObjectsOutsideItems, perform); + return DragAndDrop.DropOnHierarchyWindow(0, option, parentForDraggedObjectsOutsideItems, perform); } else { @@ -174,7 +174,7 @@ public override DragAndDropVisualMode DoDrag(TreeViewItem parentItem, TreeViewIt return DragAndDropVisualMode.Rejected; option |= HierarchyDropFlags.DropUpon; - return DragAndDrop.Drop(DragAndDropWindowTarget.hierarchy, lastScene.handle, option, null, perform); + return DragAndDrop.DropOnHierarchyWindow(lastScene.handle, option, null, perform); } } @@ -214,7 +214,7 @@ public override DragAndDropVisualMode DoDrag(TreeViewItem parentItem, TreeViewIt if (perform && SubSceneGUI.IsUsingSubScenes() && !IsValidSubSceneDropTarget(gameObjectOrSceneInstanceID, dropPos, DragAndDrop.objectReferences)) return DragAndDropVisualMode.Rejected; - return DragAndDrop.Drop(DragAndDropWindowTarget.hierarchy, gameObjectOrSceneInstanceID, option, null, perform); + return DragAndDrop.DropOnHierarchyWindow(gameObjectOrSceneInstanceID, option, null, perform); } int GetDropTargetInstanceID(GameObjectTreeViewItem hierarchyTargetItem, DropPosition dropPosition) diff --git a/Editor/Mono/GUI/TreeView/GameObjectTreeViewGUI.cs b/Editor/Mono/GUI/TreeView/GameObjectTreeViewGUI.cs index 671a8e3d6e..65af813830 100644 --- a/Editor/Mono/GUI/TreeView/GameObjectTreeViewGUI.cs +++ b/Editor/Mono/GUI/TreeView/GameObjectTreeViewGUI.cs @@ -226,6 +226,8 @@ void DoStickySceneHeaders() var sceneHeaderItem = dataSource.sceneHeaderItems.FirstOrDefault(p => p.scene == firstItem.scene); if (sceneHeaderItem != null) { + rect.y = Mathf.Round(rect.y); // Fix vertical render jittering due to fractional scroll values by rounding to nearest whole pixel + bool selected = m_TreeView.IsItemDragSelectedOrSelected(sceneHeaderItem); bool focused = m_TreeView.HasFocus(); bool boldFont = sceneHeaderItem.scene == SceneManager.GetActiveScene(); @@ -237,6 +239,10 @@ void DoStickySceneHeaders() m_TreeView.HandleUnusedMouseEventsForItem(rect, sceneHeaderItem, firstRow); HandleStickyHeaderContextClick(rect, sceneHeaderItem); + + float indent = GetContentIndent(sceneHeaderItem); + Rect indentedRect = new Rect(rect.x + indent, rect.y, rect.width - indent, rect.height); + UserCallbackRowGUI(sceneHeaderItem.id, indentedRect); } } } @@ -419,6 +425,19 @@ override protected void DoItemGUI(Rect rect, int row, TreeViewItem item, bool se SceneVisibilityHierarchyGUI.DoItemGUI(rect, goItem, selected && !IsRenaming(item.id), m_TreeView.hoveredItem == goItem, focused, isDragging); } + internal static void UserCallbackRowGUI(int itemID, Rect rect) + { + if (EditorApplication.hierarchyWindowItemOnGUI != null) + { + // Adjust rect for the right aligned column for the prefab isolation button + rect.xMax -= + GameObjectStyles.rightArrow.fixedWidth + + GameObjectStyles.rightArrow.margin.horizontal; + + EditorApplication.hierarchyWindowItemOnGUI(itemID, rect); + } + } + private void HandlePrefabInstanceOverrideStatus(GameObjectTreeViewItem goItem, Rect rect, bool selected, bool focused) { GameObject go = goItem.objectPPTR as GameObject; diff --git a/Editor/Mono/GUI/TreeView/TreeViewControl/TreeViewControl.cs b/Editor/Mono/GUI/TreeView/TreeViewControl/TreeViewControl.cs index ce67fbd11b..d981fa2618 100644 --- a/Editor/Mono/GUI/TreeView/TreeViewControl/TreeViewControl.cs +++ b/Editor/Mono/GUI/TreeView/TreeViewControl/TreeViewControl.cs @@ -563,9 +563,13 @@ protected virtual void KeyEvent() // Used to reveal an item protected virtual IList GetAncestors(int id) { + var item = FindItem(id); + if (item == null) + return new List(); + // Default behavior assumes complete tree HashSet parentsAbove = new HashSet(); - TreeViewUtility.GetParentsAboveItem(FindItem(id), parentsAbove); + TreeViewUtility.GetParentsAboveItem(item, parentsAbove); return parentsAbove.ToArray(); } @@ -583,10 +587,7 @@ TreeViewItem FindItem(int id) if (rootItem == null) throw new InvalidOperationException("FindItem failed: root item has not been created yet"); - var item = TreeViewUtility.FindItem(id, rootItem); - if (item == null) - throw new ArgumentException(string.Format("Could not find item with id: {0}. FindItem assumes complete tree is built. Most likely the item is not allocated because it is hidden under a collapsed item. Check if GetAncestors are overriden for the tree view.", id)); - return item; + return TreeViewUtility.FindItem(id, rootItem); } // Selection diff --git a/Editor/Mono/GUI/WindowLayout.cs b/Editor/Mono/GUI/WindowLayout.cs index a39b2fe521..51a2f5f8d1 100644 --- a/Editor/Mono/GUI/WindowLayout.cs +++ b/Editor/Mono/GUI/WindowLayout.cs @@ -68,6 +68,10 @@ public float size } } + const string tabsLayoutKey = "tabs"; + const string verticalLayoutKey = "vertical"; + const string horizontalLayoutKey = "horizontal"; + private const string kMaximizeRestoreFile = "CurrentMaximizeLayout.dwlt"; private const string kDefaultLayoutName = "Default.wlt"; internal static string layoutResourcesPath => Path.Combine(EditorApplication.applicationContentsPath, "Resources/Layouts"); @@ -87,15 +91,15 @@ public static void LoadDefaultWindowPreferences() public static void LoadCurrentModeLayout(bool keepMainWindow) { InitializeLayoutPreferencesFolder(); - - var layoutData = ModeService.GetModeDataSection(ModeDescriptor.LayoutKey) as JSONObject; - if (layoutData == null) + var dynamicLayout = ModeService.GetDynamicLayout(); + if (dynamicLayout == null) LoadProjectLayout(keepMainWindow); else { + var projectLayoutExists = File.Exists(ProjectLayoutPath); - if ((projectLayoutExists && Convert.ToBoolean(layoutData["restore_saved_layout"])) - || !LoadModeDynamicLayout(keepMainWindow, layoutData)) + if ((projectLayoutExists && Convert.ToBoolean(dynamicLayout["restore_saved_layout"])) + || !LoadModeDynamicLayout(keepMainWindow, dynamicLayout)) LoadProjectLayout(keepMainWindow); } } @@ -129,12 +133,16 @@ private static View LoadLayoutView(Type[] availableEditorWindowTypes, LayoutV { if (!viewInfo.used) return null; + View view = null; if (viewInfo.isContainer) { - bool useTabs = viewInfo.extendedData.Contains("tabs") && Convert.ToBoolean(viewInfo.extendedData["tabs"]); - bool useSplitter = viewInfo.extendedData.Contains("vertical") || viewInfo.extendedData.Contains("horizontal"); - bool isVertical = viewInfo.extendedData.Contains("vertical") && Convert.ToBoolean(viewInfo.extendedData["vertical"]); + bool useTabs = viewInfo.extendedData.Contains(tabsLayoutKey) && Convert.ToBoolean(viewInfo.extendedData[tabsLayoutKey]); + bool useSplitter = viewInfo.extendedData.Contains(verticalLayoutKey) || viewInfo.extendedData.Contains(horizontalLayoutKey); + bool isVertical = viewInfo.extendedData.Contains(verticalLayoutKey) && Convert.ToBoolean(viewInfo.extendedData[verticalLayoutKey]); + + if (useTabs && useSplitter) + Debug.LogWarning($"{ModeService.currentId} defines both tabs and splitter (horizontal or vertical) layouts.\n You can only define one to true (i.e. tabs = true) in the editor mode file."); if (useSplitter) { @@ -163,9 +171,9 @@ private static View LoadLayoutView(Type[] availableEditorWindowTypes, LayoutV var cw = useTabs ? width : (isVertical ? width : width * lvi.size); var ch = useTabs ? height : (isVertical ? height * lvi.size : height); - if (useTabs) + if (useTabs && view is DockArea da) { - (view as DockArea).AddTab((EditorWindow)ScriptableObject.CreateInstance(lvi.type)); + da.AddTab((EditorWindow)ScriptableObject.CreateInstance(lvi.type)); } else { @@ -239,8 +247,11 @@ private static ContainerWindow GenerateLayout(bool keepMainWindow, Type[] availa mainContainerWindow.SetMinMaxSizes(mainWindowMinSize, mainWindowMaxSize); } - mainContainerWindow.windowID = $"MainView_{ModeService.currentId}"; - mainContainerWindow.LoadGeometry(true); + var mainViewID = $"MainView_{ModeService.currentId}"; + var hasMainViewGeometrySettings = EditorPrefs.HasKey($"{mainViewID}h"); + mainContainerWindow.windowID = mainViewID; + if (hasMainViewGeometrySettings) + mainContainerWindow.LoadGeometry(true); var width = mainContainerWindow.position.width; var height = mainContainerWindow.position.height; @@ -305,15 +316,16 @@ private static bool ParseViewData(Type[] availableEditorWindowTypes, object view if (viewData is string) { viewInfo.className = Convert.ToString(viewData); - viewInfo.used = !String.IsNullOrEmpty(viewInfo.className); + viewInfo.used = !string.IsNullOrEmpty(viewInfo.className); if (!viewInfo.used) return true; } - else if (viewData is IDictionary) + else if (viewData is JSONObject viewExpandedData) { - var viewExpandedData = viewData as IDictionary; - - if (viewExpandedData.Contains("children") || viewExpandedData.Contains("vertical") || viewExpandedData.Contains("horizontal") || viewExpandedData.Contains("tabs")) + if (viewExpandedData.Contains("children") + || viewExpandedData.Contains(verticalLayoutKey) + || viewExpandedData.Contains(horizontalLayoutKey) + || viewExpandedData.Contains(tabsLayoutKey)) { viewInfo.isContainer = true; viewInfo.className = string.Empty; @@ -363,7 +375,10 @@ private static void LoadProjectLayout(bool keepMainWindow) { var currentLayoutPath = GetCurrentLayoutPath(); if (EnsureDirectoryCreated(ProjectLayoutPath)) + { + Console.WriteLine($"[LAYOUT] LoadProjectLayout: Copying Project Current Layout: {ProjectLayoutPath} from {currentLayoutPath}"); FileUtil.CopyFileOrDirectory(currentLayoutPath, ProjectLayoutPath); + } } Debug.Assert(File.Exists(ProjectLayoutPath)); @@ -418,6 +433,7 @@ private static void InitializeLayoutPreferencesFolder() if (!Directory.Exists(layoutsModePreferencesPath)) { + Console.WriteLine($"[LAYOUT] {layoutsModePreferencesPath} does not exist. Copying base layouts."); // Make sure we have a valid default mode folder initialized with the proper default layouts. if (layoutsDefaultModePreferencesPath == layoutsModePreferencesPath) { @@ -448,7 +464,7 @@ private static void InitializeLayoutPreferencesFolder() // No mode default layout, use the editor_resources Default: defaultModeLayoutPath = Path.Combine(layoutResourcesPath, kDefaultLayoutName); } - + Console.WriteLine($"[LAYOUT] Copying {defaultModeLayoutPath} to {defaultLayoutPath}"); // If not copy our default file to the preferences folder FileUtil.CopyFileOrDirectory(defaultModeLayoutPath, defaultLayoutPath); } @@ -1364,7 +1380,10 @@ internal static void LoadDefaultLayout() FileUtil.DeleteFileOrDirectory(ProjectLayoutPath); if (EnsureDirectoryCreated(ProjectLayoutPath)) + { + Console.WriteLine($"[LAYOUT] LoadDefaultLayout: Copying Project Current Layout: {ProjectLayoutPath} from {GetDefaultLayoutPath()}"); FileUtil.CopyFileOrDirectory(GetDefaultLayoutPath(), ProjectLayoutPath); + } Debug.Assert(File.Exists(ProjectLayoutPath)); LoadWindowLayout(ProjectLayoutPath, true); diff --git a/Editor/Mono/GUIView.bindings.cs b/Editor/Mono/GUIView.bindings.cs index 7f625e474c..aac0a2d304 100644 --- a/Editor/Mono/GUIView.bindings.cs +++ b/Editor/Mono/GUIView.bindings.cs @@ -43,6 +43,7 @@ internal partial class GUIView internal extern void SetInternalGameViewDimensions(Rect rect, Rect clippedRect, Vector2 targetSize); internal extern void SetMainPlayModeViewSize(Vector2 targetSize); internal extern void SetDisplayViewSize(int displayId, Vector2 targetSize); + internal extern Vector2 GetDisplayViewSize(int displayId); internal extern void SetAsStartView(); internal extern void SetAsLastPlayModeView(); internal extern void SetPlayModeView(bool value); diff --git a/Editor/Mono/GUIView.cs b/Editor/Mono/GUIView.cs index 0bbf125639..a1c000a816 100644 --- a/Editor/Mono/GUIView.cs +++ b/Editor/Mono/GUIView.cs @@ -155,9 +155,7 @@ internal IWindowBackend windowBackend set { if (m_WindowBackend != null) - { m_WindowBackend.OnDestroy(this); - } m_WindowBackend = value; m_WindowBackend?.OnCreate(this); @@ -195,6 +193,8 @@ protected virtual void OldOnGUI() {} // In that case, commands are not delegated (e.g., keyboard-based delete in Hierarchy/Project) protected virtual void OnGUI() {} + protected virtual void OnBackingScaleFactorChanged() { } + protected override void SetPosition(Rect newPos) { Rect oldWinPos = windowPosition; diff --git a/Editor/Mono/GameView/GameView.cs b/Editor/Mono/GameView/GameView.cs index 2b9f97c1ab..f552368474 100644 --- a/Editor/Mono/GameView/GameView.cs +++ b/Editor/Mono/GameView/GameView.cs @@ -4,6 +4,7 @@ using System; using UnityEngine; +using UnityEngine.Bindings; using UnityEditorInternal; using UnityEditor.SceneManagement; using UnityEditor.Modules; @@ -98,7 +99,10 @@ internal static class Styles public static GUIContent gizmosContent = EditorGUIUtility.TrTextContent("Gizmos"); public static GUIContent zoomSliderContent = EditorGUIUtility.TrTextContent("Scale", "Size of the game view on the screen."); public static GUIContent vsyncContent = EditorGUIUtility.TrTextContent("VSync"); - public static GUIContent muteContent = EditorGUIUtility.TrTextContent("Mute Audio"); + public static GUIContent muteOffContent = EditorGUIUtility.TrIconContent("SceneviewAudio On", "Mute Audio"); + public static GUIContent muteOnContent = EditorGUIUtility.TrIconContent("SceneviewAudio", "Mute Audio"); + public static GUIContent shortcutsOnContent = EditorGUIUtility.TrIconContent("Keyboard", "Unity Shortcuts"); + public static GUIContent shortcutsOffContent = EditorGUIUtility.TrIconContent("KeyboardShortcutsDisabled", "Unity Shortcuts"); public static GUIContent statsContent = EditorGUIUtility.TrTextContent("Stats"); public static GUIContent frameDebuggerOnContent = EditorGUIUtility.TrTextContent("Frame Debugger On"); public static GUIContent loadRenderDocContent = EditorGUIUtility.TrTextContent(UnityEditor.RenderDocUtil.loadRenderDocLabel); @@ -106,6 +110,7 @@ internal static class Styles public static GUIContent clearEveryFrameContextMenuContent = EditorGUIUtility.TrTextContent("Clear Every Frame in Edit Mode"); 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."); @@ -146,6 +151,9 @@ static Styles() static double s_LastScrollTime; + [FreeFunction] + extern private static bool NeedToPerformRendering(); + public GameView() { autoRepaintOnSceneChange = true; @@ -417,7 +425,7 @@ void InitializeZoomArea() m_ZoomArea = new ZoomableArea(true, false) {uniformScale = true, upDirection = ZoomableArea.YDirection.Negative}; } - private void UndoRedoPerformed() + private void UndoRedoPerformed(in UndoRedoInfo info) { Repaint(); } @@ -432,7 +440,7 @@ public void OnEnable() ModeService.modeChanged += OnEditorModeChanged; EditorApplication.playModeStateChanged += OnPlayModeStateChanged; - Undo.undoRedoPerformed += UndoRedoPerformed; + Undo.undoRedoEvent += UndoRedoPerformed; targetSize = targetRenderSize; @@ -443,7 +451,7 @@ public void OnDisable() { ModeService.modeChanged -= OnEditorModeChanged; EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; - Undo.undoRedoPerformed -= UndoRedoPerformed; + Undo.undoRedoEvent -= UndoRedoPerformed; if (m_RenderTexture) { @@ -510,9 +518,17 @@ internal override void OnResized() 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 + Rect viewInWindow = GetViewInWindow(pos); Rect viewPixelRect = GetViewPixelRect(viewInWindow); - SetDisplayViewSize(targetDisplay, new Vector2(viewPixelRect.width, viewPixelRect.height)); + var newTargetSize = + GameViewSizes.GetRenderTargetSize(viewPixelRect, currentSizeGroupType, selectedSizeIndex, out m_TargetClamped); + + if (newTargetSize == GetDisplayViewSize(targetDisplay) && currentGameViewSize.sizeType != GameViewSizeType.AspectRatio) + return; + SetDisplayViewSize(targetDisplay, new Vector2(newTargetSize.x, newTargetSize.y)); UpdateZoomAreaAndParent(); } @@ -750,6 +766,9 @@ private void DoToolbarGUI() m_Parent.CaptureMetalScene(); } + if (GUILayout.Button(Styles.frameDebuggerContent, EditorStyles.toolbarButton)) + FrameDebuggerWindow.ShowFrameDebuggerWindow(); + if (RenderDoc.IsLoaded()) { using (new EditorGUI.DisabledScope(!RenderDoc.IsSupported())) @@ -796,12 +815,17 @@ private void DoToolbarGUI() // Don't display the fullscreen selection dropdown on gameview toolbars that are already fullscreen. if (!isFullscreen) { - EditorGUI.BeginDisabled(Application.isPlaying); + EditorGUI.BeginDisabled(EditorApplication.isPlaying && !EditorApplication.isPaused); EditorGUILayout.GameViewOnPlayPopup(playModeBehaviorIdx, this, EditorStyles.toolbarDropDown); EditorGUI.EndDisabled(); } - EditorUtility.audioMasterMute = GUILayout.Toggle(EditorUtility.audioMasterMute, Styles.muteContent, EditorStyles.toolbarButton); + EditorUtility.audioMasterMute = GUILayout.Toggle(EditorUtility.audioMasterMute, + EditorUtility.audioMasterMute ? Styles.muteOnContent : Styles.muteOffContent, EditorStyles.toolbarButton); + + ShortcutIntegration.ignoreWhenPlayModeFocused = GUILayout.Toggle(ShortcutIntegration.ignoreWhenPlayModeFocused, + ShortcutIntegration.ignoreWhenPlayModeFocused ? Styles.shortcutsOffContent : Styles.shortcutsOnContent, EditorStyles.toolbarButton); + m_Stats = GUILayout.Toggle(m_Stats, Styles.statsContent, EditorStyles.toolbarButton); if (EditorGUILayout.DropDownToggle(ref m_Gizmos, Styles.gizmosContent, EditorStyles.toolbarDropDownToggleRight)) @@ -1115,8 +1139,11 @@ private void OnGUI() showGizmos = m_Gizmos; clearColor = kClearBlack; renderIMGUI = true; + viewPadding = targetInParent.position; + viewMouseScale = gameMouseScale; - if (!EditorApplication.isPlaying || (EditorApplication.isPlaying && Time.frameCount % OnDemandRendering.renderFrameInterval == 0)) + if (!EditorApplication.isPlaying || + (EditorApplication.isPlaying && NeedToPerformRendering() && !Unsupported.IsEditorPlayerLoopWaiting())) m_RenderTexture = RenderView(gameMousePosition, clearTexture); if (m_TargetClamped) @@ -1226,6 +1253,39 @@ private void DrawDuringFullscreenBackground() EditorGUILayout.LabelField(content, Styles.smallCenteredText); } + internal void SetCustomResolution(Vector2 res, string baseName) + { + GameViewSize customSize = null; + var idx = -1; + var sizes = GameViewSizes.instance.currentGroup; + for (var i = 0; i < sizes.GetTotalCount(); ++i) + { + var sz = sizes.GetGameViewSize(i); + if (sz.displayText.StartsWith(baseName)) + { + customSize= sz; + idx = i; + sz.width = (int)res.x; + sz.height = (int)res.y; + break; + } + } + + if (customSize == null) + { + customSize = new GameViewSize(GameViewSizeType.FixedResolution, (int)res.x, (int) res.y, baseName); + idx = sizes.GetTotalCount(); + sizes.AddCustomSize(customSize); + } + + GameViewSizes.instance.SaveToHDD(); + + selectedSizeIndex = idx; + UpdateZoomAreaAndParent(); + targetSize = targetRenderSize; + SceneView.RepaintAll(); + } + void IGameViewOnPlayMenuUser.OnPlayPopupSelection(int indexClicked, object objectSelected) { playModeBehaviorIdx = indexClicked; @@ -1241,6 +1301,7 @@ void IGameViewOnPlayMenuUser.OnPlayPopupSelection(int indexClicked, object objec { enterPlayModeBehavior = EnterPlayModeBehavior.PlayMaximized; fullscreenMonitorIdx = PlayModeView.kFullscreenNone; + GameViewOnPlayMenu.SetFocusedToggle(this, true); } else { diff --git a/Editor/Mono/GameView/GameViewOnPlayMenu.cs b/Editor/Mono/GameView/GameViewOnPlayMenu.cs index aa619d42d6..3713322037 100644 --- a/Editor/Mono/GameView/GameViewOnPlayMenu.cs +++ b/Editor/Mono/GameView/GameViewOnPlayMenu.cs @@ -16,6 +16,7 @@ private static class Styles 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:"); @@ -32,12 +33,14 @@ private static class Styles 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() @@ -46,7 +49,7 @@ public override Vector2 GetWindowSize() var size = CalcSize(); size.x = Mathf.Max(size.x, playFocusedToggleSize.x + Styles.kMargin * 2); - size.y += Styles.frameHeight + EditorGUI.kControlVerticalSpacing; + size.y += Styles.frameHeight + EditorGUI.kControlVerticalSpacing + EditorGUI.kSingleLineHeight; return size; } @@ -61,33 +64,51 @@ private bool IsVSyncToggleVisible() gfxDeviceType == UnityEngine.Rendering.GraphicsDeviceType.OpenGLCore; } - private void DoVSyncToggle() + private void DoVSyncToggle(Rect rect) { if (IsVSyncToggleVisible()) { - m_GameView.vSyncEnabled = GUILayout.Toggle(m_GameView.vSyncEnabled, Styles.vSyncToggleContent); + m_GameView.vSyncEnabled = GUI.Toggle(rect, m_GameView.vSyncEnabled, Styles.vSyncToggleContent); } else { m_GameView.vSyncEnabled = false; - GUILayout.Label(Styles.vSyncUnsupportedContent, EditorStyles.miniLabel); + GUI.Label(rect, Styles.vSyncUnsupportedContent, EditorStyles.miniLabel); } } - private void OnPlayFocusedToggleChanged(bool newValue) + private static void UncheckFocusToggleOnAllViews() { List playViewList; WindowLayout.ShowAppropriateViewOnEnterExitPlaymodeList(true, out playViewList); - foreach (PlayModeView playView in playViewList) { - if (playView != (m_GameView as PlayModeView)) + if (playView is IGameViewOnPlayMenuUser) { ((IGameViewOnPlayMenuUser)playView).playFocused = false; } } + } - m_GameView.playFocused = newValue; + 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) @@ -95,20 +116,23 @@ public override void OnGUI(Rect rect) var frameRect = new Rect(rect.x, rect.y, rect.width, rect.height); GUI.Label(frameRect, "", EditorStyles.viewBackground); - GUILayout.BeginHorizontal(); - GUILayout.Space(15); // Move everything slightly right so it doesn't overlap with our "repaint indicator" - bool playFocuedToggle = GUILayout.Toggle(m_GameView.playFocused, Styles.playFocusedToggleContent); - if (playFocuedToggle != m_GameView.playFocused) + 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) { - OnPlayFocusedToggleChanged(playFocuedToggle); + SetFocusedToggle(m_GameView, playFocusedToggle); } - DoVSyncToggle(); - GUILayout.EndHorizontal(); + var vsyncToggleRect = new Rect(focusTextSize.x + Styles.kMargin*2, Styles.kTopMargin, rect.width, EditorGUI.kSingleLineHeight); + DoVSyncToggle(vsyncToggleRect); - GUILayout.Label(Styles.gamePlayModeBehaviorLabelContent, EditorStyles.boldLabel); + 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.height = rect.height - Styles.contentOffset; - rect.y = rect.y + Styles.contentOffset; + rect.y = rect.y + Styles.contentOffset + EditorGUI.kSingleLineHeight; base.OnGUI(rect); } diff --git a/Editor/Mono/Graphics/EditorMaterialUtility.bindings.cs b/Editor/Mono/Graphics/EditorMaterialUtility.bindings.cs index f7f6be0ca0..5f3bf014df 100644 --- a/Editor/Mono/Graphics/EditorMaterialUtility.bindings.cs +++ b/Editor/Mono/Graphics/EditorMaterialUtility.bindings.cs @@ -28,5 +28,11 @@ public sealed partial class EditorMaterialUtility [FreeFunction("EditorMaterialUtilityBindings::SetShaderNonModifiableDefaults")] extern public static void SetShaderNonModifiableDefaults([NotNull] Shader shader, string[] name, Texture[] textures); + + [FreeFunction("EditorMaterialUtilityBindings::GetShaderDefaultTexture")] + extern internal static Texture GetShaderDefaultTexture([NotNull] Shader shader, string name); + + [FreeFunction("EditorMaterialUtilityBindings::GetMaterialParentFromFile")] + extern internal static GUID GetMaterialParentFromFile(string assetPath); } } diff --git a/Editor/Mono/Graphics/ShaderCompilerData.cs b/Editor/Mono/Graphics/ShaderCompilerData.cs index 8a52c64462..d31085e31e 100644 --- a/Editor/Mono/Graphics/ShaderCompilerData.cs +++ b/Editor/Mono/Graphics/ShaderCompilerData.cs @@ -45,6 +45,7 @@ public struct ShaderCompilerData private ShaderRequirements m_ShaderRequirements; private GraphicsTier m_GraphicsTier; private ShaderCompilerPlatform m_ShaderCompilerPlatform; + private BuildTarget m_BuildTarget; public ShaderRequirements shaderRequirements { @@ -60,6 +61,8 @@ public ShaderCompilerPlatform shaderCompilerPlatform { get { return m_ShaderCompilerPlatform; } } + + public BuildTarget buildTarget { get { return m_BuildTarget; } } } public enum ShaderCompilerPlatform @@ -75,9 +78,12 @@ public enum ShaderCompilerPlatform Vulkan = 18, // Vulkan SPIR-V, compiled with MS D3DCompiler + HLSLcc Switch = 19, // Nintendo Switch (NVN) XboxOneD3D12 = 20, // Xbox One D3D12 - GameCore = 21, // Game Core + GameCoreXboxOne = 21, // Game Core Xbox One + GameCoreXboxSeries= 22, // Game Core Scarlett PS5 = 23, // PS5 - PS5NGGC = 24 // PS5 NGGC + PS5NGGC = 24, // PS5 NGGC + [System.Obsolete(@"GameCore is deprecated, please use GameCoreXboxOne (UnityUpgradable) -> GameCoreXboxOne ", true)] + GameCore = 25 // Game Core deprecated as single platform } public enum ShaderCompilerMessageSeverity diff --git a/Editor/Mono/Grids/EditorSnap.cs b/Editor/Mono/Grids/EditorSnap.cs index 81a9eec284..7af57679bd 100644 --- a/Editor/Mono/Grids/EditorSnap.cs +++ b/Editor/Mono/Grids/EditorSnap.cs @@ -60,9 +60,9 @@ internal static bool activeToolGridSnapEnabled } } - internal static bool gridSnapActive + public static bool gridSnapActive { - get { return !incrementalSnapActive && activeToolGridSnapEnabled && instance.snapEnabled; } + get { return !incrementalSnapActive && activeToolGridSnapEnabled && gridSnapEnabled; } } public static event Action gridSnapEnabledChanged; @@ -85,12 +85,18 @@ public static bool gridSnapEnabled } } + public static Vector3 gridSize + { + get => GridSettings.size; + set => GridSettings.size = value; + } + internal static bool hotkeyActive { get { return EditorGUI.actionKey; } } - internal static bool incrementalSnapActive + public static bool incrementalSnapActive { get { return Event.current != null && EditorGUI.actionKey; } } diff --git a/Editor/Mono/HandleUtility.bindings.cs b/Editor/Mono/HandleUtility.bindings.cs index 68ff58b519..d8cdb9fd30 100644 --- a/Editor/Mono/HandleUtility.bindings.cs +++ b/Editor/Mono/HandleUtility.bindings.cs @@ -16,7 +16,7 @@ public sealed partial class HandleUtility internal static extern GameObject[] Internal_PickRectObjects(Camera cam, Rect rect, bool selectPrefabRoots, bool drawGizmos); - internal static extern bool Internal_FindNearestVertex(Camera cam, Vector2 screenPoint, Transform[] objectsToSearch, Transform[] ignoreObjects, out Vector3 vertex); + internal static extern GameObject Internal_FindNearestVertex(Camera cam, Vector2 screenPoint, Transform[] objectsToSearch, Transform[] ignoreObjects, out Vector3 vertex, out bool found); internal static extern GameObject Internal_PickClosestGO(Camera cam, int layers, Vector2 position, GameObject[] ignore, GameObject[] filter, bool drawGizmos, out int materialIndex); diff --git a/Editor/Mono/HandleUtility.cs b/Editor/Mono/HandleUtility.cs index 0f8ad8420f..9b7cd6f576 100644 --- a/Editor/Mono/HandleUtility.cs +++ b/Editor/Mono/HandleUtility.cs @@ -924,6 +924,25 @@ public static GameObject[] PickRectObjects(Rect rect, bool selectPrefabRootsOnly return Internal_PickRectObjects(cam, rect, selectPrefabRootsOnly, allowGizmos); } + public static bool FindNearestVertex(Vector2 guiPoint, out Vector3 vertex, out GameObject gameObject) + { + return FindNearestVertex(guiPoint, null, ignoreRaySnapObjects, out vertex, out gameObject); + } + + public static bool FindNearestVertex(Vector2 guiPoint, Transform[] objectsToSearch, out Vector3 vertex, out GameObject gameObject) + { + return FindNearestVertex(guiPoint, objectsToSearch, ignoreRaySnapObjects, out vertex, out gameObject); + } + + public static bool FindNearestVertex(Vector2 guiPoint, Transform[] objectsToSearch, Transform[] objectsToIgnore, out Vector3 vertex, out GameObject gameObject) + { + Camera cam = Camera.current; + var screenPoint = EditorGUIUtility.PointsToPixels(guiPoint); + screenPoint.y = cam.pixelRect.yMax - screenPoint.y; + gameObject = Internal_FindNearestVertex(cam, screenPoint, objectsToSearch, objectsToIgnore, out vertex, out bool found); + return found; + } + public static bool FindNearestVertex(Vector2 guiPoint, out Vector3 vertex) { return FindNearestVertex(guiPoint, null, ignoreRaySnapObjects, out vertex); @@ -939,7 +958,8 @@ public static bool FindNearestVertex(Vector2 guiPoint, Transform[] objectsToSear Camera cam = Camera.current; var screenPoint = EditorGUIUtility.PointsToPixels(guiPoint); screenPoint.y = cam.pixelRect.yMax - screenPoint.y; - return Internal_FindNearestVertex(cam, screenPoint, objectsToSearch, objectsToIgnore, out vertex); + Internal_FindNearestVertex(cam, screenPoint, objectsToSearch, objectsToIgnore, out vertex, out bool found); + return found; } // Until `pickGameObjectCustomPasses` can handle sorting priority correctly, we need a way to override the @@ -1567,71 +1587,78 @@ internal static void FilterRendererIDs(Renderer[] renderers, out int[] parentRen return; } - var childCount = 0; var parentIndex = 0; - var childIndex = 0; parentRendererIDs = new int[renderers.Length]; foreach (var renderer in renderers) - { - childCount += renderer.transform.hierarchyCount; parentRendererIDs[parentIndex++] = renderer.GetInstanceID(); - } - childRendererIDs = new int[childCount]; + var tempChildRendererIDs = new HashSet(); foreach (var renderer in renderers) { var children = renderer.GetComponentsInChildren(); for (int i = 1; i < children.Length; i++) { var id = children[i].GetInstanceID(); - if (!HasMatchingInstanceID(parentRendererIDs, id)) - childRendererIDs[childIndex++] = id; + if (!HasMatchingInstanceID(parentRendererIDs, id, parentIndex)) + tempChildRendererIDs.Add(id); } } + + childRendererIDs = tempChildRendererIDs.ToArray(); } - internal static void FilterRendererIDs(GameObject[] gameObjects, out int[] parentRendererIDs, out int[] childRendererIDs) + internal static void FilterInstanceIDs(GameObject[] gameObjects, out int[] parentInstanceIDs, out int[] childInstanceIDs) { if (gameObjects == null) { Debug.LogWarning("The GameObject array is null. Handles.DrawOutline will not be rendered."); - parentRendererIDs = new int[0]; - childRendererIDs = new int[0]; + parentInstanceIDs = new int[0]; + childInstanceIDs = new int[0]; return; } - var childCount = 0; - var parentIndex = 0; - var childIndex = 0; - parentRendererIDs = new int[gameObjects.Length]; - + var tempParentInstanceIDs = new HashSet(); foreach (var go in gameObjects) { - childCount += go.transform.hierarchyCount; if (go.TryGetComponent(out Renderer renderer)) - parentRendererIDs[parentIndex++] = renderer.GetInstanceID(); + tempParentInstanceIDs.Add(renderer.GetInstanceID()); + else if (go.TryGetComponent(out Terrain terrain)) + tempParentInstanceIDs.Add(terrain.GetInstanceID()); } - childRendererIDs = new int[childCount]; + var tempChildInstanceIDs = new HashSet(); foreach (var go in gameObjects) { - var children = go.GetComponentsInChildren(); - for (int i = 1; i < children.Length; i++) + var childRenderers = go.GetComponentsInChildren(); + for (int i = 0; i < childRenderers.Length; i++) { - var id = children[i].GetInstanceID(); - if (!HasMatchingInstanceID(parentRendererIDs, id)) - childRendererIDs[childIndex++] = id; + var id = childRenderers[i].GetInstanceID(); + if (!tempParentInstanceIDs.Contains(id)) + tempChildInstanceIDs.Add(id); + } + + var childTerrains = go.GetComponentsInChildren(); + for (int i = 0; i < childTerrains.Length; i++) + { + var id = childTerrains[i].GetInstanceID(); + if (!tempParentInstanceIDs.Contains(id)) + tempChildInstanceIDs.Add(id); } } + + parentInstanceIDs = tempParentInstanceIDs.ToArray(); + childInstanceIDs = tempChildInstanceIDs.ToArray(); } - static bool HasMatchingInstanceID(int[] ids, int id) + static bool HasMatchingInstanceID(int[] ids, int id, int cutoff) { for (int i = 0; i < ids.Length; i++) { if (ids[i] == id) return true; + if (i > cutoff) + return false; } return false; } diff --git a/Editor/Mono/Handles.bindings.cs b/Editor/Mono/Handles.bindings.cs index c3bb6815d0..e93ce171f6 100644 --- a/Editor/Mono/Handles.bindings.cs +++ b/Editor/Mono/Handles.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 System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Bindings; @@ -10,6 +11,13 @@ namespace UnityEditor { + [Flags] + internal enum OutlineDrawMode + { + SelectionOutline = 1 << 0, + SelectionWire = 1 << 1, + } + [NativeHeader("Editor/Src/Handles/Handles.bindings.h")] public sealed partial class Handles { @@ -51,7 +59,7 @@ public static extern Matrix4x4 matrix static extern void Internal_DrawCameraWithGrid([NotNull("NullExceptionObject")] Camera cam, DrawCameraMode renderMode, ref DrawGridParameters gridParam, bool drawGizmos, bool drawSelection); [FreeFunction] - internal static extern void Internal_DrawOutline(Color parentNodeColor, Color childNodeColor, int submeshOutlineMaterialId, int[] parentRenderers, int[] childRenderers, float parentOutlineAlpha = 0, float childOutlineAlpha = 0, bool drawSelectionWire = false); + internal static extern void Internal_DrawOutline(Color parentNodeColor, Color childNodeColor, int submeshOutlineMaterialId, int[] parentRenderers, int[] childRenderers, OutlineDrawMode outlineMode, float parentOutlineAlpha = 0, float childOutlineAlpha = 0); [FreeFunction] static extern void Internal_DrawCamera([NotNull("NullExceptionObject")] Camera cam, DrawCameraMode renderMode, bool drawGizmos, bool drawSelection); @@ -60,7 +68,7 @@ public static extern Matrix4x4 matrix static extern void Internal_DrawCameraWithFilter([NotNull("NullExceptionObject")] Camera cam, DrawCameraMode renderMode, bool drawGizmos, bool drawSelection, GameObject[] filter); [FreeFunction] - static extern void Internal_FinishDrawingCamera([NotNull("NullExceptionObject")] Camera cam, [DefaultValue("true")] bool drawGizmos); + internal static extern void Internal_FinishDrawingCamera([NotNull("NullExceptionObject")] Camera cam, [DefaultValue("true")] bool drawGizmos); static void Internal_FinishDrawingCamera(Camera cam) { Internal_FinishDrawingCamera(cam, true); } [FreeFunction] diff --git a/Editor/Mono/Handles.cs b/Editor/Mono/Handles.cs index 51dbf11201..e907a06238 100644 --- a/Editor/Mono/Handles.cs +++ b/Editor/Mono/Handles.cs @@ -336,7 +336,7 @@ internal static void DrawLine(Vector3 p1, Vector3 p2, bool dottedLine) static float ThicknessToPixels(float thickness) { var halfThicknessPixels = thickness * EditorGUIUtility.pixelsPerPoint * 0.5f; - if (halfThicknessPixels < 0.5f) + if (halfThicknessPixels < 0.9f) halfThicknessPixels = 0; return halfThicknessPixels; } @@ -511,16 +511,6 @@ public static void DrawGizmos(Camera camera) Internal_DoDrawGizmos(camera); } - public static Quaternion Disc(int id, Quaternion rotation, Vector3 position, Vector3 axis, float size, bool cutoffPlane, float snap) - { - return UnityEditorInternal.Disc.Do(id, rotation, position, axis, size, cutoffPlane, snap); - } - - public static Quaternion FreeRotateHandle(int id, Quaternion rotation, Vector3 position, float size) - { - return UnityEditorInternal.FreeRotate.Do(id, rotation, position, size); - } - // Make a 3D slider public static Vector3 Slider(Vector3 position, Vector3 direction) { @@ -591,6 +581,11 @@ internal static bool Button(int controlID, Vector3 position, Quaternion directio return UnityEditorInternal.Button.Do(controlID, position, direction, size, pickSize, capFunction); } + internal static bool Button(int controlID, Vector3 position, Quaternion direction, float size, float pickSize, CapFunction capFunction, bool checkMouseProximity) + { + return UnityEditorInternal.Button.Do(controlID, position, direction, size, pickSize, capFunction, checkMouseProximity); + } + // Draw a cube. Pass this into handle functions. public static void CubeHandleCap(int controlID, Vector3 position, Quaternion rotation, float size, EventType eventType) { @@ -631,7 +626,7 @@ public static void ConeHandleCap(int controlID, Vector3 position, Quaternion rot case EventType.MouseMove: HandleUtility.AddControl(controlID, HandleUtility.DistanceToCone(position, rotation, size)); break; - case (EventType.Repaint): + case EventType.Repaint: Graphics.DrawMeshNow(coneMesh, StartCapDraw(position, rotation, size)); break; } @@ -856,12 +851,23 @@ public static Vector3 PositionHandle(Vector3 position, Quaternion rotation) return DoPositionHandle(position, rotation); } + public static Vector3 PositionHandle(PositionHandleIds ids, Vector3 position, Quaternion rotation) + { + return DoPositionHandle(ids, position, rotation); + } + // Make a Scene view rotation handle. public static Quaternion RotationHandle(Quaternion rotation, Vector3 position) { return DoRotationHandle(rotation, position); } + public static Quaternion RotationHandle(RotationHandleIds ids, Quaternion rotation, Vector3 position) + { + return DoRotationHandle(ids, rotation, position, RotationHandleParam.Default); + } + + // Make a Scene view scale handle public static Vector3 ScaleHandle(Vector3 scale, Vector3 position, Quaternion rotation) { @@ -950,6 +956,11 @@ public static Vector3 Slider2D(Vector3 handlePos, Vector3 handleDir, Vector3 sli } // Make an unconstrained rotation handle. + public static Quaternion FreeRotateHandle(int id, Quaternion rotation, Vector3 position, float size) + { + return UnityEditorInternal.FreeRotate.Do(id, rotation, position, size); + } + public static Quaternion FreeRotateHandle(Quaternion rotation, Vector3 position, float size) { int id = GUIUtility.GetControlID(s_FreeRotateHandleHash, FocusType.Passive); @@ -957,6 +968,11 @@ public static Quaternion FreeRotateHandle(Quaternion rotation, Vector3 position, } // Make a directional scale slider + public static float ScaleSlider(int id, float scale, Vector3 position, Vector3 direction, Quaternion rotation, float size, float snap) + { + return UnityEditorInternal.SliderScale.DoAxis(id, scale, position, direction, rotation, size, snap); + } + public static float ScaleSlider(float scale, Vector3 position, Vector3 direction, Quaternion rotation, float size, float snap) { int id = GUIUtility.GetControlID(s_ScaleSliderHash, FocusType.Passive); @@ -964,6 +980,11 @@ public static float ScaleSlider(float scale, Vector3 position, Vector3 direction } // Make a 3D disc that can be dragged with the mouse + public static Quaternion Disc(int id, Quaternion rotation, Vector3 position, Vector3 axis, float size, bool cutoffPlane, float snap) + { + return UnityEditorInternal.Disc.Do(id, rotation, position, axis, size, cutoffPlane, snap); + } + public static Quaternion Disc(Quaternion rotation, Vector3 position, Vector3 axis, float size, bool cutoffPlane, float snap) { int id = GUIUtility.GetControlID(s_DiscHash, FocusType.Passive); @@ -1012,6 +1033,18 @@ public static void SnapToGrid(Transform[] transforms, SnapAxis axis = SnapAxis.A } } + // Snap all positions to the grid + public static void SnapToGrid(Vector3[] positions, SnapAxis axis = SnapAxis.All) + { + if (positions != null && positions.Length > 0) + { + for(int i = 0; i objects, Color parentNodeColor, Color childNodeColor, float fillOpacity = 0) { int[] parentRenderers, childRenderers; - HandleUtility.FilterRendererIDs((GameObject[])NoAllocHelpers.ExtractArrayFromList(objects), out parentRenderers, out childRenderers); - Internal_DrawOutline(parentNodeColor, childNodeColor, 0, parentRenderers, childRenderers, fillOpacity, fillOpacity); + HandleUtility.FilterInstanceIDs((GameObject[])NoAllocHelpers.ExtractArrayFromList(objects), out parentRenderers, out childRenderers); + Internal_DrawOutline(parentNodeColor, childNodeColor, 0, parentRenderers, childRenderers, OutlineDrawMode.SelectionOutline, fillOpacity, fillOpacity); Internal_FinishDrawingCamera(Camera.current, true); } @@ -1476,25 +1509,25 @@ public static void DrawOutline(List objects, Color color, float fill ids[index++] = renderer.GetInstanceID(); } - Internal_DrawOutline(color, color, 0, ids, null, fillOpacity, fillOpacity); + Internal_DrawOutline(color, color, 0, ids, null, OutlineDrawMode.SelectionOutline, fillOpacity, fillOpacity); Internal_FinishDrawingCamera(Camera.current, true); } - internal static void DrawOutlineInternal(Color parentNodeColor, Color childNodeColor, float outlineAlpha, int[] parentRenderers, int[] childRenderers) + internal static void DrawOutlineOrWireframeInternal(Color parentNodeColor, Color childNodeColor, float outlineAlpha, int[] parentRenderers, int[] childRenderers, OutlineDrawMode outlineMode) { // RenderOutline will swap color.a and outlineAlpha so we reverse it here to preserve correct behavior wrt Color settings in Preferences var parentOutlineAlpha = parentNodeColor.a; var childOutlineAlpha = childNodeColor.a; parentNodeColor.a = outlineAlpha; childNodeColor.a = outlineAlpha; - Internal_DrawOutline(parentNodeColor, childNodeColor, 0, parentRenderers, childRenderers, parentOutlineAlpha, childOutlineAlpha, true); + Internal_DrawOutline(parentNodeColor, childNodeColor, 0, parentRenderers, childRenderers, outlineMode, parentOutlineAlpha, childOutlineAlpha); } internal static void DrawSubmeshOutline(Color parentNodeColor, Color childNodeColor, float outlineAlpha, int submeshOutlineMaterialId) { int[] parentRenderers, childRenderers; - HandleUtility.FilterRendererIDs(Selection.gameObjects, out parentRenderers, out childRenderers); + HandleUtility.FilterInstanceIDs(Selection.gameObjects, out parentRenderers, out childRenderers); // RenderOutline will swap color.a and outlineAlpha so we reverse it here to preserve correct behavior wrt Color settings in Preferences var parentOutlineAlpha = parentNodeColor.a; @@ -1502,7 +1535,7 @@ internal static void DrawSubmeshOutline(Color parentNodeColor, Color childNodeCo parentNodeColor.a = outlineAlpha; childNodeColor.a = outlineAlpha; - Internal_DrawOutline(parentNodeColor, childNodeColor, submeshOutlineMaterialId, parentRenderers, childRenderers, parentOutlineAlpha, childOutlineAlpha); + Internal_DrawOutline(parentNodeColor, childNodeColor, submeshOutlineMaterialId, parentRenderers, childRenderers, OutlineDrawMode.SelectionOutline, parentOutlineAlpha, childOutlineAlpha); Internal_FinishDrawingCamera(Camera.current, true); } diff --git a/Editor/Mono/Help.cs b/Editor/Mono/Help.cs index 0faf3c1e25..2852dee68e 100644 --- a/Editor/Mono/Help.cs +++ b/Editor/Mono/Help.cs @@ -36,6 +36,7 @@ public partial class Help "https://docs-redirects.unity.com" }; + internal static string k_AlphaReleaseNotesUrlBase = "https://unity3d.com/unity/alpha/"; internal static string k_BetaReleaseNotesUrlBase = "https://unity3d.com/unity/beta/"; internal static string k_ReleaseNotesUrlBase = "https://unity3d.com/unity/whats-new/"; @@ -340,16 +341,15 @@ internal static string HelpFileNameForObject(Object obj) [UnityEngine.Scripting.RequiredByNativeCode] internal static void OpenReleaseNotes() { - var version = InternalEditorUtility.GetUnityVersion(); - var releaseNotesUrl = GetReleaseNotesUrl(InternalEditorUtility.IsUnityBeta(), InternalEditorUtility.GetUnityVersionDigits(), InternalEditorUtility.GetUnityDisplayVersion()); + var releaseNotesUrl = GetReleaseNotesUrl(InternalEditorUtility.GetUnityVersionDigits(), InternalEditorUtility.GetUnityDisplayVersion()); Application.OpenURL(releaseNotesUrl); } - internal static string GetReleaseNotesUrl(bool isBeta, string digitsOnlyVersion, string displayVersion) + internal static string GetReleaseNotesUrl(string digitsOnlyVersion, string displayVersion) { var url = "http://unity3d.com/whatsnew.html"; - - if (isBeta) + var isUnreleased = displayVersion.Contains("a") || displayVersion.Contains("b"); + if (isUnreleased) { var displayVersionWithBuildNumber = new Regex(@"(\d+\.\d+\.[a-z0-9]+)\.\d+"); var m = displayVersionWithBuildNumber.Match(displayVersion); @@ -357,7 +357,15 @@ internal static string GetReleaseNotesUrl(bool isBeta, string digitsOnlyVersion, { displayVersion = m.Groups[1].Value; } - url = $"{k_BetaReleaseNotesUrlBase}{displayVersion}"; + + if (displayVersion.Contains("a")) + { + url = $"{k_AlphaReleaseNotesUrlBase}{displayVersion}"; + } + else + { + url = $"{k_BetaReleaseNotesUrlBase}{displayVersion}"; + } } else { diff --git a/Editor/Mono/HostView.cs b/Editor/Mono/HostView.cs index 48fc052d84..360bf2e81c 100644 --- a/Editor/Mono/HostView.cs +++ b/Editor/Mono/HostView.cs @@ -2,18 +2,16 @@ // 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; using System.IO; -using System.Reflection; -using UnityEditor.Profiling; using System.Linq; +using System.Reflection; using UnityEditor.Overlays; +using UnityEditor.ShortcutManagement; using UnityEditor.StyleSheets; -using UnityEditor.UIElements; using UnityEditorInternal; -using Directory = UnityEditor.ShortcutManagement.Directory; +using UnityEngine; using Object = UnityEngine.Object; namespace UnityEditor @@ -27,6 +25,31 @@ static class Styles public static readonly GUIStyle paneOptions = "PaneOptions"; public static readonly GUIStyle tabWindowBackground = "TabWindowBackground"; + public static class DataModes + { + public const float switchButtonWidth = 16.0f; + + 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"); + + // Use an empty style to avoid the hover effect of normal buttons + public static readonly GUIStyle switchStyle = new GUIStyle(); + + 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") } + }; + } + static Styles() { // Fix annoying GUILayout issue: When using GUILayout in Utility windows there @@ -307,6 +330,12 @@ internal void OnLostFocus() Repaint(); } + protected override void OnBackingScaleFactorChanged() + { + if (m_ActualView != null) + m_ActualView.OnBackingScaleFactorChangedInternal(); + } + protected override void OnDestroy() { if (m_ActualView) @@ -526,6 +555,9 @@ protected void RegisterSelectedPane(bool sendEvents) EditorApplication.update += m_ActualView.CheckForWindowRepaint; } + if (m_ActualView is IDataModeHandlerAndDispatcher dataModesDispatcher) + dataModesDispatcher.dataModeChanged += OnViewDataModeChanged; + if (sendEvents) { try @@ -562,6 +594,9 @@ protected void DeregisterSelectedPane(bool clearActualView, bool sendEvents) EditorApplication.update -= m_ActualView.CheckForWindowRepaint; } + if (m_ActualView is IDataModeHandlerAndDispatcher dataModesDispatcher) + dataModesDispatcher.dataModeChanged -= OnViewDataModeChanged; + if (clearActualView) { var onLostFocus = m_OnLostFocus; @@ -640,7 +675,12 @@ internal float GetExtraButtonsWidth() { float extraWidth = 0; - if (m_ShowButton != null) extraWidth += ContainerWindow.kButtonWidth; + // Generally reserved for the lock icon + if (m_ShowButton != null) + extraWidth += ContainerWindow.kButtonWidth; + + if (ShouldDrawDataModesSwitch()) + extraWidth += Styles.DataModes.switchButtonWidth + k_iconMargin; foreach (var item in windowActions) { @@ -663,10 +703,51 @@ protected void ShowGenericMenu(float leftOffset, float topOffset) leftOffset -= paneMenu.width + k_iconMargin; } - // Give panes an option of showing a small button next to the generic menu (used for inspector lock icon + // Give panes an option of showing a small button next to the generic menu (used for inspector lock icon) if (m_ShowButton != null) m_ShowButton.Invoke(new Rect(leftOffset, topOffset, ContainerWindow.kButtonWidth, ContainerWindow.kButtonHeight)); + // Note: We are assuming the value returned by ShouldDrawDataModesSwitch() hasn't changed since + // it was called from GetExtraButtonsWidth(); if GetExtraButtonsWidth() was called at all, that is. + if (ShouldDrawDataModesSwitch()) + { + // This cast is guaranteed to work by ShouldDrawDataModesSwitch() + var dataModesClient = (IDataModeHandler) m_ActualView; + var switchContent = default(GUIContent); + + switch (dataModesClient.dataMode) + { + case DataMode.Authoring: + { + switchContent = Styles.DataModes.authoringModeContent; + break; + } + case DataMode.Mixed: + { + switchContent = Styles.DataModes.mixedModeContent; + break; + } + case DataMode.Runtime: + { + switchContent = Styles.DataModes.runtimeModeContent; + break; + } + } + + // Last chance to bail in case something weird happened + if (switchContent != default) + { + leftOffset -= Styles.DataModes.switchButtonWidth + k_iconMargin; + var switchRect = new Rect(leftOffset, topOffset, Styles.DataModes.switchButtonWidth, ContainerWindow.kButtonHeight); + + if (EditorGUI.Button(switchRect, switchContent, Styles.DataModes.switchStyle)) + { + dataModesClient.SwitchToNextDataMode(); + RefreshAfterDataModeChange(); + } + } + } + foreach (var item in windowActions) { if (item != null && (item.validateHandler == null || item.validateHandler(actualView, item)) && item.width.HasValue) @@ -687,6 +768,39 @@ 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) + { + if (!(m_ActualView is IDataModeHandler dataModesHandler)) + return; // Something very weird has happened... + + if (dataMode is DataMode mode && dataModesHandler.IsDataModeSupported(mode)) + dataModesHandler.SwitchToDataMode(mode); + else + dataModesHandler.SwitchToDefaultDataMode(); + } + + void OnViewDataModeChanged(DataMode newMode) + { + // Current implementation doesn't need to know the new data mode, but once + // we switch this to UITK (soon), knowing the new mode will be very useful. + RefreshAfterDataModeChange(); + } + + void RefreshAfterDataModeChange() + { + m_ActualView.Repaint(); + RepaintImmediately(); + } + private static WindowAction[] FetchWindowActionFromAttribute() { var methods = AttributeHelper.GetMethodsWithAttribute(); @@ -705,10 +819,23 @@ private static WindowAction[] FetchWindowActionFromAttribute() }).OrderBy(a => a.priority).ToArray(); } + private static void FlushView(EditorWindow view) + { + if (view == null) + return; + + int totalFrames = Math.Max(2, QualitySettings.maxQueuedFrames); + for (int i = 0; i < totalFrames; ++i) + view.RepaintImmediately(); + } + public void PopupGenericMenu(EditorWindow view, Rect pos) { if (!showGenericMenu) return; + + FlushView(view); + GenericMenu menu = new GenericMenu(); IHasCustomMenu menuProvider = view as IHasCustomMenu; @@ -748,7 +875,7 @@ internal static void AddWindowActionMenu(GenericMenu menu, EditorWindow view) private void Inspect(object userData) { - Selection.activeObject = (UnityEngine.Object)userData; + Selection.activeObject = (Object)userData; } internal void Reload(object userData) @@ -785,7 +912,29 @@ internal void Reload(object userData) win.Show(); } - System.IO.File.Delete(saveWindowPath); + 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) @@ -793,17 +942,38 @@ protected virtual void AddDefaultItemsToMenu(GenericMenu menu, EditorWindow wind if (menu.GetItemCount() != 0) menu.AddSeparator(""); - if (window is ISupportsOverlays) + if (window is IDataModeHandler dataModesHandler && dataModesHandler.dataMode != DataMode.Disabled) { - OverlayPresetManager.GenerateMenu(menu, "Overlays/Presets/", window); - foreach (var overlay in window.overlayCanvas.overlays) + SanitizeSupportedDataModesList(dataModesHandler.supportedDataModes, m_DataModeSanitizationCache); + + // Don't show anything if only one mode is supported + if (m_DataModeSanitizationCache.Count > 1) { - if (overlay.userControlledVisibility) - menu.AddItem(new GUIContent($"Overlays/{overlay.displayName}"), overlay.displayed, - o => ((Overlay)o).displayed = !((Overlay)o).displayed, overlay); + foreach (var mode in m_DataModeSanitizationCache) + { + menu.AddItem(Styles.DataModes.dataModeNameLabels[mode], + dataModesHandler.dataMode == mode, + SelectDataMode, + mode); + } + + menu.AddSeparator(""); } } + if(window is ISupportsOverlays) + { + var binding = ShortcutManager.instance.GetShortcutBinding("Overlays/Show Overlay Menu"); + var visibleMenu = window.overlayCanvas.menuVisible; + menu.AddItem(EditorGUIUtility.TrTextContent($"Overlay Menu _{binding}"), + visibleMenu, + () => + { + window.overlayCanvas.localMousePosition = Vector2.negativeInfinity; + window.overlayCanvas.menuVisible = !visibleMenu; + }); + } + if (window && Unsupported.IsDeveloperMode()) { menu.AddItem(EditorGUIUtility.TrTextContent("Inspect Window"), false, Inspect, window); diff --git a/Editor/Mono/IDataModeHandler.cs b/Editor/Mono/IDataModeHandler.cs new file mode 100644 index 0000000000..4182583de3 --- /dev/null +++ b/Editor/Mono/IDataModeHandler.cs @@ -0,0 +1,172 @@ +// 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 +{ + /// + /// Options for the different modes of an that implements + /// or . + /// + // + // Dev note: + // + // There should be no reason to ever add to this enum. It exists only to express the duality between author-time + // and runtime data. If you feel like you need to add to this, please reconsider: it will likely break the + // `Authoring VS Runtime` feature for DOTS. + // + // Thank you for your consideration and have a great day! + // + // Sincerely, + // The #dots-editor team + // + public enum DataMode + { + /// + /// Represents a situation or context in which the usage of data modes is not applicable. + /// + /// + /// This mode informs the docking area that the data modes switch should now be displayed. + /// + Disabled, + /// + /// Uses a mode where only authoring data is available. + /// + /// + /// In this mode, only authoring data is available. When exiting Play mode, Unity retains authoring data. + /// + Authoring, + /// + /// Uses a 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. + /// + Mixed, + /// + /// Uses a mode where only runtime data is available. + /// + /// + /// In this mode, only runtime data is available. **Important:** When exiting Play mode, Unity loses runtime + /// data. + /// + Runtime + } + + /// + /// Implement this interface to allow an to handle changes. + /// + /// + /// 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. + /// + public interface IDataModeHandler + { + /// + /// Returns the currently active for the implementor . + /// + /// + /// Unity does not serialize or store this value. It is the window's responsibility to do so. + /// + DataMode dataMode { get; } + + /// + /// + /// 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. + /// 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. + /// + /// + /// 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(); + + /// + /// Unity calls this method automatically whenever the Editor wants an to be in a + /// specific . + /// + /// + /// By convention, Unity always calls before calling this method. If the + /// data mode is not supported, is called instead. + /// + /// + /// The explicit data mode to which the Editor window should change. + /// + void SwitchToDataMode(DataMode mode); + + /// + /// 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. + /// + /// + 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. + /// + 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/ImportSettings/SpeedTreeImporterInspector.cs b/Editor/Mono/ImportSettings/SpeedTreeImporterInspector.cs index 1d1041ffc3..ad1aa221cb 100644 --- a/Editor/Mono/ImportSettings/SpeedTreeImporterInspector.cs +++ b/Editor/Mono/ImportSettings/SpeedTreeImporterInspector.cs @@ -121,7 +121,7 @@ protected override bool OnApplyRevertGUI() if (hasModified || upgrade) { - ApplyAndImport(); + SaveChanges(); applied = true; } } diff --git a/Editor/Mono/ImportSettings/TextureImporterInspector.cs b/Editor/Mono/ImportSettings/TextureImporterInspector.cs index 7a18cd3fe0..52e077e04f 100644 --- a/Editor/Mono/ImportSettings/TextureImporterInspector.cs +++ b/Editor/Mono/ImportSettings/TextureImporterInspector.cs @@ -333,7 +333,6 @@ internal class Styles EditorGUIUtility.TrTextContent("Tight"), }; - public readonly GUIContent spritePackingTag = EditorGUIUtility.TrTextContent("Packing Tag", "Tag for the Sprite Packing system."); public readonly GUIContent spritePixelsPerUnit = EditorGUIUtility.TrTextContent("Pixels Per Unit", "How many pixels in the sprite correspond to one unit in the world."); public readonly GUIContent spriteExtrude = EditorGUIUtility.TrTextContent("Extrude Edges", "How much empty area to leave around the sprite in the generated mesh."); public readonly GUIContent spriteMeshType = EditorGUIUtility.TrTextContent("Mesh Type", "Type of sprite mesh to generate."); @@ -447,7 +446,6 @@ void EnumPopup(SerializedProperty property, System.Type type, GUIContent label) SerializedProperty m_WrapV; SerializedProperty m_WrapW; - SerializedProperty m_SpritePackingTag; SerializedProperty m_SpritePixelsToUnits; SerializedProperty m_SpriteExtrude; SerializedProperty m_SpriteMeshType; @@ -517,7 +515,6 @@ void CacheSerializedProperties() m_CubemapConvolution = serializedObject.FindProperty("m_CubemapConvolution"); m_SpriteMode = serializedObject.FindProperty("m_SpriteMode"); - m_SpritePackingTag = serializedObject.FindProperty("m_SpritePackingTag"); m_SpritePixelsToUnits = serializedObject.FindProperty("m_SpritePixelsToUnits"); m_SpriteExtrude = serializedObject.FindProperty("m_SpriteExtrude"); m_SpriteMeshType = serializedObject.FindProperty("m_SpriteMeshType"); @@ -957,7 +954,10 @@ void ElementsAtlasGui(TextureInspectorGUIElement guiElements) void ColorSpaceGUI(TextureInspectorGUIElement guiElements) { - ToggleFromInt(m_sRGBTexture, s_Styles.sRGBTexture); + if (CountImportersWithHDR(targets, out int countHDR) && countHDR == 0) + { + ToggleFromInt(m_sRGBTexture, s_Styles.sRGBTexture); + } } void POTScaleGUI(TextureInspectorGUIElement guiElements) @@ -991,7 +991,7 @@ void StreamingMipmapsGUI() // some texture types are not relevant for mip streaming var type = textureType; - if (type == TextureImporterType.Sprite || type == TextureImporterType.Cookie || type == TextureImporterType.GUI || type == TextureImporterType.Cursor) + if (type == TextureImporterType.Cookie || type == TextureImporterType.GUI || type == TextureImporterType.Cursor) return; ToggleFromInt(m_StreamingMipmaps, s_Styles.streamingMipmaps); @@ -1088,9 +1088,6 @@ private void SpriteGUI(TextureInspectorGUIElement guiElements) m_ShowGenericSpriteSettings.target = (m_SpriteMode.intValue != 0); if (EditorGUILayout.BeginFadeGroup(m_ShowGenericSpriteSettings.faded)) { - using (new EditorGUI.DisabledScope(true)) - EditorGUILayout.PropertyField(m_SpritePackingTag, s_Styles.spritePackingTag); - EditorGUILayout.PropertyField(m_SpritePixelsToUnits, s_Styles.spritePixelsPerUnit); m_ShowSpriteMeshTypeOption.target = ShouldShowSpriteMeshTypeOption(); @@ -1132,7 +1129,7 @@ private void SpriteGUI(TextureInspectorGUIElement guiElements) dialogText += "Apply and continue to sprite editor or cancel."; if (EditorUtility.DisplayDialog("Unapplied import settings", dialogText, "Apply", "Cancel")) { - ApplyAndImport(); + SaveChanges(); SpriteUtilityWindow.ShowSpriteEditorWindow(this.assetTarget); // We reimported the asset which destroyed the editor, so we can't keep running the UI here. @@ -1636,9 +1633,16 @@ public override bool HasModified() return false; } + + [Obsolete("UnityUpgradeable () -> DiscardChanges")] protected override void ResetValues() { - base.ResetValues(); + DiscardChanges(); + } + + public override void DiscardChanges() + { + base.DiscardChanges(); CacheSerializedProperties(); diff --git a/Editor/Mono/Inspector/AddComponent/AddComponentDataSource.cs b/Editor/Mono/Inspector/AddComponent/AddComponentDataSource.cs index 096b15d34a..a94f6c9094 100644 --- a/Editor/Mono/Inspector/AddComponent/AddComponentDataSource.cs +++ b/Editor/Mono/Inspector/AddComponent/AddComponentDataSource.cs @@ -13,12 +13,14 @@ internal class AddComponentDataSource : AdvancedDropdownDataSource { private static readonly string kSearchHeader = L10n.Tr("Search"); AdvancedDropdownState m_State; + UnityEngine.GameObject[] m_Targets; internal static readonly string kScriptHeader = "Component/Scripts/"; - public AddComponentDataSource(AdvancedDropdownState state) + public AddComponentDataSource(AdvancedDropdownState state, UnityEngine.GameObject[] targets) { m_State = state; + m_Targets = targets; } protected override AdvancedDropdownItem FetchData() @@ -37,7 +39,7 @@ protected AdvancedDropdownItem RebuildTree() { m_SearchableElements = new List(); AdvancedDropdownItem root = new ComponentDropdownItem("ROOT"); - List menuItems = GetSortedMenuItems(); + List menuItems = GetSortedMenuItems(m_Targets); Dictionary pathHashCodeMap = new Dictionary(); @@ -86,7 +88,7 @@ protected AdvancedDropdownItem RebuildTree() return root; } - static List GetSortedMenuItems() + static List GetSortedMenuItems(UnityEngine.GameObject[] targets) { var menus = Unsupported.GetSubmenus("Component"); var commands = Unsupported.GetSubmenusCommands("Component"); @@ -95,6 +97,8 @@ static List GetSortedMenuItems() var legacyMenuItems = new List(menus.Length); const string kLegacyString = "legacy"; + var hasFilterOverride = ModeService.HasExecuteHandler("inspector_filter_component"); + for (var i = 0; i < menus.Length; i++) { var menuPath = menus[i]; @@ -106,13 +110,16 @@ static List GetSortedMenuItems() isLegacy = isLegacy }; - if (isLegacy) - { - legacyMenuItems.Add(item); - } - else + if (!hasFilterOverride || ModeService.Execute("inspector_filter_component", targets, menuPath)) { - menuItems.Add(item); + if (isLegacy) + { + legacyMenuItems.Add(item); + } + else + { + menuItems.Add(item); + } } } diff --git a/Editor/Mono/Inspector/AddComponent/AddComponentWindow.cs b/Editor/Mono/Inspector/AddComponent/AddComponentWindow.cs index 023478c034..8941fa1739 100644 --- a/Editor/Mono/Inspector/AddComponent/AddComponentWindow.cs +++ b/Editor/Mono/Inspector/AddComponent/AddComponentWindow.cs @@ -45,7 +45,7 @@ internal static bool Show(Rect rect, GameObject[] gos) { CloseAllOpenWindows(); var window = CreateInstance(); - window.dataSource = new AddComponentDataSource(s_State); + window.dataSource = new AddComponentDataSource(s_State, gos); window.gui = new AddComponentGUI(window.dataSource, window.OnCreateNewScript); window.state = s_State; window.m_GameObjects = gos; diff --git a/Editor/Mono/Inspector/AddComponent/NewScriptDropdownItem.cs b/Editor/Mono/Inspector/AddComponent/NewScriptDropdownItem.cs index c8d0d0d987..e1a018fe51 100644 --- a/Editor/Mono/Inspector/AddComponent/NewScriptDropdownItem.cs +++ b/Editor/Mono/Inspector/AddComponent/NewScriptDropdownItem.cs @@ -110,11 +110,17 @@ private bool ClassAlreadyExists() return ClassExists(m_ClassName); } + private string GetTemplatePath(){ + if(File.Exists("Assets/ScriptTemplates/81-C# Script-NewBehaviourScript.cs.txt")) + { + return "Assets/ScriptTemplates/81-C# Script-NewBehaviourScript.cs.txt"; + } + var basePath = Path.Combine(EditorApplication.applicationContentsPath, kResourcesTemplatePath); + return Path.Combine(basePath, "81-C# Script-NewBehaviourScript.cs.txt"); + } private void CreateScript() { - var basePath = Path.Combine(EditorApplication.applicationContentsPath, kResourcesTemplatePath); - var templatePath = Path.Combine(basePath, "81-C# Script-NewBehaviourScript.cs.txt"); - ProjectWindowUtil.CreateScriptAssetFromTemplate(TargetPath(), templatePath); + ProjectWindowUtil.CreateScriptAssetFromTemplate(TargetPath(), GetTemplatePath()); AssetDatabase.Refresh(); } } diff --git a/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownWindow.cs b/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownWindow.cs index fc86cea7f0..6fdf2c9ca8 100644 --- a/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownWindow.cs +++ b/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownWindow.cs @@ -157,6 +157,8 @@ void SetSelectionFromState() var child = m_State.GetSelectedChild(m_CurrentlyRenderedTree); if (child == null) break; + if (child.id == m_CurrentlyRenderedTree.id) + Debug.LogWarning($"Same id: {child.id} given to both {child.displayName} and {m_CurrentlyRenderedTree.displayName}. Selection may be wrong."); selectedIndex = m_State.GetSelectedIndex(child); if (selectedIndex < 0) break; diff --git a/Editor/Mono/Inspector/AdvancedDropdown/DataSources/MultiselectDataSource.cs b/Editor/Mono/Inspector/AdvancedDropdown/DataSources/MultiselectDataSource.cs index 4b790d03f7..d778db0a9d 100644 --- a/Editor/Mono/Inspector/AdvancedDropdown/DataSources/MultiselectDataSource.cs +++ b/Editor/Mono/Inspector/AdvancedDropdown/DataSources/MultiselectDataSource.cs @@ -28,7 +28,7 @@ public MultiselectDataSource(Enum enumValue) var enumData = EnumDataUtility.GetCachedEnumData(enumType); if (!enumData.serializable) - // this is the same message used in ScriptPopupMenus.cpp + // this is the same message used in SerializedPropertyEnumHelper.cpp throw new NotSupportedException(string.Format("Unsupported enum base type for {0}", enumType.Name)); m_Mask = EnumDataUtility.EnumFlagsToInt(enumData, enumFlags); diff --git a/Editor/Mono/Inspector/AnimationClipEditor.cs b/Editor/Mono/Inspector/AnimationClipEditor.cs index a806b7c139..09dd5b3a6b 100644 --- a/Editor/Mono/Inspector/AnimationClipEditor.cs +++ b/Editor/Mono/Inspector/AnimationClipEditor.cs @@ -16,6 +16,7 @@ namespace UnityEditor { [CustomEditor(typeof(AnimationClip))] + [CanEditMultipleObjects] internal class AnimationClipEditor : Editor { internal static void EditWithImporter(AnimationClip clip) @@ -27,6 +28,7 @@ internal static void EditWithImporter(AnimationClip clip) Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(importer.assetPath); ModelImporterEditor inspector = Editor.CreateEditor(importer) as ModelImporterEditor; EditorPrefs.SetInt(inspector.GetType().Name + "ActiveEditorIndex", 2); + Object.DestroyImmediate(inspector); int clipIndex = 0; //The ModelImporter handles "ClipAnimations" and "DefaultClipAnimations" independently, where there are no user specified clips, this clip must be a default clip. @@ -123,6 +125,8 @@ internal static class Styles public static GUIContent Events = EditorGUIUtility.TrTextContent("Events"); public static GUIContent LoopMatch = EditorGUIUtility.TrTextContent("loop match"); + public static string InvalidMultiSelection = L10n.Tr("Both legacy and non legacy Animation Clips have been selected. This combination cannot be edited together. Select either legacy or non legacy Animation Clips."); + public static GUIContent AddEventContent = EditorGUIUtility.TrIconContent("Animation.AddEvent", "Add Event."); public static GUIContent GreenLightIcon = EditorGUIUtility.IconContent("lightMeter/greenLight"); @@ -175,12 +179,12 @@ public void ShowRange(AnimationClipInfoProperties info) { UpdateEventsPopupClipInfo(info); m_ClipInfo = info; - info.AssignToPreviewClip(m_Clip); + info.AssignToPreviewClip(m_Clips[0]); } public string[] takeNames { get; set; } public int takeIndex { get; set; } - private AnimationClip m_Clip = null; + private AnimationClip[] m_Clips = null; private AnimatorController m_Controller = null; private AnimatorStateMachine m_StateMachine; private AnimatorState m_State; @@ -190,6 +194,21 @@ public void ShowRange(AnimationClipInfoProperties info) private TimeArea m_TimeArea; private TimeArea m_EventTimeArea; + private SerializedProperty m_WrapModeProperty; + private SerializedProperty m_ClipSettingsProperty; + private SerializedProperty m_LoopTimeProperty; + private SerializedProperty m_LoopBlendProperty; + private SerializedProperty m_CycleOffsetProperty; + private SerializedProperty m_LoopBlendOrientationProperty; + private SerializedProperty m_KeepOriginalOrientationProperty; + private SerializedProperty m_OrientationOffsetYProperty; + private SerializedProperty m_LoopBlendPositionYProperty; + private SerializedProperty m_KeepOriginalPositionYProperty; + private SerializedProperty m_HeightFromFeetProperty; + private SerializedProperty m_LevelProperty; + private SerializedProperty m_LoopBlendPositionXZProperty; + private SerializedProperty m_KeepOriginalPositionXZProperty; + private SerializedProperty m_MirrorProperty; private bool m_DraggingRange = false; private bool m_DraggingRangeBegin = false; @@ -214,6 +233,7 @@ public void ShowRange(AnimationClipInfoProperties info) static private bool m_ShowEvents = false; bool m_NeedsToGenerateClipInfo = false; + bool m_IsSelectingMultipleClips = false; public bool needsToGenerateClipInfo { @@ -232,7 +252,7 @@ public bool needsToGenerateClipInfo private void InitController() { - if (m_Clip.legacy) + if (m_Clips[0].legacy) return; if (m_AvatarPreview != null && m_AvatarPreview.Animator != null) @@ -263,7 +283,7 @@ private void InitController() m_State = m_StateMachine.AddState("preview"); m_State.pushUndo = false; AnimatorControllerLayer[] layers = m_Controller.layers; - m_State.motion = m_Clip; + m_State.motion = m_Clips[0]; m_Controller.layers = layers; m_State.iKOnFeet = m_AvatarPreview.IKOnFeet; @@ -293,7 +313,7 @@ private void InitController() internal override bool IsEnabled() { - if (FileUtil.IsReadOnly(m_Clip)) + if (FileUtil.IsReadOnly(m_Clips[0])) return false; return base.IsEnabled(); } @@ -365,7 +385,7 @@ private void SetPreviewAvatar() InitController(); } - void Init() + void InitPreview() { if (m_AvatarPreview == null) { @@ -385,7 +405,7 @@ void Init() public void InitClipTime() { //case 790259: The length of the clip will be changed by this inspector, so we can't use it for sampling - m_InitialClipLength = m_Clip.stopTime - m_Clip.startTime; + m_InitialClipLength = m_Clips[0].stopTime - m_Clips[0].startTime; if (m_TimeArea == null) { @@ -394,13 +414,13 @@ public void InitClipTime() m_TimeArea.vRangeLocked = true; m_TimeArea.hSlider = true; m_TimeArea.vSlider = false; - m_TimeArea.hRangeMin = m_Clip.startTime; - m_TimeArea.hRangeMax = m_Clip.stopTime; + m_TimeArea.hRangeMin = m_Clips[0].startTime; + m_TimeArea.hRangeMax = m_Clips[0].stopTime; m_TimeArea.margin = 10; m_TimeArea.scaleWithWindow = true; - m_TimeArea.minWidth = 1.0f / m_Clip.frameRate; - m_TimeArea.SetShownHRangeInsideMargins(m_Clip.startTime, m_Clip.stopTime); - m_TimeArea.hTicks.SetTickModulosForFrameRate(m_Clip.frameRate); + m_TimeArea.minWidth = 1.0f / m_Clips[0].frameRate; + m_TimeArea.SetShownHRangeInsideMargins(m_Clips[0].startTime, m_Clips[0].stopTime); + m_TimeArea.hTicks.SetTickModulosForFrameRate(m_Clips[0].frameRate); m_TimeArea.ignoreScrollWheelUntilClicked = true; } @@ -426,7 +446,33 @@ public void InitClipTime() internal void OnEnable() { - m_Clip = target as AnimationClip; + m_Clips = new AnimationClip[targets.Length]; + for (var i = 0; i < targets.Length; ++i) + m_Clips[i] = targets[i] as AnimationClip; + + m_IsSelectingMultipleClips = m_Clips.Length > 1; + InitSerializedProperties(); + + Undo.undoRedoEvent += OnUndoRedoPerformed; + } + + void InitSerializedProperties() + { + m_WrapModeProperty = serializedObject.FindProperty("m_WrapMode"); + m_ClipSettingsProperty = serializedObject.FindProperty("m_AnimationClipSettings"); + m_LoopTimeProperty = m_ClipSettingsProperty.FindPropertyRelative("m_LoopTime"); + m_LoopBlendProperty = m_ClipSettingsProperty.FindPropertyRelative("m_LoopBlend"); + m_CycleOffsetProperty = m_ClipSettingsProperty.FindPropertyRelative("m_CycleOffset"); + m_LoopBlendOrientationProperty = m_ClipSettingsProperty.FindPropertyRelative("m_LoopBlendOrientation"); + m_KeepOriginalOrientationProperty = m_ClipSettingsProperty.FindPropertyRelative("m_KeepOriginalOrientation"); + m_OrientationOffsetYProperty = m_ClipSettingsProperty.FindPropertyRelative("m_OrientationOffsetY"); + m_LoopBlendPositionYProperty = m_ClipSettingsProperty.FindPropertyRelative("m_LoopBlendPositionY"); + m_KeepOriginalPositionYProperty = m_ClipSettingsProperty.FindPropertyRelative("m_KeepOriginalPositionY"); + m_HeightFromFeetProperty = m_ClipSettingsProperty.FindPropertyRelative("m_HeightFromFeet"); + m_LevelProperty = m_ClipSettingsProperty.FindPropertyRelative("m_Level"); + m_LoopBlendPositionXZProperty = m_ClipSettingsProperty.FindPropertyRelative("m_LoopBlendPositionXZ"); + m_KeepOriginalPositionXZProperty = m_ClipSettingsProperty.FindPropertyRelative("m_KeepOriginalPositionXZ"); + m_MirrorProperty = m_ClipSettingsProperty.FindPropertyRelative("m_Mirror"); } void OnDisable() @@ -437,13 +483,27 @@ void OnDisable() m_AvatarPreview.OnDisable(); m_AvatarPreview = null; } + + Undo.undoRedoEvent -= OnUndoRedoPerformed; + } + + void OnUndoRedoPerformed(in UndoRedoInfo info) + { + if (!m_IsSelectingMultipleClips) + return; + + for (var i = 0; i < m_Clips.Length; ++i) + AnimationUtility.RebuildMecanimData(m_Clips[i]); } public override bool HasPreviewGUI() { + if (m_IsSelectingMultipleClips) + return false; + if (Event.current.type == EventType.Layout) { - Init(); + InitPreview(); } return m_AvatarPreview != null; } @@ -463,12 +523,12 @@ void CalculateQualityCurves() // [case 491172] // There is no need to sample the quality curve outside of the animation range [m_Clip.startTime, m_Clip.stopTime] because the Time area show only the animation range anyway // so it not possible for the user to see curve outside of this range. - float clipStartTime = Mathf.Clamp(m_ClipInfo.firstFrame / m_Clip.frameRate, m_Clip.startTime, m_Clip.stopTime); - float clipStopTime = Mathf.Clamp(m_ClipInfo.lastFrame / m_Clip.frameRate, m_Clip.startTime, m_Clip.stopTime); + float clipStartTime = Mathf.Clamp(m_ClipInfo.firstFrame / m_Clips[0].frameRate, m_Clips[0].startTime, m_Clips[0].stopTime); + float clipStopTime = Mathf.Clamp(m_ClipInfo.lastFrame / m_Clips[0].frameRate, m_Clips[0].startTime, m_Clips[0].stopTime); float fixedTime = (q == 0 ? clipStopTime : clipStartTime); float startTime = (q == 0 ? 0 : clipStartTime); - float stopTime = (q == 0 ? clipStopTime : m_Clip.stopTime); + float stopTime = (q == 0 ? clipStopTime : m_Clips[0].stopTime); // Start sample may be a bit before start time; stop sample may be a bit after stop time int startSample = Mathf.FloorToInt(startTime * kSamplesPerSecond); int stopSample = Mathf.CeilToInt(stopTime * kSamplesPerSecond); @@ -484,7 +544,7 @@ void CalculateQualityCurves() qualityCurvesTime.variableEndEnd = stopTime; qualityCurvesTime.q = q; - MuscleClipUtility.CalculateQualityCurves(m_Clip, qualityCurvesTime, + MuscleClipUtility.CalculateQualityCurves(m_Clips[0], qualityCurvesTime, m_QualityCurves[kPose][q], m_QualityCurves[kRotation][q], m_QualityCurves[kHeight][q], m_QualityCurves[kPosition][q]); } @@ -538,10 +598,10 @@ public void ClipRangeGUI(ref float startFrame, ref float stopFrame, out bool cha m_DraggingRangeEnd = false; bool invalidRange = ( - startFrame + 0.01f < m_Clip.startTime * m_Clip.frameRate || - startFrame - 0.01f > m_Clip.stopTime * m_Clip.frameRate || - stopFrame + 0.01f < m_Clip.startTime * m_Clip.frameRate || - stopFrame - 0.01f > m_Clip.stopTime * m_Clip.frameRate); + startFrame + 0.01f < m_Clips[0].startTime * m_Clips[0].frameRate || + startFrame - 0.01f > m_Clips[0].stopTime * m_Clips[0].frameRate || + stopFrame + 0.01f < m_Clips[0].startTime * m_Clips[0].frameRate || + stopFrame - 0.01f > m_Clips[0].stopTime * m_Clips[0].frameRate); bool fixRange = false; if (invalidRange) { @@ -574,19 +634,19 @@ public void ClipRangeGUI(ref float startFrame, ref float stopFrame, out bool cha timeRect.x = timeRect.y = -1; // Draw selected range as blue tint - float startPixel = m_TimeArea.FrameToPixel(startFrame, m_Clip.frameRate, timeRect); - float stopPixel = m_TimeArea.FrameToPixel(stopFrame, m_Clip.frameRate, timeRect); + float startPixel = m_TimeArea.FrameToPixel(startFrame, m_Clips[0].frameRate, timeRect); + float stopPixel = m_TimeArea.FrameToPixel(stopFrame, m_Clips[0].frameRate, timeRect); GUI.Label(new Rect(startPixel, timeRect.y, stopPixel - startPixel, timeRect.height), "", EditorStyles.selectionRect); // Draw time ruler - m_TimeArea.TimeRuler(timeRect, m_Clip.frameRate); + m_TimeArea.TimeRuler(timeRect, m_Clips[0].frameRate); // Current time indicator TimeArea.DrawPlayhead(m_TimeArea.TimeToPixel(m_AvatarPreview.timeControl.currentTime, timeRect), timeRect.yMin, timeRect.yMax, 2f, 1f); using (new EditorGUI.DisabledScope(invalidRange)) { // Range handles - float startTime = startFrame / m_Clip.frameRate; + float startTime = startFrame / m_Clips[0].frameRate; TimeArea.TimeRulerDragMode inPoint = m_TimeArea.BrowseRuler(timeRect, startHandleId, ref startTime, 0, false, "TL InPoint"); if (inPoint == TimeArea.TimeRulerDragMode.Cancel) { @@ -594,12 +654,12 @@ public void ClipRangeGUI(ref float startFrame, ref float stopFrame, out bool cha } else if (inPoint != TimeArea.TimeRulerDragMode.None) { - startFrame = startTime * m_Clip.frameRate; + startFrame = startTime * m_Clips[0].frameRate; // Snapping bias. Snap to whole frames when zoomed out. - startFrame = MathUtils.RoundBasedOnMinimumDifference(startFrame, m_TimeArea.PixelDeltaToTime(timeRect) * m_Clip.frameRate * 10); + startFrame = MathUtils.RoundBasedOnMinimumDifference(startFrame, m_TimeArea.PixelDeltaToTime(timeRect) * m_Clips[0].frameRate * 10); changedStart = true; } - float stopTime = stopFrame / m_Clip.frameRate; + float stopTime = stopFrame / m_Clips[0].frameRate; TimeArea.TimeRulerDragMode outPoint = m_TimeArea.BrowseRuler(timeRect, stopHandleId, ref stopTime, 0, false, "TL OutPoint"); if (outPoint == TimeArea.TimeRulerDragMode.Cancel) @@ -608,16 +668,16 @@ public void ClipRangeGUI(ref float startFrame, ref float stopFrame, out bool cha } else if (outPoint != TimeArea.TimeRulerDragMode.None) { - stopFrame = stopTime * m_Clip.frameRate; + stopFrame = stopTime * m_Clips[0].frameRate; // Snapping bias. Snap to whole frames when zoomed out. - stopFrame = MathUtils.RoundBasedOnMinimumDifference(stopFrame, m_TimeArea.PixelDeltaToTime(timeRect) * m_Clip.frameRate * 10); + stopFrame = MathUtils.RoundBasedOnMinimumDifference(stopFrame, m_TimeArea.PixelDeltaToTime(timeRect) * m_Clips[0].frameRate * 10); changedStop = true; } // Additive pose frame Handle if (showAdditivePoseFrame) { - float additivePoseTime = additivePoseframe / m_Clip.frameRate; + float additivePoseTime = additivePoseframe / m_Clips[0].frameRate; TimeArea.TimeRulerDragMode additivePoint = m_TimeArea.BrowseRuler(timeRect, additiveHandleId, ref additivePoseTime, 0, false, "TL playhead"); if (additivePoint == TimeArea.TimeRulerDragMode.Cancel) { @@ -625,9 +685,9 @@ public void ClipRangeGUI(ref float startFrame, ref float stopFrame, out bool cha } else if (additivePoint != TimeArea.TimeRulerDragMode.None) { - additivePoseframe = additivePoseTime * m_Clip.frameRate; + additivePoseframe = additivePoseTime * m_Clips[0].frameRate; // Snapping bias. Snap to whole frames when zoomed out. - additivePoseframe = MathUtils.RoundBasedOnMinimumDifference(additivePoseframe, m_TimeArea.PixelDeltaToTime(timeRect) * m_Clip.frameRate * 10); + additivePoseframe = MathUtils.RoundBasedOnMinimumDifference(additivePoseframe, m_TimeArea.PixelDeltaToTime(timeRect) * m_Clips[0].frameRate * 10); changedAdditivePoseframe = true; } } @@ -667,13 +727,13 @@ public void ClipRangeGUI(ref float startFrame, ref float stopFrame, out bool cha // Start and stop time value clamping if (changedStart) - startFrame = Mathf.Clamp(startFrame, m_Clip.startTime * m_Clip.frameRate, Mathf.Clamp(stopFrame, m_Clip.startTime * m_Clip.frameRate, stopFrame)); + startFrame = Mathf.Clamp(startFrame, m_Clips[0].startTime * m_Clips[0].frameRate, Mathf.Clamp(stopFrame, m_Clips[0].startTime * m_Clips[0].frameRate, stopFrame)); if (changedStop) - stopFrame = Mathf.Clamp(stopFrame, startFrame, m_Clip.stopTime * m_Clip.frameRate); + stopFrame = Mathf.Clamp(stopFrame, startFrame, m_Clips[0].stopTime * m_Clips[0].frameRate); if (changedAdditivePoseframe) - additivePoseframe = Mathf.Clamp(additivePoseframe, m_Clip.startTime * m_Clip.frameRate, m_Clip.stopTime * m_Clip.frameRate); + additivePoseframe = Mathf.Clamp(additivePoseframe, m_Clips[0].startTime * m_Clips[0].frameRate, m_Clips[0].stopTime * m_Clips[0].frameRate); // Keep track of whether we're currently dragging the range or not if (changedStart || changedStop || changedAdditivePoseframe) @@ -702,7 +762,7 @@ string GetStatsText() // Muscle clip info is currently only available for humanoid if (IsHumanClip) { - statsText = UnityString.Format(Styles.AverageVelocity, m_Clip.averageSpeed.ToString("0.000"), (m_Clip.averageAngularSpeed * 180.0f / Mathf.PI).ToString("0.0", CultureInfo.InvariantCulture.NumberFormat)); + statsText = UnityString.Format(Styles.AverageVelocity, m_Clips[0].averageSpeed.ToString("0.000"), (m_Clips[0].averageAngularSpeed * 180.0f / Mathf.PI).ToString("0.0", CultureInfo.InvariantCulture.NumberFormat)); } // Only show stats in final clip not for the preview clip @@ -737,12 +797,12 @@ string GetStatsText() return statsText; } - private float GetClipLength() + private float GetClipLength(int clipIndex) { if (m_ClipInfo == null) - return m_Clip.length; + return m_Clips[clipIndex].length; else - return (m_ClipInfo.lastFrame - m_ClipInfo.firstFrame) / m_Clip.frameRate; + return (m_ClipInfo.lastFrame - m_ClipInfo.firstFrame) / m_Clips[clipIndex].frameRate; } // A minimal list of settings to be shown in the Asset Store preview inspector @@ -753,7 +813,6 @@ internal override void OnAssetStoreInspectorGUI() public override void OnInspectorGUI() { - Init(); EditorGUIUtility.labelWidth = 50; EditorGUIUtility.fieldWidth = 30; @@ -761,15 +820,249 @@ public override void OnInspectorGUI() { using (new EditorGUI.DisabledScope(true)) { + var hasSameClipLength = true; + var hasSameFrameRate = true; + var firstClipLength = GetClipLength(0); + var firstClipFrameRate = m_Clips[0].frameRate; + for (var i = 1; i < m_Clips.Length; ++i) + { + if (Mathf.Abs(GetClipLength(i) - firstClipLength) > Mathf.Epsilon) + hasSameClipLength = false; + if (Mathf.Abs(m_Clips[i].frameRate - firstClipFrameRate) > Mathf.Epsilon) + hasSameFrameRate = false; + } + GUILayout.Label(Styles.Length, EditorStyles.label, GUILayout.Width(50 - 4)); - GUILayout.Label(GetClipLength().ToString("0.000", CultureInfo.InvariantCulture.NumberFormat), EditorStyles.label); + var clipLengthStr = hasSameClipLength ? firstClipLength.ToString("0.000", CultureInfo.InvariantCulture.NumberFormat) : "-"; + GUILayout.Label(clipLengthStr, EditorStyles.label); + GUILayout.FlexibleSpace(); - GUILayout.Label(m_Clip.frameRate + " FPS", EditorStyles.label); + + var clipFrameRateStr = hasSameFrameRate ? firstClipFrameRate.ToString() : "-"; + GUILayout.Label(clipFrameRateStr + " FPS", EditorStyles.label); } } EditorGUILayout.EndHorizontal(); - if (!m_Clip.legacy) + if (m_IsSelectingMultipleClips) + MultiClipInspectorGUI(); + else + SingleClipInspectorGUI(); + } + + private void MultiClipInspectorGUI() + { + var sameTypeOfClip = true; + for (var i = 1; i < m_Clips.Length; ++i) + { + if (m_Clips[i].legacy != m_Clips[0].legacy) + { + sameTypeOfClip = false; + break; + } + } + + if (!sameTypeOfClip) + { + EditorGUILayout.HelpBox(Styles.InvalidMultiSelection, MessageType.Error); + return; + } + + if (m_Clips[0].legacy) + MultiLegacyClipGUI(); + else + MultiMuscleClipGUI(); + } + + private void MultiLegacyClipGUI() + { + serializedObject.Update(); + + EditorGUIUtility.labelWidth = 0; + EditorGUIUtility.fieldWidth = 0; + + EditorGUI.BeginChangeCheck(); + + var wrapMode = serializedObject.FindProperty("m_WrapMode"); + EditorGUILayout.PropertyField(wrapMode, Styles.WrapMode); + + serializedObject.ApplyModifiedProperties(); + } + + private void MultiMuscleClipGUI() + { + serializedObject.Update(); + + EditorGUIUtility.labelWidth = 0; + EditorGUIUtility.fieldWidth = 0; + + var toggleLoopTimeRect = EditorGUILayout.GetControlRect(); + EditorGUI.PropertyField(toggleLoopTimeRect, m_LoopTimeProperty, Styles.LoopTime); + + using (new EditorGUI.DisabledScope(!m_LoopTimeProperty.boolValue)) + { + EditorGUI.indentLevel++; + + var toggleLoopPoseRect = EditorGUILayout.GetControlRect(); + EditorGUI.PropertyField(toggleLoopPoseRect, m_LoopBlendProperty, Styles.LoopPose); + EditorGUILayout.PropertyField(m_CycleOffsetProperty, Styles.LoopCycleOffset); + + EditorGUI.indentLevel--; + } + + EditorGUILayout.Space(); + + var areAllClipsEitherHumanOrNonHuman = true; + var isSelectionHumanClip = true; + var doesSelectionHaveRootCurves = true; + var doesSelectionHaveMotionCurves = false; + for (var i = 0; i < m_Clips.Length; ++i) + { + if (m_Clips[0].isHumanMotion != m_Clips[i]) + areAllClipsEitherHumanOrNonHuman = false; + if (!m_Clips[i].isHumanMotion) + isSelectionHumanClip = false; + if (!m_Clips[i].hasRootCurves) + doesSelectionHaveRootCurves = false; + if (m_Clips[i].hasMotionCurves) + doesSelectionHaveMotionCurves = true; + } + + if (doesSelectionHaveRootCurves && !doesSelectionHaveMotionCurves) + { + // Rotation + GUILayout.Label(Styles.RootTransformRotation, EditorStyles.label); + EditorGUI.indentLevel++; + + // Toggle + var toggleRotRect = EditorGUILayout.GetControlRect(); + EditorGUI.PropertyField(toggleRotRect, m_LoopBlendOrientationProperty, Styles.BakeIntoPoseOrientation); + + // Reference + BoolPropertyPopup(m_KeepOriginalOrientationProperty, + m_LoopBlendOrientationProperty.boolValue ? Styles.BasedUponOrientation : Styles.BasedUponStartOrientation, + m_KeepOriginalOrientationProperty.boolValue ? 0 : 1, + isSelectionHumanClip ? Styles.BasedUponRotationHumanOpt : Styles.BasedUponRotationOpt); + + // Offset + EditorGUILayout.PropertyField(m_OrientationOffsetYProperty, Styles.OrientationOffsetY); + EditorGUI.indentLevel--; + + EditorGUILayout.Space(); + + // Position Y + GUILayout.Label(Styles.RootTransformRotationY, EditorStyles.label); + EditorGUI.indentLevel++; + + // Toggle + var toggleYRect = EditorGUILayout.GetControlRect(); + EditorGUI.PropertyField(toggleYRect, m_LoopBlendPositionYProperty, Styles.BakeIntoPosePositionY); + + // Reference + if (isSelectionHumanClip && areAllClipsEitherHumanOrNonHuman) + { + int offsetHeight; + if (m_KeepOriginalPositionYProperty.boolValue) + offsetHeight = 0; + else if (m_HeightFromFeetProperty.boolValue) + offsetHeight = 2; + else + offsetHeight = 1; + + var showMixedValueDefault = EditorGUI.showMixedValue; + EditorGUI.showMixedValue = m_KeepOriginalPositionYProperty.hasMultipleDifferentValues || m_HeightFromFeetProperty.hasMultipleDifferentValues; + + offsetHeight = + EditorGUILayout.Popup( + m_LoopBlendPositionYProperty.boolValue ? Styles.BasedUponStartPositionY : Styles.BasedUponPositionY, + offsetHeight, + Styles.BasedUponPositionYHumanOpt); + + EditorGUI.showMixedValue = showMixedValueDefault; + + if (offsetHeight == 0) + { + m_KeepOriginalPositionYProperty.boolValue = true; + m_HeightFromFeetProperty.boolValue = false; + } + else if (offsetHeight == 1) + { + m_KeepOriginalPositionYProperty.boolValue = false; + m_HeightFromFeetProperty.boolValue = false; + } + else + { + m_KeepOriginalPositionYProperty.boolValue = false; + m_HeightFromFeetProperty.boolValue = true; + } + } + else if (areAllClipsEitherHumanOrNonHuman) + { + BoolPropertyPopup(m_KeepOriginalPositionYProperty, + m_LoopBlendPositionYProperty.boolValue ? Styles.BasedUponStartPositionY : Styles.BasedUponPositionY, + m_KeepOriginalPositionYProperty.boolValue ? 0 : 1, + Styles.BasedUponPositionYOpt); + } + + // Offset + EditorGUILayout.PropertyField(m_LevelProperty, Styles.PositionOffsetY); + EditorGUI.indentLevel--; + + EditorGUILayout.Space(); + + // Position XZ + GUILayout.Label(Styles.RootTransformPositionXZ, EditorStyles.label); + EditorGUI.indentLevel++; + + // Toggle + var toggleXZRect = EditorGUILayout.GetControlRect(); + EditorGUI.PropertyField(toggleXZRect, m_LoopBlendPositionXZProperty, Styles.BakeIntoPosePositionXZ); + + // Reference + BoolPropertyPopup(m_KeepOriginalPositionXZProperty, + m_LoopBlendPositionXZProperty.boolValue ? Styles.BasedUponStartPositionXZ : Styles.BasedUponPositionXZ, + m_KeepOriginalPositionXZProperty.boolValue ? 0 : 1, + isSelectionHumanClip ? Styles.BasedUponPositionXZHumanOpt : Styles.BasedUponPositionXZOpt); + + EditorGUI.indentLevel--; + + EditorGUILayout.Space(); + } + + if (isSelectionHumanClip) + { + EditorGUILayout.PropertyField(m_MirrorProperty, Styles.Mirror); + } + + if (serializedObject.hasModifiedProperties) + { + serializedObject.ApplyModifiedProperties(); + foreach(var clip in m_Clips) + AnimationUtility.RebuildMecanimData(clip); + } + } + + private static void BoolPropertyPopup(SerializedProperty property, GUIContent label, int selectedIndex, GUIContent[] displayOptions) + { + var showMixedValueDefault = EditorGUI.showMixedValue; + + var hasMultipleDifferentValues = property.hasMultipleDifferentValues; + EditorGUI.showMixedValue = hasMultipleDifferentValues; + + EditorGUI.BeginChangeCheck(); + selectedIndex = EditorGUILayout.Popup(label, hasMultipleDifferentValues ? -1 : selectedIndex, displayOptions); + + if (EditorGUI.EndChangeCheck()) + property.boolValue = (selectedIndex == 0); + + EditorGUI.showMixedValue = showMixedValueDefault; + } + + private void SingleClipInspectorGUI() + { + InitPreview(); + + if (!m_Clips[0].legacy) MuscleClipGUI(); else AnimationClipGUI(); @@ -791,13 +1084,13 @@ private void AnimationClipGUI() if (changedStop) m_ClipInfo.lastFrame = stopFrame; - m_AvatarPreview.timeControl.startTime = startFrame / m_Clip.frameRate; - m_AvatarPreview.timeControl.stopTime = stopFrame / m_Clip.frameRate; + m_AvatarPreview.timeControl.startTime = startFrame / m_Clips[0].frameRate; + m_AvatarPreview.timeControl.stopTime = stopFrame / m_Clips[0].frameRate; } else { m_AvatarPreview.timeControl.startTime = 0; - m_AvatarPreview.timeControl.stopTime = m_Clip.length; + m_AvatarPreview.timeControl.stopTime = m_Clips[0].length; } EditorGUIUtility.labelWidth = 0; @@ -807,14 +1100,14 @@ private void AnimationClipGUI() m_ClipInfo.loop = EditorGUILayout.Toggle(Styles.AddLoopFrame, m_ClipInfo.loop); EditorGUI.BeginChangeCheck(); - int wrap = m_ClipInfo != null ? m_ClipInfo.wrapMode : (int)m_Clip.wrapMode; + int wrap = m_ClipInfo != null ? m_ClipInfo.wrapMode : (int)m_Clips[0].wrapMode; wrap = (int)(WrapModeFixed)EditorGUILayout.EnumPopup(Styles.WrapMode, (WrapModeFixed)wrap); if (EditorGUI.EndChangeCheck()) { if (m_ClipInfo != null) m_ClipInfo.wrapMode = wrap; else - m_Clip.wrapMode = (WrapMode)wrap; + m_Clips[0].wrapMode = (WrapMode)wrap; } } @@ -1014,24 +1307,24 @@ private void MuscleClipGUI() InitController(); - AnimationClipSettings animationClipSettings = AnimationUtility.GetAnimationClipSettings(m_Clip); + AnimationClipSettings animationClipSettings = AnimationUtility.GetAnimationClipSettings(m_Clips[0]); - m_StartFrame = m_DraggingRange ? m_StartFrame : animationClipSettings.startTime * m_Clip.frameRate; - m_StopFrame = m_DraggingRange ? m_StopFrame : animationClipSettings.stopTime * m_Clip.frameRate; - m_AdditivePoseFrame = m_DraggingRange ? m_AdditivePoseFrame : animationClipSettings.additiveReferencePoseTime * m_Clip.frameRate; + m_StartFrame = m_DraggingRange ? m_StartFrame : animationClipSettings.startTime * m_Clips[0].frameRate; + m_StopFrame = m_DraggingRange ? m_StopFrame : animationClipSettings.stopTime * m_Clips[0].frameRate; + m_AdditivePoseFrame = m_DraggingRange ? m_AdditivePoseFrame : animationClipSettings.additiveReferencePoseTime * m_Clips[0].frameRate; - float startTime = m_StartFrame / m_Clip.frameRate; - float stopTime = m_StopFrame / m_Clip.frameRate; - float additivePoseTime = m_AdditivePoseFrame / m_Clip.frameRate; + float startTime = m_StartFrame / m_Clips[0].frameRate; + float stopTime = m_StopFrame / m_Clips[0].frameRate; + float additivePoseTime = m_AdditivePoseFrame / m_Clips[0].frameRate; - MuscleClipQualityInfo clipQualityInfo = MuscleClipUtility.GetMuscleClipQualityInfo(m_Clip, startTime, + MuscleClipQualityInfo clipQualityInfo = MuscleClipUtility.GetMuscleClipQualityInfo(m_Clips[0], startTime, stopTime); bool IsHumanClip = (target as Motion).isHumanMotion; - bool hasMotionCurves = m_Clip.hasMotionCurves; - bool hasRootCurves = m_Clip.hasRootCurves; - bool hasGenericRootTransform = m_Clip.hasGenericRootTransform; - bool hasMotionFloatCurves = m_Clip.hasMotionFloatCurves; + bool hasMotionCurves = m_Clips[0].hasMotionCurves; + bool hasRootCurves = m_Clips[0].hasRootCurves; + bool hasGenericRootTransform = m_Clips[0].hasGenericRootTransform; + bool hasMotionFloatCurves = m_Clips[0].hasMotionFloatCurves; bool hasAnyRootCurves = hasRootCurves || hasMotionCurves; bool changedStart = false; @@ -1254,9 +1547,9 @@ private void MuscleClipGUI() EditorGUI.indentLevel++; m_AdditivePoseFrame = EditorGUILayout.FloatField(Styles.AdditiveReferencePoseFrame, m_AdditivePoseFrame); - m_AdditivePoseFrame = Mathf.Clamp(m_AdditivePoseFrame, m_Clip.startTime * m_Clip.frameRate, m_Clip.stopTime * m_Clip.frameRate); + m_AdditivePoseFrame = Mathf.Clamp(m_AdditivePoseFrame, m_Clips[0].startTime * m_Clips[0].frameRate, m_Clips[0].stopTime * m_Clips[0].frameRate); - animationClipSettings.additiveReferencePoseTime = m_AdditivePoseFrame / m_Clip.frameRate; + animationClipSettings.additiveReferencePoseTime = m_AdditivePoseFrame / m_Clips[0].frameRate; EditorGUI.indentLevel--; } } @@ -1317,15 +1610,15 @@ private void MuscleClipGUI() animationClipSettings.loopBlendPositionY = false; animationClipSettings.loopBlendPositionXZ = false; - m_DraggingStartFrame = animationClipSettings.startTime * m_Clip.frameRate; - m_DraggingStopFrame = animationClipSettings.stopTime * m_Clip.frameRate; - m_DraggingAdditivePoseFrame = animationClipSettings.additiveReferencePoseTime * m_Clip.frameRate; + m_DraggingStartFrame = animationClipSettings.startTime * m_Clips[0].frameRate; + m_DraggingStopFrame = animationClipSettings.stopTime * m_Clips[0].frameRate; + m_DraggingAdditivePoseFrame = animationClipSettings.additiveReferencePoseTime * m_Clips[0].frameRate; //case 790259: The length of the clip will be changed by this inspector, so we can't use it for sampling animationClipSettings.startTime = 0; animationClipSettings.stopTime = m_InitialClipLength; - AnimationUtility.SetAnimationClipSettingsNoDirty(m_Clip, animationClipSettings); + AnimationUtility.SetAnimationClipSettingsNoDirty(m_Clips[0], animationClipSettings); DestroyController(); } @@ -1343,9 +1636,9 @@ private void MuscleClipGUI() { if (!m_DraggingRange) { - Undo.RegisterCompleteObjectUndo(m_Clip, "Muscle Clip Edit"); - AnimationUtility.SetAnimationClipSettingsNoDirty(m_Clip, animationClipSettings); - EditorUtility.SetDirty(m_Clip); + Undo.RegisterCompleteObjectUndo(m_Clips, "Muscle Clip Edit"); + AnimationUtility.SetAnimationClipSettingsNoDirty(m_Clips[0], animationClipSettings); + EditorUtility.SetDirty(m_Clips[0]); DestroyController(); } } @@ -1460,10 +1753,10 @@ private void LoopQualityLampAndCurve(Rect position, float value, int lightMeterH // Draw start and end markers GUI.color = new Color(0.3f, 0.6f, 1.0f); // Draw marker for moving end - float timePixel = matrix.MultiplyPoint3x4(new Vector3((changedStart ? m_StartFrame : m_StopFrame) / m_Clip.frameRate, 0, 0)).x; + float timePixel = matrix.MultiplyPoint3x4(new Vector3((changedStart ? m_StartFrame : m_StopFrame) / m_Clips[0].frameRate, 0, 0)).x; GUI.DrawTexture(new Rect(timePixel, 0, 1, r.height), EditorGUIUtility.whiteTexture); // Draw marker for static end - timePixel = matrix.MultiplyPoint3x4(new Vector3((changedStart ? m_StopFrame : m_StartFrame) / m_Clip.frameRate, 0, 0)).x; + timePixel = matrix.MultiplyPoint3x4(new Vector3((changedStart ? m_StopFrame : m_StartFrame) / m_Clips[0].frameRate, 0, 0)).x; GUI.DrawTexture(new Rect(timePixel, 0, 1, r.height), EditorGUIUtility.whiteTexture); GUI.color = Color.white; } diff --git a/Editor/Mono/Inspector/AssemblyDefinitionImporterInspector.cs b/Editor/Mono/Inspector/AssemblyDefinitionImporterInspector.cs index c0551a1c0c..27719993f8 100644 --- a/Editor/Mono/Inspector/AssemblyDefinitionImporterInspector.cs +++ b/Editor/Mono/Inspector/AssemblyDefinitionImporterInspector.cs @@ -306,7 +306,9 @@ public override void OnInspectorGUI() GUILayout.Label(Styles.references, EditorStyles.boldLabel); EditorGUILayout.BeginVertical(GUI.skin.box); + EditorGUI.BeginDisabled(m_ReferencesList.serializedProperty.arraySize == 0); EditorGUILayout.PropertyField(m_UseGUIDs, Styles.useGUIDs); + EditorGUI.EndDisabled(); EditorGUILayout.EndVertical(); m_ReferencesList.DoLayoutList(); @@ -675,7 +677,10 @@ private void DrawPrecompiledReferenceListElement(Rect rect, int index, bool isac if (selectedIndex > 0) { var selectedAssemblyName = m_PrecompileReferenceListEntry[selectedIndex]; - var assembly = m_AssemblyProvider.GetPrecompiledAssemblies(true, EditorUserBuildSettings.activeBuildTargetGroup, EditorUserBuildSettings.activeBuildTarget) + var assembly = m_AssemblyProvider.GetPrecompiledAssemblies( + EditorScriptCompilationOptions.BuildingForEditor | EditorScriptCompilationOptions.BuildingWithAsserts, + EditorUserBuildSettings.activeBuildTargetGroup, + EditorUserBuildSettings.activeBuildTarget) .First(x => AssetPath.GetFileName(x.Path) == selectedAssemblyName); nameProp.stringValue = selectedAssemblyName; pathProp.stringValue = assembly.Path; @@ -800,7 +805,10 @@ static void LoadAssemblyDefinitionState(AssemblyDefinitionState state, string pa } var nameToPrecompiledReference = EditorCompilationInterface.Instance.PrecompiledAssemblyProvider - .GetPrecompiledAssemblies(true, EditorUserBuildSettings.activeBuildTargetGroup, EditorUserBuildSettings.activeBuildTarget) + .GetPrecompiledAssemblies( + EditorScriptCompilationOptions.BuildingForEditor | EditorScriptCompilationOptions.BuildingWithAsserts, + EditorUserBuildSettings.activeBuildTargetGroup, + EditorUserBuildSettings.activeBuildTarget) .Where(x => (x.Flags & AssemblyFlags.UserAssembly) == AssemblyFlags.UserAssembly) .Distinct() .ToDictionary(x => AssetPath.GetFileName(x.Path), x => x); diff --git a/Editor/Mono/Inspector/AssemblyDefinitionReferenceImporterInspector.cs b/Editor/Mono/Inspector/AssemblyDefinitionReferenceImporterInspector.cs index edca56ff59..fff4954108 100644 --- a/Editor/Mono/Inspector/AssemblyDefinitionReferenceImporterInspector.cs +++ b/Editor/Mono/Inspector/AssemblyDefinitionReferenceImporterInspector.cs @@ -70,8 +70,9 @@ public override void OnInspectorGUI() } extraDataSerializedObject.Update(); - + EditorGUI.BeginDisabled(m_ReferenceAsset.objectReferenceValue == null); EditorGUILayout.PropertyField(m_UseGUIDs, Styles.useGUID); + EditorGUI.EndDisabled(); EditorGUI.BeginChangeCheck(); var obj = EditorGUILayout.ObjectField(Styles.assemblyDefinition, m_ReferenceAsset.objectReferenceValue, typeof(AssemblyDefinitionAsset), false); diff --git a/Editor/Mono/Inspector/AutodeskInteractiveShaderGUI.cs b/Editor/Mono/Inspector/AutodeskInteractiveShaderGUI.cs index 0db4b96c4e..0a26a40f74 100644 --- a/Editor/Mono/Inspector/AutodeskInteractiveShaderGUI.cs +++ b/Editor/Mono/Inspector/AutodeskInteractiveShaderGUI.cs @@ -188,7 +188,7 @@ public override void AssignNewShaderToMaterial(Material material, Shader oldShad void BlendModePopup() { - EditorGUI.showMixedValue = blendMode.hasMixedValue; + MaterialEditor.BeginProperty(blendMode); var mode = (BlendMode)blendMode.floatValue; EditorGUI.BeginChangeCheck(); @@ -199,7 +199,7 @@ void BlendModePopup() blendMode.floatValue = (float)mode; } - EditorGUI.showMixedValue = false; + MaterialEditor.EndProperty(); } void DoAlbedoArea(Material material) diff --git a/Editor/Mono/Inspector/AvatarMaskInspector.cs b/Editor/Mono/Inspector/AvatarMaskInspector.cs index d78ec705cc..a2ea8985cc 100644 --- a/Editor/Mono/Inspector/AvatarMaskInspector.cs +++ b/Editor/Mono/Inspector/AvatarMaskInspector.cs @@ -750,16 +750,19 @@ protected override void ToggleAll() static bool GetFirstActiveValue(SerializedNodeInfo parent, out bool value) { - foreach (var treeViewItem in parent.children) + if (parent.children != null) { - var child = (SerializedNodeInfo)treeViewItem; - if (child.m_State != SerializedNodeInfo.State.Disabled) + foreach (var treeViewItem in parent.children) { - value = child.nodeState; - return true; + var child = (SerializedNodeInfo)treeViewItem; + if (child.m_State != SerializedNodeInfo.State.Disabled) + { + value = child.nodeState; + return true; + } + if (GetFirstActiveValue(child, out value)) + return true; } - if (GetFirstActiveValue(child, out value)) - return true; } value = false; return false; diff --git a/Editor/Mono/Inspector/BoxCollider2DEditor.cs b/Editor/Mono/Inspector/BoxCollider2DEditor.cs index 47b9a3f101..80a2ad5716 100644 --- a/Editor/Mono/Inspector/BoxCollider2DEditor.cs +++ b/Editor/Mono/Inspector/BoxCollider2DEditor.cs @@ -65,8 +65,6 @@ public override void OnInspectorGUI() EditorGUILayout.PropertyField(m_EdgeRadius); EditorGUILayout.EndFadeGroup(); - serializedObject.ApplyModifiedProperties(); - FinalizeInspectorGUI(); } } diff --git a/Editor/Mono/Inspector/CameraEditor.cs b/Editor/Mono/Inspector/CameraEditor.cs index 7256123de5..a4fe8fd553 100644 --- a/Editor/Mono/Inspector/CameraEditor.cs +++ b/Editor/Mono/Inspector/CameraEditor.cs @@ -9,11 +9,9 @@ using UnityEngine; using UnityEngine.Rendering; using UnityEngine.XR; -using UnityEngine.Experimental.Rendering; using AnimatedBool = UnityEditor.AnimatedValues.AnimBool; using UnityEngine.Scripting; using UnityEditor.Modules; -using UnityEditor.Overlays; using UnityEditorInternal.VR; using Object = UnityEngine.Object; @@ -34,9 +32,17 @@ private static class Styles public static GUIContent viewportRect = EditorGUIUtility.TrTextContent("Viewport Rect", "Four values that indicate where on the screen this camera view will be drawn. Measured in Viewport Coordinates (values 0-1)."); public static GUIContent sensorSize = EditorGUIUtility.TrTextContent("Sensor Size", "The size of the camera sensor in millimeters."); public static GUIContent lensShift = EditorGUIUtility.TrTextContent("Lens Shift", "Offset from the camera sensor. Use these properties to simulate a shift lens. Measured as a multiple of the sensor size."); + public static GUIContent iso = EditorGUIUtility.TrTextContent("ISO", "The sensor sensitivity (ISO)."); + public static GUIContent shutterSpeed = EditorGUIUtility.TrTextContent("Shutter Speed", "The exposure time, in second."); + public static GUIContent aperture = EditorGUIUtility.TrTextContent("Aperture", "The aperture number, in f-stop."); + public static GUIContent focusDistance = EditorGUIUtility.TrTextContent("FocusDistance", "The focus distance of the lens. The Depth of Field Volume override uses this value if you set focusDistanceMode to FocusDistanceMode.Camera."); + public static GUIContent bladeCount = EditorGUIUtility.TrTextContent("Blade Count", "The number of diaphragm blades."); + public static GUIContent curvature = EditorGUIUtility.TrTextContent("Curvature", "Maps an aperture range to blade curvature."); + public static GUIContent barrelClipping = EditorGUIUtility.TrTextContent("Barrel Clipping", "The strength of the \"cat eye\" effect on bokeh (optical vignetting)."); + public static GUIContent anamorphism = EditorGUIUtility.TrTextContent("Anamorphism", "Stretches the sensor to simulate an anamorphic look. Positive values distort the Camera vertically, negative will distort the Camera horizontally."); public static GUIContent physicalCamera = EditorGUIUtility.TrTextContent("Physical Camera", "Enables Physical camera mode. When checked, the field of view is calculated from properties for simulating physical attributes (focal length, sensor size, and lens shift)"); public static GUIContent cameraType = EditorGUIUtility.TrTextContent("Sensor Type", "Common sensor sizes. Choose an item to set Sensor Size, or edit Sensor Size for your custom settings."); - public static GUIContent renderingPath = EditorGUIUtility.TrTextContent("Rendering Path", "Choose a rendering method for this camera.\n\nUse Graphics Settings to use the rendering path specified in Player settings.\n\nUse Forward to render all objects with one pass per material.\n\nUse Deferred to draw all objects once without lighting and then draw the lighting of all objects at the end of the render queue.\n\nUse Legacy Vertex Lit to render all lights in a single pass, calculated in vertices.\n\nLegacy Deferred has been deprecated."); + public static GUIContent renderingPath = EditorGUIUtility.TrTextContent("Rendering Path", "Choose a rendering method for this camera.\n\nUse Graphics Settings to use the rendering path specified in graphics settings.\n\nUse Forward to render all objects with one pass per light.\n\nUse Deferred to draw all objects once without lighting and then draw the lighting of all objects at the end of the render queue.\n\nUse Legacy Vertex Lit to render all lights in a single pass, calculated at vertices."); public static GUIContent focalLength = EditorGUIUtility.TrTextContent("Focal Length", "The simulated distance between the lens and the sensor of the physical camera. Larger values give a narrower field of view."); public static GUIContent allowOcclusionCulling = EditorGUIUtility.TrTextContent("Occlusion Culling", "Occlusion Culling disables rendering of objects when they are not currently seen by the camera because they are obscured (occluded) by other objects."); public static GUIContent allowHDR = EditorGUIUtility.TrTextContent("HDR", "High Dynamic Range gives you a wider range of light intensities, so your lighting looks more realistic. With it, you can still see details and experience less saturation even with bright light."); @@ -70,6 +76,10 @@ public Settings(SerializedObject so) "35mm 2-perf", "35mm Academy", "Super-35", + "35mm TV Projection", + "35mm Full Aperture", + "35mm 1.85 Projection", + "35mm Anamorphic", "65mm ALEXA", "70mm", "70mm IMAX", @@ -83,12 +93,16 @@ public Settings(SerializedObject so) new Vector2(4.8f, 3.5f) , // 8mm new Vector2(5.79f, 4.01f) , // Super 8mm new Vector2(10.26f, 7.49f) , // 16mm - new Vector2(12.52f, 7.41f) , // Super 16mm + new Vector2(12.522f, 7.417f) , // Super 16mm new Vector2(21.95f, 9.35f) , // 35mm 2-perf - new Vector2(21.0f, 15.2f) , // 35mm academy + new Vector2(21.946f, 16.002f) , // 35mm academy new Vector2(24.89f, 18.66f) , // Super-35 + new Vector2(20.726f, 15.545f), // 35mm TV Projection + new Vector2(24.892f, 18.669f), // 35mm Full Aperture + new Vector2(20.955f, 11.328f), // 35mm 1.85 Projection + new Vector2(21.946f, 18.593f), // 35mm Anamorphic new Vector2(54.12f, 25.59f) , // 65mm ALEXA - new Vector2(70.0f, 51.0f) , // 70mm + new Vector2(52.476f, 23.012f) , // 70mm new Vector2(70.41f, 52.63f), // 70mm IMAX }; @@ -99,25 +113,31 @@ public Settings(SerializedObject so) EditorGUIUtility.TrTextContent("Use Graphics Settings"), EditorGUIUtility.TrTextContent("Forward"), EditorGUIUtility.TrTextContent("Deferred"), - EditorGUIUtility.TrTextContent("Legacy Vertex Lit"), - EditorGUIUtility.TrTextContent("Legacy Deferred (light prepass)") + EditorGUIUtility.TrTextContent("Legacy Vertex Lit") }; private static readonly int[] kCameraRenderPathValues = { (int)RenderingPath.UsePlayerSettings, (int)RenderingPath.Forward, (int)RenderingPath.DeferredShading, - (int)RenderingPath.VertexLit, - (int)RenderingPath.DeferredLighting + (int)RenderingPath.VertexLit }; public SerializedProperty clearFlags { get; private set; } public SerializedProperty backgroundColor { get; private set; } public SerializedProperty normalizedViewPortRect { get; private set; } internal SerializedProperty projectionMatrixMode { get; private set; } + public SerializedProperty iso { get; private set; } + public SerializedProperty shutterSpeed { get; private set; } + public SerializedProperty aperture { get; private set; } + public SerializedProperty focusDistance { get; private set; } + public SerializedProperty focalLength { get; private set; } + public SerializedProperty bladeCount { get; private set; } + public SerializedProperty curvature { get; private set; } + public SerializedProperty barrelClipping { get; private set; } + public SerializedProperty anamorphism { get; private set; } public SerializedProperty sensorSize { get; private set; } public SerializedProperty lensShift { get; private set; } - public SerializedProperty focalLength { get; private set; } public SerializedProperty gateFit { get; private set; } public SerializedProperty verticalFOV { get; private set; } public SerializedProperty orthographic { get; private set; } @@ -154,9 +174,17 @@ public void OnEnable() clearFlags = m_SerializedObject.FindProperty("m_ClearFlags"); backgroundColor = m_SerializedObject.FindProperty("m_BackGroundColor"); normalizedViewPortRect = m_SerializedObject.FindProperty("m_NormalizedViewPortRect"); + iso = m_SerializedObject.FindProperty("m_Iso"); + shutterSpeed = m_SerializedObject.FindProperty("m_ShutterSpeed"); + aperture = m_SerializedObject.FindProperty("m_Aperture"); + focusDistance = m_SerializedObject.FindProperty("m_FocusDistance"); + focalLength = m_SerializedObject.FindProperty("m_FocalLength"); + bladeCount = m_SerializedObject.FindProperty("m_BladeCount"); + curvature = m_SerializedObject.FindProperty("m_Curvature"); + barrelClipping = m_SerializedObject.FindProperty("m_BarrelClipping"); + anamorphism = m_SerializedObject.FindProperty("m_Anamorphism"); sensorSize = m_SerializedObject.FindProperty("m_SensorSize"); lensShift = m_SerializedObject.FindProperty("m_LensShift"); - focalLength = m_SerializedObject.FindProperty("m_FocalLength"); gateFit = m_SerializedObject.FindProperty("m_GateFitMode"); projectionMatrixMode = m_SerializedObject.FindProperty("m_projectionMatrixMode"); nearClippingPlane = m_SerializedObject.FindProperty("near clip plane"); @@ -297,6 +325,15 @@ public void DrawProjection() { using (new EditorGUI.IndentLevelScope()) { + EditorGUILayout.PropertyField(iso, Styles.iso); + EditorGUILayout.PropertyField(shutterSpeed, Styles.shutterSpeed); + EditorGUILayout.PropertyField(aperture, Styles.aperture); + EditorGUILayout.PropertyField(focusDistance, Styles.focusDistance); + EditorGUILayout.PropertyField(bladeCount, Styles.bladeCount); + EditorGUILayout.PropertyField(curvature, Styles.curvature); + EditorGUILayout.PropertyField(barrelClipping, Styles.barrelClipping); + EditorGUILayout.PropertyField(anamorphism, Styles.anamorphism); + using (var horizontal = new EditorGUILayout.HorizontalScope()) using (new EditorGUI.PropertyScope(horizontal.rect, Styles.focalLength, focalLength)) using (var checkScope = new EditorGUI.ChangeCheckScope()) @@ -474,7 +511,7 @@ protected Camera previewCamera } - private static bool IsDeferredRenderingPath(RenderingPath rp) { return rp == RenderingPath.DeferredLighting || rp == RenderingPath.DeferredShading; } + private static bool IsDeferredRenderingPath(RenderingPath rp) { return rp == RenderingPath.DeferredShading; } private bool wantDeferredRendering { diff --git a/Editor/Mono/Inspector/CameraOverlay.cs b/Editor/Mono/Inspector/CameraOverlay.cs index 9ff80d77ec..944fd580b0 100644 --- a/Editor/Mono/Inspector/CameraOverlay.cs +++ b/Editor/Mono/Inspector/CameraOverlay.cs @@ -34,6 +34,11 @@ class SceneViewCameraOverlay : TransientSceneViewOverlay public SceneViewCameraOverlay() { + minSize = new Vector2(40, 40); + maxSize = new Vector2(4000, 4000); + + sizeOverridenChanged += UpdateSize; + OnSelectionChanged(); } @@ -97,8 +102,21 @@ RenderTexture GetPreviewTextureWithSizeAndAA(int width, int height) return m_PreviewTexture; } + void UpdateSize() + { + if (!sizeOverridden) + size = new Vector2(240, 135); + } + public override void OnGUI() { + UpdateSize(); + + imguiContainer.style.minWidth = collapsed ? new StyleLength(240) : new StyleLength(StyleKeyword.Auto); + imguiContainer.style.minHeight = collapsed ? new StyleLength(135) : new StyleLength(StyleKeyword.Auto); + imguiContainer.parent.style.flexGrow = 1; + imguiContainer.style.flexGrow = 1; + if (selectedCamera == null) { GUILayout.Label("No camera selected", EditorStyles.centeredGreyMiniLabel); @@ -115,66 +133,16 @@ public override void OnGUI() if (!sceneView.IsGameObjectInThisSceneView(selectedCamera.gameObject)) return; - Vector2 previewSize = selectedCamera.targetTexture - ? new Vector2(selectedCamera.targetTexture.width, selectedCamera.targetTexture.height) - : PlayModeView.GetMainPlayModeViewTargetSize(); - - if (previewSize.x < 0f) - { - // Fallback to Scene View of not a valid game view size - previewSize.x = sceneView.position.width; - previewSize.y = sceneView.position.height; - } - - // Apply normalized viewport rect of camera - Rect normalizedViewPortRect = selectedCamera.rect; - - // clamp normalized rect in [0,1] - normalizedViewPortRect.xMin = Math.Max(normalizedViewPortRect.xMin, 0f); - normalizedViewPortRect.yMin = Math.Max(normalizedViewPortRect.yMin, 0f); - normalizedViewPortRect.xMax = Math.Min(normalizedViewPortRect.xMax, 1f); - normalizedViewPortRect.yMax = Math.Min(normalizedViewPortRect.yMax, 1f); - - previewSize.x *= Mathf.Max(normalizedViewPortRect.width, 0f); - previewSize.y *= Mathf.Max(normalizedViewPortRect.height, 0f); + var cameraRect = imguiContainer.rect; + cameraRect.width = Mathf.Floor(cameraRect.width); - // Prevent using invalid previewSize - if (previewSize.x < 1f || previewSize.y < 1f) + if (cameraRect.width < 1 || cameraRect.height < 1) return; - float aspect = previewSize.x / previewSize.y; - - // Scale down (fit to scene view) - previewSize.y = kPreviewNormalizedSize * sceneView.position.height; - previewSize.x = previewSize.y * aspect; - if (previewSize.y > sceneView.position.height * 0.5f) - { - previewSize.y = sceneView.position.height * 0.5f; - previewSize.x = previewSize.y * aspect; - } - if (previewSize.x > sceneView.position.width * 0.5f) - { - previewSize.x = sceneView.position.width * 0.5f; - previewSize.y = previewSize.x / aspect; - } - - // Get and reserve rect - drawingContainer.style.minWidth = previewSize.x; - drawingContainer.style.minHeight = previewSize.y; - var cameraRect = drawingContainer.rect; - cameraRect.width = Mathf.Floor(cameraRect.width); - if (Event.current.type == EventType.Repaint) { Graphics.DrawTexture(cameraRect, Texture2D.whiteTexture, new Rect(0, 0, 1, 1), 0, 0, 0, 0, Color.black); - } - - var properWidth = cameraRect.height * aspect; - cameraRect.x += (cameraRect.width - properWidth) * 0.5f; - cameraRect.width = properWidth; - if (Event.current.type == EventType.Repaint) - { // setup camera and render previewCamera.CopyFrom(selectedCamera); @@ -200,9 +168,34 @@ public override void OnGUI() } } - var previewTexture = GetPreviewTextureWithSizeAndAA((int)cameraRect.width, (int)cameraRect.height); + Vector2 previewSize = selectedCamera.targetTexture + ? new Vector2(selectedCamera.targetTexture.width, selectedCamera.targetTexture.height) + : PlayModeView.GetMainPlayModeViewTargetSize(); + + if (previewSize.x < 0f) + { + // Fallback to Scene View of not a valid game view size + previewSize.x = sceneView.position.width; + previewSize.y = sceneView.position.height; + } + + float rectAspect = cameraRect.width / cameraRect.height; + float previewAspect = previewSize.x / previewSize.y; + Rect previewRect = cameraRect; + if (rectAspect > previewAspect) + { + float stretch = previewAspect / rectAspect; + previewRect = new Rect(cameraRect.xMin + cameraRect.width * (1.0f - stretch) * .5f, cameraRect.yMin, stretch * cameraRect.width, cameraRect.height); + } + else + { + float stretch = rectAspect / previewAspect; + previewRect = new Rect(cameraRect.xMin, cameraRect.yMin + cameraRect.height * (1.0f - stretch) * .5f, cameraRect.width, stretch * cameraRect.height); + } + + var previewTexture = GetPreviewTextureWithSizeAndAA((int)previewRect.width, (int)previewRect.height); previewCamera.targetTexture = previewTexture; - previewCamera.pixelRect = new Rect(0, 0, cameraRect.width, cameraRect.height); + previewCamera.pixelRect = new Rect(0, 0, previewRect.width, previewRect.height); Handles.EmitGUIGeometryForCamera(selectedCamera, previewCamera); @@ -216,7 +209,8 @@ public override void OnGUI() } previewCamera.Render(); - Graphics.DrawTexture(cameraRect, previewTexture, new Rect(0, 0, 1, 1), 0, 0, 0, 0, GUI.color, EditorGUIUtility.GUITextureBlit2SRGBMaterial); + + Graphics.DrawTexture(previewRect, previewTexture, new Rect(0, 0, 1, 1), 0, 0, 0, 0, GUI.color, EditorGUIUtility.GUITextureBlit2SRGBMaterial); } } } diff --git a/Editor/Mono/Inspector/CapsuleCollider2DEditor.cs b/Editor/Mono/Inspector/CapsuleCollider2DEditor.cs index 8dc5d88cbc..64cdcedc6a 100644 --- a/Editor/Mono/Inspector/CapsuleCollider2DEditor.cs +++ b/Editor/Mono/Inspector/CapsuleCollider2DEditor.cs @@ -35,8 +35,6 @@ public override void OnInspectorGUI() EditorGUILayout.PropertyField(m_Size); EditorGUILayout.PropertyField(m_Direction); - serializedObject.ApplyModifiedProperties(); - FinalizeInspectorGUI(); } } diff --git a/Editor/Mono/Inspector/CircleCollider2DEditor.cs b/Editor/Mono/Inspector/CircleCollider2DEditor.cs index a7a4579291..c85522f579 100644 --- a/Editor/Mono/Inspector/CircleCollider2DEditor.cs +++ b/Editor/Mono/Inspector/CircleCollider2DEditor.cs @@ -31,8 +31,6 @@ public override void OnInspectorGUI() EditorGUILayout.PropertyField(m_Radius); - serializedObject.ApplyModifiedProperties(); - FinalizeInspectorGUI(); } } diff --git a/Editor/Mono/Inspector/ClothInspector.cs b/Editor/Mono/Inspector/ClothInspector.cs index 057cffc3cc..45a93f4bfa 100644 --- a/Editor/Mono/Inspector/ClothInspector.cs +++ b/Editor/Mono/Inspector/ClothInspector.cs @@ -1624,6 +1624,9 @@ void OnSceneEditConstraintsGUI() if (Selection.gameObjects.Length > 1) return; + if (!IsMeshValid()) + return; + s_Inspector = this; if (Event.current.type == EventType.Repaint) @@ -1659,6 +1662,9 @@ void OnSceneEditSelfAndInterCollisionParticlesGUI() if (Selection.gameObjects.Length > 1) return; + if (!IsMeshValid()) + return; + s_Inspector = this; Event evt = Event.current; @@ -1949,7 +1955,8 @@ public override bool visible public override void OnGUI() { - s_Inspector.ConstraintEditing(); + if(s_Inspector != null) + s_Inspector.ConstraintEditing(); } } diff --git a/Editor/Mono/Inspector/Collider2DEditorBase.cs b/Editor/Mono/Inspector/Collider2DEditorBase.cs index 7003e28518..f1e950bb4a 100644 --- a/Editor/Mono/Inspector/Collider2DEditorBase.cs +++ b/Editor/Mono/Inspector/Collider2DEditorBase.cs @@ -21,12 +21,14 @@ protected class Styles private SerializedProperty m_Density; private readonly AnimBool m_ShowDensity = new AnimBool(); + private readonly AnimBool m_ShowLayerOverrides = new AnimBool(); private readonly AnimBool m_ShowInfo = new AnimBool(); private readonly AnimBool m_ShowContacts = new AnimBool(); Vector2 m_ContactScrollPosition; static ContactPoint2D[] m_Contacts = new ContactPoint2D[100]; + private SavedBool m_ShowLayerOverridesFoldout; private SavedBool m_ShowInfoFoldout; private bool m_RequiresConstantRepaint; @@ -36,6 +38,11 @@ protected class Styles private SerializedProperty m_UsedByComposite; private SerializedProperty m_Offset; protected SerializedProperty m_AutoTiling; + private SerializedProperty m_LayerOverridePriority; + private SerializedProperty m_IncludeLayers; + private SerializedProperty m_ExcludeLayers; + private SerializedProperty m_ForceSendLayers; + private SerializedProperty m_ForceReceiveLayers; private readonly AnimBool m_ShowCompositeRedundants = new AnimBool(); @@ -48,8 +55,12 @@ public override void OnEnable() m_ShowDensity.value = ShouldShowDensity(); m_ShowDensity.valueChanged.AddListener(Repaint); + m_ShowLayerOverrides.valueChanged.AddListener(Repaint); + m_ShowLayerOverridesFoldout = new SavedBool($"{target.GetType() }.ShowLayerOverridesFoldout", false); + m_ShowLayerOverrides.value = m_ShowLayerOverridesFoldout.value; + m_ShowInfo.valueChanged.AddListener(Repaint); - m_ShowInfoFoldout = new SavedBool($"{target.GetType()}.ShowFoldout", false); + m_ShowInfoFoldout = new SavedBool($"{target.GetType()}.ShowInfoFoldout", false); m_ShowInfo.value = m_ShowInfoFoldout.value; m_ShowContacts.valueChanged.AddListener(Repaint); m_ContactScrollPosition = Vector2.zero; @@ -60,6 +71,11 @@ public override void OnEnable() m_UsedByComposite = serializedObject.FindProperty("m_UsedByComposite"); m_Offset = serializedObject.FindProperty("m_Offset"); m_AutoTiling = serializedObject.FindProperty("m_AutoTiling"); + m_LayerOverridePriority = serializedObject.FindProperty("m_LayerOverridePriority"); + m_IncludeLayers = serializedObject.FindProperty("m_IncludeLayers"); + m_ExcludeLayers = serializedObject.FindProperty("m_ExcludeLayers"); + m_ForceSendLayers = serializedObject.FindProperty("m_ForceSendLayers"); + m_ForceReceiveLayers = serializedObject.FindProperty("m_ForceReceiveLayers"); m_ShowCompositeRedundants.value = !m_UsedByComposite.boolValue; m_ShowCompositeRedundants.valueChanged.AddListener(Repaint); @@ -70,6 +86,7 @@ public override void OnEnable() public override void OnDisable() { m_ShowDensity.valueChanged.RemoveListener(Repaint); + m_ShowLayerOverrides.valueChanged.RemoveListener(Repaint); m_ShowInfo.valueChanged.RemoveListener(Repaint); m_ShowContacts.valueChanged.RemoveListener(Repaint); m_ShowCompositeRedundants.valueChanged.RemoveListener(Repaint); @@ -88,7 +105,6 @@ public override void OnInspectorGUI() EditorGUILayout.PropertyField(m_Density); EditorGUILayout.EndFadeGroup(); - EditorGUILayout.PropertyField(m_Material); EditorGUILayout.PropertyField(m_IsTrigger); EditorGUILayout.PropertyField(m_UsedByEffector); @@ -107,6 +123,7 @@ public override void OnInspectorGUI() public void FinalizeInspectorGUI() { + ShowLayerOverridesProperties(); ShowColliderInfoProperties(); // Check for collider error state. @@ -122,6 +139,31 @@ public void FinalizeInspectorGUI() // Check for effector warnings. Effector2DEditor.CheckEffectorWarnings(target as Collider2D); + + EndColliderInspector(); + } + + private void ShowLayerOverridesProperties() + { + // Show Layer Overrides. + m_ShowLayerOverridesFoldout.value = m_ShowLayerOverrides.target = EditorGUILayout.Foldout(m_ShowLayerOverrides.target, "Layer Overrides", true); + if (EditorGUILayout.BeginFadeGroup(m_ShowLayerOverrides.faded)) + { + EditorGUI.indentLevel++; + EditorGUILayout.PropertyField(m_LayerOverridePriority); + EditorGUILayout.PropertyField(m_IncludeLayers); + EditorGUILayout.PropertyField(m_ExcludeLayers); + + // Only show force send/receive if we're not dealing with triggers. + if (targets.Count(x => (x as Collider2D).isTrigger) == 0) + { + EditorGUILayout.Space(); + EditorGUILayout.PropertyField(m_ForceSendLayers); + EditorGUILayout.PropertyField(m_ForceReceiveLayers); + } + EditorGUI.indentLevel--; + } + EditorGUILayout.EndFadeGroup(); } private void ShowColliderInfoProperties() @@ -231,7 +273,7 @@ protected void CheckColliderErrorState() } } - protected void BeginColliderInspector() + protected void BeginEditColliderInspector() { serializedObject.Update(); using (new EditorGUI.DisabledScope(targets.Length > 1)) diff --git a/Editor/Mono/Inspector/CompositeCollider2DEditor.cs b/Editor/Mono/Inspector/CompositeCollider2DEditor.cs index 857f5de21a..d8e04044b0 100644 --- a/Editor/Mono/Inspector/CompositeCollider2DEditor.cs +++ b/Editor/Mono/Inspector/CompositeCollider2DEditor.cs @@ -87,8 +87,6 @@ public override void OnInspectorGUI() (x as CompositeCollider2D).attachedRigidbody.bodyType == RigidbodyType2D.Dynamic) > 0) EditorGUILayout.HelpBox("Outline geometry is composed of edges and will not preserve the original collider's center-of-mass or rotational inertia. The CompositeCollider2D is attached to a Dynamic Rigidbody2D so you may need to explicitly set these if they are required.", MessageType.Info); - serializedObject.ApplyModifiedProperties(); - FinalizeInspectorGUI(); } } diff --git a/Editor/Mono/Inspector/ConstrainProportionsTransformScale.cs b/Editor/Mono/Inspector/ConstrainProportionsTransformScale.cs index b5af711663..5f42d32a89 100644 --- a/Editor/Mono/Inspector/ConstrainProportionsTransformScale.cs +++ b/Editor/Mono/Inspector/ConstrainProportionsTransformScale.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.Collections.Generic; +using JetBrains.Annotations; using UnityEditor.ShortcutManagement; using UnityEngine; using Object = UnityEngine.Object; @@ -13,13 +12,17 @@ namespace UnityEditor internal class ConstrainProportionsTransformScale { bool m_ConstrainProportionsScale; + internal static bool m_IsAnimationPreview; Vector3 m_InitialScale; + static bool s_IsPropertyPaste; + internal bool constrainProportionsScale { get => m_ConstrainProportionsScale; set => m_ConstrainProportionsScale = value; } internal ConstrainProportionsTransformScale(Vector3 previousScale) { m_InitialScale = previousScale != Vector3.zero ? previousScale : Vector3.one; + s_IsPropertyPaste = false; } internal Vector3 DoGUI(Rect rect, GUIContent scaleContent, Vector3 value, UnityEngine.Object[] targetObjects, ref int axisModified, SerializedProperty property = null, SerializedProperty constrainProportionsProperty = null) @@ -33,19 +36,12 @@ internal Vector3 DoGUI(Rect rect, GUIContent scaleContent, Vector3 value, Unity if (previousIsProportionalScale != m_ConstrainProportionsScale) { // Every time scale becomes proportional, update initial scale value - if (m_ConstrainProportionsScale) + if (m_ConstrainProportionsScale && !m_IsAnimationPreview) { m_InitialScale = value != Vector3.zero ? value : Vector3.one; } - foreach (var obj in targetObjects) - { - var tr = obj as Transform; - if (tr == null) - continue; - Undo.RecordObject(tr, "Proportional Scale Toggle Changed"); - tr.constrainProportionsScale = m_ConstrainProportionsScale; - } + SetConstrainProportions(targetObjects, m_ConstrainProportionsScale); } return scale; @@ -60,16 +56,27 @@ static void ToggleConstrainProportionsScale() return; bool isProportionalScale = !Selection.DoAllGOsHaveConstrainProportionsEnabled(selected); - foreach (var obj in selected) - { - Undo.RecordObject(obj.transform, "Proportional Scale Toggle Changed"); - obj.transform.constrainProportionsScale = isProportionalScale; - } + + SetConstrainProportions(selected, isProportionalScale); // To make sure all inspector windows have a proper greyout if initial values are zero, rebuild. EditorUtility.ForceRebuildInspectors(); } + internal static void SetConstrainProportions([NotNull] Object[] objects, bool value) + { + foreach (var obj in objects) + { + Transform t = obj?.GetType() == typeof(GameObject)? (obj as GameObject)?.transform : obj as Transform; + + if (t == null) + continue; + + Undo.RecordObject(t, "Proportional Scale Toggle Changed"); + t.constrainProportionsScale = value; + } + } + internal static Vector3 GetVector3WithRatio(Vector3 vector, float ratio) { //If there are any fields with the same values, use already precalculated values @@ -86,15 +93,21 @@ internal static Vector3 GetVector3WithRatio(Vector3 vector, float ratio) internal static Vector3 DoScaleProportions(Vector3 value, Vector3 previousValue, Vector3 initialScale, ref int axisModified) { float ratio = 1; + bool ratioChanged = false; + + if (!Selection.DoAllGOsHaveConstrainProportionsEnabled(Selection.gameObjects)) + return value; if (previousValue != value) { + if (m_IsAnimationPreview && initialScale == Vector3.zero) + initialScale = Vector3.one; + // Check which axis was modified and set locked fields and ratio //AxisModified values [-1;2] : [none, x, y, z] // X axis ratio = SetRatio(value.x, previousValue.x, initialScale.x); axisModified = ratio != 1 || !Mathf.Approximately(value.x, previousValue.x) ? 0 : -1; - // Y axis if (axisModified == -1) { @@ -102,16 +115,45 @@ internal static Vector3 DoScaleProportions(Vector3 value, Vector3 previousValue, axisModified = ratio != 1 || !Mathf.Approximately(value.y, previousValue.y) ? 1 : -1; } // Z axis - if (axisModified == -1) { ratio = SetRatio(value.z, previousValue.z, initialScale.z); axisModified = ratio != 1 || !Mathf.Approximately(value.z, previousValue.z) ? 2 : -1; } - value = GetVector3WithRatio(initialScale, ratio); + ratioChanged = true; + } + // If customer has pasted a scale property via a context menu, we might need to enforce proportions + else if (s_IsPropertyPaste) + { + s_IsPropertyPaste = false; + // Catch if any value has changed by checking scale based on X axis + if (initialScale * (previousValue.x / initialScale.x) != value) + { + Vector3 axisRatios = new Vector3(previousValue.x / initialScale.x, previousValue.y / initialScale.y, + previousValue.z / initialScale.z); + + if (axisRatios.x != axisRatios.y && axisRatios.x != axisRatios.z && IsValidRatio(axisRatios.x)) + { + axisModified = 0; + ratio = axisRatios.x; + } + else if (axisRatios.y != axisRatios.x && axisRatios.y != axisRatios.z && IsValidRatio(axisRatios.y)) + { + axisModified = 1; + ratio = axisRatios.y; + } + else if (axisRatios.z != axisRatios.x && axisRatios.z != axisRatios.y && IsValidRatio(axisRatios.z)) + { + axisModified = 2; + ratio = axisRatios.z; + } + + ratioChanged = axisModified != -1; + } } - return value; + + return ratioChanged ? GetVector3WithRatio(initialScale, ratio) : value; } static float SetRatio(float value, float previousValue, float initialValue) @@ -243,5 +285,30 @@ internal static uint SetBit(uint mask, int index, bool value) else return mask & (~bitmask); } + + static bool IsValidRatio(float value) + { + return !float.IsNaN(value) && !float.IsInfinity(value); + } + + internal static void NotifyPropertyPasted(string propertyPath) + { + // If user has pasted a scale property via a context menu, we might need to enforce proportions. + s_IsPropertyPaste = propertyPath.StartsWith("m_LocalScale"); + } + + internal static bool ShouldForceEnablePropertyFields(float[] values) + { + if (!m_IsAnimationPreview) + return false; + + foreach (var value in values) + { + if (value != 0) + return false; + } + + return true; + } } } diff --git a/Editor/Mono/Inspector/ConstraintEditorBase.cs b/Editor/Mono/Inspector/ConstraintEditorBase.cs index 55b54254a4..276c55def1 100644 --- a/Editor/Mono/Inspector/ConstraintEditorBase.cs +++ b/Editor/Mono/Inspector/ConstraintEditorBase.cs @@ -69,7 +69,7 @@ internal abstract class ConstraintEditorBase : Editor public void OnEnable(ConstraintStyleBase style) { - Undo.undoRedoPerformed += OnUndoRedoPerformed; + Undo.undoRedoEvent += OnUndoRedoPerformed; m_SourceList = new ReorderableList(serializedObject, sources, sources.editable, true, sources.editable, sources.editable); m_SourceList.drawElementCallback += DrawElementCallback; @@ -87,10 +87,10 @@ public void OnEnable(ConstraintStyleBase style) public void OnDisable() { - Undo.undoRedoPerformed -= OnUndoRedoPerformed; + Undo.undoRedoEvent -= OnUndoRedoPerformed; } - internal void OnUndoRedoPerformed() + internal void OnUndoRedoPerformed(in UndoRedoInfo info) { // must call UserUpdateOffset to allow the offsets to be updated by the Undo system, otherwise the constraint can override them foreach (var t in targets) diff --git a/Editor/Mono/Inspector/CubemapPreview.cs b/Editor/Mono/Inspector/CubemapPreview.cs index 9ab6e84117..a1d7ebf74d 100644 --- a/Editor/Mono/Inspector/CubemapPreview.cs +++ b/Editor/Mono/Inspector/CubemapPreview.cs @@ -14,6 +14,7 @@ internal class CubemapPreview static readonly int s_ShaderIntensity = Shader.PropertyToID("_Intensity"); static readonly int s_ShaderIsNormalMap = Shader.PropertyToID("_IsNormalMap"); static readonly int s_ShaderExposure = Shader.PropertyToID("_Exposure"); + static readonly int s_ColorspaceIsGamma = Shader.PropertyToID("_ColorspaceIsGamma"); private enum PreviewType { @@ -181,18 +182,10 @@ private void RenderCubemap(Texture t, Vector2 previewDir, float previewDistance, m_Material.SetFloat(s_ShaderIntensity, m_Intensity); m_Material.SetFloat(s_ShaderIsNormalMap, TextureInspector.IsNormalMap(t) ? 1.0f : 0.0f); m_Material.SetFloat(s_ShaderExposure, exposure); - - if (PlayerSettings.colorSpace == ColorSpace.Linear) - { - m_Material.SetInt("_ColorspaceIsGamma", 0); - } - else - { - m_Material.SetInt("_ColorspaceIsGamma", 1); - } + m_Material.SetInt(s_ColorspaceIsGamma, PlayerSettings.colorSpace == ColorSpace.Linear ? 0 : 1); m_PreviewUtility.DrawMesh(m_Mesh, Vector3.zero, rot, m_Material, 0); - m_PreviewUtility.Render(); + m_PreviewUtility.Render(Unsupported.useScriptableRenderPipeline); } } } diff --git a/Editor/Mono/Inspector/CustomCollider2DEditor.cs b/Editor/Mono/Inspector/CustomCollider2DEditor.cs index 3bd51e2a8a..8ca134b5eb 100644 --- a/Editor/Mono/Inspector/CustomCollider2DEditor.cs +++ b/Editor/Mono/Inspector/CustomCollider2DEditor.cs @@ -27,8 +27,6 @@ public override void OnInspectorGUI() EditorGUI.EndDisabledGroup(); } - serializedObject.ApplyModifiedProperties(); - FinalizeInspectorGUI(); } } diff --git a/Editor/Mono/Inspector/EdgeCollider2DEditor.cs b/Editor/Mono/Inspector/EdgeCollider2DEditor.cs index aa6f98f686..55b371ab0f 100644 --- a/Editor/Mono/Inspector/EdgeCollider2DEditor.cs +++ b/Editor/Mono/Inspector/EdgeCollider2DEditor.cs @@ -41,7 +41,7 @@ public override void OnEnable() public override void OnInspectorGUI() { - BeginColliderInspector(); + BeginEditColliderInspector(); GUILayout.Space(5); base.OnInspectorGUI(); @@ -72,8 +72,6 @@ public override void OnInspectorGUI() } EditorGUILayout.EndFadeGroup(); - EndColliderInspector(); - FinalizeInspectorGUI(); } } diff --git a/Editor/Mono/Inspector/EditMode.cs b/Editor/Mono/Inspector/EditMode.cs index 89b4620786..34f33864c6 100644 --- a/Editor/Mono/Inspector/EditMode.cs +++ b/Editor/Mono/Inspector/EditMode.cs @@ -72,14 +72,8 @@ public enum SceneViewEditMode GridBox, GridSelect, GridMove, - ParticleSystemCollisionModulePlanesMove, - ParticleSystemCollisionModulePlanesRotate, LineRendererEdit, LineRendererCreate, - ParticleSystemShapeModuleGizmo, - ParticleSystemShapeModulePosition, - ParticleSystemShapeModuleRotation, - ParticleSystemShapeModuleScale } public static bool IsOwner(Editor editor) diff --git a/Editor/Mono/Inspector/EditablePathTool.cs b/Editor/Mono/Inspector/EditablePathTool.cs index f1b9978fd7..76ab3fcea2 100644 --- a/Editor/Mono/Inspector/EditablePathTool.cs +++ b/Editor/Mono/Inspector/EditablePathTool.cs @@ -44,16 +44,21 @@ abstract class EditablePathTool : EditorTool void OnEnable() { CacheColliderData(); - Undo.undoRedoPerformed += CacheColliderData; + Undo.undoRedoEvent += OnUndoRedo; Selection.selectionChanged += CacheColliderData; } void OnDisable() { - Undo.undoRedoPerformed -= CacheColliderData; + Undo.undoRedoEvent -= OnUndoRedo; Selection.selectionChanged -= CacheColliderData; } + void OnUndoRedo(in UndoRedoInfo info) + { + CacheColliderData(); + } + void CacheColliderData() { m_EditablePaths.Clear(); diff --git a/Editor/Mono/Inspector/Editor.cs b/Editor/Mono/Inspector/Editor.cs index 7164f7992f..80106b06bf 100644 --- a/Editor/Mono/Inspector/Editor.cs +++ b/Editor/Mono/Inspector/Editor.cs @@ -11,6 +11,7 @@ using UnityEngine; using UnityEngine.Internal; using UnityEngine.Rendering; +using UnityEngine.Scripting; using UnityEngine.UIElements; using Component = UnityEngine.Component; using UnityObject = UnityEngine.Object; @@ -384,7 +385,6 @@ internal InspectorMode inspectorMode } } - internal static float kLineHeight = EditorGUI.kSingleLineHeight; internal bool hideInspector = false; @@ -398,6 +398,45 @@ internal InspectorMode inspectorMode internal static bool m_AllowMultiObjectAccess = true; + bool m_HasUnsavedChanges = false; + + [UsedByNativeCode] + private bool GetHasUnsavedChanges() + { + return hasUnsavedChanges; + } + + public bool hasUnsavedChanges + { + get + { + return m_HasUnsavedChanges; + } + protected set + { + if (m_HasUnsavedChanges != value) + { + m_HasUnsavedChanges = value; + if (propertyViewer != null) + { + propertyViewer.UnsavedChangesStateChanged(this, value); + } + } + } + } + + public string saveChangesMessage { get; protected set; } + + public virtual void SaveChanges() + { + hasUnsavedChanges = false; + } + + public virtual void DiscardChanges() + { + hasUnsavedChanges = false; + } + // used internally to know if this the first editor in the inspector window internal bool firstInspectedEditor { get; set; } @@ -674,7 +713,7 @@ internal static void AssignCachedProperties(T self, SerializedProperty root) } } - internal virtual void InternalSetTargets(UnityObject[] t) { m_Targets = t; } + internal void InternalSetTargets(UnityObject[] t) { m_Targets = t; } internal void InternalSetHidden(bool hidden) { hideInspector = hidden; } internal void InternalSetContextObject(UnityObject context) { m_Context = context; } @@ -915,15 +954,29 @@ internal virtual void OnHeaderControlsGUI() internal void ShowOpenButton(UnityObject[] assets, bool enableCondition = true) { - enableCondition &= (CanOpenMultipleObjects() || assets.Length == 1); + int assetCount = assets != null? assets.Length : 0; + enableCondition &= (CanOpenMultipleObjects() || assetCount == 1); bool previousGUIState = GUI.enabled; GUI.enabled = enableCondition; if (GUILayout.Button(BaseStyles.open, EditorStyles.miniButton)) { - if (!ShouldTryToMakeEditableOnOpen() || AssetDatabase.MakeEditable( - assets.Select(AssetDatabase.GetAssetPath).ToArray(), - "Do you want to check out this file or files?")) + bool openAssets = false; + + // 'Check Out and Open' dialog + openAssets = AssetDatabase.MakeEditable(assets.Select(AssetDatabase.GetAssetPath).ToArray(), + "Do you want to check out " + + (assetCount > 1 ? String.Format("these {0} files?", assetCount) : "this file?")); + + // 'Open multiple assets' dialog + if (openAssets && assetCount > 1) + { + openAssets = (EditorUtility.DisplayDialog("Open Selected Assets?", + String.Format("Are you sure you want to open {0} selected assets?", assetCount), "Open", + "Cancel")); + } + + if (openAssets) { AssetDatabase.OpenAsset(assets); GUIUtility.ExitGUI(); @@ -1062,6 +1115,11 @@ internal static Rect DrawHeaderGUI(Editor editor, string header, float leftMargi else titleRect = new Rect(r.x + kImageSectionWidth, r.y + 6, r.width - kImageSectionWidth, titleHeight); + if (editor && editor.hasUnsavedChanges && !string.IsNullOrEmpty(header)) + { + header += " *"; + } + // Title if (editor) editor.OnHeaderTitleGUI(titleRect, header); @@ -1088,7 +1146,7 @@ internal static Rect DrawHeaderGUI(Editor editor, string header, float leftMargi internal static void CheckForMainObjectNameMismatch(Editor editor) { - if (editor && editor.target && AssetDatabase.IsNativeAsset(editor.target) && AssetDatabase.IsMainAsset(editor.target)) + if (editor && editor.target && AssetDatabase.IsNativeAsset(editor.target) && AssetDatabase.IsMainAsset(editor.target) && !Unsupported.GetSerializedAssetInterfaceSingleton(editor.target.name)) { var mainObjectName = editor.target.name; var fileName = FileUtil.GetLastPathNameComponent(AssetDatabase.GetAssetPath(editor.target)); diff --git a/Editor/Mono/Inspector/EditorDragging.cs b/Editor/Mono/Inspector/EditorDragging.cs index 5ea1884e13..a8f8d4f572 100644 --- a/Editor/Mono/Inspector/EditorDragging.cs +++ b/Editor/Mono/Inspector/EditorDragging.cs @@ -97,7 +97,7 @@ void HandleNativeDragDropInBottomArea(Editor[] editors, Rect rect) return; } - DragAndDrop.visualMode = DragAndDrop.Drop(DragAndDropWindowTarget.inspector, editor.targets, Event.current.type == EventType.DragPerform); + DragAndDrop.visualMode = DragAndDrop.DropOnInspectorWindow(editor.targets, Event.current.type == EventType.DragPerform); if (Event.current.type == EventType.DragPerform) { diff --git a/Editor/Mono/Inspector/EditorElementUpdater.cs b/Editor/Mono/Inspector/EditorElementUpdater.cs index 10cbd88e3d..488371110c 100644 --- a/Editor/Mono/Inspector/EditorElementUpdater.cs +++ b/Editor/Mono/Inspector/EditorElementUpdater.cs @@ -65,8 +65,8 @@ public void CreateMinimumInspectorElementsWithoutLayout(int count) /// The number of elements to create. public void CreateInspectorElementsWithoutLayout(int count) { - for (var i = 0; m_Index < m_EditorElements.Count && i < count; i++, m_Index++) - m_EditorElements[m_Index].CreateInspectorElement(); + for (var i = 0; m_Index < m_EditorElements.Count && i < count; i++) + m_EditorElements[m_Index++].CreateInspectorElement(); } /// diff --git a/Editor/Mono/Inspector/EditorSettingsInspector.cs b/Editor/Mono/Inspector/EditorSettingsInspector.cs index ea4006544c..85e7c1c8bb 100644 --- a/Editor/Mono/Inspector/EditorSettingsInspector.cs +++ b/Editor/Mono/Inspector/EditorSettingsInspector.cs @@ -92,6 +92,9 @@ class Content public static readonly GUIContent numberingScheme = EditorGUIUtility.TrTextContent("Numbering Scheme"); + public static readonly GUIContent inspectorSettings = EditorGUIUtility.TrTextContent("Inspector"); + public static readonly GUIContent inspectorUseIMGUIDefaultInspector = EditorGUIUtility.TrTextContent("Use IMGUI Default Inspector", "Revert to using IMGUI to generate Default Inspectors where no custom Inspector/Editor was defined."); + public static readonly GUIContent[] numberingSchemeNames = { EditorGUIUtility.TrTextContent("Prefab (1)", "Number in parentheses"), @@ -268,6 +271,7 @@ public PopupElement(string id, string content) SerializedProperty m_ProjectGenerationIncludedExtensions; SerializedProperty m_ProjectGenerationRootNamespace; SerializedProperty m_CacheServerValidationMode; + SerializedProperty m_InspectorUseIMGUIDefaultInspector; bool m_IsGlobalSettings; @@ -361,6 +365,9 @@ public void OnEnable() m_CacheServerConnectionState = CacheServerConnectionState.Unknown; + m_InspectorUseIMGUIDefaultInspector = serializedObject.FindProperty("m_InspectorUseIMGUIDefaultInspector"); + Assert.IsNotNull(m_InspectorUseIMGUIDefaultInspector); + m_IsGlobalSettings = EditorSettings.GetEditorSettings() == target; } @@ -570,6 +577,7 @@ public override void OnInspectorGUI() DoShaderCompilationSettings(); DoEnterPlayModeSettings(); DoNumberingSchemeSettings(); + DoEnterInspectorSettings(); serializedObject.ApplyModifiedProperties(); if (compressorsChanged) @@ -658,7 +666,11 @@ private void DoAssetPipelineSettings() if (parallelImportEnabledOld != parallelImportEnabledNew) EditorSettings.refreshImportMode = parallelImportEnabledNew ? AssetDatabase.RefreshImportMode.OutOfProcessPerQueue : AssetDatabase.RefreshImportMode.InProcess; if (GUILayout.Button(Content.parallelImportLearnMore, EditorStyles.linkLabel)) - Application.OpenURL("https://docs.unity3d.com/Manual/ParallelImport.html"); + { + // Known issue with Docs redirect - versioned pages might not open offline docs + var help = Help.FindHelpNamed("ParallelImport"); + Application.OpenURL(help); + } GUILayout.EndHorizontal(); } @@ -731,7 +743,9 @@ private void DoCacheServerSettings() if (GUILayout.Button(Content.cacheServerLearnMore, EditorStyles.linkLabel)) { - Application.OpenURL("https://docs.unity3d.com/Manual/UnityAccelerator.html#UsingWithAssetPipeline"); + // Known issue with Docs redirect - versioned pages might not open offline docs + var help = Help.FindHelpNamed("UnityAccelerator"); + Application.OpenURL(help); } GUILayout.EndHorizontal(); @@ -952,6 +966,33 @@ private void DoEnterPlayModeSettings() EditorGUI.indentLevel--; } + private void DoEnterInspectorSettings() + { + GUILayout.Space(10); + GUILayout.Label(Content.inspectorSettings, EditorStyles.boldLabel); + + EditorGUI.BeginChangeCheck(); + EditorGUILayout.PropertyField(m_InspectorUseIMGUIDefaultInspector, Content.inspectorUseIMGUIDefaultInspector); + 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; + } + } + + 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++) @@ -1044,6 +1085,13 @@ internal static void DoPopup(Rect popupRect, PopupElement[] elements, int select private void SetAssetSerializationMode(object data) { int popupIndex = (int)data; + + if (m_SerializationMode.intValue == popupIndex) return; + + if (!EditorUtility.DisplayDialog("Change Asset Serialization Mode?", + "Changing the serialization method for assets may force a reimport of some or all assets immediately in the project.\n\nAre you sure you wish to change the asset serialization mode?", + "Yes", "No")) return; + m_SerializationMode.intValue = popupIndex; if (m_IsGlobalSettings) EditorSettings.serializationMode = (SerializationMode)popupIndex; diff --git a/Editor/Mono/Inspector/Effector2DEditor.cs b/Editor/Mono/Inspector/Effector2DEditor.cs index 47e495d96e..6c1446287b 100644 --- a/Editor/Mono/Inspector/Effector2DEditor.cs +++ b/Editor/Mono/Inspector/Effector2DEditor.cs @@ -85,6 +85,12 @@ public static void CheckEffectorWarnings(Collider2D collider) return; } + // If there is an effector (we are using it here) and it's using its collider-mask and we're using overrides then we need to highlight that they are not used here. + if (effector != null && effector.useColliderMask && (collider.includeLayers != 0 || collider.excludeLayers != 0)) + { + EditorGUILayout.HelpBox("This collider has Layer Overrides set but these are not used because the '" + effector.GetType().Name + "' component is using its Collider Mask option.", MessageType.Warning); + } + // Handle collision/trigger effector preferences. if (effector.designedForNonTrigger && collider.isTrigger) { diff --git a/Editor/Mono/Inspector/Enlighten/LightmapParameters.cs b/Editor/Mono/Inspector/Enlighten/LightmapParameters.cs index 656728fe2c..28b31cd483 100644 --- a/Editor/Mono/Inspector/Enlighten/LightmapParameters.cs +++ b/Editor/Mono/Inspector/Enlighten/LightmapParameters.cs @@ -91,7 +91,8 @@ public override void OnInspectorGUI() } EditorGUILayout.PropertyField(m_AntiAliasingSamples, Styles.antiAliasingSamplesContent); - EditorGUILayout.Slider(m_Pushoff, 0.0f, 1.0f, Styles.pushoffContent); + 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)) { diff --git a/Editor/Mono/Inspector/GameObjectInspector.cs b/Editor/Mono/Inspector/GameObjectInspector.cs index 2db1c3fcb8..360e979cc9 100644 --- a/Editor/Mono/Inspector/GameObjectInspector.cs +++ b/Editor/Mono/Inspector/GameObjectInspector.cs @@ -130,6 +130,9 @@ bool IsPrefabFileTooLargeForInteractivePreview(UnityObject prefabObject) var artifactKey = new ArtifactKey(new GUID(guidString)); var artifactID = AssetDatabaseExperimental.LookupArtifact(artifactKey); + // The artifactID can be invalid if we are in the middle of an AssetDatabase.Refresh. + if (!artifactID.isValid) + return false; AssetDatabaseExperimental.GetArtifactPaths(artifactID, out var paths); if (paths.Length != 1) { diff --git a/Editor/Mono/Inspector/GenericInspector.cs b/Editor/Mono/Inspector/GenericInspector.cs index 263dd5117e..debe3050f8 100644 --- a/Editor/Mono/Inspector/GenericInspector.cs +++ b/Editor/Mono/Inspector/GenericInspector.cs @@ -125,6 +125,12 @@ internal override bool OnOptimizedInspectorGUI(Rect contentRect) { while (property.NextVisible(childrenAreExpanded)) { + if (GUI.isInsideList && property.depth <= EditorGUI.GetInsideListDepth()) + EditorGUI.EndIsInsideList(); + + if (property.isArray) + EditorGUI.BeginIsInsideList(property.depth); + var handler = ScriptAttributeUtility.GetHandler(property); var hasPropertyDrawer = handler.propertyDrawer != null; childrenAreExpanded = !hasPropertyDrawer && property.isExpanded && EditorGUI.HasVisibleChildFields(property); diff --git a/Editor/Mono/Inspector/GraphicsSettingsInspector.cs b/Editor/Mono/Inspector/GraphicsSettingsInspector.cs index 7cb08bb622..8ba091cbc9 100644 --- a/Editor/Mono/Inspector/GraphicsSettingsInspector.cs +++ b/Editor/Mono/Inspector/GraphicsSettingsInspector.cs @@ -29,6 +29,17 @@ internal class Styles public static readonly GUIContent shaderStrippingSettings = EditorGUIUtility.TrTextContent("Shader Stripping"); public static readonly GUIContent shaderPreloadSettings = EditorGUIUtility.TrTextContent("Shader Loading"); public static readonly GUIContent logWhenShaderIsCompiled = EditorGUIUtility.TrTextContent("Log Shader Compilation", "When enabled, the player will print shader information each time a shader is being compiled (development and debug mode only)."); + public static readonly GUIContent lightProbeOutsideHullStrategy = EditorGUIUtility.TrTextContent("Renderer Light Probe Selection", "Finding the Light Probes closest to a Renderer positioned outside of the tetrahedral Light Probe hull can be very expensive in terms of CPU cycles. Use this option to configure if Unity should spend time searching the hull to find the closest probe, or if it should use the global Ambient Probe instead."); + public static readonly int[] lightProbeOutsideHullStrategyValues = + { + (int)LightProbeOutsideHullStrategy.kLightProbeSearchTetrahedralHull, + (int)LightProbeOutsideHullStrategy.kLightProbeUseAmbientProbe + }; + public static readonly GUIContent[] lightProbeOutsideHullStrategyStrings = + { + EditorGUIUtility.TrTextContent("Find closest Light Probe"), + EditorGUIUtility.TrTextContent("Use Ambient Probe"), + }; 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"); @@ -44,6 +55,7 @@ internal class Styles SerializedProperty m_TransparencySortAxis; SerializedProperty m_ScriptableRenderLoop; SerializedProperty m_LogWhenShaderIsCompiled; + SerializedProperty m_LightProbeOutsideHullStrategy; Object graphicsSettings { @@ -86,6 +98,7 @@ public void OnEnable() m_TransparencySortAxis = serializedObject.FindProperty("m_TransparencySortAxis"); m_ScriptableRenderLoop = serializedObject.FindProperty("m_CustomRenderPipeline"); m_LogWhenShaderIsCompiled = serializedObject.FindProperty("m_LogWhenShaderIsCompiled"); + m_LightProbeOutsideHullStrategy = serializedObject.FindProperty("m_LightProbeOutsideHullStrategy"); tierSettingsAnimator = new AnimatedValues.AnimBool(showTierSettingsUI, Repaint); } @@ -137,7 +150,7 @@ public override void OnInspectorGUI() serializedObject.Update(); GUILayout.Label(Styles.renderPipeSettings, EditorStyles.boldLabel); - RenderPipelineAssetSelector.Draw(serializedObject, m_ScriptableRenderLoop); + EditorGUI.RenderPipelineAssetField(serializedObject, m_ScriptableRenderLoop); EditorGUILayout.Space(); bool usingSRP = GraphicsSettings.currentRenderPipeline != null; @@ -182,6 +195,8 @@ public override void OnInspectorGUI() EditorGUILayout.Space(); GUILayout.Label(Styles.shaderPreloadSettings, EditorStyles.boldLabel); EditorGUILayout.PropertyField(m_LogWhenShaderIsCompiled, Styles.logWhenShaderIsCompiled); + EditorGUILayout.IntPopup(m_LightProbeOutsideHullStrategy, Styles.lightProbeOutsideHullStrategyStrings, Styles.lightProbeOutsideHullStrategyValues, Styles.lightProbeOutsideHullStrategy); + shaderPreloadEditor.OnInspectorGUI(); serializedObject.ApplyModifiedProperties(); diff --git a/Editor/Mono/Inspector/InspectorWindow.cs b/Editor/Mono/Inspector/InspectorWindow.cs index 08ab597219..11511ce87f 100644 --- a/Editor/Mono/Inspector/InspectorWindow.cs +++ b/Editor/Mono/Inspector/InspectorWindow.cs @@ -436,12 +436,8 @@ internal static void ApplyChanges() { foreach (var editor in inspector.tracker.activeEditors) { - AssetImporterEditor assetImporterEditor = editor as AssetImporterEditor; - - if (assetImporterEditor != null && assetImporterEditor.HasModified()) - { - assetImporterEditor.ApplyAndImport(); - } + if(editor.hasUnsavedChanges) + editor.SaveChanges(); } } } diff --git a/Editor/Mono/Inspector/Joint2DEditor.cs b/Editor/Mono/Inspector/Joint2DEditor.cs index 891fce133d..6af62b42cc 100644 --- a/Editor/Mono/Inspector/Joint2DEditor.cs +++ b/Editor/Mono/Inspector/Joint2DEditor.cs @@ -11,11 +11,13 @@ namespace UnityEditor [CanEditMultipleObjects] internal class Joint2DEditor : Editor { + SerializedProperty m_BreakAction; SerializedProperty m_BreakForce; SerializedProperty m_BreakTorque; public void OnEnable() { + m_BreakAction = serializedObject.FindProperty("m_BreakAction"); m_BreakForce = serializedObject.FindProperty("m_BreakForce"); m_BreakTorque = serializedObject.FindProperty("m_BreakTorque"); } @@ -36,6 +38,7 @@ public override void OnInspectorGUI() base.OnInspectorGUI(); + EditorGUILayout.PropertyField(m_BreakAction); EditorGUILayout.PropertyField(m_BreakForce); // Distance/Spring/Target joints produce no reaction torque so they're not supported. diff --git a/Editor/Mono/Inspector/LODGroupEditor.cs b/Editor/Mono/Inspector/LODGroupEditor.cs index 9ec02df3fd..88700e4f67 100644 --- a/Editor/Mono/Inspector/LODGroupEditor.cs +++ b/Editor/Mono/Inspector/LODGroupEditor.cs @@ -62,7 +62,7 @@ void InitAndSetFoldoutLabelTextures() } } - void OnUndoRedoPerformed() + void OnUndoRedoPerformed(in UndoRedoInfo info) { if (target == null || serializedObject == null) return; @@ -109,7 +109,7 @@ void OnEnable() m_TargetTransform = (target as LODGroup)?.gameObject?.transform; - Undo.undoRedoPerformed += OnUndoRedoPerformed; + Undo.undoRedoEvent += OnUndoRedoPerformed; Repaint(); } @@ -175,7 +175,7 @@ protected virtual void AddLODRendererMeshToList(ReorderableList list) void OnDisable() { EditorApplication.update -= Update; - Undo.undoRedoPerformed -= OnUndoRedoPerformed; + Undo.undoRedoEvent -= OnUndoRedoPerformed; m_ShowAnimateCrossFading.valueChanged.RemoveListener(Repaint); m_ShowFadeTransitionWidth.valueChanged.RemoveListener(Repaint); diff --git a/Editor/Mono/Inspector/LightEditor.cs b/Editor/Mono/Inspector/LightEditor.cs index b7c228362b..a6f8393b8a 100644 --- a/Editor/Mono/Inspector/LightEditor.cs +++ b/Editor/Mono/Inspector/LightEditor.cs @@ -487,48 +487,26 @@ public void DrawBounceIntensity() } } - Object TextureValidator(Object[] references, System.Type objType, SerializedProperty property, EditorGUI.ObjectFieldValidatorOptions options) + static Object TextureValidator(Object[] references, System.Type objType, SerializedProperty property, EditorGUI.ObjectFieldValidatorOptions options) { - LightType lightTypeInfo = (LightType)lightType.intValue; - Object assetTexture = null; - foreach (Object assetObject in references) + // Accept RenderTextures of correct dimension + Texture validated = (RenderTexture)EditorGUI.ValidateObjectFieldAssignment(references, typeof(RenderTexture), property, options); + if (validated != null) { - if (assetObject is Texture texture) - { - if (assetTexture == null) - { - assetTexture = texture; - } - - switch (lightTypeInfo) - { - case LightType.Spot: - case LightType.Directional: - if (texture is Texture2D) - return assetObject; - break; - - case LightType.Point: - if (texture is Cubemap) - return assetObject; - break; - - default: - if (texture is Texture) - return assetObject; - break; - } - } - } - - if (assetTexture != null && typeof(RenderTexture).IsAssignableFrom(assetTexture.GetType())) - { - return assetTexture; + if (objType == typeof(Texture2D) && validated.dimension != TextureDimension.Tex2D) + validated = null; + else if (objType == typeof(Texture3D) && validated.dimension != TextureDimension.Tex3D) + validated = null; + else if (objType == typeof(Cubemap) && validated.dimension != TextureDimension.Cube) + validated = null; } - return null; + // Accept regular textures + if (validated == null) + validated = (Texture)EditorGUI.ValidateObjectFieldAssignment(references, objType, property, options); + return validated; } - void TexturePropertyBody(Rect position, SerializedProperty prop, LightType cookieLightType) + static void TexturePropertyBody(Rect position, SerializedProperty prop, LightType cookieLightType) { EditorGUI.BeginChangeCheck(); int controlID = GUIUtility.GetControlID(12354, FocusType.Keyboard, position); @@ -538,6 +516,8 @@ void TexturePropertyBody(Rect position, SerializedProperty prop, LightType cooki { case LightType.Spot: case LightType.Directional: + case LightType.Rectangle: + case LightType.Disc: type = typeof(Texture2D); break; diff --git a/Editor/Mono/Inspector/LightProbeGroupInspector.cs b/Editor/Mono/Inspector/LightProbeGroupInspector.cs index 4668ed30c7..a32bbffc56 100644 --- a/Editor/Mono/Inspector/LightProbeGroupInspector.cs +++ b/Editor/Mono/Inspector/LightProbeGroupInspector.cs @@ -36,8 +36,8 @@ internal class LightProbeGroupEditor : IEditablePoint public LightProbeGroupEditor(LightProbeGroup group) { m_Group = group; - m_ShouldRecalculateTetrahedra = false; - m_SourcePositionsDirty = false; + m_ShouldRecalculateTetrahedra = true; + m_SourcePositionsDirty = true; m_SerializedSelectedProbes = ScriptableObject.CreateInstance(); m_SerializedSelectedProbes.hideFlags = HideFlags.HideAndDontSave; } @@ -543,7 +543,7 @@ public void OnEnable() m_Editor.drawTetrahedra = new SavedBool($"{target.GetType()}.drawTetrahedra", true); - Undo.undoRedoPerformed += UndoRedoPerformed; + Undo.undoRedoEvent += UndoRedoPerformed; EditMode.editModeStarted += OnEditModeStarted; EditMode.editModeEnded += OnEditModeEnded; } @@ -595,7 +595,7 @@ private void EndEditProbes() public void OnDisable() { EndEditProbes(); - Undo.undoRedoPerformed -= UndoRedoPerformed; + Undo.undoRedoEvent-= UndoRedoPerformed; EditMode.editModeStarted -= OnEditModeStarted; EditMode.editModeEnded -= OnEditModeEnded; @@ -606,7 +606,7 @@ public void OnDisable() } } - private void UndoRedoPerformed() + private void UndoRedoPerformed(in UndoRedoInfo info) { // Update the cached probe positions from the ones just restored in the LightProbeGroup m_Editor.PullProbePositions(); diff --git a/Editor/Mono/Inspector/LightingSettingsEditor.cs b/Editor/Mono/Inspector/LightingSettingsEditor.cs index 3cf49a5bf7..47a2fce421 100644 --- a/Editor/Mono/Inspector/LightingSettingsEditor.cs +++ b/Editor/Mono/Inspector/LightingSettingsEditor.cs @@ -164,13 +164,6 @@ static class Styles EditorGUIUtility.TrTextContent("None") }; - public static readonly GUIContent[] helpStringsMixed = - { - EditorGUIUtility.TrTextContent("Mixed lights provide realtime direct lighting while indirect light is baked into lightmaps and light probes."), - EditorGUIUtility.TrTextContent("Mixed lights provide baked direct and indirect lighting for static objects. Dynamic objects receive realtime direct lighting and cast shadows on static objects using the main directional light in the scene."), - EditorGUIUtility.TrTextContent("Mixed lights provide realtime direct lighting. Indirect lighting gets baked into lightmaps and light probes. Shadowmasks and light probes occlusion get generated for baked shadows. ") - }; - public static readonly int[] lightmapCompressionValues = { (int)LightmapCompression.None, @@ -226,7 +219,7 @@ static class Styles public static readonly GUIContent finalGather = EditorGUIUtility.TrTextContent("Final Gather", "Specifies whether the final light bounce of the global illumination calculation is calculated at the same resolution as the baked lightmap. When enabled, visual quality is improved at the cost of additional time required to bake the lighting."); public static readonly GUIContent finalGatherRayCount = EditorGUIUtility.TrTextContent("Ray Count", "Controls the number of rays emitted for every final gather point."); public static readonly GUIContent finalGatherFiltering = EditorGUIUtility.TrTextContent("Denoising", "Controls whether a denoising filter is applied to the final gather output."); - public static readonly GUIContent mixedLightMode = EditorGUIUtility.TrTextContent("Lighting Mode", "Specifies which Scene lighting mode will be used for all Mixed lights in the Scene. Options are Baked Indirect, Shadowmask and Subtractive."); + public static readonly GUIContent mixedLightMode = EditorGUIUtility.TrTextContent("Lighting Mode", "Specifies the lighting mode of all Mixed lights in the Scene."); public static readonly GUIContent useRealtimeGI = EditorGUIUtility.TrTextContent("Realtime Global Illumination", "Precomputed Realtime Global Illumination using Enlighten. Provides diffuse Realtime Global Illumination for static geometry via low resolution lightmaps and via Light Probes for dynamic geometry."); public static readonly GUIContent bakedGIDisabledInfo = EditorGUIUtility.TrTextContent("All Baked and Mixed lights in the Scene are currently being overridden to Realtime light modes. Enable Baked Global Illumination to allow the use of Baked and Mixed light modes."); public static readonly GUIContent bakeBackend = EditorGUIUtility.TrTextContent("Lightmapper", "Specifies which baking system will be used to generate baked lightmaps."); @@ -253,6 +246,9 @@ static class Styles public static readonly GUIContent environmentImportanceSampling = EditorGUIUtility.TrTextContent("Importance Sampling", "Specifies whether to use importance sampling for sampling environment lighting. In most environments importance sampling facilitates faster convergence while generating lightmaps. In certain low frequency environments, importance sampling can produce noisy results."); public static readonly GUIContent environmentSampleCount = EditorGUIUtility.TrTextContent("Environment Samples", "Controls the number of samples the lightmapper will use for environment lighting calculations. Increasing this value may improve the quality of lightmaps but increases the time required for baking to complete."); public static readonly GUIContent probeSampleCountMultiplier = EditorGUIUtility.TrTextContent("Light Probe Sample Multiplier", "Controls how many samples are used for Light Probes as a multiplier of the general sample counts above. Higher values improve the quality of Light Probes, but also take longer to bake. Enable the Light Probe sample count multiplier by disabling Project Settings > Editor > Use legacy Light Probe sample counts"); + public static readonly GUIContent texelsPerUnit = EditorGUIUtility.TrTextContent(" texels per unit"); + public static readonly GUIContent texels = EditorGUIUtility.TrTextContent(" texels"); + public static readonly GUIContent sigma = EditorGUIUtility.TrTextContent(" sigma"); public static readonly GUIStyle labelStyle = EditorStyles.wordWrappedMiniLabel; } @@ -415,7 +411,7 @@ void RealtimeLightingGUI(bool compact) using (new EditorGUI.DisabledScope((m_BakeBackend.intValue != (int)LightingSettings.Lightmapper.Enlighten) && !enableRealtimeGI)) { - DrawResolutionField(m_RealtimeResolution, Styles.indirectResolution); + DrawPropertyFieldWithPostfixLabel(m_RealtimeResolution, Styles.indirectResolution, Styles.texelsPerUnit); } EditorGUI.indentLevel -= 2; @@ -479,20 +475,10 @@ void MixedLightingGUI(bool compact) } EditorGUI.EndProperty(); - if (mixedGISupported) + if (mixedGISupported && !SupportedRenderingFeatures.IsMixedLightingModeSupported((MixedLightingMode)m_MixedBakeMode.intValue)) { - if (!SupportedRenderingFeatures.IsMixedLightingModeSupported((MixedLightingMode)m_MixedBakeMode.intValue)) - { - string fallbackMode = Styles.mixedModeStrings[(int)SupportedRenderingFeatures.FallbackMixedLightingMode()].text; - EditorGUILayout.HelpBox(Styles.mixedModeNotSupportedWarning.text + fallbackMode, MessageType.Warning); - } - else if (enableBakedGI) - { - if (m_MixedBakeMode.intValue == (int)MixedLightingMode.Shadowmask) - EditorGUILayout.HelpBox(Styles.helpStringsMixed[m_MixedBakeMode.intValue].text + EditorGUIUtility.TrTextContent(SupportedRenderingFeatures.active.shadowmaskMessage).text, MessageType.Info); - else - EditorGUILayout.HelpBox(Styles.helpStringsMixed[m_MixedBakeMode.intValue].text, MessageType.Info); - } + string fallbackMode = Styles.mixedModeStrings[(int)SupportedRenderingFeatures.FallbackMixedLightingMode()].text; + EditorGUILayout.HelpBox(Styles.mixedModeNotSupportedWarning.text + fallbackMode, MessageType.Warning); } } } @@ -569,18 +555,7 @@ void GeneralLightmapSettingsGUI(bool compact) EditorGUILayout.PropertyField(m_LightProbeSampleCountMultiplier, Styles.probeSampleCountMultiplier); } - // Case 1320615: clamping of min bounces can be annoying because it resets when typing in a new max bounce value. - GUILayout.BeginHorizontal(); - EditorGUILayout.PrefixLabel(Styles.bounces); - GUILayout.Space(-28); - int minBouncesValue = EditorGUILayout.DelayedIntField(m_PVRMinBounces.intValue, GUILayout.MaxWidth(80)); - GUILayout.Label("minimum", EditorStyles.miniLabel); - int maxBouncesValue = EditorGUILayout.DelayedIntField(m_PVRBounces.intValue, GUILayout.MaxWidth(80)); - GUILayout.Label("maximum", EditorStyles.miniLabel); - GUILayout.EndHorizontal(); - - m_PVRBounces.intValue = maxBouncesValue; - m_PVRMinBounces.intValue = minBouncesValue; + DrawBouncesField(m_PVRMinBounces, m_PVRBounces); // Filtering EditorGUILayout.PropertyField(m_PVRFilteringMode, Styles.filteringMode); @@ -676,19 +651,16 @@ void GeneralLightmapSettingsGUI(bool compact) // Show the Indirect Resolution field if the user is using Enlighten baked and the backend is supported. if (bakedGISupported && (m_BakeBackend.intValue == (int)LightingSettings.Lightmapper.Enlighten) && lightmapperSupported) { - DrawResolutionField(m_RealtimeResolution, Styles.indirectResolution); + DrawPropertyFieldWithPostfixLabel(m_RealtimeResolution, Styles.indirectResolution, Styles.texelsPerUnit); } if (bakedGISupported) { using (new EditorGUI.DisabledScope(!enableBakedGI)) { - DrawResolutionField(m_BakeResolution, Styles.lightmapResolution); + DrawPropertyFieldWithPostfixLabel(m_BakeResolution, Styles.lightmapResolution, Styles.texelsPerUnit); - GUILayout.BeginHorizontal(); - EditorGUILayout.PropertyField(m_Padding, Styles.padding); - GUILayout.Label(" texels", Styles.labelStyle); - GUILayout.EndHorizontal(); + DrawPropertyFieldWithPostfixLabel(m_Padding, Styles.padding, Styles.texels); EditorGUILayout.IntPopup(m_LightmapMaxSize, Styles.lightmapMaxSizeStrings, Styles.lightmapMaxSizeValues, Styles.lightmapMaxSize); @@ -792,13 +764,68 @@ static bool PlayerHasSM20Support() return hasSM20Api; } - static void DrawResolutionField(SerializedProperty resolution, GUIContent label) + static void DrawBouncesField(SerializedProperty minimumProperty, SerializedProperty maximumProperty) { - GUILayout.BeginHorizontal(); - EditorGUILayout.PropertyField(resolution, label); + Rect bounceLabelRect = GUILayoutUtility.GetRect( + EditorGUILayout.kLabelFloatMinW, + EditorGUILayout.kLabelFloatMaxW, + EditorGUI.kSingleLineHeight, + EditorGUI.kSingleLineHeight, + EditorStyles.numberField); + + Rect remainingRect = EditorGUI.PrefixLabel(bounceLabelRect, Styles.bounces); + + float subfieldWidth = remainingRect.width * 0.5f; + + Rect inputRect = remainingRect; + Rect labelRect = remainingRect; + + const float postfixLabelWidth = 53.0f; + const float minimumInputWidth = 30.0f; + + inputRect.width = Mathf.Max(subfieldWidth - postfixLabelWidth, minimumInputWidth); + labelRect.x = labelRect.x + inputRect.width; + + int indent = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + + minimumProperty.intValue = EditorGUI.DelayedIntField(inputRect, minimumProperty.intValue); + EditorGUI.LabelField(labelRect, GUIContent.Temp(" minimum"), EditorStyles.miniLabel); + + inputRect.x += inputRect.width + postfixLabelWidth; + labelRect.x += inputRect.width + postfixLabelWidth; + + maximumProperty.intValue = EditorGUI.DelayedIntField(inputRect, maximumProperty.intValue); + EditorGUI.LabelField(labelRect, GUIContent.Temp(" maximum"), EditorStyles.miniLabel); - GUILayout.Label(" texels per unit", Styles.labelStyle); - GUILayout.EndHorizontal(); + EditorGUI.indentLevel = indent; + } + + static void DrawPropertyFieldWithPostfixLabel(SerializedProperty property, GUIContent label, GUIContent postfixLabel) + { + 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; + } } static void DrawFilterSettingField(SerializedProperty gaussSetting, @@ -807,23 +834,52 @@ static void DrawFilterSettingField(SerializedProperty gaussSetting, GUIContent atrousLabel, LightingSettings.FilterType type) { - if (type == LightingSettings.FilterType.None) - return; + const float minimumWidth = 230.0f; + const float postfixLabelWidth = 40.0f; - GUILayout.BeginHorizontal(); - - if (type == LightingSettings.FilterType.Gaussian) + switch(type) { - EditorGUILayout.IntSlider(gaussSetting, 0, 5, gaussLabel); - GUILayout.Label(" texels", Styles.labelStyle); - } - else if (type == LightingSettings.FilterType.ATrous) - { - EditorGUILayout.Slider(atrousSetting, 0.0f, 2.0f, atrousLabel); - GUILayout.Label(" sigma", Styles.labelStyle); + case LightingSettings.FilterType.Gaussian: + DrawFieldWithPostfixLabel( + (Rect propertyRect) => { EditorGUI.IntSlider(propertyRect, gaussSetting, 0, 5, gaussLabel); }, + Styles.texels, + EditorStyles.toolbarSlider, + minimumWidth, + postfixLabelWidth); + break; + + case LightingSettings.FilterType.ATrous: + DrawFieldWithPostfixLabel( + (Rect propertyRect) => { EditorGUI.Slider(propertyRect, atrousSetting, 0.0f, 2.0f, atrousLabel); }, + Styles.sigma, + EditorStyles.toolbarSlider, + minimumWidth, + postfixLabelWidth); + break; } + } + + static void DrawFieldWithPostfixLabel(Action drawFieldLambda, GUIContent postfixLabel, GUIStyle style, float minWidth, float postfixLabelWidth) + { + Rect propertyRect = GUILayoutUtility.GetRect( + EditorGUILayout.kLabelFloatMinW, + EditorGUILayout.kLabelFloatMaxW, + EditorGUI.kSingleLineHeight, + EditorGUI.kSingleLineHeight, + style); + + propertyRect.width = Mathf.Max(propertyRect.width - postfixLabelWidth, minWidth); + + drawFieldLambda(propertyRect); + + int indent = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + + Rect labelRect = propertyRect; + labelRect.x += propertyRect.width; + EditorGUI.LabelField(labelRect, postfixLabel, Styles.labelStyle); - GUILayout.EndHorizontal(); + EditorGUI.indentLevel = indent; } static bool isBuiltIn(SerializedProperty prop) diff --git a/Editor/Mono/Inspector/LineRendererCurveEditor.cs b/Editor/Mono/Inspector/LineRendererCurveEditor.cs index 84a4b50193..203f2e2bc0 100644 --- a/Editor/Mono/Inspector/LineRendererCurveEditor.cs +++ b/Editor/Mono/Inspector/LineRendererCurveEditor.cs @@ -55,13 +55,13 @@ public void OnEnable(SerializedObject serializedObject) m_Editor.SetShownVRangeInsideMargins(0.0f, 1.0f); m_Editor.ignoreScrollWheelUntilClicked = true; - Undo.undoRedoPerformed += UndoRedoPerformed; + Undo.undoRedoEvent += UndoRedoPerformed; } public void OnDisable() { m_Editor.OnDisable(); - Undo.undoRedoPerformed -= UndoRedoPerformed; + Undo.undoRedoEvent -= UndoRedoPerformed; } private CurveWrapper GetCurveWrapper(AnimationCurve curve) @@ -93,7 +93,7 @@ public void SetAxisScalars(Vector2 scalars) m_Refresh = true; } - private void UndoRedoPerformed() + private void UndoRedoPerformed(in UndoRedoInfo info) { m_Refresh = true; } diff --git a/Editor/Mono/Inspector/LineRendererEditor.cs b/Editor/Mono/Inspector/LineRendererEditor.cs index 49990d4564..454a101e04 100644 --- a/Editor/Mono/Inspector/LineRendererEditor.cs +++ b/Editor/Mono/Inspector/LineRendererEditor.cs @@ -5,10 +5,9 @@ using System.Collections.Generic; using System.Linq; using UnityEditor.AnimatedValues; +using UnityEditor.EditorTools; using UnityEditor.IMGUI.Controls; -using UnityEditorInternal; using UnityEngine; -using UnityEngine.Rendering; namespace UnityEditor { @@ -36,33 +35,176 @@ class Styles public static readonly GUIContent textureMode = EditorGUIUtility.TrTextContent("Texture Mode", "Should the U coordinate be stretched or tiled?"); public static readonly GUIContent textureScale = EditorGUIUtility.TrTextContent("Texture Scale", "Scale the texture along the UV coordinates using this multiplier."); public static readonly GUIContent tolerance = EditorGUIUtility.TrTextContent("Tolerance", "Used to evaluate which points should be removed from the line. A higher value results in a simpler line (fewer points). A value of 0 results in the exact same line with little to no reduction."); - public static readonly GUIStyle richTextMiniLabel = new GUIStyle(EditorStyles.miniLabel) { richText = true }; public static readonly GUIContent shadowBias = EditorGUIUtility.TrTextContent("Shadow Bias", "Apply a shadow bias to prevent self-shadowing artifacts. The specified value is the proportion of the line width at each segment."); public static readonly GUIContent generateLightingData = EditorGUIUtility.TrTextContent("Generate Lighting Data", "Toggle generation of normal and tangent data, for use in lit shaders."); public static readonly GUIContent sceneTools = EditorGUIUtility.TrTextContent("Scene Tools"); public static readonly GUIContent applyActiveColorSpace = EditorGUIUtility.TrTextContent("Apply Active Color Space", "When using Linear Rendering, colors will be converted appropriately before being passed to the GPU."); + } + + abstract class LineRendererTool : EditorTool + { + protected LineRendererEditor pointEditor + { + get + { + if (m_PointEditor == null) + m_PointEditor = new LineRendererEditor(target as LineRenderer); + return m_PointEditor; + } + } + + public override void OnActivated() + { + pointEditor.Deselect(); + } + + public override void OnWillBeDeactivated() + { + pointEditor.Deselect(); + } + + public void RemoveInvalidSelections() + { + pointEditor.RemoveInvalidSelections(); + } + + public List pointSelection + { + set => pointEditor.m_Selection = value; + get => pointEditor.m_Selection; + } + + public Bounds selectedPositionsBounds + { + get => pointEditor.selectedPositionsBounds; + } + + public override bool IsAvailable() + { + return !targets.Skip(1).Any(); // TODO - multi-edit disabled for now. Need to make LineRendererEditor support multiple targets, and fix m_IsGameObjectEditable + } + + public abstract void DrawToolbar(SerializedProperty positions); + public virtual void OnSceneGUIDelegate(SerializedProperty positions, LineRendererPositionsView positionsView) { } + + private LineRendererEditor m_PointEditor; + } + + [EditorTool("Edit Points", typeof(LineRenderer))] + class LineRendererEditPointsTool : LineRendererTool + { + public override GUIContent toolbarIcon + { + get { return EditorGUIUtility.TrIconContent("EditCollider", "Edit LineRenderer Points in the Scene View."); } + } + + public override void OnToolGUI(EditorWindow window) + { + pointEditor.EditSceneGUI(window); + } + + public override void DrawToolbar(SerializedProperty positions) + { + EditorGUI.BeginChangeCheck(); + LineRendererEditor.showWireframe = GUILayout.Toggle(LineRendererEditor.showWireframe, Styles.showWireframe); + if (EditorGUI.EndChangeCheck()) + { + SceneView.RepaintAll(); + } + + bool adjacentPointsSelected = HasAdjacentPointsSelected(); + using (new EditorGUI.DisabledGroupScope(!adjacentPointsSelected)) + { + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + if (GUILayout.Button(Styles.subdivide, GUILayout.Width(150))) + { + SubdivideSelected(positions); + } + GUILayout.EndHorizontal(); + } + } - public static readonly GUIContent[] toolContents = + public override void OnSceneGUIDelegate(SerializedProperty positions, LineRendererPositionsView positionsView) { - EditorGUIUtility.IconContent("EditCollider", "|Edit Points in Scene View"), - EditorGUIUtility.IconContent("Toolbar Plus", "|Create Points in Scene View.") - }; + // We need to wait for m_Positions to be updated next frame or we risk calling SetSelection with invalid indexes. + if (pointEditor.Count == positions.arraySize) + { + if (positions.arraySize != positionsView.GetRows().Count) + positionsView.Reload(); + positionsView.SetSelection(pointSelection, TreeViewSelectionOptions.RevealAndFrame); + } + } - public static readonly EditMode.SceneViewEditMode[] sceneViewEditModes = new[] + private bool HasAdjacentPointsSelected() { - EditMode.SceneViewEditMode.LineRendererEdit, - EditMode.SceneViewEditMode.LineRendererCreate - }; + var selection = pointSelection; + selection.Sort(); + if (selection.Count < 2) + return false; + + for (int i = 0; i < selection.Count - 1; ++i) + { + if (selection[i + 1] == selection[i] + 1) + return true; + } + + return false; + } - public static readonly string baseSceneEditingToolText = "Line Renderer Scene Editing Mode: "; - public static readonly GUIContent[] ToolNames = + private void SubdivideSelected(SerializedProperty positions) { - new GUIContent(L10n.Tr(baseSceneEditingToolText + "Edit Points"), ""), - new GUIContent(L10n.Tr(baseSceneEditingToolText + "Create Points"), "") - }; + var selection = pointSelection; + if (selection.Count < 2) + return; + selection.Sort(); + var insertedIndexes = new List(); + int numInserted = 0; // As we insert new nodes, the selected indexes will become offset so we need to keep track of this. + for (int i = 0; i < selection.Count - 1; ++i) + { + if (selection[i + 1] == selection[i] + 1) + { + int fromIndex = selection[i] + numInserted; + int toIndex = selection[i + 1] + numInserted; + var from = positions.GetArrayElementAtIndex(fromIndex).vector3Value; + var to = positions.GetArrayElementAtIndex(toIndex).vector3Value; + var midPoint = Vector3.Lerp(from, to, 0.5f); + positions.InsertArrayElementAtIndex(toIndex); + positions.GetArrayElementAtIndex(toIndex).vector3Value = midPoint; + insertedIndexes.Add(toIndex); + numInserted++; + } + } + + pointSelection = insertedIndexes; + } } - private bool m_EditingPositions; + [EditorTool("Add Points", typeof(LineRenderer))] + class LineRendererAddPointsTool : LineRendererTool + { + public override GUIContent toolbarIcon + { + get { return EditorGUIUtility.TrIconContent("Toolbar Plus", "Create LineRenderer Points in the Scene View."); } + } + + public override void OnToolGUI(EditorWindow window) + { + pointEditor.CreateSceneGUI(); + } + + public override void DrawToolbar(SerializedProperty positions) + { + LineRendererEditor.inputMode = (LineRendererEditor.InputMode)EditorGUILayout.EnumPopup(Styles.inputMode, LineRendererEditor.inputMode); + if (LineRendererEditor.inputMode == LineRendererEditor.InputMode.PhysicsRaycast) + { + LineRendererEditor.raycastMask = EditorGUILayout.LayerMaskField(LineRendererEditor.raycastMask, Styles.layerMask); + } + + LineRendererEditor.createPointSeparation = EditorGUILayout.FloatField(Styles.pointSeparation, LineRendererEditor.createPointSeparation); + LineRendererEditor.creationOffset = EditorGUILayout.FloatField(Styles.normalOffset, LineRendererEditor.creationOffset); + } + } public static float simplifyTolerance { @@ -79,7 +221,6 @@ public static bool showSimplifyPreview private Vector3[] m_PreviewPoints; private LineRendererCurveEditor m_CurveEditor = new LineRendererCurveEditor(); - private LineRendererEditor m_PointEditor; private SerializedProperty m_Alignment; private SerializedProperty m_ColorGradient; @@ -105,14 +246,9 @@ public static bool showSimplifyPreview public static readonly float kPositionsViewMinHeight = 30; - private static bool IsLineRendererEditMode(EditMode.SceneViewEditMode editMode) + private LineRendererTool activeSceneViewTool { - return editMode == EditMode.SceneViewEditMode.LineRendererEdit || editMode == EditMode.SceneViewEditMode.LineRendererCreate; - } - - private bool sceneViewEditing - { - get { return IsLineRendererEditMode(EditMode.editMode) && EditMode.IsOwner(this); } + get { return EditorToolManager.activeTool as LineRendererTool; } } private bool canEditInScene @@ -125,13 +261,9 @@ public override void OnEnable() base.OnEnable(); var lineRenderer = target as LineRenderer; - m_PointEditor = new LineRendererEditor(lineRenderer, this); - m_PointEditor.Deselect(); SceneView.duringSceneGui += OnSceneGUIDelegate; - Undo.undoRedoPerformed += UndoRedoPerformed; - EditMode.onEditModeStartDelegate += EditModeStarted; - EditMode.onEditModeEndDelegate += EditModeEnded; + Undo.undoRedoEvent += UndoRedoPerformed; m_CurveEditor.OnEnable(serializedObject); m_Loop = serializedObject.FindProperty("m_Loop"); @@ -188,81 +320,33 @@ void OnPropertyContextMenu(GenericMenu menu, SerializedProperty property) void PositionsViewSelectionChanged(List selected) { - m_PointEditor.m_Selection = selected; + var sceneTool = activeSceneViewTool; + if (sceneTool != null) + sceneTool.pointSelection = selected; + SceneView.RepaintAll(); } public void OnDisable() { m_CurveEditor.OnDisable(); - EndEditPositions(); - Undo.undoRedoPerformed -= UndoRedoPerformed; + Undo.undoRedoEvent -= UndoRedoPerformed; SceneView.duringSceneGui -= OnSceneGUIDelegate; EditorApplication.contextualPropertyMenu -= OnPropertyContextMenu; } - private void UndoRedoPerformed() + private void UndoRedoPerformed(in UndoRedoInfo info) { - m_PointEditor.RemoveInvalidSelections(); m_PositionsView.Reload(); - m_PositionsView.SetSelection(m_PointEditor.m_Selection); - ResetSimplifyPreview(); - } - - private void EditModeEnded(Editor editor) - { - if (editor == this) - { - EndEditPositions(); - } - } - - private void EditModeStarted(Editor editor, EditMode.SceneViewEditMode mode) - { - if (editor == this && IsLineRendererEditMode(mode)) - { - StartEditPositions(); - } - } - - private void DrawEditPointTools() - { - EditorGUI.BeginChangeCheck(); - LineRendererEditor.showWireframe = GUILayout.Toggle(LineRendererEditor.showWireframe, Styles.showWireframe); - if (EditorGUI.EndChangeCheck()) - { - SceneView.RepaintAll(); - } - - bool adjacentPointsSelected = HasAdjacentPointsSelected(); - using (new EditorGUI.DisabledGroupScope(!adjacentPointsSelected)) - { - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - if (GUILayout.Button(Styles.subdivide, GUILayout.Width(150))) - { - SubdivideSelected(); - } - GUILayout.EndHorizontal(); - } - } - private static void CreatePointTools() - { - LineRendererEditor.inputMode = (LineRendererEditor.InputMode)EditorGUILayout.EnumPopup(Styles.inputMode, LineRendererEditor.inputMode); - if (LineRendererEditor.inputMode == LineRendererEditor.InputMode.PhysicsRaycast) + var sceneTool = activeSceneViewTool; + if (sceneTool != null) { - LineRendererEditor.raycastMask = EditorGUILayout.LayerMaskField(LineRendererEditor.raycastMask, Styles.layerMask); + sceneTool.RemoveInvalidSelections(); + m_PositionsView.SetSelection(sceneTool.pointSelection); } - LineRendererEditor.createPointSeparation = EditorGUILayout.FloatField(Styles.pointSeparation, LineRendererEditor.createPointSeparation); - LineRendererEditor.creationOffset = EditorGUILayout.FloatField(Styles.normalOffset, LineRendererEditor.creationOffset); - } - - Bounds GetBounds() - { - var lineRenderer = (target as LineRenderer); - return m_Positions.arraySize > 0 ? lineRenderer.bounds : new Bounds(lineRenderer.useWorldSpace ? lineRenderer.transform.position : Vector3.zero, Vector3.zero); + ResetSimplifyPreview(); } private void DrawToolbar() @@ -273,43 +357,17 @@ private void DrawToolbar() } EditorGUILayout.BeginVertical("GroupBox"); - GUILayout.Label(Styles.sceneTools); EditorGUI.BeginDisabled(!canEditInScene); - EditorGUILayout.Space(); - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - EditMode.DoInspectorToolbar(Styles.sceneViewEditModes, Styles.toolContents, GetBounds, this); - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - - // Tools box - GUILayout.BeginVertical(EditorStyles.helpBox); - string helpText = Styles.baseSceneEditingToolText; - if (sceneViewEditing) - { - int index = ArrayUtility.IndexOf(Styles.sceneViewEditModes, EditMode.editMode); - if (index >= 0) - helpText = Styles.ToolNames[index].text; - } - - GUILayout.Label(helpText, Styles.richTextMiniLabel); - GUILayout.EndVertical(); + EditorGUILayout.EditorToolbarForTarget(Styles.sceneTools, this); // Editing mode toolbar - if (sceneViewEditing) + var sceneTools = activeSceneViewTool; + if (sceneTools != null) { - switch (EditMode.editMode) - { - case EditMode.SceneViewEditMode.LineRendererEdit: - DrawEditPointTools(); - break; - case EditMode.SceneViewEditMode.LineRendererCreate: - CreatePointTools(); - break; - } + sceneTools.DrawToolbar(m_Positions); } - if (!sceneViewEditing) + else { EditorGUI.BeginChangeCheck(); showSimplifyPreview = EditorGUILayout.Toggle(Styles.simplifyPreview, showSimplifyPreview); @@ -333,49 +391,6 @@ private void DrawToolbar() EditorGUILayout.EndVertical(); } - private bool HasAdjacentPointsSelected() - { - var selection = m_PointEditor.m_Selection; - selection.Sort(); - if (selection.Count < 2) - return false; - - for (int i = 0; i < selection.Count - 1; ++i) - { - if (selection[i + 1] == selection[i] + 1) - return true; - } - - return false; - } - - private void SubdivideSelected() - { - var selection = m_PointEditor.m_Selection; - if (selection.Count < 2) - return; - selection.Sort(); - var insertedIndexes = new List(); - int numInserted = 0; // As we insert new nodes, the selected indexes will become offset so we need to keep track of this. - for (int i = 0; i < selection.Count - 1; ++i) - { - if (selection[i + 1] == selection[i] + 1) - { - int fromIndex = selection[i] + numInserted; - int toIndex = selection[i + 1] + numInserted; - var from = m_Positions.GetArrayElementAtIndex(fromIndex).vector3Value; - var to = m_Positions.GetArrayElementAtIndex(toIndex).vector3Value; - var midPoint = Vector3.Lerp(from, to, 0.5f); - m_Positions.InsertArrayElementAtIndex(toIndex); - m_Positions.GetArrayElementAtIndex(toIndex).vector3Value = midPoint; - insertedIndexes.Add(toIndex); - numInserted++; - } - } - - m_PointEditor.m_Selection = insertedIndexes; - } - private void SimplifyPoints() { var lineRenderer = target as LineRenderer; @@ -468,76 +483,37 @@ public override void OnInspectorGUI() serializedObject.ApplyModifiedProperties(); } - public void StartEditPositions() - { - if (m_EditingPositions) - return; - - m_EditingPositions = true; - Tools.s_Hidden = true; - SceneView.RepaintAll(); - } - - public void EndEditPositions() - { - if (!m_EditingPositions) - return; - - if (m_PointEditor != null) - m_PointEditor.Deselect(); - - ResetSimplifyPreview(); - Tools.s_Hidden = false; - SceneView.RepaintAll(); - } - - void InternalOnSceneView() + public void OnSceneGUIDelegate(SceneView sceneView) { - switch (EditMode.editMode) + var sceneTool = activeSceneViewTool; + if (sceneTool != null) { - case EditMode.SceneViewEditMode.LineRendererEdit: - m_PointEditor.EditSceneGUI(); - - // We need to wait for m_Positions to be updated next frame or we risk calling SetSelection with invalid indexes. - if (m_PointEditor.Count != m_Positions.arraySize) - break; - - if (m_Positions.arraySize != m_PositionsView.GetRows().Count) - { - m_PositionsView.Reload(); - ResetSimplifyPreview(); - } - m_PositionsView.SetSelection(m_PointEditor.m_Selection, TreeViewSelectionOptions.RevealAndFrame); - break; - case EditMode.SceneViewEditMode.LineRendererCreate: - m_PointEditor.CreateSceneGUI(); - break; + sceneTool.OnSceneGUIDelegate(m_Positions, m_PositionsView); + ResetSimplifyPreview(); } - } - - public void OnSceneGUIDelegate(SceneView sceneView) - { - if (m_EditingPositions) - InternalOnSceneView(); - - if (!sceneViewEditing) + else + { DrawSimplifyPreview(); + } } public bool HasFrameBounds() { - return m_EditingPositions && m_PointEditor.m_Selection.Count > 0; + var sceneTool = activeSceneViewTool; + return (sceneTool != null) && (sceneTool.pointSelection.Count > 0); } public Bounds OnGetFrameBounds() { - return m_PointEditor.selectedPositionsBounds; + var sceneTool = activeSceneViewTool; + return (sceneTool != null) ? sceneTool.selectedPositionsBounds : new Bounds(); } internal override Bounds GetWorldBoundsOfTarget(Object targetObject) { - if (sceneViewEditing) - return OnGetFrameBounds(); + var sceneTool = activeSceneViewTool; + if (sceneTool != null) + return sceneTool.selectedPositionsBounds; return base.GetWorldBoundsOfTarget(targetObject); } } diff --git a/Editor/Mono/Inspector/LineRendererToolModes.cs b/Editor/Mono/Inspector/LineRendererToolModes.cs index 4e8a803b08..72c4051bc1 100644 --- a/Editor/Mono/Inspector/LineRendererToolModes.cs +++ b/Editor/Mono/Inspector/LineRendererToolModes.cs @@ -17,7 +17,6 @@ public enum InputMode } private LineRenderer m_LineRenderer; - private LineRendererInspector m_Inspector; public List m_Selection = new List(); public static float createPointSeparation @@ -53,10 +52,9 @@ public static bool showWireframe private static readonly Color kCloudColor = new Color(200f / 255f, 200f / 255f, 20f / 255f, 0.85f); private static readonly Color kSelectedCloudColor = new Color(.3f, .6f, 1, 1); - public LineRendererEditor(LineRenderer lineRenderer, LineRendererInspector inspector) + public LineRendererEditor(LineRenderer lineRenderer) { m_LineRenderer = lineRenderer; - m_Inspector = inspector; } public void Deselect() @@ -64,7 +62,7 @@ public void Deselect() m_Selection.Clear(); } - public void HandleEditMenuHotKeyCommands() + public void HandleEditMenuHotKeyCommands(EditorWindow window) { //Handle other events! if (Event.current.type == EventType.ValidateCommand @@ -86,7 +84,7 @@ public void HandleEditMenuHotKeyCommands() break; case "SelectAll": if (execute) - SelectAllPoints(); + SelectAllPoints(window); Event.current.Use(); break; case "Cut": @@ -107,12 +105,12 @@ public void RemoveInvalidSelections() m_Selection = m_Selection.Where(o => o < points).ToList(); } - public void SelectAllPoints() + private void SelectAllPoints(EditorWindow window) { m_Selection.Clear(); for (int i = 0; i < m_LineRenderer.positionCount; ++i) m_Selection.Add(i); - m_Inspector.Repaint(); + window.Repaint(); } public void DestroySelected() @@ -199,7 +197,7 @@ public void CreateSceneGUI() PointCreator.Draw(); } - public void EditSceneGUI() + public void EditSceneGUI(EditorWindow window) { Transform transform = m_LineRenderer.useWorldSpace ? null : m_LineRenderer.transform; @@ -212,7 +210,7 @@ public void EditSceneGUI() if (showWireframe) DrawWireframe(); - HandleEditMenuHotKeyCommands(); + HandleEditMenuHotKeyCommands(window); } public int Count { get { return m_LineRenderer.positionCount; } } diff --git a/Editor/Mono/Inspector/MaskFieldDropdown.cs b/Editor/Mono/Inspector/MaskFieldDropdown.cs index 46a3f9afc8..0ec71e088f 100644 --- a/Editor/Mono/Inspector/MaskFieldDropdown.cs +++ b/Editor/Mono/Inspector/MaskFieldDropdown.cs @@ -234,18 +234,18 @@ public override void OnOpen() } m_windowSize = Mathf.Clamp(m_windowSize, 100, Screen.currentResolution.width * 0.95f); - Undo.undoRedoPerformed += OnUndoRedoPerformed; + Undo.undoRedoEvent += OnUndoRedoPerformed; } public override void OnClose() { - Undo.undoRedoPerformed -= OnUndoRedoPerformed; + Undo.undoRedoEvent -= OnUndoRedoPerformed; Event.current?.Use(); MaskFieldGUI.DestroyMaskCallBackInfo(); base.OnClose(); } - void OnUndoRedoPerformed() + void OnUndoRedoPerformed(in UndoRedoInfo info) { editorWindow.Close(); } @@ -368,16 +368,16 @@ public override void OnOpen() } GetMultiSelectionValues(m_SerializedProperty, out m_SelectionMaskValues, out m_SelectionMatch, m_OptionCount); - Undo.undoRedoPerformed += OnUndoRedoPerformed; + Undo.undoRedoEvent += OnUndoRedoPerformed; } public override void OnClose() { - Undo.undoRedoPerformed -= OnUndoRedoPerformed; + Undo.undoRedoEvent -= OnUndoRedoPerformed; base.OnClose(); } - void OnUndoRedoPerformed() + void OnUndoRedoPerformed(in UndoRedoInfo info) { editorWindow.Close(); } diff --git a/Editor/Mono/Inspector/MaterialEditor.cs b/Editor/Mono/Inspector/MaterialEditor.cs index 4d53cd8eee..c489ff4496 100644 --- a/Editor/Mono/Inspector/MaterialEditor.cs +++ b/Editor/Mono/Inspector/MaterialEditor.cs @@ -14,6 +14,7 @@ using UnityEngine.Scripting; using VirtualTexturing = UnityEngine.Rendering.VirtualTexturing; using StackValidationResult = UnityEngine.Rendering.VirtualTexturing.EditorHelpers.StackValidationResult; +using System.IO; namespace UnityEditor { @@ -71,17 +72,36 @@ private static class Styles public const string undoAssignMaterial = "Assign Material"; public const string undoAssignSkyboxMaterial = "Assign Skybox Material"; + + public static readonly GUIContent parentContent = EditorGUIUtility.TrTextContent("Parent", "Specify the parent of this material."); + public static readonly GUIContent hierarchyIcon = EditorGUIUtility.IconContent("UnityEditor.SceneHierarchyWindow", "|Open Material Hierarchy Popup."); // right of | means tooltip + public static readonly GUIContent convertIcon = EditorGUIUtility.IconContent("d_RotateTool", "|This material is in a conversion process."); // right of | means tooltip + + public const int kPadding = 3; + public const int kHierarchyIconWidth = 44; + public const float kSpaceForFoldoutArrow = 10f; } private static readonly List s_MaterialEditors = new List(4); + private int m_VariantCountCache = -1, m_HasMixedParentCache = -1; private bool m_CheckSetup; 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; + internal override string targetTitle + { + get + { + var typeName = AllTargetsAreVariants() ? "Material Variant" : "Material"; + return (!m_AllowMultiObjectAccess || targets.Length == 1) ? target.name + " (" + typeName + ")" : targets.Length + " " + typeName + "s"; + } + } + private enum PreviewType { Mesh = 0, @@ -111,8 +131,6 @@ private static bool DoesPreviewAllowRotation(PreviewType type) public bool isVisible { get { return firstInspectedEditor || InternalEditorUtility.GetIsInspectorExpanded(target); } } private Shader m_Shader; - private SerializedProperty m_EnableInstancing; - private SerializedProperty m_DoubleSidedGI; private string m_InfoMessage; private Vector2 m_PreviewDir = new Vector2(0, -20); @@ -132,6 +150,9 @@ private static bool DoesPreviewAllowRotation(PreviewType type) Renderer[] m_RenderersForAnimationMode; Component m_MeshRendererComp; + internal enum ConvertAction { None, Flatten, Convert } + internal ConvertAction convertState; + private bool ShouldEditorBeHidden() { //When a MeshRenderer is hidden in the inspector, the MaterialEditor should also be hidden @@ -340,6 +361,158 @@ private void ShaderPopup(GUIStyle style) GUI.enabled = wasEnabled; } + bool HasMixedParent() + { + if (m_HasMixedParentCache != -1) + return m_HasMixedParentCache == 1; + + m_HasMixedParentCache = 0; + if (targets.Length != 0) + { + var parent = ((Material)targets[0]).parent; + bool isVariant = ((Material)targets[0]).isVariant; + for (int i = 1; i < targets.Length; i++) + { + if (((Material)targets[i]).parent != parent || ((Material)targets[i]).isVariant != isVariant) + { + m_HasMixedParentCache = 1; + break; + } + } + } + return m_HasMixedParentCache == 1; + } + + int GetVariantCount() + { + if (m_VariantCountCache == -1) + m_VariantCountCache = GetVariantCount(targets); + + return m_VariantCountCache; + } + + bool AllTargetsAreVariants() + { + return GetVariantCount() == targets.Length; + } + + // returns true if mat is a child of any element of the targets array + private static bool IsChildOfAnyTarget(Material mat, Object[] targets) + { + foreach (var target in targets) + { + if (mat == target || mat.IsChildOf(target as Material)) + return true; + } + return false; + } + + private static Material HandleParentDragAndDrop(Rect rect, int controlID, EventType eventType, Object[] targets) + { + Material parent = (targets[0] as Material).parent; + + // We handle drag and drop ourselves because we accept assets containing a material artifact + if (eventType == EventType.DragUpdated || eventType == EventType.DragPerform) + { + if (rect.Contains(Event.current.mousePosition) && GUI.enabled) + { + Object[] references = DragAndDrop.objectReferences; + Material validatedObject = EditorGUI.ValidateObjectFieldAssignment(references, typeof(Material), null, EditorGUI.ObjectFieldValidatorOptions.None) as Material; + if (validatedObject == null) + { + foreach (var asset in AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(validatedObject))) + { + validatedObject = asset as Material; + if (validatedObject) + break; + } + } + if (validatedObject != null && !IsChildOfAnyTarget(validatedObject, targets)) + { + DragAndDrop.visualMode = DragAndDropVisualMode.Generic; + if (eventType == EventType.DragPerform) + { + GUI.changed = true; + DragAndDrop.AcceptDrag(); + DragAndDrop.activeControlID = 0; + parent = validatedObject; + } + else + DragAndDrop.activeControlID = controlID; + } + Event.current.Use(); + } + } + return parent; + } + + private static bool HasMissingParent(Material material) + { + return material.isVariant && material.parent == null; + } + + private static Material DoParentObjectField(Rect rect, Object[] targets) + { + // This is an augmented object field, preventing cyclic dependencies in the material hierarchy, handling missing parent, and with custom drap and drop rejection + + int controlID = GUIUtility.GetControlID(FocusType.Keyboard, rect); + var eventType = Event.current.type; + + Material parent = HandleParentDragAndDrop(rect, controlID, eventType, targets); + + if (eventType == EventType.Repaint && HasMissingParent(targets[0] as Material)) + { + GUIContent content = EditorGUIUtility.TempContent("Missing (Material)"); + + var mousePos = Event.current.mousePosition; + Rect buttonRect = EditorStyles.objectFieldButton.margin.Remove(new Rect(rect.xMax - 19, rect.y, 19, rect.height)); + + EditorGUI.BeginHandleMixedValueContentColor(); + EditorStyles.objectField.Draw(rect, content, controlID, DragAndDrop.activeControlID == controlID, rect.Contains(mousePos)); + EditorStyles.objectFieldButton.Draw(buttonRect, GUIContent.none, controlID, DragAndDrop.activeControlID == controlID, buttonRect.Contains(mousePos)); + EditorGUI.EndHandleMixedValueContentColor(); + return parent; + } + + return EditorGUI.DoObjectField(rect, rect, controlID, parent, null, typeof(Material), null, true) as Material; + } + + internal void ParentField(Rect rect, bool showMixedValue = false) + { + rect = EditorGUI.PrefixLabel(rect, Styles.parentContent); + + EditorGUI.BeginChangeCheck(); + EditorGUI.showMixedValue = showMixedValue; + var parent = DoParentObjectField(rect, targets); + EditorGUI.showMixedValue = false; + if (EditorGUI.EndChangeCheck()) + { + convertState = ConvertAction.None; + Undo.RecordObjects(targets, "Assign parent"); + foreach (Material target in targets) + target.parent = parent; + } + } + + private bool DoHierarchyButton() + { + bool clicked = EditorGUILayout.DropdownButton(GUIContent.none, FocusType.Passive, GUILayout.MaxWidth(Styles.kHierarchyIconWidth)); + + Rect rect = GUILayoutUtility.topLevel.GetLast(); + rect.x += 6; + if (convertState == ConvertAction.Convert) + { + float height = rect.height; + rect.height *= 0.8f; + rect.y += (height - rect.height) * 0.5f; + EditorGUI.LabelField(rect, Styles.convertIcon); + } + else + EditorGUI.LabelField(rect, Styles.hierarchyIcon); + + return clicked; + } + private class ShaderSelectionDropdown : AdvancedDropdown { Action m_OnSelectedShaderPopup; @@ -782,8 +955,7 @@ protected override void OnHeaderGUI() if (ShouldEditorBeHidden()) return; - const float spaceForFoldoutArrow = 10f; - Rect titleRect = DrawHeaderGUI(this, targetTitle, firstInspectedEditor ? 0 : spaceForFoldoutArrow); + Rect titleRect = DrawHeaderGUI(this, targetTitle, firstInspectedEditor ? 0 : Styles.kSpaceForFoldoutArrow); int id = GUIUtility.GetControlID(45678, FocusType.Passive); if (!firstInspectedEditor) @@ -801,28 +973,50 @@ protected override void OnHeaderGUI() internal override void OnHeaderControlsGUI() { + // Clear cache in case material is modified externally + m_VariantCountCache = -1; + m_HasMixedParentCache = -1; + if (ShouldEditorBeHidden()) return; serializedObject.Update(); var oldLabelWidth = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth = 50; - using (new EditorGUI.DisabledScope(!IsEnabled())) - { - EditorGUIUtility.labelWidth = 50; - - // Shader selection dropdown + // Shader selection dropdown + using (new EditorGUI.DisabledScope(!IsEnabled() || GetVariantCount() != 0)) ShaderPopup("MiniPulldown"); - // Edit button for custom shaders - if (m_Shader != null && !HasMultipleMixedShaderValues() && (m_Shader.hideFlags & HideFlags.DontSave) == 0) + // Edit button for custom shaders + if (m_Shader != null && !HasMultipleMixedShaderValues() && (m_Shader.hideFlags & HideFlags.DontSave) == 0) + { + if (GUILayout.Button("Edit...", EditorStyles.miniButton, GUILayout.ExpandWidth(false))) + AssetDatabase.OpenAsset(m_Shader); + } + + using (new EditorGUI.DisabledScope(targets.Length != 1)) + { + if (DoHierarchyButton()) { - if (GUILayout.Button("Edit...", EditorStyles.miniButton, GUILayout.ExpandWidth(false))) - AssetDatabase.OpenAsset(m_Shader); + var rect = GUILayoutUtility.topLevel.GetLast(); + PopupWindow.Show(rect, new MaterialHierarchyPopup(target as Material, IsEnabled(), this, rect)); + GUIUtility.ExitGUI(); } } + if (AllTargetsAreVariants() || convertState == ConvertAction.Convert) + { + // Start a new line for parent + EditorGUILayout.EndHorizontal(); + EditorGUILayout.BeginHorizontal(); + if (!firstInspectedEditor) + GUILayout.Space(Styles.kSpaceForFoldoutArrow); + using (new EditorGUI.DisabledScope(!IsEnabled())) + ParentField(EditorGUILayout.GetControlRect(), HasMixedParent()); + } + EditorGUIUtility.labelWidth = oldLabelWidth; } @@ -958,6 +1152,69 @@ public void SetTextureOffset(string propertyName, Vector2 value, int coord) } } + // -------- helper functions to handle material variant overrides + + internal static int GetVariantCount(Object[] targets) + { + int count = 0; + foreach (Material target in targets) + count += target.isVariant ? 1 : 0; + return count; + } + + static bool AllTargetsAreVariants(Object[] targets) + { + return GetVariantCount(targets) == targets.Length; + } + + static MaterialSerializedProperty GetMaterialSerializedProperty(SerializedProperty property) + { + if (property.propertyPath == "m_LightmapFlags") + return MaterialSerializedProperty.LightmapFlags; + if (property.propertyPath == "m_EnableInstancingVariants") + return MaterialSerializedProperty.EnableInstancingVariants; + if (property.propertyPath == "m_DoubleSidedGI") + return MaterialSerializedProperty.DoubleSidedGI; + if (property.propertyPath == "m_CustomRenderQueue") + return MaterialSerializedProperty.CustomRenderQueue; + throw new ArgumentException(string.Format("The SerializedProperty '{0}' is not supported by BeginProperty.", property.propertyPath)); + } + + internal static void BeginProperty(MaterialSerializedProperty property, Object[] targets) + { + MaterialProperty.BeginProperty(property, targets); + } + + internal static void BeginProperty(Rect rect, MaterialSerializedProperty property, Object[] targets) + { + MaterialProperty.BeginProperty(rect, null, property, targets); + } + + public static void BeginProperty(SerializedProperty property) + { + MaterialProperty.BeginProperty(GetMaterialSerializedProperty(property), property.serializedObject.targetObjects); + } + + public static void BeginProperty(Rect rect, SerializedProperty property) + { + MaterialProperty.BeginProperty(rect, null, GetMaterialSerializedProperty(property), property.serializedObject.targetObjects); + } + + public static void BeginProperty(MaterialProperty property) + { + MaterialProperty.BeginProperty(property, property.targets); + } + + public static void BeginProperty(Rect rect, MaterialProperty property) + { + MaterialProperty.BeginProperty(rect, property, 0, property.targets); + } + + public static void EndProperty() + { + MaterialProperty.EndProperty(); + } + // -------- helper functions to display common material controls // The 'Property' methods that accept GUIContent are internal with different name to avoid @@ -987,8 +1244,9 @@ internal static float RangePropertyInternal(Rect position, MaterialProperty prop internal static float DoPowerRangeProperty(Rect position, MaterialProperty prop, GUIContent label, float power) { + BeginProperty(position, prop); + EditorGUI.BeginChangeCheck(); - EditorGUI.showMixedValue = prop.hasMixedValue; // For range properties we want to show the slider so we adjust label width to use default width (setting it to 0) // See SetDefaultGUIWidths where we set: EditorGUIUtility.labelWidth = GUIClip.visibleRect.width - EditorGUIUtility.fieldWidth - 17; @@ -1001,20 +1259,22 @@ internal static float DoPowerRangeProperty(Rect position, MaterialProperty prop, float value = Mathf.Clamp(prop.floatValue, invert ? prop.rangeLimits.y : prop.rangeLimits.x, invert ? prop.rangeLimits.x : prop.rangeLimits.y); float newValue = EditorGUI.PowerSlider(position, label, value, prop.rangeLimits.x, prop.rangeLimits.y, power); - EditorGUI.showMixedValue = false; EditorGUIUtility.labelWidth = oldLabelWidth; if (EditorGUI.EndChangeCheck()) prop.floatValue = newValue; + EndProperty(); + return prop.floatValue; } internal static int DoIntRangeProperty(Rect position, MaterialProperty prop, GUIContent label) { + BeginProperty(position, prop); + EditorGUI.BeginChangeCheck(); - EditorGUI.showMixedValue = prop.hasMixedValue; // For range properties we want to show the slider so we adjust label width to use default width (setting it to 0) // See SetDefaultGUIWidths where we set: EditorGUIUtility.labelWidth = GUIClip.visibleRect.width - EditorGUIUtility.fieldWidth - 17; @@ -1022,13 +1282,14 @@ internal static int DoIntRangeProperty(Rect position, MaterialProperty prop, GUI EditorGUIUtility.labelWidth = 0f; int newValue = EditorGUI.IntSlider(position, label, (int)prop.floatValue, (int)prop.rangeLimits.x, (int)prop.rangeLimits.y); - EditorGUI.showMixedValue = false; EditorGUIUtility.labelWidth = oldLabelWidth; if (EditorGUI.EndChangeCheck()) prop.floatValue = (float)newValue; + EndProperty(); + return (int)prop.floatValue; } @@ -1050,13 +1311,15 @@ public int IntegerProperty(Rect position, MaterialProperty prop, string label) internal int IntegerPropertyInternal(Rect position, MaterialProperty prop, GUIContent label) { + BeginProperty(position, prop); + EditorGUI.BeginChangeCheck(); - EditorGUI.showMixedValue = prop.hasMixedValue; int newValue = EditorGUI.IntField(position, label, prop.intValue); - EditorGUI.showMixedValue = false; if (EditorGUI.EndChangeCheck()) prop.intValue = newValue; + EndProperty(); + return prop.intValue; } @@ -1078,13 +1341,15 @@ public float FloatProperty(Rect position, MaterialProperty prop, string label) internal static float FloatPropertyInternal(Rect position, MaterialProperty prop, GUIContent label) { + BeginProperty(position, prop); + EditorGUI.BeginChangeCheck(); - EditorGUI.showMixedValue = prop.hasMixedValue; float newValue = EditorGUI.FloatField(position, label, prop.floatValue); - EditorGUI.showMixedValue = false; if (EditorGUI.EndChangeCheck()) prop.floatValue = newValue; + EndProperty(); + return prop.floatValue; } @@ -1106,15 +1371,17 @@ public Color ColorProperty(Rect position, MaterialProperty prop, string label) internal static Color ColorPropertyInternal(Rect position, MaterialProperty prop, GUIContent label) { + BeginProperty(position, prop); + EditorGUI.BeginChangeCheck(); - EditorGUI.showMixedValue = prop.hasMixedValue; bool isHDR = ((prop.flags & MaterialProperty.PropFlags.HDR) != 0); bool showAlpha = true; Color newValue = EditorGUI.ColorField(position, label, prop.colorValue, true, showAlpha, isHDR); - EditorGUI.showMixedValue = false; if (EditorGUI.EndChangeCheck()) prop.colorValue = newValue; + EndProperty(); + return prop.colorValue; } @@ -1131,8 +1398,9 @@ public Vector4 VectorProperty(Rect position, MaterialProperty prop, string label internal static Vector4 VectorPropertyInternal(in Rect position, in MaterialProperty prop, in string label) { + BeginProperty(position, prop); + EditorGUI.BeginChangeCheck(); - EditorGUI.showMixedValue = prop.hasMixedValue; // We want to make room for the field in case it's drawn on the same line as the label // Set label width to default width (zero) temporarily @@ -1143,10 +1411,11 @@ internal static Vector4 VectorPropertyInternal(in Rect position, in MaterialProp EditorGUIUtility.labelWidth = oldLabelWidth; - EditorGUI.showMixedValue = false; if (EditorGUI.EndChangeCheck()) prop.vectorValue = newValue; + EndProperty(); + return prop.vectorValue; } @@ -1168,6 +1437,8 @@ public float TextureScaleOffsetProperty(Rect position, MaterialProperty property { BeginAnimatedCheck(position, property); + BeginProperty(position, property); + EditorGUI.BeginChangeCheck(); // Mixed value mask is 4 bits for the uv offset & scale (First bit is for the texture itself) int mixedValuemask = property.mixedValueMask >> 1; @@ -1176,6 +1447,8 @@ public float TextureScaleOffsetProperty(Rect position, MaterialProperty property if (EditorGUI.EndChangeCheck()) property.textureScaleAndOffset = scaleAndOffset; + EndProperty(); + EndAnimatedCheck(); return 2 * kLineHeight; } @@ -1257,6 +1530,8 @@ public void TextureCompatibilityWarning(MaterialProperty prop) public Texture TexturePropertyMiniThumbnail(Rect position, MaterialProperty prop, string label, string tooltip) { + BeginProperty(position, prop); + BeginAnimatedCheck(position, prop); Rect thumbRect, labelRect; EditorGUI.GetRectsForMiniThumbnailField(position, out thumbRect, out labelRect); @@ -1271,6 +1546,8 @@ public Texture TexturePropertyMiniThumbnail(Rect position, MaterialProperty prop TextureCompatibilityWarning(prop); + EndProperty(); + return retValue; } @@ -1299,6 +1576,9 @@ public Texture TextureProperty(Rect position, MaterialProperty prop, string labe public Texture TextureProperty(Rect position, MaterialProperty prop, string label, string tooltip, 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)); @@ -1308,6 +1588,8 @@ public Texture TextureProperty(Rect position, MaterialProperty prop, string labe texPos.xMin = texPos.xMax - EditorGUIUtility.fieldWidth; Texture value = TexturePropertyBody(texPos, prop); + EndProperty(); + // UV scale and offset if (scaleOffset) { @@ -1568,6 +1850,7 @@ public void LightmapEmissionProperty(Rect position, int labelIndent) isMixed = true; } + BeginProperty(position, MaterialSerializedProperty.LightmapFlags, targets); EditorGUI.BeginChangeCheck(); bool realtimeGISupported = SupportedRenderingFeatures.IsLightmapBakeTypeSupported(LightmapBakeType.Realtime); @@ -1591,6 +1874,7 @@ public void LightmapEmissionProperty(Rect position, int labelIndent) } } + EndProperty(); EditorGUI.indentLevel -= labelIndent; } @@ -1615,6 +1899,8 @@ public bool EmissionEnabledProperty() } } + BeginProperty(MaterialSerializedProperty.LightmapFlags, targets); + // initial checkbox for enabling/disabling emission EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = isMixed; @@ -1622,12 +1908,16 @@ public bool EmissionEnabledProperty() EditorGUI.showMixedValue = false; if (EditorGUI.EndChangeCheck()) { + RegisterPropertyChangeUndo("Emission Flags"); foreach (Material mat in materials) { mat.globalIlluminationFlags = enabled ? defaultEnabled : MaterialGlobalIlluminationFlags.EmissiveIsBlack; } + EndProperty(); return enabled; } + + EndProperty(); return !isMixed && enabled; } @@ -1666,6 +1956,7 @@ public void LightmapEmissionFlagsProperty(int indent, bool enabled, bool ignoreE isMixed = isMixed || (materials[i].globalIlluminationFlags & any_em) != giFlags; } + BeginProperty(MaterialSerializedProperty.LightmapFlags, targets); EditorGUI.BeginChangeCheck(); bool realtimeGISupported = SupportedRenderingFeatures.IsLightmapBakeTypeSupported(LightmapBakeType.Realtime); @@ -1678,6 +1969,7 @@ public void LightmapEmissionFlagsProperty(int indent, bool enabled, bool ignoreE EditorGUI.showMixedValue = false; // Apply flags. But only the part that this tool modifies (RealtimeEmissive, BakedEmissive, None) + RegisterPropertyChangeUndo("Emission Flags"); bool applyFlags = EditorGUI.EndChangeCheck(); foreach (Material mat in materials) { @@ -1687,6 +1979,8 @@ public void LightmapEmissionFlagsProperty(int indent, bool enabled, bool ignoreE FixupEmissiveFlag(mat); } } + + EndProperty(); } void ShaderPropertyInternal(Rect position, MaterialProperty prop, GUIContent label) @@ -1938,6 +2232,16 @@ public void SetDefaultGUIWidths() EditorGUIUtility.labelWidth = GUIClip.visibleRect.width - EditorGUIUtility.fieldWidth - 25; } + internal override bool GetOptimizedGUIBlock(bool isDirty, bool isVisible, out float height) + { + // Shift UI to the right to leave space for locks + // Done here because it's the only place between the creation of the vertical group and the call ot OnInspectorGUI + // And because OnInspectorGUI might be overriden by a user editor + var style = GUILayoutUtility.topLevel.style = new GUIStyle(GUILayoutUtility.topLevel.style); + style.padding.left += (int)EditorGUI.kIndentPerLevel; + return base.GetOptimizedGUIBlock(isDirty, isVisible, out height); + } + private bool IsMaterialEditor(string customEditorName) { string unityEditorFullName = "UnityEditor." + customEditorName; // for convenience: adding UnityEditor namespace is not needed in the shader @@ -1975,17 +2279,6 @@ void CreateCustomShaderEditorIfNeeded(Shader shader) public bool PropertiesGUI() { - // OnInspectorGUI is wrapped inside a BeginVertical/EndVertical block that adds padding, - // which we don't want here so we could have the VC bar span the entire Material Editor width - // we stop the vertical block, draw the VC bar, and then start a new vertical block with the same style. - var style = GUILayoutUtility.topLevel.style; - EditorGUILayout.EndVertical(); - - // setting the GUI to enabled where the VC status bar is drawn because it gets disabled by the parent inspector - // for non-checked out materials, and we need the version control status bar to be always active - bool wasGUIEnabled = GUI.enabled; - GUI.enabled = true; - // Material Editor is the first inspected editor when accessed through the Project panel // and this is the scenario where we do not want to redraw the VC status bar // since InspectorWindow already takes care of that. Otherwise, the Material Editor @@ -1993,11 +2286,22 @@ public bool PropertiesGUI() // thus we draw the VC status bar if (!firstInspectedEditor) { + // OnInspectorGUI is wrapped inside a BeginVertical/EndVertical block that adds padding, + // which we don't want here so we could have the VC bar span the entire Material Editor width + // we stop the vertical block, draw the VC bar, and then start a new vertical block with the same style. + var style = GUILayoutUtility.topLevel.style; + EditorGUILayout.EndVertical(); + + // setting the GUI to enabled where the VC status bar is drawn because it gets disabled by the parent inspector + // for non-checked out materials, and we need the version control status bar to be always active + bool wasGUIEnabled = GUI.enabled; + GUI.enabled = true; + PropertyEditor.VersionControlBar(this); - } - GUI.enabled = wasGUIEnabled; - EditorGUILayout.BeginVertical(style); + GUI.enabled = wasGUIEnabled; + EditorGUILayout.BeginVertical(style); + } var eventType = Event.current.type; bool isRunningCommand = eventType == EventType.ExecuteCommand || eventType == EventType.ValidateCommand; @@ -2266,6 +2570,8 @@ public sealed override Texture2D RenderStaticPreview(string assetPath, Object[] var previewRenderUtility = GetPreviewRendererUtility(); EditorUtility.SetCameraAnimateMaterials(previewRenderUtility.camera, true); + previewRenderUtility.ambientColor = kAmbientLightColor; + previewRenderUtility.BeginStaticPreview(new Rect(0, 0, width, height)); StreamRenderResources(); DoRenderPreview(previewRenderUtility, true); @@ -2375,7 +2681,7 @@ private void DoRenderPreview(PreviewRenderUtility previewRenderUtility, bool ove previewRenderUtility.lights[1].intensity = 1.0f; } - previewRenderUtility.ambientColor = new Color(.2f, .2f, .2f, 0); + previewRenderUtility.ambientColor = kAmbientLightColor; Quaternion rot = Quaternion.identity; if (DoesPreviewAllowRotation(viewType)) @@ -2497,11 +2803,8 @@ public virtual void OnEnable() m_CustomEditorClassName = ""; CreateCustomShaderEditorIfNeeded(m_Shader); - m_EnableInstancing = serializedObject.FindProperty("m_EnableInstancingVariants"); - m_DoubleSidedGI = serializedObject.FindProperty("m_DoubleSidedGI"); - s_MaterialEditors.Add(this); - Undo.undoRedoPerformed += UndoRedoPerformed; + Undo.undoRedoEvent += UndoRedoPerformed; PropertiesChanged(); m_PropertyBlock = new MaterialPropertyBlock(); @@ -2509,6 +2812,7 @@ public virtual void OnEnable() m_ReflectionProbePicker.OnEnable(); } + [Obsolete("MaterialEditor.UndoRedoPerformed() has been deprecated. Use MaterialEditor.UndoRedoPerformed(in UndoRedoInfo) instead", false)] public virtual void UndoRedoPerformed() { // Undo could've restored old shader which might lead to change in custom editor class @@ -2518,6 +2822,15 @@ public virtual void UndoRedoPerformed() PropertiesChanged(); } + public virtual void UndoRedoPerformed(in UndoRedoInfo info) + { + // Undo could've restored old shader which might lead to change in custom editor class + // therefore we need to rebuild inspector + UpdateAllOpenMaterialEditors(); + + PropertiesChanged(); + } + public virtual void OnDisable() { s_NumberOfEditors--; @@ -2526,7 +2839,7 @@ public virtual void OnDisable() m_ReflectionProbePicker.OnDisable(); s_MaterialEditors.Remove(this); - Undo.undoRedoPerformed -= UndoRedoPerformed; + Undo.undoRedoEvent -= UndoRedoPerformed; } // Handle dragging of material onto renderers @@ -2688,5 +3001,75 @@ internal override void OnHeaderIconGUI(Rect iconRect) { OnPreviewGUI(iconRect, Styles.inspectorBigInner); } + + [MenuItem("CONTEXT/Material/Flatten Material Variant", true)] + static bool FlattenMaterialValidate(MenuCommand command) + { + Material mat = command.context as Material; + return mat.isVariant; + } + + [MenuItem("CONTEXT/Material/Flatten Material Variant", false, 502)] + static void FlattenMaterial(MenuCommand command) + { + Material mat = command.context as Material; + + Undo.RecordObject(mat, "Flatten Material Variant"); + mat.parent = null; + } + + // We need to access renderer data in the GenericMenu callback, which is called from a static function + // So we backup these variables during header rendering in case context menu is openned + static Renderer[] renderersForContextMenu = null; + static Material materialForContextMenu = null; + + internal override Rect DrawHeaderHelpAndSettingsGUI(Rect r) + { + if (!firstInspectedEditor && targets.Length == 1) + { + renderersForContextMenu = GetAssociatedRenderersFromInspector(); + materialForContextMenu = target as Material; + } + return base.DrawHeaderHelpAndSettingsGUI(r); + } + + internal static void AddAdditionalMaterialMenuItems(GenericMenu menu) + { + if (renderersForContextMenu == null || renderersForContextMenu.Length == 0) + return; + + // Capture local copies for lambda, and clear static values + var renderers = renderersForContextMenu; + var material = materialForContextMenu; + renderersForContextMenu = null; + materialForContextMenu = null; + + menu.AddItem(new GUIContent("Create Variant for Renderer"), false, () => { + + var directory = "Assets/Materials"; + var assetPath = AssetDatabase.GetAssetPath(material); + var assetName = Path.GetFileNameWithoutExtension(assetPath) + " Variant"; + var package = PackageManager.PackageInfo.FindForAssetPath(assetPath); + if (package == null || package.source == PackageManager.PackageSource.Local) + directory = Path.GetDirectoryName(assetPath); + + 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) + { + Undo.RegisterCompleteObjectUndo(renderer, "Assign Material"); + var materials = renderer.sharedMaterials; + for (int i = 0; i < materials.Length; i++) + { + if (materials[i] == material) + materials[i] = variant; + } + renderer.sharedMaterials = materials; + } + }); + } } } // namespace UnityEditor diff --git a/Editor/Mono/Inspector/MaterialEditorGUIHelpers.cs b/Editor/Mono/Inspector/MaterialEditorGUIHelpers.cs index bcd69cb2ee..57b32b5ab0 100644 --- a/Editor/Mono/Inspector/MaterialEditorGUIHelpers.cs +++ b/Editor/Mono/Inspector/MaterialEditorGUIHelpers.cs @@ -5,6 +5,7 @@ using UnityEngine; using UnityEngine.Rendering; using System; +using System.Collections.Generic; namespace UnityEditor { @@ -26,20 +27,6 @@ private bool isPrefabAsset } } - // Do currently edited materials have different render queue values? - private bool HasMultipleMixedQueueValues() - { - int queue = (targets[0] as Material).rawRenderQueue; - for (int i = 1; i < targets.Length; ++i) - { - if (queue != (targets[i] as Material).rawRenderQueue) - { - return true; - } - } - return false; - } - // Field for editing render queue value, with an automatically calculated rect public void RenderQueueField() { @@ -50,8 +37,7 @@ public void RenderQueueField() // Field for editing render queue value, with an explicit rect public void RenderQueueField(Rect r) { - var mixedValue = HasMultipleMixedQueueValues(); - EditorGUI.showMixedValue = mixedValue; + BeginProperty(r, MaterialSerializedProperty.CustomRenderQueue, targets); var mat = targets[0] as Material; int curRawQueue = mat.rawRenderQueue; @@ -130,7 +116,8 @@ public void RenderQueueField(Rect r) EditorGUIUtility.labelWidth = oldLabelWidth; EditorGUIUtility.fieldWidth = oldFieldWidth; - EditorGUI.showMixedValue = false; + + EndProperty(); } public bool EnableInstancingField() @@ -144,19 +131,24 @@ public bool EnableInstancingField() public void EnableInstancingField(Rect r) { + BeginProperty(r, MaterialSerializedProperty.EnableInstancingVariants, targets); + using (var scope = new EditorGUI.ChangeCheckScope()) { - var newBoolValue = EditorGUI.Toggle(r, Styles.enableInstancingLabel, m_EnableInstancing.boolValue); + bool enableInstancing = EditorGUI.Toggle(r, Styles.enableInstancingLabel, (targets[0] as Material).enableInstancing); if (scope.changed) - m_EnableInstancing.boolValue = newBoolValue; + { + foreach (Material material in targets) + material.enableInstancing = enableInstancing; + } } - serializedObject.ApplyModifiedProperties(); + EndProperty(); } public bool IsInstancingEnabled() { - return ShaderUtil.HasInstancing(m_Shader) && m_EnableInstancing.boolValue; + return ShaderUtil.HasInstancing(m_Shader) && (targets[0] as Material).enableInstancing; } public bool DoubleSidedGIField() @@ -166,13 +158,17 @@ public bool DoubleSidedGIField() Rect r = GetControlRectForSingleLine(); if (isPrefabAsset || !isEnlightenLightMapper) { - using (var scope = new EditorGUI.ChangeCheckScope()) + BeginProperty(r, MaterialSerializedProperty.DoubleSidedGI, targets); + + EditorGUI.BeginChangeCheck(); + bool doubleSidedGI = EditorGUI.Toggle(r, Styles.doubleSidedGILabel, (targets[0] as Material).doubleSidedGI); + if (EditorGUI.EndChangeCheck()) { - var newBoolValue = EditorGUI.Toggle(r, Styles.doubleSidedGILabel, m_DoubleSidedGI.boolValue); - if (scope.changed) - m_DoubleSidedGI.boolValue = newBoolValue; + foreach (Material material in targets) + material.doubleSidedGI = doubleSidedGI; } - serializedObject.ApplyModifiedProperties(); + + EndProperty(); return true; } @@ -214,10 +210,16 @@ public Rect TexturePropertySingleLine(GUIContent label, MaterialProperty texture public Rect TexturePropertySingleLine(GUIContent label, MaterialProperty textureProp, MaterialProperty extraProperty1, MaterialProperty extraProperty2) { Rect r = GetControlRectForSingleLine(); + + bool hasExtraProp = !(extraProperty1 == null && extraProperty2 == null); + if (hasExtraProp) BeginProperty(r, textureProp); + if (extraProperty1 != null) BeginProperty(r, extraProperty1); + if (extraProperty2 != null) BeginProperty(r, extraProperty2); + TexturePropertyMiniThumbnail(r, textureProp, label.text, label.tooltip); // No extra properties: early out - if (extraProperty1 == null && extraProperty2 == null) + if (!hasExtraProp) return r; // Temporarily reset the indent level as it was already used earlier to compute the positions of the layout items. See issue 946082. @@ -246,6 +248,10 @@ public Rect TexturePropertySingleLine(GUIContent label, MaterialProperty texture } // Restore the indent level EditorGUI.indentLevel = oldIndentLevel; + + if (extraProperty2 != null) EndProperty(); + if (extraProperty1 != null) EndProperty(); + if (hasExtraProp) EndProperty(); return r; } @@ -260,9 +266,17 @@ public Rect TexturePropertyWithHDRColor( public Rect TexturePropertyWithHDRColor(GUIContent label, MaterialProperty textureProp, MaterialProperty colorProperty, bool showAlpha) { Rect r = GetControlRectForSingleLine(); + + bool isColorProperty = colorProperty.type == MaterialProperty.PropType.Color; + if (isColorProperty) + { + BeginProperty(r, textureProp); + BeginProperty(r, colorProperty); + } + TexturePropertyMiniThumbnail(r, textureProp, label.text, label.tooltip); - if (colorProperty.type != MaterialProperty.PropType.Color) + if (!isColorProperty) { Debug.LogError("Assuming MaterialProperty.PropType.Color (was " + colorProperty.type + ")"); return r; @@ -286,6 +300,12 @@ public Rect TexturePropertyWithHDRColor(GUIContent label, MaterialProperty textu // Restore the indent level EditorGUI.indentLevel = oldIndentLevel; + if (isColorProperty) + { + EndProperty(); + EndProperty(); + } + return r; } @@ -299,6 +319,10 @@ public Rect TexturePropertyTwoLines(GUIContent label, MaterialProperty texturePr } Rect r = GetControlRectForSingleLine(); + + BeginProperty(r, textureProp); + BeginProperty(r, extraProperty1); + TexturePropertyMiniThumbnail(r, textureProp, label.text, label.tooltip); // Temporarily reset the indent level. See issue 946082. @@ -311,6 +335,9 @@ public Rect TexturePropertyTwoLines(GUIContent label, MaterialProperty texturePr r1 = GetLeftAlignedFieldRect(r); ExtraPropertyAfterTexture(r1, extraProperty1); + EndProperty(); + EndProperty(); + // New line for extraProperty2 Rect r2 = GetControlRectForSingleLine(); ShaderProperty(r2, extraProperty2, label2.text, MaterialEditor.kMiniTextureFieldLabelIndentLevel + 1); diff --git a/Editor/Mono/Inspector/MaterialPropertyDrawer.cs b/Editor/Mono/Inspector/MaterialPropertyDrawer.cs index 1e27844de4..e0d336a28e 100644 --- a/Editor/Mono/Inspector/MaterialPropertyDrawer.cs +++ b/Editor/Mono/Inspector/MaterialPropertyDrawer.cs @@ -300,6 +300,8 @@ public override void OnGUI(Rect position, MaterialProperty prop, GUIContent labe return; } + MaterialEditor.BeginProperty(position, prop); + if (prop.type != MaterialProperty.PropType.Int) { EditorGUI.BeginChangeCheck(); @@ -328,6 +330,8 @@ public override void OnGUI(Rect position, MaterialProperty prop, GUIContent labe SetKeyword(prop, value); } } + + MaterialEditor.EndProperty(); } public override void Apply(MaterialProperty prop) @@ -504,6 +508,8 @@ public override void OnGUI(Rect position, MaterialProperty prop, GUIContent labe return; } + MaterialEditor.BeginProperty(position, prop); + if (prop.type != MaterialProperty.PropType.Int) { EditorGUI.BeginChangeCheck(); @@ -532,6 +538,8 @@ public override void OnGUI(Rect position, MaterialProperty prop, GUIContent labe SetKeyword(prop, value); } } + + MaterialEditor.EndProperty(); } public override void Apply(MaterialProperty prop) @@ -607,6 +615,11 @@ public MaterialEnumDrawer(string[] enumNames, float[] vals) values[i] = (int)vals[i]; } + static bool IsPropertyTypeSuitable(MaterialProperty prop) + { + return prop.type == MaterialProperty.PropType.Float || prop.type == MaterialProperty.PropType.Range || prop.type == MaterialProperty.PropType.Int; + } + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) { if (prop.type != MaterialProperty.PropType.Float && prop.type != MaterialProperty.PropType.Range && prop.type != MaterialProperty.PropType.Int) @@ -618,6 +631,16 @@ public override float GetPropertyHeight(MaterialProperty prop, string label, Mat public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) { + if (!IsPropertyTypeSuitable(prop)) + { + GUIContent c = EditorGUIUtility.TempContent("Enum used on a non-float property: " + prop.name, + EditorGUIUtility.GetHelpIcon(MessageType.Warning)); + EditorGUI.LabelField(position, c, EditorStyles.helpBox); + return; + } + + MaterialEditor.BeginProperty(position, prop); + if (prop.type == MaterialProperty.PropType.Float || prop.type == MaterialProperty.PropType.Range) { EditorGUI.BeginChangeCheck(); @@ -642,7 +665,7 @@ public override void OnGUI(Rect position, MaterialProperty prop, GUIContent labe prop.floatValue = (float)values[selIndex]; } } - else if (prop.type == MaterialProperty.PropType.Int) + else { EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = prop.hasMixedValue; @@ -666,12 +689,8 @@ public override void OnGUI(Rect position, MaterialProperty prop, GUIContent labe prop.intValue = values[selIndex]; } } - else - { - GUIContent c = EditorGUIUtility.TempContent("Enum used on a non-float property: " + prop.name, - EditorGUIUtility.GetHelpIcon(MessageType.Warning)); - EditorGUI.LabelField(position, c, EditorStyles.helpBox); - } + + MaterialEditor.EndProperty(); } } diff --git a/Editor/Mono/Inspector/MeshPreview.cs b/Editor/Mono/Inspector/MeshPreview.cs index c2fc742641..7d16ae62c1 100644 --- a/Editor/Mono/Inspector/MeshPreview.cs +++ b/Editor/Mono/Inspector/MeshPreview.cs @@ -25,27 +25,56 @@ internal static class Styles internal class Settings : IDisposable { - public DisplayMode displayMode = DisplayMode.Shaded; - public int activeUVChannel = 0; - public int activeBlendshape = 0; - public bool drawWire = true; - - public Vector3 orthoPosition = new Vector3(0.0f, 0.0f, 0.0f); - public Vector2 previewDir = new Vector2(0, 0); - public Vector2 lightDir = new Vector2(0, 0); - public Vector3 pivotPositionOffset = Vector3.zero; - public float zoomFactor = 1.0f; - public int checkerTextureMultiplier = 10; - - public Material shadedPreviewMaterial; - public Material activeMaterial; - public Material meshMultiPreviewMaterial; - public Material wireMaterial; - public Material lineMaterial; - public Texture2D checkeredTexture; - - public bool[] availableDisplayModes = Enumerable.Repeat(true, 7).ToArray(); - public bool[] availableUVChannels = Enumerable.Repeat(true, 8).ToArray(); + public DisplayMode displayMode { get => m_DisplayMode; set => SetValue(ref m_DisplayMode, value); } + DisplayMode m_DisplayMode = DisplayMode.Shaded; + + public int activeUVChannel { get => m_ActiveUVChannel; set => SetValue(ref m_ActiveUVChannel, value); } + int m_ActiveUVChannel = 0; + + public int activeBlendshape { get => m_ActiveBlendshape; set => SetValue(ref m_ActiveBlendshape, value); } + int m_ActiveBlendshape = 0; + + public bool drawWire { get => m_DrawWire; set => SetValue(ref m_DrawWire, value); } + bool m_DrawWire = true; + + public Vector3 orthoPosition { get => m_OrthoPosition; set => SetValue(ref m_OrthoPosition, value); } + Vector3 m_OrthoPosition = new Vector3(0.0f, 0.0f, 0.0f); + + public Vector2 previewDir { get => m_PreviewDir; set => SetValue(ref m_PreviewDir, value); } + Vector2 m_PreviewDir = new Vector2(0, 0); + + public Vector2 lightDir { get => m_LightDir; set => SetValue(ref m_LightDir, value); } + Vector2 m_LightDir = new Vector2(0, 0); + + public Vector3 pivotPositionOffset { get => m_PivotPositionOffset; set => SetValue(ref m_PivotPositionOffset, value); } + Vector3 m_PivotPositionOffset = Vector3.zero; + + public float zoomFactor { get => m_ZoomFactor; set => SetValue(ref m_ZoomFactor, value); } + float m_ZoomFactor = 1.0f; + + public int checkerTextureMultiplier { get => m_CheckerTextureMultiplier; set => SetValue(ref m_CheckerTextureMultiplier, value); } + int m_CheckerTextureMultiplier = 10; + + public Material shadedPreviewMaterial { get => m_ShadedPreviewMaterial; set => SetValue(ref m_ShadedPreviewMaterial, value); } + Material m_ShadedPreviewMaterial; + public Material activeMaterial { get => m_ActiveMaterial; set => SetValue(ref m_ActiveMaterial, value); } + Material m_ActiveMaterial; + public Material meshMultiPreviewMaterial { get => m_MeshMultiPreviewMaterial; set => SetValue(ref m_MeshMultiPreviewMaterial, value); } + Material m_MeshMultiPreviewMaterial; + public Material wireMaterial { get => m_WireMaterial; set => SetValue(ref m_WireMaterial, value); } + Material m_WireMaterial; + public Material lineMaterial { get => m_LineMaterial; set => SetValue(ref m_LineMaterial, value); } + Material m_LineMaterial; + public Texture2D checkeredTexture { get => m_CheckeredTexture; set => SetValue(ref m_CheckeredTexture, value); } + Texture2D m_CheckeredTexture; + + public bool[] availableDisplayModes { get => m_AvailableDisplayModes; set => SetValue(ref m_AvailableDisplayModes, value); } + bool[] m_AvailableDisplayModes = Enumerable.Repeat(true, 7).ToArray(); + + public bool[] availableUVChannels { get => m_AvailableUVChannels; set => SetValue(ref m_AvailableUVChannels, value); } + bool[] m_AvailableUVChannels = Enumerable.Repeat(true, 8).ToArray(); + + public event Action changed; public Settings() { @@ -73,6 +102,43 @@ public void Dispose() if (lineMaterial != null) UObject.DestroyImmediate(lineMaterial); } + + void SetValue(ref T setting, T newValue) + { + if (setting == null || !setting.Equals(newValue)) + { + setting = newValue; + changed?.Invoke(); + } + } + + public void Copy(Settings other) + { + displayMode = other.displayMode; + activeUVChannel = other.activeUVChannel; + activeBlendshape = other.activeBlendshape; + drawWire = other.drawWire; + + orthoPosition = other.orthoPosition; + previewDir = other.previewDir; + lightDir = other.lightDir; + pivotPositionOffset = other.pivotPositionOffset; + zoomFactor = other.zoomFactor; + checkerTextureMultiplier = other.checkerTextureMultiplier; + + shadedPreviewMaterial = other.shadedPreviewMaterial; + activeMaterial = other.activeMaterial; + meshMultiPreviewMaterial = other.meshMultiPreviewMaterial; + wireMaterial = other.wireMaterial; + lineMaterial = other.lineMaterial; + checkeredTexture = other.checkeredTexture; + + availableDisplayModes = new bool[other.availableDisplayModes.Length]; + Array.Copy(other.availableDisplayModes, availableDisplayModes, other.availableDisplayModes.Length); + + availableUVChannels = new bool[other.availableUVChannels.Length]; + Array.Copy(other.availableUVChannels, availableUVChannels, other.availableUVChannels.Length); + } } static string[] m_DisplayModes = @@ -108,6 +174,8 @@ public Mesh mesh PreviewRenderUtility m_PreviewUtility; Settings m_Settings; + internal event Action settingsChanged; + Mesh m_BakedSkinnedMesh; List m_BlendShapes; @@ -120,6 +188,7 @@ public MeshPreview(Mesh target) m_PreviewUtility.camera.transform.position = new Vector3(5, 5, 0); m_Settings = new Settings(); + m_Settings.changed += OnSettingsChanged; m_BlendShapes = new List(); CheckAvailableAttributes(); } @@ -128,9 +197,16 @@ public void Dispose() { DestroyBakedSkinnedMesh(); m_PreviewUtility.Cleanup(); + m_Settings.changed -= OnSettingsChanged; m_Settings.Dispose(); } + + void OnSettingsChanged() + { + settingsChanged?.Invoke(this); + } + static Material CreateWireframeMaterial() { var shader = Shader.FindBuiltin("Internal-Colored.shader"); @@ -316,6 +392,11 @@ void SetBlendshape(object data) BakeSkinnedMesh(); } + internal void CopySettings(MeshPreview other) + { + m_Settings.Copy(other.m_Settings); + } + internal static void RenderMeshPreview( Mesh mesh, PreviewRenderUtility previewUtility, @@ -707,8 +788,7 @@ void MeshPreviewZoom(Rect rect, Event evt) } else { - m_Settings.orthoPosition.x = newCamPos.x; - m_Settings.orthoPosition.y = newCamPos.y; + m_Settings.orthoPosition = new Vector3(newCamPos.x, newCamPos.y, m_Settings.orthoPosition.z); } m_Settings.zoomFactor = newZoom; @@ -734,8 +814,8 @@ void MeshPreviewPan(Rect rect, Event evt) screenPos = cam.WorldToScreenPoint(m_Settings.orthoPosition); screenPos += delta; worldPos = cam.ScreenToWorldPoint(screenPos); - m_Settings.orthoPosition.x = worldPos.x; - m_Settings.orthoPosition.y = worldPos.y; + + m_Settings.orthoPosition = new Vector3(worldPos.x, worldPos.y, m_Settings.orthoPosition.z); } else { diff --git a/Editor/Mono/Inspector/ModelInspector.cs b/Editor/Mono/Inspector/ModelInspector.cs index a3e0cdf31c..e9a4feddea 100644 --- a/Editor/Mono/Inspector/ModelInspector.cs +++ b/Editor/Mono/Inspector/ModelInspector.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Rendering; @@ -14,26 +15,66 @@ namespace UnityEditor [CanEditMultipleObjects] class ModelInspector : Editor { - MeshPreview m_MeshPreview; + Dictionary m_MeshPreviews = new Dictionary(); + MeshPreview m_DirtyMeshPreview = null; + static Vector2 m_ScrollPos; void OnEnable() { - m_MeshPreview = new MeshPreview(targets?.FirstOrDefault(x => x is Mesh) as Mesh); + foreach (var previewTarget in targets) + { + var meshPreview = new MeshPreview(previewTarget as Mesh); + meshPreview.settingsChanged += OnPreviewSettingsChanged; + m_MeshPreviews.Add(previewTarget, meshPreview); + } } void OnDisable() { - m_MeshPreview.Dispose(); + foreach (var previewTarget in targets) + { + var meshPreview = m_MeshPreviews[previewTarget]; + meshPreview.settingsChanged -= OnPreviewSettingsChanged; + meshPreview.Dispose(); + } + + m_MeshPreviews.Clear(); + } + + public override void OnPreviewSettings() + { + if (m_MeshPreviews.TryGetValue(target, out var targetMeshPreview)) + targetMeshPreview.OnPreviewSettings(); + + if (m_DirtyMeshPreview != null) + { + foreach (var meshPreview in m_MeshPreviews.Values) + { + if (meshPreview != m_DirtyMeshPreview) + meshPreview.CopySettings(m_DirtyMeshPreview); + } + + m_DirtyMeshPreview = null; + } } - public override void OnPreviewSettings() => m_MeshPreview.OnPreviewSettings(); + void OnPreviewSettingsChanged(MeshPreview preview) + { + m_DirtyMeshPreview = preview; + } public override Texture2D RenderStaticPreview( string assetPath, Object[] subAssets, int width, - int height) => m_MeshPreview.RenderStaticPreview(width, height); + int height) + { + if (m_MeshPreviews.TryGetValue(target, out var meshPreview)) + return meshPreview.RenderStaticPreview(width, height); + + return null; + } public override bool HasPreviewGUI() { @@ -246,7 +287,11 @@ static void DrawColorRect(Rect rect, Color color) EditorGUI.DrawRect(new Rect(rect.x, rect.y + rect.height - 1, rect.width, 1), dimmed); } - public override void OnPreviewGUI(Rect r, GUIStyle background) => m_MeshPreview.OnPreviewGUI(r, background); + public override void OnPreviewGUI(Rect r, GUIStyle background) + { + if (m_MeshPreviews.TryGetValue(target, out var meshPreview)) + meshPreview.OnPreviewGUI(r, background); + } public override string GetInfoString() => MeshPreview.GetInfoString(target as Mesh); } diff --git a/Editor/Mono/Inspector/MonoScriptInspector.cs b/Editor/Mono/Inspector/MonoScriptInspector.cs index 094786b1f5..2782ec46a6 100644 --- a/Editor/Mono/Inspector/MonoScriptInspector.cs +++ b/Editor/Mono/Inspector/MonoScriptInspector.cs @@ -53,6 +53,12 @@ internal override void OnHeaderIconGUI(Rect iconRect) m_Icon = m_TargetObject.FindProperty("m_Icon"); } + if (m_Icon == null) + { + base.OnHeaderIconGUI(iconRect); + return; + } + Rect dropDownRect = iconRect; dropDownRect.size = EditorGUI.GetObjectIconDropDownSize(iconRect.width, iconRect.height); diff --git a/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsEditor.cs b/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsEditor.cs index db289c1e49..67df2a6204 100644 --- a/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsEditor.cs +++ b/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsEditor.cs @@ -21,6 +21,7 @@ using GraphicsDeviceType = UnityEngine.Rendering.GraphicsDeviceType; using TargetAttributes = UnityEditor.BuildTargetDiscovery.TargetAttributes; using UnityEngine.Rendering; +using UnityEngine.Scripting; // ************************************* READ BEFORE EDITING ************************************** // @@ -49,13 +50,15 @@ static Styles() class SettingsContent { - public static readonly GUIContent frameTimingStatsWebGLWarning = EditorGUIUtility.TrTextContent("Frame timing stats are supported in WebGL 2 only. Uncheck 'Automatic Graphics API' if it's set and remove the WebGL 1 API."); - public static readonly GUIContent lightmapEncodingWebGLWarning = EditorGUIUtility.TrTextContent("High quality lightmap encoding requires WebGL 2 only. Uncheck 'Automatic Graphics API' if it's set and remove the WebGL 1 API."); + public static readonly GUIContent frameTimingStatsWebGLWarning = EditorGUIUtility.TrTextContent("Frame timing stats are supported in WebGL 2 only. Uncheck 'Automatic Graphics API' if it is set and remove the WebGL 1 API."); + public static readonly GUIContent lightmapEncodingWebGLWarning = EditorGUIUtility.TrTextContent("High quality lightmap encoding requires WebGL 2. Navigate to 'WebGL Player Settings', uncheck 'Automatic Graphics API' if it is set and remove the WebGL 1 API."); + public static readonly GUIContent hdrCubemapEncodingWebGLWarning = EditorGUIUtility.TrTextContent("High quality HDR cubemap encoding requires WebGL 2. Navigate to 'WebGL Player Settings', uncheck 'Automatic Graphics API' if it is set and remove the WebGL 1 API."); public static readonly GUIContent colorSpaceAndroidWarning = EditorGUIUtility.TrTextContent("Linear colorspace requires OpenGL ES 3.0 or Vulkan, remove OpenGL ES 2 API from the list. Blit Type for non-SRP projects must be Always Blit or Auto."); public static readonly GUIContent colorSpaceWebGLWarning = EditorGUIUtility.TrTextContent("Linear colorspace requires WebGL 2, uncheck 'Automatic Graphics API' to remove WebGL 1 API. WARNING: If DXT sRGB is not supported by the browser, texture will be decompressed"); public static readonly GUIContent colorSpaceIOSWarning = EditorGUIUtility.TrTextContent("Linear colorspace requires Metal API only. Uncheck 'Automatic Graphics API' and remove OpenGL ES 2/3 APIs."); public static readonly GUIContent colorSpaceEmbeddedLinuxWarning = EditorGUIUtility.TrTextContent("Linear colorspace is not supported with OpenGL ES 2.0, uncheck 'Automatic Graphics API' to remove OpenGL ES 2 API"); public static readonly GUIContent recordingInfo = EditorGUIUtility.TrTextContent("Reordering the list will switch editor to the first available platform"); + public static readonly GUIContent appleSiliconOpenGLWarning = EditorGUIUtility.TrTextContent("OpenGL is not supported on Apple Silicon chips. Metal will be used on devices with Apple Silicon chips instead."); public static readonly GUIContent sharedBetweenPlatformsInfo = EditorGUIUtility.TrTextContent("* Shared setting between multiple platforms."); public static readonly GUIContent cursorHotspot = EditorGUIUtility.TrTextContent("Cursor Hotspot"); @@ -94,6 +97,10 @@ class SettingsContent public static readonly GUIContent strictShaderVariantMatching = EditorGUIUtility.TrTextContent("Strict shader variant matching*", "When enabled, if a shader variant is missing, Unity uses the error shader and displays an error in the Console."); public static readonly GUIContent mipStripping = EditorGUIUtility.TrTextContent("Texture MipMap Stripping*", "Remove unused texture levels from package builds, reducing package size on disk. Limits the texture quality settings to the highest mip that was included during the build."); public static readonly GUIContent enableFrameTimingStats = EditorGUIUtility.TrTextContent("Frame Timing Stats", "Enable gathering of CPU/GPU frame timing statistics."); + public static readonly GUIContent enableOpenGLProfilerGPURecorders = EditorGUIUtility.TrTextContent("OpenGL: Profiler GPU Recorders","Enable Profiler Recorders when rendering with OpenGL. Always enabled with other rendering APIs. Optional on OpenGL due to potential incompatibility with Frame Timing Stats and the GPU Profiler."); + public static readonly GUIContent openGLFrameTimingStatsOnGPURecordersOnWarning = EditorGUIUtility.TrTextContent("On OpenGL, Frame Timing Stats may disable Profiler GPU Recorders and the GPU Profiler."); + public static readonly GUIContent openGLFrameTimingStatsOnGPURecordersOffInfo = EditorGUIUtility.TrTextContent("On OpenGL, Frame Timing Stats may disable the GPU Profiler."); + public static readonly GUIContent openGLFrameTimingStatsOffGPURecordersOnInfo = EditorGUIUtility.TrTextContent("On OpenGL, Profiler GPU Recorders may disable the GPU Profiler."); public static readonly GUIContent useOSAutoRotation = EditorGUIUtility.TrTextContent("Use Animated Autorotation", "If set OS native animated autorotation method will be used. Otherwise orientation will be changed immediately."); public static readonly GUIContent defaultScreenWidth = EditorGUIUtility.TrTextContent("Default Screen Width"); public static readonly GUIContent defaultScreenHeight = EditorGUIUtility.TrTextContent("Default Screen Height"); @@ -155,8 +162,6 @@ class SettingsContent 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 assemblyVersionValidation = EditorGUIUtility.TrTextContent("Assembly Version Validation", "When Mono Resolves types from a assembly that is Strong Named, versions have to match with the one already loaded."); - public static readonly GUIContent assemblyVersionValidationEditorOnly = EditorGUIUtility.TrTextContent("Assembly Version Validation (editor only)", "When Mono Resolves types from a assembly that is Strong Named, versions have to match with the one already loaded."); 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."); @@ -194,6 +199,7 @@ class SettingsContent public static readonly GUIContent[] il2cppCodeGenerationNames = new GUIContent[] { EditorGUIUtility.TrTextContent("Faster runtime"), EditorGUIUtility.TrTextContent("Faster (smaller) builds") }; public static readonly GUIContent scriptingMono2x = EditorGUIUtility.TrTextContent("Mono"); public static readonly GUIContent scriptingIL2CPP = EditorGUIUtility.TrTextContent("IL2CPP"); + public static readonly GUIContent scriptingCoreCLR = EditorGUIUtility.TrTextContent("CoreCLR"); public static readonly GUIContent scriptingDefault = EditorGUIUtility.TrTextContent("Default"); public static readonly GUIContent strippingDisabled = EditorGUIUtility.TrTextContent("Disabled"); public static readonly GUIContent strippingMinimal = EditorGUIUtility.TrTextContent("Minimal"); @@ -217,9 +223,12 @@ class SettingsContent public static readonly GUIContent[] normalMapEncodingNames = { EditorGUIUtility.TrTextContent("XYZ"), EditorGUIUtility.TrTextContent("DXT5nm-style") }; public static readonly GUIContent lightmapEncodingLabel = EditorGUIUtility.TrTextContent("Lightmap Encoding", "Affects the encoding scheme and compression format of the lightmaps."); public static readonly GUIContent[] lightmapEncodingNames = { EditorGUIUtility.TrTextContent("Low Quality"), EditorGUIUtility.TrTextContent("Normal Quality"), EditorGUIUtility.TrTextContent("High Quality") }; + public static readonly GUIContent hdrCubemapEncodingLabel = EditorGUIUtility.TrTextContent("HDR Cubemap Encoding", "Determines which encoding scheme Unity uses to encode HDR cubemaps."); + public static readonly GUIContent[] hdrCubemapEncodingNames = { EditorGUIUtility.TrTextContent("Low Quality"), EditorGUIUtility.TrTextContent("Normal Quality"), EditorGUIUtility.TrTextContent("High Quality") }; public static readonly GUIContent lightmapStreamingEnabled = EditorGUIUtility.TrTextContent("Lightmap Streaming", "Only load larger lightmap mipmaps as needed to render the current game cameras. Requires texture streaming to be enabled in quality settings. This value is applied to the light map textures as they are generated."); public static readonly GUIContent lightmapStreamingPriority = EditorGUIUtility.TrTextContent("Streaming Priority", "Lightmap mipmap streaming priority when there's contention for resources. Positive numbers represent higher priority. Valid range is -128 to 127. This value is applied to the light map textures as they are generated."); - public static readonly GUIContent lightmapQualityAndroidWarning = EditorGUIUtility.TrTextContent("The selected Lightmap Encoding requires OpenGL ES 3.0 or Vulkan. Please remove the OpenGL ES 2 API."); + 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 virtualTexturingUnsupportedPlatformWarning = EditorGUIUtility.TrTextContent("The current target platform does not support Virtual Texturing. To build for this platform, uncheck Enable Virtual Texturing."); @@ -348,7 +357,6 @@ PlayerSettingsIconsEditor iconsEditor SerializedProperty m_SuppressCommonWarnings; SerializedProperty m_AllowUnsafeCode; SerializedProperty m_GCIncremental; - SerializedProperty m_AssemblyVersionValidation; SerializedProperty m_OverrideDefaultApplicationIdentifier; SerializedProperty m_ApplicationIdentifier; @@ -393,8 +401,6 @@ PlayerSettingsIconsEditor iconsEditor SerializedProperty m_RunInBackground; SerializedProperty m_CaptureSingleScreen; - SerializedProperty m_SupportedAspectRatios; - SerializedProperty m_SkinOnGPU; // OpenGL ES 3.1+ @@ -403,6 +409,7 @@ PlayerSettingsIconsEditor iconsEditor SerializedProperty m_RequireES32; SerializedProperty m_LightmapEncodingQuality; + SerializedProperty m_HDRCubemapEncodingQuality; SerializedProperty m_LightmapStreamingEnabled; SerializedProperty m_LightmapStreamingPriority; @@ -494,9 +501,10 @@ public SerializedProperty FindPropertyAssert(string name) Debug.LogError("Failed to find:" + name); return property; } - + private static List s_activeEditors = new List(); void OnEnable() { + s_activeEditors.Add(this); isPreset = Preset.IsEditorTargetAPreset(target); validPlatforms = BuildPlatforms.instance.GetValidPlatforms(true).ToArray(); @@ -560,7 +568,6 @@ void OnEnable() m_SuppressCommonWarnings = FindPropertyAssert("suppressCommonWarnings"); m_AllowUnsafeCode = FindPropertyAssert("allowUnsafeCode"); m_GCIncremental = FindPropertyAssert("gcIncremental"); - m_AssemblyVersionValidation = FindPropertyAssert("assemblyVersionValidation"); m_UseDeterministicCompilation = FindPropertyAssert("useDeterministicCompilation"); m_ScriptingBackend = FindPropertyAssert("scriptingBackend"); m_EnableRoslynAnalyzers = FindPropertyAssert("enableRoslynAnalyzers"); @@ -593,7 +600,6 @@ void OnEnable() m_DefaultIsNativeResolution = FindPropertyAssert("defaultIsNativeResolution"); m_MacRetinaSupport = FindPropertyAssert("macRetinaSupport"); m_CaptureSingleScreen = FindPropertyAssert("captureSingleScreen"); - m_SupportedAspectRatios = FindPropertyAssert("m_SupportedAspectRatios"); m_UsePlayerLog = FindPropertyAssert("usePlayerLog"); m_KeepLoadedShadersAlive = FindPropertyAssert("keepLoadedShadersAlive"); @@ -663,6 +669,25 @@ void OnEnable() } void OnDisable() + { + s_activeEditors.Remove(this); + HandlePendingChangesRequiringRecompilation(); + + // Ensure script compilation handling is returned to to EditorOnlyPlayerSettings + if (!isPreset) + PlayerSettings.isHandlingScriptRecompile = true; + } + + [RequiredByNativeCode] + private static void HandlePendingChangesBeforeEnterPlaymode() + { + foreach(var editor in s_activeEditors) + { + editor.HandlePendingChangesRequiringRecompilation(); + } + } + + private void HandlePendingChangesRequiringRecompilation() { if (hasScriptingDefinesBeenModified) { @@ -671,6 +696,10 @@ void OnDisable() SetScriptingDefineSymbolsForGroup(lastNamedBuildTarget, scriptingDefinesList.ToArray()); SetReason(RecompileReason.scriptingDefineSymbolsModified); } + else + { + InitReorderableScriptingDefineSymbolsList(lastNamedBuildTarget); + } hasScriptingDefinesBeenModified = false; } @@ -682,6 +711,10 @@ void OnDisable() SetAdditionalCompilerArgumentsForGroup(lastNamedBuildTarget, additionalCompilerArgumentsList.ToArray()); SetReason(RecompileReason.additionalCompilerArgumentsModified); } + else + { + InitReorderableAdditionalCompilerArgumentsList(lastNamedBuildTarget); + } hasAdditionalCompilerArgumentsBeenModified = false; } @@ -691,10 +724,6 @@ void OnDisable() serializedObject.ApplyModifiedProperties(); RecompileScripts(); } - - // Ensure script compilation handling is returned to to EditorOnlyPlayerSettings - if (!isPreset) - PlayerSettings.isHandlingScriptRecompile = true; } public void SetValueChangeListeners(UnityAction action) @@ -1068,14 +1097,6 @@ public void ResolutionSectionGUI(NamedBuildTarget namedBuildTarget, ISettingEdit EditorGUILayout.PropertyField(m_ForceSingleInstance); EditorGUILayout.PropertyField(m_UseFlipModelSwapchain, SettingsContent.useFlipModelSwapChain); - if (isPreset) - EditorGUI.indentLevel++; - - EditorGUILayout.PropertyField(m_SupportedAspectRatios, true); - - if (isPreset) - EditorGUI.indentLevel--; - EditorGUILayout.Space(); } @@ -1407,6 +1428,12 @@ void GraphicsAPIsGUIOnePlatform(BuildTargetGroup targetGroup, BuildTarget target s_GraphicsDeviceLists.Add(targetPlatform, rlist); } + + if (targetPlatform == BuildTarget.StandaloneOSX && s_GraphicsDeviceLists[BuildTarget.StandaloneOSX].list.Contains(GraphicsDeviceType.OpenGLCore)) + { + EditorGUILayout.HelpBox(SettingsContent.appleSiliconOpenGLWarning.text, MessageType.Warning, true); + } + s_GraphicsDeviceLists[targetPlatform].DoLayoutList(); //@TODO: undo @@ -1821,12 +1848,12 @@ private void OtherSectionRenderingGUI(BuildPlatform platform, ISettingEditorExte bool hdrDisplaySupported = false; bool gfxJobModesSupported = false; - bool customLightmapEncodingSupported = (platform.namedBuildTarget.ToBuildTargetGroup() == BuildTargetGroup.Standalone || platform.namedBuildTarget == NamedBuildTarget.WebGL); + bool hdrEncodingSupportedByPlatform = (platform.namedBuildTarget.ToBuildTargetGroup() == BuildTargetGroup.Standalone || platform.namedBuildTarget == NamedBuildTarget.WebGL); if (settingsExtension != null) { hdrDisplaySupported = settingsExtension.SupportsHighDynamicRangeDisplays(); gfxJobModesSupported = settingsExtension.SupportsGfxJobModes(); - customLightmapEncodingSupported = customLightmapEncodingSupported || settingsExtension.SupportsCustomLightmapEncoding(); + hdrEncodingSupportedByPlatform = hdrEncodingSupportedByPlatform || settingsExtension.SupportsCustomLightmapEncoding(); } else { @@ -1967,54 +1994,80 @@ private void OtherSectionRenderingGUI(BuildPlatform platform, ISettingEditorExte } } - // Show Lightmap Encoding quality option - if (customLightmapEncodingSupported && !isPreset) + // Show Lightmap Encoding and HDR Cubemap Encoding quality options + if (hdrEncodingSupportedByPlatform && !isPreset) { using (new EditorGUI.DisabledScope(EditorApplication.isPlaying || Lightmapping.isRunning)) { - EditorGUI.BeginChangeCheck(); - LightmapEncodingQuality encodingQuality = PlayerSettings.GetLightmapEncodingQualityForPlatformGroup(platform.namedBuildTarget.ToBuildTargetGroup()); - LightmapEncodingQuality[] lightmapEncodingValues = { LightmapEncodingQuality.Low, LightmapEncodingQuality.Normal, LightmapEncodingQuality.High }; - LightmapEncodingQuality newEncodingQuality = BuildEnumPopup(SettingsContent.lightmapEncodingLabel, encodingQuality, lightmapEncodingValues, SettingsContent.lightmapEncodingNames); - if (EditorGUI.EndChangeCheck() && encodingQuality != newEncodingQuality) { - PlayerSettings.SetLightmapEncodingQualityForPlatformGroup(platform.namedBuildTarget.ToBuildTargetGroup(), newEncodingQuality); + EditorGUI.BeginChangeCheck(); + LightmapEncodingQuality encodingQuality = PlayerSettings.GetLightmapEncodingQualityForPlatformGroup(platform.namedBuildTarget.ToBuildTargetGroup()); + LightmapEncodingQuality[] lightmapEncodingValues = { LightmapEncodingQuality.Low, LightmapEncodingQuality.Normal, LightmapEncodingQuality.High }; + LightmapEncodingQuality newEncodingQuality = BuildEnumPopup(SettingsContent.lightmapEncodingLabel, encodingQuality, lightmapEncodingValues, SettingsContent.lightmapEncodingNames); + if (EditorGUI.EndChangeCheck() && encodingQuality != newEncodingQuality) + { + PlayerSettings.SetLightmapEncodingQualityForPlatformGroup(platform.namedBuildTarget.ToBuildTargetGroup(), newEncodingQuality); - Lightmapping.OnUpdateLightmapEncoding(platform.namedBuildTarget.ToBuildTargetGroup()); + Lightmapping.OnUpdateLightmapEncoding(platform.namedBuildTarget.ToBuildTargetGroup()); - serializedObject.ApplyModifiedProperties(); + serializedObject.ApplyModifiedProperties(); - GUIUtility.ExitGUI(); - } + GUIUtility.ExitGUI(); + } - if (encodingQuality == LightmapEncodingQuality.High) - { - if (platform.namedBuildTarget == NamedBuildTarget.WebGL) + if (encodingQuality == LightmapEncodingQuality.High && + platform.namedBuildTarget == NamedBuildTarget.WebGL && + PlayerSettings.GetGraphicsAPIs(BuildTarget.WebGL).Contains(GraphicsDeviceType.OpenGLES2)) { - var apis = PlayerSettings.GetGraphicsAPIs(BuildTarget.WebGL); - if (apis.Contains(GraphicsDeviceType.OpenGLES2)) - { - EditorGUILayout.HelpBox(SettingsContent.lightmapEncodingWebGLWarning.text, MessageType.Warning); - } + EditorGUILayout.HelpBox(SettingsContent.lightmapEncodingWebGLWarning.text, MessageType.Warning); + } + + if (encodingQuality != LightmapEncodingQuality.Low && platform.namedBuildTarget == NamedBuildTarget.Android) + { + var apis = PlayerSettings.GetGraphicsAPIs(BuildTarget.Android); + var hasMinAPI = (apis.Contains(GraphicsDeviceType.Vulkan) || apis.Contains(GraphicsDeviceType.OpenGLES3)) && !apis.Contains(GraphicsDeviceType.OpenGLES2); + if (!hasMinAPI) + EditorGUILayout.HelpBox(SettingsContent.lightmapQualityAndroidWarning.text, MessageType.Warning); } } - if (encodingQuality != LightmapEncodingQuality.Low) { - if (platform.namedBuildTarget == NamedBuildTarget.Android) + EditorGUI.BeginChangeCheck(); + HDRCubemapEncodingQuality encodingQuality = PlayerSettings.GetHDRCubemapEncodingQualityForPlatformGroup(platform.namedBuildTarget.ToBuildTargetGroup()); + HDRCubemapEncodingQuality[] hdrCubemapProbeEncodingValues = { HDRCubemapEncodingQuality.Low, HDRCubemapEncodingQuality.Normal, HDRCubemapEncodingQuality.High }; + HDRCubemapEncodingQuality newEncodingQuality = BuildEnumPopup(SettingsContent.hdrCubemapEncodingLabel, encodingQuality, hdrCubemapProbeEncodingValues, SettingsContent.hdrCubemapEncodingNames); + if (EditorGUI.EndChangeCheck() && encodingQuality != newEncodingQuality) + { + PlayerSettings.SetHDRCubemapEncodingQualityForPlatformGroup(platform.namedBuildTarget.ToBuildTargetGroup(), newEncodingQuality); + + Lightmapping.OnUpdateHDRCubemapEncoding(platform.namedBuildTarget.ToBuildTargetGroup()); + + serializedObject.ApplyModifiedProperties(); + + GUIUtility.ExitGUI(); + } + + if (encodingQuality == HDRCubemapEncodingQuality.High && + platform.namedBuildTarget == NamedBuildTarget.WebGL && + PlayerSettings.GetGraphicsAPIs(BuildTarget.WebGL).Contains(GraphicsDeviceType.OpenGLES2)) + { + EditorGUILayout.HelpBox(SettingsContent.hdrCubemapEncodingWebGLWarning.text, MessageType.Warning); + } + + if (encodingQuality != HDRCubemapEncodingQuality.Low && platform.namedBuildTarget == NamedBuildTarget.Android) { var apis = PlayerSettings.GetGraphicsAPIs(BuildTarget.Android); var hasMinAPI = (apis.Contains(GraphicsDeviceType.Vulkan) || apis.Contains(GraphicsDeviceType.OpenGLES3)) && !apis.Contains(GraphicsDeviceType.OpenGLES2); if (!hasMinAPI) - EditorGUILayout.HelpBox(SettingsContent.lightmapQualityAndroidWarning.text, MessageType.Warning); + EditorGUILayout.HelpBox(SettingsContent.hdrCubemapQualityAndroidWarning.text, MessageType.Warning); } } } } - // Light map settings if (!isPreset) { + // Light map settings using (new EditorGUI.DisabledScope(EditorApplication.isPlaying || Lightmapping.isRunning)) { bool streamingEnabled = PlayerSettings.GetLightmapStreamingEnabledForPlatformGroup(platform.namedBuildTarget.ToBuildTargetGroup()); @@ -2041,6 +2094,7 @@ private void OtherSectionRenderingGUI(BuildPlatform platform, ISettingEditorExte } } + // Tickbox for Frame Timing Stats. if (platform.namedBuildTarget.ToBuildTargetGroup() == BuildTargetGroup.Standalone || platform.namedBuildTarget == NamedBuildTarget.WindowsStoreApps || platform.namedBuildTarget == NamedBuildTarget.WebGL || (settingsExtension != null && settingsExtension.SupportsFrameTimingStatistics())) { PlayerSettings.enableFrameTimingStats = EditorGUILayout.Toggle(SettingsContent.enableFrameTimingStats, PlayerSettings.enableFrameTimingStats); @@ -2052,6 +2106,24 @@ private void OtherSectionRenderingGUI(BuildPlatform platform, ISettingEditorExte EditorGUILayout.HelpBox(SettingsContent.frameTimingStatsWebGLWarning.text, MessageType.Warning); } } + if(PlayerSettings.enableFrameTimingStats) + { + EditorGUILayout.HelpBox(SettingsContent.openGLFrameTimingStatsOnGPURecordersOffInfo.text, MessageType.Info); + } + } + + // Tickbox for OpenGL-only option to toggle Profiler GPU Recorders. + if (platform.namedBuildTarget == NamedBuildTarget.Standalone || platform.namedBuildTarget == NamedBuildTarget.Android) + { + PlayerSettings.enableOpenGLProfilerGPURecorders = EditorGUILayout.Toggle(SettingsContent.enableOpenGLProfilerGPURecorders, PlayerSettings.enableOpenGLProfilerGPURecorders); + + // Add different notes/warnings depending on the tickbox combinations. + // These concern Frame Timing Stats as well as Profiler GPU Recorders, + // so are listed below both to (hopefully) highlight that they're linked. + if (PlayerSettings.enableOpenGLProfilerGPURecorders) + { + EditorGUILayout.HelpBox(SettingsContent.openGLFrameTimingStatsOffGPURecordersOnInfo.text, MessageType.Info); + } } if (hdrDisplaySupported) @@ -2201,7 +2273,6 @@ private void OtherSectionIdentificationGUI(BuildPlatform platform, ISettingEdito GUILayout.Label(SettingsContent.macAppStoreTitle, EditorStyles.boldLabel); EditorGUILayout.PropertyField(m_OverrideDefaultApplicationIdentifier, EditorGUIUtility.TrTextContent("Override Default Bundle Identifier")); - string defaultIdentifier = String.Format("com.{0}.{1}", m_CompanyName.stringValue, m_ProductName.stringValue); using (var horizontal = new EditorGUILayout.HorizontalScope()) { @@ -2209,7 +2280,7 @@ private void OtherSectionIdentificationGUI(BuildPlatform platform, ISettingEdito { using (new EditorGUI.IndentLevelScope()) { - PlayerSettingsEditor.ShowApplicationIdentifierUI(m_ApplicationIdentifier, BuildTargetGroup.Standalone, m_OverrideDefaultApplicationIdentifier.boolValue, defaultIdentifier, "Bundle Identifier", "'CFBundleIdentifier'"); + ShowApplicationIdentifierUI(BuildTargetGroup.Standalone, "Bundle Identifier", "'CFBundleIdentifier'"); } } } @@ -2258,17 +2329,33 @@ internal static GUIContent GetApplicationIdentifierError(BuildTargetGroup target return SettingsContent.applicationIdentifierError; } - internal static void ShowApplicationIdentifierUI(SerializedProperty prop, BuildTargetGroup targetGroup, bool overrideDefaultID, string defaultID, string label, string tooltip) + internal void ShowApplicationIdentifierUI(BuildTargetGroup targetGroup, string label, string tooltip) { + var overrideDefaultID = m_OverrideDefaultApplicationIdentifier.boolValue; + var defaultIdentifier = String.Format("com.{0}.{1}", m_CompanyName.stringValue, m_ProductName.stringValue); var oldIdentifier = ""; - var currentIdentifier = PlayerSettings.SanitizeApplicationIdentifier(defaultID, targetGroup); + var currentIdentifier = PlayerSettings.SanitizeApplicationIdentifier(defaultIdentifier, targetGroup); var buildTargetGroup = BuildPipeline.GetBuildTargetGroupName(targetGroup); var warningMessage = SettingsContent.applicationIdentifierWarning.text; var errorMessage = GetApplicationIdentifierError(targetGroup).text; - if (!prop.serializedObject.isEditingMultipleObjects) + string GetSanitizedApplicationIdentifier() { - prop.TryGetMapEntry(buildTargetGroup, out var entry); + var sanitizedIdentifier = PlayerSettings.SanitizeApplicationIdentifier(currentIdentifier, targetGroup); + + if (currentIdentifier != oldIdentifier) { + if (!overrideDefaultID && !PlayerSettings.IsApplicationIdentifierValid(currentIdentifier, targetGroup)) + Debug.LogError(errorMessage); + else if (overrideDefaultID && sanitizedIdentifier != currentIdentifier) + Debug.LogWarning(warningMessage); + } + + return sanitizedIdentifier; + } + + if (!m_ApplicationIdentifier.serializedObject.isEditingMultipleObjects) + { + m_ApplicationIdentifier.TryGetMapEntry(buildTargetGroup, out var entry); if (entry != null) oldIdentifier = entry.FindPropertyRelative("second").stringValue; @@ -2278,7 +2365,7 @@ internal static void ShowApplicationIdentifierUI(SerializedProperty prop, BuildT if (overrideDefaultID) currentIdentifier = oldIdentifier; else - prop.SetMapValue(buildTargetGroup, currentIdentifier); + m_ApplicationIdentifier.SetMapValue(buildTargetGroup, currentIdentifier); } EditorGUILayout.BeginVertical(); @@ -2286,25 +2373,21 @@ internal static void ShowApplicationIdentifierUI(SerializedProperty prop, BuildT using (new EditorGUI.DisabledScope(!overrideDefaultID)) { - var sanitizedIdentifier = PlayerSettings.SanitizeApplicationIdentifier(currentIdentifier, targetGroup); - if (currentIdentifier != oldIdentifier && (sanitizedIdentifier != currentIdentifier || currentIdentifier != defaultID)) - Debug.LogError(warningMessage); - currentIdentifier = sanitizedIdentifier; + currentIdentifier = GetSanitizedApplicationIdentifier(); currentIdentifier = EditorGUILayout.TextField(EditorGUIUtility.TrTextContent(label, tooltip), currentIdentifier); } if (EditorGUI.EndChangeCheck()) { - var sanitizedIdentifier = PlayerSettings.SanitizeApplicationIdentifier(currentIdentifier, targetGroup); - if (currentIdentifier != oldIdentifier && (sanitizedIdentifier != currentIdentifier || currentIdentifier != defaultID)) - Debug.LogError(warningMessage); - currentIdentifier = sanitizedIdentifier; - prop.SetMapValue(buildTargetGroup, currentIdentifier); + currentIdentifier = GetSanitizedApplicationIdentifier(); + m_ApplicationIdentifier.SetMapValue(buildTargetGroup, currentIdentifier); } - if (!PlayerSettings.IsApplicationIdentifierValid(currentIdentifier, targetGroup)) + if (currentIdentifier == "com.Company.ProductName" || currentIdentifier == "com.unity3d.player") + EditorGUILayout.HelpBox("Don't forget to set the Application Identifier.", MessageType.Warning); + else if (!PlayerSettings.IsApplicationIdentifierValid(currentIdentifier, targetGroup)) EditorGUILayout.HelpBox(errorMessage, MessageType.Error); - else if (!overrideDefaultID && currentIdentifier != defaultID) + else if (!overrideDefaultID && currentIdentifier != defaultIdentifier) EditorGUILayout.HelpBox(warningMessage, MessageType.Warning); EditorGUILayout.EndVertical(); @@ -2532,9 +2615,6 @@ private void OtherSectionConfigurationGUI(BuildPlatform platform, ISettingEditor m_GCIncremental.boolValue = oldValue; } } - - EditorGUILayout.PropertyField(m_AssemblyVersionValidation, - (PlayerSettings.GetScriptingBackend(platform.namedBuildTarget) != ScriptingImplementation.Mono2x) ? SettingsContent.assemblyVersionValidationEditorOnly : SettingsContent.assemblyVersionValidation); } var insecureHttp = BuildEnumPopup(m_InsecureHttpOption, SettingsContent.insecureHttpOption, new[] { InsecureHttpOption.NotAllowed, InsecureHttpOption.DevelopmentOnly, InsecureHttpOption.AlwaysAllowed }, SettingsContent.insecureHttpOptions); @@ -3097,6 +3177,9 @@ static GUIContent GetNiceScriptingBackendName(ScriptingImplementation scriptingB return SettingsContent.scriptingMono2x; case ScriptingImplementation.IL2CPP: return SettingsContent.scriptingIL2CPP; + #pragma warning disable 618 + case ScriptingImplementation.CoreCLR: + return SettingsContent.scriptingCoreCLR; default: throw new ArgumentException($"Scripting backend value {scriptingBackend} is not supported.", nameof(scriptingBackend)); } diff --git a/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsSplashScreenEditor.cs b/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsSplashScreenEditor.cs index e56ed05d1f..db95a28427 100644 --- a/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsSplashScreenEditor.cs +++ b/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsSplashScreenEditor.cs @@ -48,13 +48,18 @@ internal partial class PlayerSettingsSplashScreenEditor static readonly float k_LogoListPropertyLabelWidth = 100; static readonly float k_MinPersonalEditionOverlayOpacity = 0.5f; static readonly float k_MinProEditionOverlayOpacity = 0.0f; + static readonly Color32 k_DarkOnLightBgColor = new Color32(204, 204, 204, 255);// #CCCCCC + static readonly Color32 k_LightOnDarkBgColor = new Color32(35, 31, 32, 255); - static Sprite s_UnityLogo; + static Sprite s_UnityLogoLight; // We use this version as a placeholder when the logo is in the list. + static Sprite s_UnityLogoDark; readonly AnimBool m_ShowAnimationControlsAnimator = new AnimBool(); readonly AnimBool m_ShowBackgroundColorAnimator = new AnimBool(); readonly AnimBool m_ShowLogoControlsAnimator = new AnimBool(); + Sprite UnityLogo => m_SplashScreenLogoStyle.intValue == (int)PlayerSettings.SplashScreen.UnityLogoStyle.DarkOnLight ? s_UnityLogoDark : s_UnityLogoLight; + // If the user changes an asset(delete, re-import etc) then we should cancel the splash screen to avoid using invalid data. (case 857060) class CancelSplashScreenOnAssetChange : AssetPostprocessor { @@ -137,8 +142,10 @@ public void OnEnable() m_ShowLogoControlsAnimator.value = m_ShowUnitySplashLogo.boolValue; SetValueChangeListeners(m_Owner.Repaint); - if (s_UnityLogo == null) - s_UnityLogo = Resources.GetBuiltinResource("UnitySplash-cube.png"); + if (s_UnityLogoLight == null) + s_UnityLogoLight = AssetDatabase.GetBuiltinExtraResource("SplashScreen/UnitySplash-Light.png"); + if (s_UnityLogoDark == null) + s_UnityLogoDark = AssetDatabase.GetBuiltinExtraResource("SplashScreen/UnitySplash-Dark.png"); } internal void SetValueChangeListeners(UnityAction action) @@ -167,10 +174,7 @@ private void DrawElementUnityLogo(Rect rect, int index, bool isActive, bool isFo // Unity logo float logoWidth = Mathf.Clamp(rect.width - k_LogoListPropertyMinWidth, k_LogoListUnityLogoMinWidth, k_LogoListUnityLogoMaxWidth); var logoRect = new Rect(rect.x, rect.y, logoWidth, rect.height); - var oldCol = GUI.color; - GUI.color = (m_SplashScreenLogoStyle.intValue == (int)PlayerSettings.SplashScreen.UnityLogoStyle.DarkOnLight ? Color.black : Color.white); - GUI.DrawTexture(logoRect, s_UnityLogo.texture, ScaleMode.ScaleToFit); - GUI.color = oldCol; + GUI.DrawTexture(logoRect, UnityLogo.texture, ScaleMode.ScaleToFit); // Properties var oldLabelWidth = EditorGUIUtility.labelWidth; @@ -194,7 +198,7 @@ private void DrawLogoListElementCallback(Rect rect, int index, bool isActive, bo var element = m_SplashScreenLogos.GetArrayElementAtIndex(index); var logo = element.FindPropertyRelative("logo"); - if ((Sprite)logo.objectReferenceValue == s_UnityLogo) + if ((Sprite)logo.objectReferenceValue == s_UnityLogoLight) { DrawElementUnityLogo(rect, index, isActive, isFocused); return; @@ -244,11 +248,11 @@ private void OnLogoListAddCallback(ReorderableList list) } // Prevent users removing the unity logo. - private static bool OnLogoListCanRemoveCallback(ReorderableList list) + private bool OnLogoListCanRemoveCallback(ReorderableList list) { var element = list.serializedProperty.GetArrayElementAtIndex(list.index); var logo = (Sprite)element.FindPropertyRelative("logo").objectReferenceValue; - return logo != s_UnityLogo; + return logo != s_UnityLogoLight; } private void AddUnityLogoToLogosList() @@ -258,7 +262,7 @@ private void AddUnityLogoToLogosList() { var listElement = m_SplashScreenLogos.GetArrayElementAtIndex(i); var listLogo = listElement.FindPropertyRelative("logo"); - if ((Sprite)listLogo.objectReferenceValue == s_UnityLogo) + if ((Sprite)listLogo.objectReferenceValue == s_UnityLogoLight) return; } @@ -266,7 +270,7 @@ private void AddUnityLogoToLogosList() var element = m_SplashScreenLogos.GetArrayElementAtIndex(0); var logo = element.FindPropertyRelative("logo"); var duration = element.FindPropertyRelative("duration"); - logo.objectReferenceValue = s_UnityLogo; + logo.objectReferenceValue = s_UnityLogoLight; duration.floatValue = k_DefaultLogoTime; } @@ -276,7 +280,7 @@ private void RemoveUnityLogoFromLogosList() { var element = m_SplashScreenLogos.GetArrayElementAtIndex(i); var logo = element.FindPropertyRelative("logo"); - if ((Sprite)logo.objectReferenceValue == s_UnityLogo) + if ((Sprite)logo.objectReferenceValue == s_UnityLogoLight) { m_SplashScreenLogos.DeleteArrayElementAtIndex(i); --i; // Continue checking in case we have duplicates. @@ -366,7 +370,15 @@ private void BuiltinCustomSplashScreenGUI(BuildTargetGroup targetGroup, ISetting GameView.RepaintAll(); } + EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(m_SplashScreenLogoStyle, k_Texts.splashStyle); + if (EditorGUI.EndChangeCheck()) + { + if (m_SplashScreenLogoStyle.intValue == (int)PlayerSettings.SplashScreen.UnityLogoStyle.DarkOnLight) + m_SplashScreenBackgroundColor.colorValue = k_DarkOnLightBgColor; + else + m_SplashScreenBackgroundColor.colorValue = k_LightOnDarkBgColor; + } // Animation EditorGUILayout.PropertyField(m_SplashScreenAnimation, k_Texts.animate); diff --git a/Editor/Mono/Inspector/PolygonCollider2DEditor.cs b/Editor/Mono/Inspector/PolygonCollider2DEditor.cs index 7dfd57e3ab..30c0050919 100644 --- a/Editor/Mono/Inspector/PolygonCollider2DEditor.cs +++ b/Editor/Mono/Inspector/PolygonCollider2DEditor.cs @@ -36,7 +36,7 @@ public override void OnInspectorGUI() } else { - BeginColliderInspector(); + BeginEditColliderInspector(); } // Grab this as the offset to the top of the drag target. @@ -52,8 +52,6 @@ public override void OnInspectorGUI() EditorGUI.EndDisabledGroup(); } - EndColliderInspector(); - FinalizeInspectorGUI(); HandleDragAndDrop(GUILayoutUtility.GetLastRect()); diff --git a/Editor/Mono/Inspector/PolygonEditorUtility.cs b/Editor/Mono/Inspector/PolygonEditorUtility.cs index 9fe0573ef1..581d1d4dca 100644 --- a/Editor/Mono/Inspector/PolygonEditorUtility.cs +++ b/Editor/Mono/Inspector/PolygonEditorUtility.cs @@ -44,7 +44,7 @@ public void Reset() m_HandleEdge = false; } - private void UndoRedoPerformed() + private void UndoRedoPerformed(in UndoRedoInfo info) { if (m_ActiveCollider != null) { @@ -56,7 +56,7 @@ private void UndoRedoPerformed() public void StartEditing(Collider2D collider) { - Undo.undoRedoPerformed += UndoRedoPerformed; + Undo.undoRedoEvent += UndoRedoPerformed; Reset(); @@ -88,7 +88,7 @@ public void StopEditing() PolygonEditor.StopEditing(); m_ActiveCollider = null; - Undo.undoRedoPerformed -= UndoRedoPerformed; + Undo.undoRedoEvent -= UndoRedoPerformed; } public void OnSceneGUI() diff --git a/Editor/Mono/Inspector/PreviewRenderUtility.cs b/Editor/Mono/Inspector/PreviewRenderUtility.cs index 260924c6ac..29aa496442 100644 --- a/Editor/Mono/Inspector/PreviewRenderUtility.cs +++ b/Editor/Mono/Inspector/PreviewRenderUtility.cs @@ -280,17 +280,14 @@ public void BeginStaticPreview(Rect r) Graphics.DrawTexture(new Rect(0, 0, m_RenderTexture.width, m_RenderTexture.height), darkGreyBackground); Object.DestroyImmediate(darkGreyBackground); - if (!EditorApplication.isUpdating) + if (!EditorApplication.isUpdating && Unsupported.SetOverrideLightingSettings(previewScene.scene)) { - var oldProbe = RenderSettings.ambientProbe; - Texture defaultEnvTexture = ReflectionProbe.defaultTexture; - if (Unsupported.SetOverrideLightingSettings(previewScene.scene)) - { - // Most preview windows just want the light probe from the main scene so by default we copy it here. It can then be overridden if user wants. - RenderSettings.ambientProbe = oldProbe; - RenderSettings.defaultReflectionMode = UnityEngine.Rendering.DefaultReflectionMode.Custom; - RenderSettings.customReflectionTexture = defaultEnvTexture; - } + // 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; } } diff --git a/Editor/Mono/Inspector/PropertyEditor.cs b/Editor/Mono/Inspector/PropertyEditor.cs index 1573176c17..cb377c319e 100644 --- a/Editor/Mono/Inspector/PropertyEditor.cs +++ b/Editor/Mono/Inspector/PropertyEditor.cs @@ -6,9 +6,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using UnityEditor.AddComponent; using UnityEditor.VersionControl; using UnityEngine; +using UnityEngine.Scripting; using UnityEditorInternal; using UnityEditorInternal.VersionControl; using UnityEditor.StyleSheets; @@ -20,6 +22,7 @@ using AssetImporterEditor = UnityEditor.AssetImporters.AssetImporterEditor; using JetBrains.Annotations; using Unity.Profiling; +using UnityEditor.UIElements; namespace UnityEditor { @@ -27,7 +30,6 @@ interface IPropertyView { ActiveEditorTracker tracker { get; } InspectorMode inspectorMode { get; } - bool useUIElementsDefaultInspector { get; } HashSet editorsWithImportedObjectLabel { get; } Editor lastInteractedEditor { get; set; } GUIView parent { get; } @@ -37,6 +39,7 @@ interface IPropertyView bool WasEditorVisible(Editor[] editors, int editorIndex, Object target); bool ShouldCullEditor(Editor[] editors, int editorIndex); void Repaint(); + void UnsavedChangesStateChanged(Editor editor, bool value); } interface IPropertySourceOpener @@ -92,6 +95,11 @@ class PropertyEditor : EditorWindow, IPropertyView, IHasCustomMenu protected Component[] m_ComponentsInPrefabSource; protected HashSet m_RemovedComponents; protected HashSet m_SuppressedComponents; + // Map that maps from editorIndex to list of removed asset components + // This is later used to determine if removed components visual elements need to be added to some editor at some index + protected Dictionary> m_RemovedComponentDict; + // List of removed components at the end of the editor list that needs to have visual elements append to the editor list + protected List m_AdditionalRemovedComponents; protected bool m_ResetKeyboardControl; internal bool m_OpenAddComponentMenu = false; protected ActiveEditorTracker m_Tracker; @@ -121,7 +129,6 @@ class PropertyEditor : EditorWindow, IPropertyView, IHasCustomMenu public GUIView parent => m_Parent; public HashSet editorsWithImportedObjectLabel { get; } = new HashSet(); public EditorDragging editorDragging { get; } - public bool useUIElementsDefaultInspector { get; internal set; } = false; internal int inspectorElementModeOverride { get; set; } = 0; public Editor lastInteractedEditor { get; set; } internal static PropertyEditor HoveredPropertyEditor { get; private set; } @@ -319,7 +326,7 @@ protected virtual void OnEnable() CreateTracker(); EditorApplication.focusChanged += OnFocusChanged; - Undo.undoRedoPerformed += OnUndoRedoPerformed; + Undo.undoRedoEvent += OnUndoRedoPerformed; PrefabUtility.prefabInstanceUnpacked += OnPrefabInstanceUnpacked; ObjectChangeEvents.changesPublished += OnObjectChanged; @@ -341,7 +348,7 @@ protected virtual void OnDisable() m_LastVerticalScrollValue = m_ScrollView?.verticalScroller.value ?? 0; EditorApplication.focusChanged -= OnFocusChanged; - Undo.undoRedoPerformed -= OnUndoRedoPerformed; + Undo.undoRedoEvent -= OnUndoRedoPerformed; PrefabUtility.prefabInstanceUnpacked -= OnPrefabInstanceUnpacked; ObjectChangeEvents.changesPublished -= OnObjectChanged; @@ -369,12 +376,6 @@ protected virtual void OnLostFocus() protected virtual bool CloseIfEmpty() { - // It should never close if its tracker is not locked. - if (!tracker.isLocked) - { - return false; - } - // We can rely on the tracker to always keep valid Objects // even after an assemblyreload or assetdatabase refresh. List locked = new List(); @@ -394,6 +395,7 @@ protected virtual void OnInspectorUpdate() // Check if scripts have changed without calling set dirty tracker.VerifyModifiedMonoBehaviours(); + InspectorUtility.DirtyLivePropertyChanges(tracker); if (!tracker.isDirty || !ReadyToRepaint()) return; @@ -531,9 +533,8 @@ private void OnGeometryChanged(GeometryChangedEvent e) RestoreVerticalScrollIfNeeded(); } - private void SetUseUIEDefaultInspector() + internal void ClearEditorsAndRebuild() { - useUIElementsDefaultInspector = !useUIElementsDefaultInspector; // Clear the editors Element so that a real rebuild is done editorsElement.Clear(); m_EditorElementUpdater.Clear(); @@ -563,7 +564,6 @@ public virtual void AddDebugItemsToMenu(GenericMenu menu) if (Unsupported.IsDeveloperMode()) { menu.AddItem(EditorGUIUtility.TrTextContent("Debug-Internal"), m_InspectorMode == InspectorMode.DebugInternal, SetDebugInternal); - menu.AddItem(EditorGUIUtility.TrTextContent("Use UI Toolkit Default Inspector"), useUIElementsDefaultInspector, SetUseUIEDefaultInspector); } } @@ -723,7 +723,7 @@ protected virtual void UpdateWindowObjectNameTitle() Repaint(); } - private void OnUndoRedoPerformed() + private void OnUndoRedoPerformed(in UndoRedoInfo info) { // Early out if we have no removed or suppressed components. // It's only a change from suppressed to removed or vice versa that needs to be handled specially on undo/redo. @@ -735,11 +735,87 @@ private void OnUndoRedoPerformed() RebuildContentsContainers(); } + private void DetermineInsertionPointOfVisualElementForRemovedComponent(int targetGameObjectIndex, Editor[] editors) + { + // Calculate which editor should have the removed component visual element added, if any. + // The visual element for removed components is added to the top of the chosen editor. + // It is assumed the asset components comes in the same order in the editors list, but there can be additional added + // components in the editors list, i.e. the editors for assets components can't be moved by the user + // Added components can appear anywhere in the editors list + if (m_RemovedComponentDict == null) + m_RemovedComponentDict = new Dictionary>(); + if (m_AdditionalRemovedComponents == null) + m_AdditionalRemovedComponents = new List(); + + m_RemovedComponentDict.Clear(); + m_AdditionalRemovedComponents.Clear(); + + int editorIndex = targetGameObjectIndex + 1; + + foreach(var sourceComponent in m_ComponentsInPrefabSource) + { + // editorCounter is used to look forward in the list of editor, this is because added components might have be inserted between prefab components + // it starts at editorIndex because there is no need to look at the previous editors, based on the assumption that asset components and instance components + // always comes in the same order + int editorCounter = editorIndex; + + // Move forwards through the list of editors to find one that matches the asset component + while (editorCounter < editors.Length) + { + Object editorTarget = editors[editorCounter].target; + + // Skip added Components + if (editorTarget is Component && PrefabUtility.GetPrefabInstanceHandle(editorTarget) == null) + { + // If editorIndex and editorCounter are identical we also increment the editor index because we don't + // want to add the removed component visual element to added components editors + // For consistency the visual element for removed components are never added to added components, so if the current + // editor is an added component we skip the current editor + if (editorIndex == editorCounter) + ++editorIndex; + ++editorCounter; + continue; + } + + Object correspondingSource = PrefabUtility.GetCorrespondingObjectFromSource(editorTarget); + if (correspondingSource == sourceComponent) + { + // When we found an editor that matches the asset component, we move the start index because we don't have to test + // this editor again + editorIndex = editorCounter + 1; + break; + } + + ++editorCounter; + } + + // If the forward looking counter has reached the end of the editors list, the component must have been removed from the instance + if (editorCounter >= editors.Length) + { + // If the editorIndex has also reached the end we have removed components at the end of the list and those need to have their + // visual element added separately + if (editorIndex >= editors.Length) + { + m_AdditionalRemovedComponents.Add(sourceComponent); + } + else + { + if (!m_RemovedComponentDict.ContainsKey(editorIndex)) + m_RemovedComponentDict.Add(editorIndex, new List()); + + m_RemovedComponentDict[editorIndex].Add(sourceComponent); + } + } + } + } + private void ExtractPrefabComponents() { m_LastInitialEditorInstanceID = m_Tracker.activeEditors.Length == 0 ? 0 : m_Tracker.activeEditors[0].GetInstanceID(); m_ComponentsInPrefabSource = null; + m_RemovedComponentDict = null; + m_AdditionalRemovedComponents = null; if (m_RemovedComponents == null) { m_RemovedComponents = new HashSet(); @@ -895,6 +971,13 @@ internal virtual void RebuildContentsContainers() { m_RemovedPrefabComponentsElement.RemoveFromHierarchy(); m_RemovedPrefabComponentsElement = null; + + } + + if (m_RemovedComponentDict != null) + { + m_RemovedComponentDict = null; + m_AdditionalRemovedComponents = null; } BeginRebuildContentContainers(); @@ -965,7 +1048,7 @@ internal virtual void RebuildContentsContainers() k_CreateInspectorElements.Begin(); // Only trigger the fixed count and viewport creation if this is the first build. Otherwise let the update method handle it. - if (m_EditorElementUpdater.Position == 0) + if (m_EditorElementUpdater.Position == 0 && editors.Any()) { // Force create a certain number of inspector elements without invoking a layout pass. // We always want a minimum number of elements to be added. @@ -1124,7 +1207,7 @@ protected IPreviewable[] GetEditorsWithPreviews(Editor[] editors) foreach (Editor e in editors) { ++i; - if (e.target == null) + if (!e || e.target == null) continue; // If target is an asset, but not the same asset as the asset @@ -1176,14 +1259,15 @@ private void ResetKeyboardControl() } } - private static bool HasLabel(Object target) + private static bool IsOpenForEdit(Object target) { - return HasLabel(target, AssetDatabase.GetAssetPath(target)); - } + if (EditorUtility.IsPersistent(target)) + { + var assetPath = AssetDatabase.GetAssetPath(target); + return Provider.PathHasMetaFile(assetPath) && AssetDatabase.IsMetaFileOpenForEdit(target); + } - private static bool HasLabel(Object target, string assetPath) - { - return EditorUtility.IsPersistent(target) && assetPath.StartsWith("assets", StringComparison.OrdinalIgnoreCase); + return false; } private Object[] GetInspectedAssets() @@ -1193,13 +1277,13 @@ private Object[] GetInspectedAssets() if (assetEditor != null && assetEditor.targets.Length == 1) { string assetPath = AssetDatabase.GetAssetPath(assetEditor.target); - if (HasLabel(assetEditor.target, assetPath) && !Directory.Exists(assetPath)) + if (IsOpenForEdit(assetEditor.target) && !Directory.Exists(assetPath)) return assetEditor.targets; } // 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(HasLabel).ToArray(); + return Selection.objects.Where(IsOpenForEdit).ToArray(); } protected virtual bool BeginDrawPreviewAndLabels() { return true; } @@ -1712,7 +1796,6 @@ private void DrawEditors(Editor[] editors) if (m_RemovedComponents == null) ExtractPrefabComponents(); // needed after assembly reload (due to HashSet not being serializable) - int prefabComponentIndex = -1; int targetGameObjectIndex = -1; GameObject targetGameObject = null; if (m_ComponentsInPrefabSource != null) @@ -1721,30 +1804,23 @@ private void DrawEditors(Editor[] editors) targetGameObject = (GameObject)editors[targetGameObjectIndex].target; } + if (m_ComponentsInPrefabSource != null && m_RemovedComponents.Count > 0 && m_RemovedComponentDict == null) + DetermineInsertionPointOfVisualElementForRemovedComponent(targetGameObjectIndex, editors); + for (int editorIndex = 0; editorIndex < editors.Length; editorIndex++) { + if (!editors[editorIndex]) + continue; editors[editorIndex].propertyViewer = this; + VisualElement prefabsComponentElement = new VisualElement() { name = "PrefabComponentElement" }; - if (m_ComponentsInPrefabSource != null && editorIndex > targetGameObjectIndex) - { - if (prefabComponentIndex == -1) - prefabComponentIndex = 0; - while (prefabComponentIndex < m_ComponentsInPrefabSource.Length) - { - Object target = editors[editorIndex].target; - // This is possible if there's a component with a missing script. - if (target != null) - { - Object correspondingSource = PrefabUtility.GetCorrespondingObjectFromSource(target); - Component nextInSource = m_ComponentsInPrefabSource[prefabComponentIndex]; + Object target = editors[editorIndex].target; - if (correspondingSource == nextInSource) - break; - AddRemovedPrefabComponentElement(targetGameObject, nextInSource, prefabsComponentElement); - } - prefabComponentIndex++; - } - prefabComponentIndex++; + if (m_RemovedComponentDict != null && m_RemovedComponentDict.ContainsKey(editorIndex)) + { + var objectList = m_RemovedComponentDict[editorIndex]; + foreach (var sourceComponent in objectList) + AddRemovedPrefabComponentElement(targetGameObject, sourceComponent, prefabsComponentElement); } try @@ -1766,10 +1842,10 @@ private void DrawEditors(Editor[] editors) ? "Nothing Selected" : ObjectNames.GetInspectorTitle(editorTarget); culledEditorContainer = - EditorUIService.instance.CreateCulledEditorElement(editorIndex, this, editorTitle); + new UIElements.EditorElement(editorIndex, this, true) { name = editorTitle }; editorsElement.Add(culledEditorContainer as VisualElement); - if (!EditorUIService.disableInspectorElementThrottling) + if (!InspectorElement.disabledThrottling) m_EditorElementUpdater.Add(culledEditorContainer); } @@ -1781,10 +1857,9 @@ private void DrawEditors(Editor[] editors) string editorTitle = editorTarget == null ? "Nothing Selected" : $"{editor.GetType().Name}_{editorTarget.GetType().Name}_{editorTarget.GetInstanceID()}"; - editorContainer = EditorUIService.instance.CreateEditorElement(editorIndex, this, editorTitle); + editorContainer = new UIElements.EditorElement(editorIndex, this) { name = editorTitle }; editorsElement.Add(editorContainer as VisualElement); - - if (!EditorUIService.disableInspectorElementThrottling) + if (!InspectorElement.disabledThrottling) m_EditorElementUpdater.Add(editorContainer); } @@ -1810,16 +1885,11 @@ private void DrawEditors(Editor[] editors) } // Make sure to display any remaining removed components that come after the last component on the GameObject. - if (m_ComponentsInPrefabSource != null) + if (m_AdditionalRemovedComponents != null && m_AdditionalRemovedComponents.Count() > 0) { VisualElement prefabsComponentElement = new VisualElement() { name = "RemainingPrefabComponentElement" }; - while (prefabComponentIndex > -1 && prefabComponentIndex < m_ComponentsInPrefabSource.Length) - { - Component nextInSource = m_ComponentsInPrefabSource[prefabComponentIndex]; - AddRemovedPrefabComponentElement(targetGameObject, nextInSource, prefabsComponentElement); - - prefabComponentIndex++; - } + foreach(var sourceComponent in m_AdditionalRemovedComponents) + AddRemovedPrefabComponentElement(targetGameObject, sourceComponent, prefabsComponentElement); if (prefabsComponentElement.childCount > 0) { @@ -1839,7 +1909,7 @@ private void RestoreVerticalScrollIfNeeded() m_LastInspectedObjectInstanceID = -1; // reset to make sure the restore occurs once } - void OnPrefabInstanceUnpacked(GameObject unpackedPrefabInstance) + void OnPrefabInstanceUnpacked(GameObject unpackedPrefabInstance, PrefabUnpackMode unpackMode) { if (m_RemovedComponents == null) return; @@ -1984,6 +2054,60 @@ public bool ShouldCullEditor(Editor[] editors, int editorIndex) return false; } + public override void SaveChanges() + { + base.SaveChanges(); + foreach (var editor in tracker.activeEditors) + { + editor.SaveChanges(); + } + } + + public override void DiscardChanges() + { + base.DiscardChanges(); + foreach (var editor in tracker.activeEditors) + { + editor.DiscardChanges(); + } + } + + public void UnsavedChangesStateChanged(Editor editor, bool value) + { + tracker.UnsavedChangesStateChanged(editor, value); + hasUnsavedChanges = tracker.hasUnsavedChanges; + if (hasUnsavedChanges) + { + StringBuilder str = new StringBuilder(); + foreach (var activeEditor in tracker.activeEditors) + { + if (activeEditor.hasUnsavedChanges) + { + str.AppendLine(activeEditor.saveChangesMessage); + } + } + saveChangesMessage = str.ToString(); + } + else + { + saveChangesMessage = string.Empty; + } + } + + [RequiredByNativeCode] + private static bool PrivateRequestRebuild(ActiveEditorTracker tracker) + { + foreach (var inspector in Resources.FindObjectsOfTypeAll()) + { + if (inspector.tracker.Equals(tracker)) + { + return ContainerWindow.PrivateRequestClose(new List() {inspector}); + } + } + + return true; + } + private void DrawSelectionPickerList() { if (m_TypeSelectionList == null) @@ -2032,8 +2156,12 @@ private void AddComponentButton(Editor[] editors) return; Editor editor = InspectorWindowUtils.GetFirstNonImportInspectorEditor(editors); + if (editor != null && editor.target != null && editor.target is GameObject && editor.IsEnabled()) { + if (ModeService.HasExecuteHandler("inspector_read_only") && ModeService.Execute("inspector_read_only", editor.target)) + return; + EditorGUILayout.BeginHorizontal(GUIContent.none, GUIStyle.none, GUILayout.Height(kAddComponentButtonHeight)); { GUILayout.FlexibleSpace(); diff --git a/Editor/Mono/Inspector/QualitySettingsEditor.cs b/Editor/Mono/Inspector/QualitySettingsEditor.cs index 33c8c1b9af..0b3fd40d6c 100644 --- a/Editor/Mono/Inspector/QualitySettingsEditor.cs +++ b/Editor/Mono/Inspector/QualitySettingsEditor.cs @@ -31,6 +31,7 @@ private class Content 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 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 kAsyncUploadTimeSlice = EditorGUIUtility.TrTextContent("Time Slice", "The amount of time (in milliseconds) Unity spends uploading Texture and Mesh data to the GPU per frame."); @@ -411,7 +412,7 @@ void SoftParticlesHintGUI() return; RenderingPath renderPath = mainCamera.actualRenderingPath; - if (renderPath == RenderingPath.DeferredLighting || renderPath == RenderingPath.DeferredShading) + if (renderPath == RenderingPath.DeferredShading) return; // using deferred, all is good if ((mainCamera.depthTextureMode & DepthTextureMode.Depth) != 0) @@ -522,6 +523,7 @@ public override void OnInspectorGUI() var vSyncCountProperty = currentSettings.FindPropertyRelative("vSyncCount"); var lodBiasProperty = currentSettings.FindPropertyRelative("lodBias"); var maximumLODLevelProperty = currentSettings.FindPropertyRelative("maximumLODLevel"); + var enableLODCrossFadeProperty = currentSettings.FindPropertyRelative("enableLODCrossFade"); var particleRaycastBudgetProperty = currentSettings.FindPropertyRelative("particleRaycastBudget"); var asyncUploadTimeSliceProperty = currentSettings.FindPropertyRelative("asyncUploadTimeSlice"); var asyncUploadBufferSizeProperty = currentSettings.FindPropertyRelative("asyncUploadBufferSize"); @@ -545,7 +547,10 @@ public override void OnInspectorGUI() GUILayout.Label(EditorGUIUtility.TempContent("Rendering"), EditorStyles.boldLabel); - RenderPipelineAssetSelector.Draw(Content.kRenderPipelineObject, m_QualitySettings, customRenderPipeline); + EditorGUI.RenderPipelineAssetField(Content.kRenderPipelineObject, m_QualitySettings, customRenderPipeline); + if (!usingSRP && customRenderPipeline.objectReferenceValue != null) + EditorGUILayout.HelpBox("Missing a Scriptable Render Pipeline in Graphics: \"Scriptable Render Pipeline Settings\" to use Scriptable Render Pipeline from Quality: \"Custom Render Pipeline\".", MessageType.Warning); + if (!usingSRP) EditorGUILayout.PropertyField(pixelLightCountProperty); @@ -661,6 +666,8 @@ public override void OnInspectorGUI() EditorGUILayout.PropertyField(lodBiasProperty, Content.kLODBiasLabel); if (!SupportedRenderingFeatures.active.overridesMaximumLODLevel) EditorGUILayout.PropertyField(maximumLODLevelProperty, Content.kMaximumLODLevelLabel); + if (!SupportedRenderingFeatures.active.overridesEnableLODCrossFade) + EditorGUILayout.PropertyField(enableLODCrossFadeProperty, Content.kEnableLODCrossFadeLabel); GUILayout.Space(10); GUILayout.Label(EditorGUIUtility.TempContent("Meshes"), EditorStyles.boldLabel); diff --git a/Editor/Mono/Inspector/RenderPipelineAssetSelector.cs b/Editor/Mono/Inspector/RenderPipelineAssetSelector.cs index 7e7c3aa382..daa12c7af6 100644 --- a/Editor/Mono/Inspector/RenderPipelineAssetSelector.cs +++ b/Editor/Mono/Inspector/RenderPipelineAssetSelector.cs @@ -11,9 +11,9 @@ namespace UnityEditor /// /// Provides a display for a and prompts the user to confirm the change. /// - internal static class RenderPipelineAssetSelector + public sealed partial class EditorGUI { - static class Styles + static class RenderPipelineAssetSelectorStyles { public static readonly GUIContent renderPipeLabel = EditorGUIUtility.TrTextContent("Scriptable Render Pipeline"); @@ -23,35 +23,16 @@ static class Styles public static string cancelLabel => LocalizationDatabase.GetLocalizedString("Cancel"); } - static void AskRenderPipelineChangeConfirmation(SerializedObject serializedObject, SerializedProperty serializedProperty, Object selectedRenderPipelineAsset) + static void PromptConfirmation(SerializedObject serializedObject, SerializedProperty serializedProperty, Object selectedRenderPipelineAsset) { if (selectedRenderPipelineAsset == serializedProperty.objectReferenceValue) return; - if (EditorUtility.DisplayDialog(Styles.renderPipeChangedTitleBox, Styles.renderPipeChangedWarning, Styles.renderPipeChangedConfirmation, Styles.cancelLabel)) + if (EditorUtility.DisplayDialog(RenderPipelineAssetSelectorStyles.renderPipeChangedTitleBox, RenderPipelineAssetSelectorStyles.renderPipeChangedWarning, RenderPipelineAssetSelectorStyles.renderPipeChangedConfirmation, RenderPipelineAssetSelectorStyles.cancelLabel)) { serializedProperty.objectReferenceValue = selectedRenderPipelineAsset; serializedObject.ApplyModifiedProperties(); } - - GUIUtility.ExitGUI(); - } - - static void ApplyChangeIfNeeded(SerializedObject serializedObject, SerializedProperty serializedProperty, Object selectedRenderPipelineAsset) - { - if (!ObjectSelector.isVisible) - { - AskRenderPipelineChangeConfirmation(serializedObject, serializedProperty, selectedRenderPipelineAsset); - return; - } - - if (Event.current.type == EventType.ExecuteCommand && - Event.current.commandName == ObjectSelector.ObjectSelectorClosedCommand) - { - AskRenderPipelineChangeConfirmation(serializedObject, serializedProperty, ObjectSelector.GetCurrentObject()); - Event.current.Use(); - GUIUtility.ExitGUI(); - } } /// @@ -60,25 +41,50 @@ static void ApplyChangeIfNeeded(SerializedObject serializedObject, SerializedPro /// The label. /// The that holds the with the new render pipeline asset. /// The to modify with the new render pipeline asset. - public static void Draw(GUIContent content, SerializedObject serializedObject, SerializedProperty serializedProperty) + internal static void RenderPipelineAssetField(GUIContent content, SerializedObject serializedObject, SerializedProperty serializedProperty) { - var selectedRenderPipelineAsset = EditorGUILayout.ObjectField(content, serializedProperty.objectReferenceValue, typeof(RenderPipelineAsset), false); - ApplyChangeIfNeeded(serializedObject, serializedProperty, selectedRenderPipelineAsset); + using (new EditorGUILayout.HorizontalScope()) + { + EditorGUILayout.PrefixLabel(content); + RenderPipelineAssetField(serializedObject, serializedProperty); + } } /// /// Draws the object field and, if the user attempts to change the value, asks the user for confirmation. /// - /// TThe that holds the with the new render pipeline asset. + /// The that holds the with the new render pipeline asset. /// The to modify with the new render pipeline asset. - public static void Draw(SerializedObject serializedObject, SerializedProperty serializedProperty) + internal static void RenderPipelineAssetField(SerializedObject serializedObject, SerializedProperty serializedProperty) { Rect renderLoopRect = EditorGUILayout.GetControlRect(true, EditorGUI.GetPropertyHeight(serializedProperty)); - EditorGUI.BeginProperty(renderLoopRect, Styles.renderPipeLabel, serializedProperty); + EditorGUI.BeginProperty(renderLoopRect, RenderPipelineAssetSelectorStyles.renderPipeLabel, serializedProperty); + + int id = GUIUtility.GetControlID(s_ObjectFieldHash, FocusType.Keyboard, renderLoopRect); - var selectedRenderPipelineAsset = EditorGUI.ObjectField(renderLoopRect, serializedProperty.objectReferenceValue, typeof(RenderPipelineAsset), false); - ApplyChangeIfNeeded(serializedObject, serializedProperty, selectedRenderPipelineAsset); + var selectedRenderPipelineAsset = DoObjectField( + position: IndentedRect(renderLoopRect), + dropRect: IndentedRect(renderLoopRect), + id: id, + obj: serializedProperty.objectReferenceValue, + objBeingEdited: null, + objType: typeof(RenderPipelineAsset), + additionalType: null, + property: null, + validator: null, + allowSceneObjects: false, + style: EditorStyles.objectField, + onObjectSelectorClosed: obj => + { + if (!ObjectSelector.SelectionCanceled()) + PromptConfirmation(serializedObject, serializedProperty, obj); + }); + + if (!ObjectSelector.isVisible) + { + PromptConfirmation(serializedObject, serializedProperty, selectedRenderPipelineAsset); + } EditorGUI.EndProperty(); } diff --git a/Editor/Mono/Inspector/RenderTextureEditor.cs b/Editor/Mono/Inspector/RenderTextureEditor.cs index 68d9dd4a2b..db07e6b22d 100644 --- a/Editor/Mono/Inspector/RenderTextureEditor.cs +++ b/Editor/Mono/Inspector/RenderTextureEditor.cs @@ -86,6 +86,24 @@ protected override void OnEnable() m_sRGB = serializedObject.FindProperty("m_SRGB"); m_UseDynamicScale = serializedObject.FindProperty("m_UseDynamicScale"); m_ShadowSamplingMode = serializedObject.FindProperty("m_ShadowSamplingMode"); + + Undo.undoRedoEvent += OnUndoRedoPerformed; + } + + protected override void OnDisable() + { + base.OnDisable(); + + Undo.undoRedoEvent -= OnUndoRedoPerformed; + } + + private void OnUndoRedoPerformed(in UndoRedoInfo info) + { + var rt = target as RenderTexture; + if (rt != null) + { + rt.Release(); + } } protected void OnRenderTextureGUI(GUIElements guiElements) diff --git a/Editor/Mono/Inspector/ReorderableListWrapper.cs b/Editor/Mono/Inspector/ReorderableListWrapper.cs index cdbe537458..95afc9c45b 100644 --- a/Editor/Mono/Inspector/ReorderableListWrapper.cs +++ b/Editor/Mono/Inspector/ReorderableListWrapper.cs @@ -2,6 +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 UnityEditor; using UnityEditor.SceneManagement; @@ -40,6 +41,7 @@ internal SerializedProperty Property set { m_OriginalProperty = value; + if (!m_OriginalProperty.isValid) return; m_ArraySize = m_OriginalProperty.FindPropertyRelative("Array.size"); if (m_ReorderableList != null) @@ -50,7 +52,7 @@ internal SerializedProperty Property if (versionChanged || m_ArraySize != null && m_LastArraySize != m_ArraySize.intValue) { - m_ReorderableList.ClearCacheRecursive(); + m_ReorderableList.InvalidateCacheRecursive(); ReorderableList.InvalidateParentCaches(m_ReorderableList.serializedProperty.propertyPath); if (m_ArraySize != null) m_LastArraySize = m_ArraySize.intValue; @@ -61,7 +63,15 @@ internal SerializedProperty Property public static string GetPropertyIdentifier(SerializedProperty serializedProperty) { - return serializedProperty.propertyPath + (GUIView.current?.nativeHandle.ToInt32() ?? -1); + // Property may be disposed + try + { + return serializedProperty?.propertyPath + serializedProperty.serializedObject.targetObject.GetInstanceID() + (GUIView.current?.nativeHandle.ToInt32() ?? -1); + } + catch (NullReferenceException) + { + return string.Empty; + } } ReorderableListWrapper() {} @@ -97,10 +107,7 @@ void Init(bool reorderable) }; } - internal void ClearCache() - { - m_ReorderableList.ClearCache(); - } + internal void InvalidateCache() => m_ReorderableList.InvalidateCache(); public float GetHeight() { @@ -155,7 +162,7 @@ public void Draw(GUIContent label, Rect r, Rect visibleArea, string tooltip, boo EditorGUI.SetExpandedRecurse(Property, Property.isExpanded); } - m_ReorderableList.ClearCacheRecursive(); + m_ReorderableList.InvalidateCacheRecursive(); } DrawChildren(r, headerRect, sizeRect, visibleArea, prevType); @@ -165,8 +172,10 @@ void DrawChildren(Rect listRect, Rect headerRect, Rect sizeRect, Rect visibleRec { if (Event.current.type == EventType.Used && sizeRect.Contains(Event.current.mousePosition)) Event.current.type = previousEvent; + EditorGUI.BeginChangeCheck(); EditorGUI.DefaultPropertyField(sizeRect, m_ArraySize, GUIContent.none); EditorGUI.LabelField(sizeRect, new GUIContent("", "Array Size")); + if (EditorGUI.EndChangeCheck()) m_ReorderableList.InvalidateCache(); if (headerRect.Contains(Event.current.mousePosition)) { diff --git a/Editor/Mono/Inspector/Rigidbody2DEditor.cs b/Editor/Mono/Inspector/Rigidbody2DEditor.cs index af7e7fd6b2..a4ad7d3f0b 100644 --- a/Editor/Mono/Inspector/Rigidbody2DEditor.cs +++ b/Editor/Mono/Inspector/Rigidbody2DEditor.cs @@ -28,10 +28,13 @@ internal class Rigidbody2DEditor : Editor SerializedProperty m_SleepingMode; SerializedProperty m_CollisionDetection; SerializedProperty m_Constraints; + SerializedProperty m_IncludeLayers; + SerializedProperty m_ExcludeLayers; readonly AnimBool m_ShowIsStatic = new AnimBool(); readonly AnimBool m_ShowIsKinematic = new AnimBool(); + readonly AnimBool m_ShowLayerOverrides = new AnimBool(); readonly AnimBool m_ShowInfo = new AnimBool(); readonly AnimBool m_ShowContacts = new AnimBool(); Vector2 m_ContactScrollPosition; @@ -41,6 +44,7 @@ internal class Rigidbody2DEditor : Editor static List m_Contacts = new List(64); + private SavedBool m_ShowLayerOverridesFoldout; private SavedBool m_ShowInfoFoldout; private bool m_RequiresConstantRepaint; @@ -63,6 +67,8 @@ public void OnEnable() m_SleepingMode = serializedObject.FindProperty("m_SleepingMode"); m_CollisionDetection = serializedObject.FindProperty("m_CollisionDetection"); m_Constraints = serializedObject.FindProperty("m_Constraints"); + m_IncludeLayers = serializedObject.FindProperty("m_IncludeLayers"); + m_ExcludeLayers = serializedObject.FindProperty("m_ExcludeLayers"); m_ShowIsStatic.value = body.bodyType != RigidbodyType2D.Static; m_ShowIsStatic.valueChanged.AddListener(Repaint); @@ -70,8 +76,11 @@ public void OnEnable() m_ShowIsKinematic.value = body.bodyType != RigidbodyType2D.Kinematic; m_ShowIsKinematic.valueChanged.AddListener(Repaint); + m_ShowLayerOverrides.valueChanged.AddListener(Repaint); + m_ShowLayerOverridesFoldout = new SavedBool($"{target.GetType() }.ShowLayerOverridesFoldout", false); + m_ShowLayerOverrides.value = m_ShowLayerOverridesFoldout.value; m_ShowInfo.valueChanged.AddListener(Repaint); - m_ShowInfoFoldout = new SavedBool($"{target.GetType()}.ShowFoldout", false); + m_ShowInfoFoldout = new SavedBool($"{target.GetType()}.ShowInfoFoldout", false); m_ShowInfo.value = m_ShowInfoFoldout.value; m_ShowContacts.valueChanged.AddListener(Repaint); m_ContactScrollPosition = Vector2.zero; @@ -83,6 +92,7 @@ public void OnDisable() { m_ShowIsStatic.valueChanged.RemoveListener(Repaint); m_ShowIsKinematic.valueChanged.RemoveListener(Repaint); + m_ShowLayerOverrides.valueChanged.RemoveListener(Repaint); m_ShowInfo.valueChanged.RemoveListener(Repaint); m_ShowContacts.valueChanged.RemoveListener(Repaint); } @@ -174,9 +184,24 @@ public override void OnInspectorGUI() EditorGUILayout.EndFadeGroup(); } + ShowLayerOverridesProperties(); + ShowBodyInfoProperties(); + serializedObject.ApplyModifiedProperties(); + } - ShowBodyInfoProperties(); + private void ShowLayerOverridesProperties() + { + // Show Layer Overrides. + m_ShowLayerOverridesFoldout.value = m_ShowLayerOverrides.target = EditorGUILayout.Foldout(m_ShowLayerOverrides.target, "Layer Overrides", true); + if (EditorGUILayout.BeginFadeGroup(m_ShowLayerOverrides.faded)) + { + EditorGUI.indentLevel++; + EditorGUILayout.PropertyField(m_IncludeLayers); + EditorGUILayout.PropertyField(m_ExcludeLayers); + EditorGUI.indentLevel--; + } + EditorGUILayout.EndFadeGroup(); } private void ShowBodyInfoProperties() diff --git a/Editor/Mono/Inspector/ShaderInspector.cs b/Editor/Mono/Inspector/ShaderInspector.cs index 32d252e711..3a96f2c07f 100644 --- a/Editor/Mono/Inspector/ShaderInspector.cs +++ b/Editor/Mono/Inspector/ShaderInspector.cs @@ -503,6 +503,9 @@ private class Styles private readonly Shader m_Shader; + private ulong totalVariants; + private ulong variantsWithUsage; + public static int currentMode { @@ -560,6 +563,8 @@ public ShaderInspectorPlatformsPopup(Shader shader) { m_Shader = shader; InitializeShaderPlatforms(); + totalVariants = 0; + variantsWithUsage = 0; } static void InitializeShaderPlatforms() @@ -679,7 +684,7 @@ static string FormatCount(ulong count) if (count > 1000 * 1000) return ((double)count / 1000000.0).ToString("f2", CultureInfo.InvariantCulture.NumberFormat) + "M"; if (count > 1000) - return ((double)count / 1000.0).ToString("f2", CultureInfo.InvariantCulture.NumberFormat) + "k"; + return ((double)count / 1000.0).ToString("f2", CultureInfo.InvariantCulture.NumberFormat) + "K"; return count.ToString(); } @@ -694,7 +699,19 @@ void DoShaderVariants(EditorWindow caller, ref Rect drawPos) // display included variant count, and a button to show list of them drawPos.y += kSeparatorHeight; - ulong variantCount = ShaderUtil.GetVariantCount(m_Shader, strip); + ulong variantCount = 0; + if (strip) + { + if (variantsWithUsage == 0) + variantsWithUsage = ShaderUtil.GetVariantCount(m_Shader, true); + variantCount = variantsWithUsage; + } + else + { + if (totalVariants == 0) + totalVariants = ShaderUtil.GetVariantCount(m_Shader, false); + variantCount = totalVariants; + } var variantText = FormatCount(variantCount) + (strip ? " variants included" : diff --git a/Editor/Mono/Inspector/SpriteFrameInspector.cs b/Editor/Mono/Inspector/SpriteFrameInspector.cs index 92d63aa511..b27603b0a9 100644 --- a/Editor/Mono/Inspector/SpriteFrameInspector.cs +++ b/Editor/Mono/Inspector/SpriteFrameInspector.cs @@ -92,25 +92,37 @@ public override void OnInspectorGUI() EditorGUILayout.LabelField(Styles.borderLabel, Styles.multiValueText); } - public static Texture2D BuildPreviewTexture(Sprite sprite, Material spriteRendererMaterial, bool isPolygon) + + public static Texture2D BuildPreviewTexture(Sprite sprite, Material spriteRendererMaterial, bool isPolygon, int width, int height) + { + return BuildPreviewTexture(sprite, spriteRendererMaterial, isPolygon, width, height, Color.white, Matrix4x4.identity); + } + + public static Texture2D BuildPreviewTexture(Sprite sprite, Material spriteRendererMaterial, bool isPolygon, int width, int height, Color color, Matrix4x4 transform) { - if (!ShaderUtil.hardwareSupportsRectRenderTexture) + if (!ShaderUtil.hardwareSupportsRectRenderTexture || sprite == null) { return null; } - float spriteWidth = sprite.rect.width; - float spriteHeight = sprite.rect.height; + var spriteWidth = sprite.rect.width; + var spriteHeight = sprite.rect.height; - int width = (int)spriteWidth; - int height = (int)spriteHeight; Texture2D texture = UnityEditor.Sprites.SpriteUtility.GetSpriteTexture(sprite, false); // only adjust the preview texture size if the sprite is not in polygon mode. // In polygon mode, we are looking at a 4x4 texture will detailed mesh. It's better to maximize the display of it. if (!isPolygon) { - PreviewHelpers.AdjustWidthAndHeightForStaticPreview((int)spriteWidth, (int)spriteHeight, ref width, ref height); + // 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); + + 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); } SavedRenderTargetState savedRTState = new SavedRenderTargetState(); @@ -182,22 +194,20 @@ public static Texture2D BuildPreviewTexture(Sprite sprite, Material spriteRender GL.PushMatrix(); GL.LoadOrtho(); - GL.Color(new Color(1, 1, 1, 1)); GL.Begin(GL.TRIANGLES); for (int i = 0; i < triangles.Length; ++i) { ushort index = triangles[i]; - Vector2 vertex = vertices[index]; + Vector3 vertex = vertices[index]; + vertex = transform.MultiplyPoint(vertex); Vector2 uv = uvs[index]; + GL.Color(colors != null ? colors.Value[index] * color : color); GL.TexCoord(new Vector3(uv.x, uv.y, 0)); - if (colors != null) - GL.Color(colors.Value[index]); GL.Vertex3((vertex.x * pixelsToUnits + pivot.x) / spriteWidth, (vertex.y * pixelsToUnits + pivot.y) / spriteHeight, 0); } GL.End(); GL.PopMatrix(); - if (spriteRendererMaterial != null) { if (_matHasTexture) @@ -239,7 +249,7 @@ public override Texture2D RenderStaticPreview(string assetPath, Object[] subAsse isPolygonSpriteAsset = textureImporter.spriteImportMode == SpriteImportMode.Polygon; } - return BuildPreviewTexture(sprite, null, isPolygonSpriteAsset); + return BuildPreviewTexture(sprite, null, isPolygonSpriteAsset, width, height); } public override bool HasPreviewGUI() @@ -276,7 +286,7 @@ public static void DrawPreview(Rect r, Sprite frame, Material spriteRendererMate Rect wantedRect = new Rect(r.x, r.y, frame.rect.width * zoomLevel, frame.rect.height * zoomLevel); wantedRect.center = r.center; - Texture2D previewTexture = BuildPreviewTexture(frame, spriteRendererMaterial, isPolygon); + Texture2D previewTexture = BuildPreviewTexture(frame, spriteRendererMaterial, isPolygon, (int) wantedRect.width, (int) wantedRect.height); EditorGUI.DrawTextureTransparent(wantedRect, previewTexture, ScaleMode.ScaleToFit); var border = frame.border; diff --git a/Editor/Mono/Inspector/SpriteRendererEditor.cs b/Editor/Mono/Inspector/SpriteRendererEditor.cs index 9f2f9a9504..d0e6c680cb 100644 --- a/Editor/Mono/Inspector/SpriteRendererEditor.cs +++ b/Editor/Mono/Inspector/SpriteRendererEditor.cs @@ -39,6 +39,7 @@ class Styles public static readonly GUIContent maskInteractionLabel = EditorGUIUtility.TrTextContent("Mask Interaction", "SpriteRenderer's interaction with a Sprite Mask"); public static readonly GUIContent spriteSortPointLabel = EditorGUIUtility.TrTextContent("Sprite Sort Point", "Determines which position of the Sprite which is used for sorting"); public static readonly Texture2D warningIcon = EditorGUIUtility.LoadIcon("console.warnicon"); + public static readonly GUIContent drawModeChange = EditorGUIUtility.TrTextContent("Draw mode Change"); } private SerializedProperty m_FlipX; @@ -89,7 +90,14 @@ public override void OnInspectorGUI() using (new EditorGUI.DisabledGroupScope(IsTextureless())) { - EditorGUILayout.PropertyField(m_DrawMode, Styles.drawModeLabel); + var showMixedValue = EditorGUI.showMixedValue; + if (m_DrawMode.hasMultipleDifferentValues) + EditorGUI.showMixedValue = true; + SpriteDrawMode drawMode = (SpriteDrawMode)m_DrawMode.intValue; + drawMode = (SpriteDrawMode)EditorGUILayout.EnumPopup(Styles.drawModeLabel, drawMode); + SetDrawMode(drawMode); + EditorGUI.showMixedValue = showMixedValue; + m_ShowDrawMode.target = ShouldShowDrawMode(); if (EditorGUILayout.BeginFadeGroup(m_ShowDrawMode.faded)) { @@ -139,6 +147,26 @@ public override void OnInspectorGUI() serializedObject.ApplyModifiedProperties(); } + internal void SetDrawMode(SpriteDrawMode drawMode) + { + if (drawMode != (SpriteDrawMode)m_DrawMode.intValue) + { + foreach (var target in serializedObject.targetObjects) + { + var sr = (SpriteRenderer)target; + var t = sr.transform; + Undo.RecordObjects(new UnityEngine.Object[] {sr, t}, Styles.drawModeChange.text); + sr.drawMode = drawMode; + foreach (var editor in ActiveEditorTracker.sharedTracker.activeEditors) + { + if(editor.target == t) + editor.serializedObject.SetIsDifferentCacheDirty(); + } + } + serializedObject.SetIsDifferentCacheDirty(); + } + } + void FloatFieldLabelAbove(GUIContent contentLabel, SerializedProperty sp) { EditorGUILayout.BeginVertical(); @@ -217,7 +245,6 @@ void FlipToggle(Rect r, GUIContent label, SerializedProperty property) EditorGUI.indentLevel = oldIndent; if (EditorGUI.EndChangeCheck()) { - Undo.RecordObjects(targets, "Edit Constraints"); property.boolValue = toggle; } @@ -241,20 +268,6 @@ private void ShowMaterialError() private bool IsMaterialTextureAtlasConflict() { - Material material = (target as SpriteRenderer).sharedMaterial; - if (material == null) - return false; - string tag = material.GetTag("CanUseSpriteAtlas", false); - if (tag.ToLower() == "false") - { - Sprite frame = m_Sprite.objectReferenceValue as Sprite; - TextureImporter ti = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(frame)) as TextureImporter; - if (ti != null && ti.spritePackingTag != null && ti.spritePackingTag.Length > 0) - { - return true; - } - } - return false; } diff --git a/Editor/Mono/Inspector/StandardShaderGUI.cs b/Editor/Mono/Inspector/StandardShaderGUI.cs index 0a44867c6f..3779b6cc5f 100644 --- a/Editor/Mono/Inspector/StandardShaderGUI.cs +++ b/Editor/Mono/Inspector/StandardShaderGUI.cs @@ -4,7 +4,6 @@ using System; using UnityEngine; -using TargetAttributes = UnityEditor.BuildTargetDiscovery.TargetAttributes; namespace UnityEditor { @@ -90,6 +89,11 @@ private static class Styles MaterialEditor m_MaterialEditor; WorkflowMode m_WorkflowMode = WorkflowMode.Specular; + static int _SpecGlossMap = Shader.PropertyToID("_SpecGlossMap"); + static int _SpecColor = Shader.PropertyToID("_SpecColor"); + static int _MetallicGlossMap = Shader.PropertyToID("_MetallicGlossMap"); + static int _Metallic = Shader.PropertyToID("_Metallic"); + public void FindProperties(MaterialProperty[] props) { blendMode = FindProperty("_Mode", props); @@ -191,11 +195,22 @@ public void ShaderPropertiesGUI(Material material) m_MaterialEditor.DoubleSidedGIField(); } + bool ShaderHasProperty(Shader shader, int nameId) + { + for (int i = 0, count = shader.GetPropertyCount(); i < count; i++) + { + if (shader.GetPropertyNameId(i) == nameId) + return true; + } + return false; + } + internal void DetermineWorkflow(Material material) { - if (material.HasProperty("_SpecGlossMap") && material.HasProperty("_SpecColor")) + var shader = material.shader; + if (ShaderHasProperty(shader, _SpecGlossMap) && ShaderHasProperty(shader, _SpecColor)) m_WorkflowMode = WorkflowMode.Specular; - if (material.HasProperty("_MetallicGlossMap") && material.HasProperty("_Metallic")) + else if (ShaderHasProperty(shader, _MetallicGlossMap) && ShaderHasProperty(shader, _Metallic)) m_WorkflowMode = WorkflowMode.Metallic; else m_WorkflowMode = WorkflowMode.Dielectric; @@ -236,7 +251,7 @@ public override void AssignNewShaderToMaterial(Material material, Shader oldShad bool BlendModePopup() { - EditorGUI.showMixedValue = blendMode.hasMixedValue; + MaterialEditor.BeginProperty(blendMode); var mode = (BlendMode)blendMode.floatValue; EditorGUI.BeginChangeCheck(); @@ -248,7 +263,7 @@ bool BlendModePopup() blendMode.floatValue = (float)mode; } - EditorGUI.showMixedValue = false; + MaterialEditor.EndProperty(); return result; } @@ -257,7 +272,7 @@ void DoNormalArea() { m_MaterialEditor.TexturePropertySingleLine(Styles.normalMapText, bumpMap, bumpMap.textureValue != null ? bumpScale : null); if (bumpScale.floatValue != 1 - && BuildTargetDiscovery.PlatformHasFlag(EditorUserBuildSettings.activeBuildTarget, TargetAttributes.HasIntegratedGPU)) + && UnityEditorInternal.InternalEditorUtility.IsMobilePlatform(EditorUserBuildSettings.activeBuildTarget)) if (m_MaterialEditor.HelpBoxWithButton( EditorGUIUtility.TrTextContent("Bump scale is not supported on mobile platforms"), EditorGUIUtility.TrTextContent("Fix Now"))) @@ -285,6 +300,13 @@ void DoEmissionArea(Material material) // Texture and HDR color controls m_MaterialEditor.TexturePropertyWithHDRColor(Styles.emissionText, emissionMap, emissionColorForRendering, false); + if (material.globalIlluminationFlags.HasFlag(MaterialGlobalIlluminationFlags.EmissiveIsBlack)) + { + material.GetPropertyState(MaterialSerializedProperty.LightmapFlags, out _, out _, out bool lockedByAncestor); + if (lockedByAncestor) + EditorGUILayout.HelpBox("Emissive lighting is locked to black by a parent Material. Changing the emissive color will have no effect.", MessageType.Warning); + } + // If texture was assigned and color was black set color to white float brightness = emissionColorForRendering.colorValue.maxColorComponent; if (emissionMap.textureValue != null && !hadEmissionTexture && brightness <= 0f) diff --git a/Editor/Mono/Inspector/TagManagerInspector.cs b/Editor/Mono/Inspector/TagManagerInspector.cs index ddfa324787..1ac0eb1f36 100644 --- a/Editor/Mono/Inspector/TagManagerInspector.cs +++ b/Editor/Mono/Inspector/TagManagerInspector.cs @@ -244,6 +244,7 @@ void AddToSortLayerList(ReorderableList list) public void ReorderSortLayerList(ReorderableList list) { + serializedObject.ApplyModifiedProperties(); tagManager.UpdateSortingLayersOrder(); } diff --git a/Editor/Mono/Inspector/Texture3DPreview.cs b/Editor/Mono/Inspector/Texture3DPreview.cs index 187b5ed714..bf7057e6a6 100644 --- a/Editor/Mono/Inspector/Texture3DPreview.cs +++ b/Editor/Mono/Inspector/Texture3DPreview.cs @@ -10,7 +10,7 @@ namespace UnityEditor { - internal class Texture3DPreview : Editor + internal class Texture3DPreview : ScriptableObject { enum Preview3DMode { @@ -181,7 +181,7 @@ static Texture2D TurboColorRamp float m_StepScale = 1; float m_SurfaceOffset = 0; - public override string GetInfoString() + public string GetInfoString() { if (Texture == null) return ""; @@ -438,7 +438,7 @@ void DrawPreview() m_PreviewUtility.Render(); } - public override void OnPreviewGUI(Rect r, GUIStyle background) + public void OnPreviewGUI(Rect r, GUIStyle background) { if (!ShaderUtil.hardwareSupportsRectRenderTexture || !SystemInfo.supports3DTextures) { diff --git a/Editor/Mono/Inspector/TextureInspector.cs b/Editor/Mono/Inspector/TextureInspector.cs index 27a604368d..e62bd25ad6 100644 --- a/Editor/Mono/Inspector/TextureInspector.cs +++ b/Editor/Mono/Inspector/TextureInspector.cs @@ -172,8 +172,11 @@ protected virtual void OnEnable() SetMipLevelDefaultForVT(); - if (m_Texture3DPreview == null) m_Texture3DPreview = CreateInstance(); - m_Texture3DPreview.Texture = target as Texture; + m_Texture3DPreview = CreateInstance(); + if (IsTexture3D()) + { + m_Texture3DPreview.Texture = target as Texture; + } m_Texture3DPreview.OnEnable(); } @@ -231,7 +234,7 @@ protected virtual void OnDisable() RestoreLastTextureMipLevels(); m_CubemapPreview.OnDisable(); - m_Texture3DPreview.OnDisable(); + DestroyImmediate(m_Texture3DPreview); } public override bool RequiresConstantRepaint() @@ -809,7 +812,7 @@ public override void OnPreviewGUI(Rect r, GUIStyle background) { string path = AssetDatabase.GetAssetPath(t); TextureImporter textureImporter = AssetImporter.GetAtPath(path) as TextureImporter; - SpriteMetaData[] spritesheet = textureImporter != null ? textureImporter.spritesheet : null; + SpriteMetaData[] spritesheet = textureImporter != null ? textureImporter.GetSpriteMetaDatas() : null; if (spritesheet != null && textureImporter.spriteImportMode == SpriteImportMode.Multiple) { @@ -895,7 +898,7 @@ public override Texture2D RenderStaticPreview(string assetPath, Object[] subAsse { Sprite sprite = subAssets[0] as Sprite; if (sprite) - return SpriteInspector.BuildPreviewTexture(sprite, null, true); + return SpriteInspector.BuildPreviewTexture(sprite, null, true, width, height); } else return null; diff --git a/Editor/Mono/Inspector/TrailRendererEditor.cs b/Editor/Mono/Inspector/TrailRendererEditor.cs index 46aff439e5..ff3d7b5ffc 100644 --- a/Editor/Mono/Inspector/TrailRendererEditor.cs +++ b/Editor/Mono/Inspector/TrailRendererEditor.cs @@ -3,8 +3,12 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System.Collections.Generic; +using System.Linq; +using UnityEditor.EditorTools; +using UnityEditor.IMGUI.Controls; +using UnityEditor.Overlays; +using UnityEditor.ShortcutManagement; using UnityEngine; -using UnityEngine.Rendering; namespace UnityEditor { @@ -23,8 +27,59 @@ private class Styles public static readonly GUIContent shadowBias = EditorGUIUtility.TrTextContent("Shadow Bias", "Apply a shadow bias to prevent self-shadowing artifacts. The specified value is the proportion of the trail width at each segment."); public static readonly GUIContent generateLightingData = EditorGUIUtility.TrTextContent("Generate Lighting Data", "Toggle generation of normal and tangent data, for use in lit shaders."); public static readonly GUIContent applyActiveColorSpace = EditorGUIUtility.TrTextContent("Apply Active Color Space", "When using Linear Rendering, colors will be converted appropriately before being passed to the GPU."); + + public static readonly GUIContent play = EditorGUIUtility.TrTextContent("Play"); + public static readonly GUIContent playDisabled = EditorGUIUtility.TrTextContent("Play", "Play is disabled, because the Time Scale in the Time Manager is set to 0.0."); + public static readonly GUIContent stop = EditorGUIUtility.TrTextContent("Stop"); + public static readonly GUIContent pause = EditorGUIUtility.TrTextContent("Pause"); + public static readonly GUIContent restart = EditorGUIUtility.TrTextContent("Restart"); + public static readonly GUIContent movementSpeed = EditorGUIUtility.TrTextContent("Movement Speed", "Speed is also affected by the Time Scale setting in the Time Manager."); + public static readonly GUIContent movementSpeedDisabled = EditorGUIUtility.TrTextContent("Movement Speed", "Speed is locked to 0.0, because the Time Scale in the Time Manager is set to 0.0."); + public static readonly GUIContent timeScale = EditorGUIUtility.TrTextContent("Time Scale", "Speed up or slow down the preview of the trail."); + public static readonly GUIContent timeScaleDisabled = EditorGUIUtility.TrTextContent("Time Scale", "Time Scale is locked to 0.0, because the Time Scale in the Time Manager is set to 0.0."); + public static readonly GUIContent showBounds = EditorGUIUtility.TrTextContent("Show Bounds", "Show world space bounding boxes."); + public static readonly GUIContent previewShape = EditorGUIUtility.TrTextContent("Shape", "The trail preview will follow the selected shape."); + public static readonly GUIContent previewShapeSize = EditorGUIUtility.TrTextContent("Shape Size", "The size of the shape."); + + public static readonly GUIContent toolIcon = EditorGUIUtility.IconContent("ParticleShapeTool", "Shape gizmo editing mode."); + + public static readonly string secondsFloatFieldFormatString = "f2"; + } + + private enum PreviewShape + { + Circle, + Square, + Line, + SineWave, + Spring } + private const string k_PreviewMovementSpeed = "TrailPreviewMovementSpeed"; + private const string k_PreviewTimeScale = "TrailPreviewTimeScale"; + private const string k_PreviewShape = "TrailPreviewShape"; + private const string k_PreviewShapeSize = "TrailPreviewShapeSize"; + + private static LinkedList s_Inspectors = new LinkedList(); + private static bool s_PreviewIsPlaying; + private static bool s_PreviewIsPaused; + private static float s_PreviewMovementSpeed; + private static float s_PreviewTimeScale; + private static bool s_PreviewShowBounds; + private static PreviewShape s_PreviewShape; + private static float s_PreviewShapeSize; + private Vector3? m_PreviewBackupPosition; + private bool m_PreviewIsFirstMove; + private float m_PreviewTimePercentage; + + private static readonly Matrix4x4 s_ArcHandleOffsetMatrix = Matrix4x4.TRS(Vector3.zero, Quaternion.AngleAxis(90f, Vector3.right) * Quaternion.AngleAxis(90f, Vector3.up), Vector3.one); + private const float k_SineWaveRepeat = 2.0f; + private const float k_SineWaveHeightMultiplier = 0.5f; + private const float k_SpringRotations = 5.0f; + private const float k_PreviewMinSize = 0.1f; + private const float k_SceneViewOverlayLabelWidth = 110.0f; + private ArcHandle m_PreviewArcHandle = new ArcHandle(); + private LineRendererCurveEditor m_CurveEditor = new LineRendererCurveEditor(); private SerializedProperty m_Time; private SerializedProperty m_MinVertexDistance; @@ -41,11 +96,90 @@ private class Styles private SerializedProperty m_GenerateLightingData; private SerializedProperty m_MaskInteraction; + public class ShortcutContext : IShortcutToolContext + { + public bool active { get; set; } + } + + private ShortcutContext m_ShortcutContext = new ShortcutContext { active = true }; + + private static Event CreateCommandEvent(string commandName) + { + return new Event { type = EventType.ExecuteCommand, commandName = "TrailRenderer/" + commandName }; + } + + private static Event s_PlayEvent; + private static Event s_StopEvent; + private static Event s_RestartEvent; + private static Event s_ShowBoundsEvent; + + private static PrefColor s_BoundsColor = new PrefColor("Trail Renderer/Bounds", 1.0f, 235.0f / 255.0f, 4.0f / 255.0f, 1.0f); + private static PrefColor s_GizmoColor = new PrefColor("Trail Renderer/Shape Gizmos", 148f / 255f, 229f / 255f, 1f, 0.9f); + + private static void DispatchShortcutEvent(Event evt) + { + var sceneView = SceneView.lastActiveSceneView; + if (sceneView != null) + { + if (sceneView.SendEvent(evt)) + return; + } + + var inspectors = Resources.FindObjectsOfTypeAll(); + foreach (var inspector in inspectors) + { + if (inspector != null) + { + if (inspector.HandleShortcutEvent(evt)) + return; + } + } + } + + [Shortcut("TrailRenderer/Play", typeof(ShortcutContext), KeyCode.Comma)] + private static void PlayPauseShortcut(ShortcutArguments args) + { + DispatchShortcutEvent(s_PlayEvent); + } + + [Shortcut("TrailRenderer/Stop", typeof(ShortcutContext), KeyCode.Period)] + private static void StopShortcut(ShortcutArguments args) + { + DispatchShortcutEvent(s_StopEvent); + } + + [Shortcut("TrailRenderer/Restart", typeof(ShortcutContext), KeyCode.Slash)] + private static void RestartShortcut(ShortcutArguments args) + { + DispatchShortcutEvent(s_RestartEvent); + } + + [Shortcut("TrailRenderer/ShowBounds", typeof(ShortcutContext))] + private static void ShowBoundsShortcut(ShortcutArguments args) + { + DispatchShortcutEvent(s_ShowBoundsEvent); + } + public override void OnEnable() { base.OnEnable(); + s_PreviewMovementSpeed = SessionState.GetFloat(k_PreviewMovementSpeed, 0.2f); + s_PreviewTimeScale = SessionState.GetFloat(k_PreviewTimeScale, 1.0f); + s_PreviewShape = (PreviewShape)SessionState.GetInt(k_PreviewShape, 0); + s_PreviewShapeSize = SessionState.GetFloat(k_PreviewShapeSize, 5.0f); + m_CurveEditor.OnEnable(serializedObject); + s_Inspectors.AddLast(this); + SceneView.duringSceneGui += OnSceneViewGUI; + EditorApplication.update += RepaintSceneView; + ShortcutIntegration.instance.contextManager.RegisterToolContext(m_ShortcutContext); + + s_PlayEvent = CreateCommandEvent("Play"); + s_StopEvent = CreateCommandEvent("Stop"); + s_RestartEvent = CreateCommandEvent("Restart"); + s_ShowBoundsEvent = CreateCommandEvent("ShowBounds"); + m_Time = serializedObject.FindProperty("m_Time"); m_MinVertexDistance = serializedObject.FindProperty("m_MinVertexDistance"); m_Autodestruct = serializedObject.FindProperty("m_Autodestruct"); @@ -64,7 +198,76 @@ public override void OnEnable() public void OnDisable() { + Stop(); + m_CurveEditor.OnDisable(); + s_Inspectors.Remove(this); + SceneView.duringSceneGui -= OnSceneViewGUI; + EditorApplication.update -= RepaintSceneView; + ShortcutIntegration.instance.contextManager.DeregisterToolContext(m_ShortcutContext); + + SessionState.SetFloat(k_PreviewMovementSpeed, s_PreviewMovementSpeed); + SessionState.SetFloat(k_PreviewTimeScale, s_PreviewTimeScale); + SessionState.SetInt(k_PreviewShape, (int)s_PreviewShape); + SessionState.SetFloat(k_PreviewShapeSize, s_PreviewShapeSize); + } + + private static float ClampPreviewSize(TrailRenderer tr, float size) + { + float minSize = Mathf.Max(k_PreviewMinSize, tr.widthMultiplier * 0.5f); + return Mathf.Max(size, minSize); + } + + private void SavePositionForPreview() + { + if (!m_PreviewBackupPosition.HasValue) + { + if (target is TrailRenderer tr) + { + m_PreviewBackupPosition = tr.transform.localPosition; + m_PreviewIsFirstMove = true; + + s_PreviewShapeSize = ClampPreviewSize(tr, s_PreviewShapeSize); + + DrivenPropertyManager.TryRegisterProperty(this, tr.transform, "m_LocalPosition"); + DrivenPropertyManager.TryRegisterProperty(this, tr, "s_PreviewTimeScale"); + EditorGUIUtility.beginProperty += BeginDrivenPropertyCheck; + } + + m_PreviewTimePercentage = 0.0f; + Tools.hidden = true; + } + } + + private void RestorePositionAfterPreview() + { + DrivenPropertyManager.UnregisterProperties(this); + EditorGUIUtility.beginProperty -= BeginDrivenPropertyCheck; + + if (m_PreviewBackupPosition.HasValue) + { + if (target is TrailRenderer tr) + tr.Clear(); + m_PreviewBackupPosition = null; + } + + Tools.hidden = false; + } + + private void BeginDrivenPropertyCheck(Rect rect, SerializedProperty property) + { + if (EditorApplication.isPlayingOrWillChangePlaymode) + return; + + if (DrivenPropertyManagerInternal.IsDriving(this, property.serializedObject.targetObject, property.propertyPath)) + { + // Properties driven by a UnityEvent are disabled as changes to them would be ignored. + GUI.enabled = false; + + Color animatedColor = AnimationMode.animatedPropertyColor; + animatedColor.a *= GUI.backgroundColor.a; + GUI.backgroundColor = AnimationMode.animatedPropertyColor; + } } public override void OnInspectorGUI() @@ -95,5 +298,508 @@ public override void OnInspectorGUI() serializedObject.ApplyModifiedProperties(); } + + private void OnSceneViewGUI(SceneView sceneView) + { + // Bounds + if (s_PreviewShowBounds) + { + var oldCol = Handles.color; + Handles.color = s_BoundsColor; + + foreach (var obj in targets) + { + if (obj is TrailRenderer tr) + { + var worldBounds = tr.bounds; + Handles.DrawWireCube(worldBounds.center, worldBounds.size); + } + } + + Handles.color = oldCol; + } + + // Move trail using shape + if (m_PreviewBackupPosition.HasValue) + { + if (target is TrailRenderer tr) + { + if (s_PreviewIsPlaying && !s_PreviewIsPaused) + { + Tools.hidden = !(EditorToolManager.activeTool is ShapeGizmoTool); + + float previousPreviewTimePercentage = m_PreviewTimePercentage; + float timeStep = Mathf.Min(Time.deltaTime, 0.1f); + + float moveSpeed = s_PreviewMovementSpeed; + if (s_PreviewShape == PreviewShape.Spring) + moveSpeed *= 0.5f; + + m_PreviewTimePercentage += (timeStep * moveSpeed * s_PreviewTimeScale); + m_PreviewTimePercentage %= 1.0f; + + tr.previewTimeScale = s_PreviewTimeScale; + + var transform = tr.transform; + Matrix4x4 localTransform = Matrix4x4.TRS(Vector3.zero, transform.localRotation, transform.localScale); + + switch (s_PreviewShape) + { + case PreviewShape.Circle: + transform.localPosition = m_PreviewBackupPosition.Value + localTransform.MultiplyPoint(new Vector3(Mathf.Sin(m_PreviewTimePercentage * Mathf.PI * 2.0f), Mathf.Cos(m_PreviewTimePercentage * Mathf.PI * 2.0f), 0.0f) * s_PreviewShapeSize); + break; + case PreviewShape.Square: + if (m_PreviewTimePercentage < 0.25f) + transform.localPosition = m_PreviewBackupPosition.Value + localTransform.MultiplyPoint(new Vector3(Mathf.Lerp(-1.0f, 1.0f, m_PreviewTimePercentage * 4.0f), -1.0f, 0.0f) * s_PreviewShapeSize); + else if (m_PreviewTimePercentage < 0.5f) + transform.localPosition = m_PreviewBackupPosition.Value + localTransform.MultiplyPoint(new Vector3(1.0f, Mathf.Lerp(-1.0f, 1.0f, (m_PreviewTimePercentage - 0.25f) * 4.0f), 0.0f) * s_PreviewShapeSize); + else if (m_PreviewTimePercentage < 0.75f) + transform.localPosition = m_PreviewBackupPosition.Value + localTransform.MultiplyPoint(new Vector3(Mathf.Lerp(1.0f, -1.0f, (m_PreviewTimePercentage - 0.5f) * 4.0f), 1.0f, 0.0f) * s_PreviewShapeSize); + else + transform.localPosition = m_PreviewBackupPosition.Value + localTransform.MultiplyPoint(new Vector3(-1.0f, Mathf.Lerp(1.0f, -1.0f, (m_PreviewTimePercentage - 0.75f) * 4.0f), 0.0f) * s_PreviewShapeSize); + break; + case PreviewShape.Line: + transform.localPosition = m_PreviewBackupPosition.Value + localTransform.MultiplyPoint(new Vector3(Mathf.Lerp(-1.0f, 1.0f, m_PreviewTimePercentage), 0.0f, 0.0f) * s_PreviewShapeSize); + break; + case PreviewShape.SineWave: + transform.localPosition = m_PreviewBackupPosition.Value + localTransform.MultiplyPoint(new Vector3(Mathf.Lerp(-1.0f, 1.0f, m_PreviewTimePercentage), Mathf.Sin(m_PreviewTimePercentage * Mathf.PI * 2.0f * k_SineWaveRepeat) * k_SineWaveHeightMultiplier, 0.0f) * s_PreviewShapeSize); + break; + case PreviewShape.Spring: + transform.localPosition = m_PreviewBackupPosition.Value + localTransform.MultiplyPoint(new Vector3(Mathf.Sin(m_PreviewTimePercentage * Mathf.PI * 2.0f * k_SpringRotations), Mathf.Cos(m_PreviewTimePercentage * Mathf.PI * 2.0f * k_SpringRotations), Mathf.Lerp(-0.5f, 0.5f, m_PreviewTimePercentage) * k_SpringRotations) * s_PreviewShapeSize); + break; + } + + // Clear non-looping shapes when they repeat + if (previousPreviewTimePercentage > m_PreviewTimePercentage) + { + if (s_PreviewShape == PreviewShape.Line || s_PreviewShape == PreviewShape.SineWave || s_PreviewShape == PreviewShape.Spring) + tr.Clear(); + } + + // Clear the trail when a preview starts + if (m_PreviewIsFirstMove) + { + tr.Clear(); + m_PreviewIsFirstMove = false; + } + } + else if (s_PreviewIsPaused) + { + tr.previewTimeScale = 0.0f; + } + } + } + } + + private void RepaintSceneView() + { + if (s_PreviewIsPlaying && !s_PreviewIsPaused) + SceneView.RepaintAll(); + } + + private void Play() + { + if (!s_PreviewIsPlaying && !s_PreviewIsPaused) + SavePositionForPreview(); + + s_PreviewIsPlaying = true; + s_PreviewIsPaused = false; + } + + private void Pause() + { + s_PreviewIsPlaying = false; + s_PreviewIsPaused = true; + Tools.hidden = false; + } + + private void Stop() + { + s_PreviewIsPlaying = false; + s_PreviewIsPaused = false; + RestorePositionAfterPreview(); + } + + private void PlayStopGUI() + { + bool disablePlayButton = (Time.timeScale == 0.0f); + GUIContent playText = disablePlayButton ? Styles.playDisabled : Styles.play; + + // Play/Stop buttons + GUILayout.BeginHorizontal(GUILayout.Width(220.0f)); + { + using (new EditorGUI.DisabledScope(disablePlayButton)) + { + bool isPlaying = s_PreviewIsPlaying && !s_PreviewIsPaused && !disablePlayButton; + if (GUILayout.Button(isPlaying ? Styles.pause : playText, "ButtonLeft")) + { + if (isPlaying) + Pause(); + else + Play(); + } + } + + if (GUILayout.Button(Styles.restart, "ButtonMid")) + { + Stop(); + Play(); + } + + if (GUILayout.Button(Styles.stop, "ButtonRight")) + { + Stop(); + } + } + GUILayout.EndHorizontal(); + + // Playback info + PlayBackInfoGUI(); + + // Handle shortcut keys last so we do not activate them if inputfield has used the event + HandleKeyboardShortcuts(); + } + + private void PlayBackInfoGUI() + { + EventType oldEventType = Event.current.type; + int oldHotControl = GUIUtility.hotControl; + string oldFormat = EditorGUI.kFloatFieldFormatString; + + EditorGUIUtility.labelWidth = k_SceneViewOverlayLabelWidth; + + EditorGUI.kFloatFieldFormatString = Styles.secondsFloatFieldFormatString; + if (Time.timeScale == 0.0f) + { + using (new EditorGUI.DisabledScope(true)) + { + EditorGUILayout.FloatField(Styles.movementSpeedDisabled, 0.0f); + EditorGUILayout.FloatField(Styles.timeScaleDisabled, 0.0f); + } + } + else + { + EditorGUI.BeginChangeCheck(); + s_PreviewMovementSpeed = Mathf.Clamp(EditorGUILayout.FloatField(Styles.movementSpeed, s_PreviewMovementSpeed), 0.0f, 2.0f); + s_PreviewTimeScale = Mathf.Clamp(EditorGUILayout.FloatField(Styles.timeScale, s_PreviewTimeScale), 0.1f, 1.0f); + if (EditorGUI.EndChangeCheck()) + m_PreviewIsFirstMove = true; + } + + EditorGUI.BeginChangeCheck(); + s_PreviewShape = (PreviewShape)EditorGUILayout.EnumPopup(Styles.previewShape, s_PreviewShape); + if (EditorGUI.EndChangeCheck()) + m_PreviewIsFirstMove = true; + + EditorGUI.BeginChangeCheck(); + s_PreviewShapeSize = EditorGUILayout.FloatField(Styles.previewShapeSize, s_PreviewShapeSize); + if (EditorGUI.EndChangeCheck()) + { + s_PreviewShapeSize = ClampPreviewSize(target as TrailRenderer, s_PreviewShapeSize); + m_PreviewIsFirstMove = true; + } + + EditorGUI.kFloatFieldFormatString = oldFormat; + + s_PreviewShowBounds = GUILayout.Toggle(s_PreviewShowBounds, Styles.showBounds, EditorStyles.toggle); + + EditorGUIUtility.labelWidth = 0.0f; + } + + private void HandleKeyboardShortcuts() + { + var evt = Event.current; + + if (evt.type == EventType.ExecuteCommand) + { + if (HandleShortcutEvent(evt)) + evt.Use(); + } + } + + private bool HandleShortcutEvent(Event evt) + { + if (EditorApplication.isPlaying) + return false; + + if (evt.commandName == s_PlayEvent.commandName) + { + if (!s_PreviewIsPlaying) + Play(); + else + Pause(); + + return true; + } + else if (evt.commandName == s_StopEvent.commandName) + { + Stop(); + return true; + } + else if (evt.commandName == s_RestartEvent.commandName) + { + Stop(); + Play(); + return true; + } + else if (evt.commandName == s_ShowBoundsEvent.commandName) + { + s_PreviewShowBounds = !s_PreviewShowBounds; + return true; + } + + return false; + } + + private static Matrix4x4 GetPreviewMatrix(Vector3? backupPosition, Transform transform) + { + Matrix4x4 parentMatrix = Matrix4x4.identity; + if (transform.parent) + parentMatrix = transform.parent.localToWorldMatrix; + + Vector3 localPosition = backupPosition.HasValue ? backupPosition.Value : transform.localPosition; + Matrix4x4 localMatrix = Matrix4x4.TRS(localPosition, transform.localRotation, transform.localScale); + return parentMatrix * localMatrix; + } + + [Overlay(typeof(SceneView), k_OverlayId, k_DisplayName)] + class SceneViewTrailRendererOverlay : TransientSceneViewOverlay + { + const string k_OverlayId = "Scene View/TrailRenderer"; + const string k_DisplayName = "Trail Renderer"; + + public override bool visible + { + get { return s_Inspectors != null && s_Inspectors.Count > 0 && !EditorApplication.isPlaying && (s_Inspectors.Last().targets.Length == 1); } + } + + public override void OnGUI() + { + if (!visible) + return; + s_Inspectors.Last().PlayStopGUI(); + } + } + + [EditorTool("Shape", typeof(TrailRenderer))] + class ShapeGizmoTool : EditorTool, IDrawSelectedHandles + { + public override GUIContent toolbarIcon + { + get { return Styles.toolIcon; } + } + + public override bool IsAvailable() + { + // Check for count != 1 + if (targets.Skip(1).Any()) + return false; + if (s_Inspectors == null || s_Inspectors.Count == 0) + return false; + + return !EditorApplication.isPlaying; + } + + public override void OnToolGUI(EditorWindow window) + { + if (s_Inspectors == null || !s_Inspectors.Any()) + return; + + DrawHandles(s_Inspectors.Last(), true); + } + + public void OnDrawHandles() + { + if (!s_PreviewIsPlaying) + DrawHandles(s_Inspectors.Last(), false); + } + + private void DrawHandles(TrailRendererInspector inspector, bool allowGizmoEditing) + { + Color gizmoEditingColor = new Color(1.0f, 1.0f, 1.0f, allowGizmoEditing && !Event.current.alt ? 1.0f : 0.0f); + + Color origCol = Handles.color; + Handles.color = s_GizmoColor; + + Matrix4x4 orgMatrix = Handles.matrix; + + foreach (var tr in targets.OfType()) + { + Handles.matrix = GetPreviewMatrix(inspector.m_PreviewBackupPosition, tr.transform); + + switch (s_PreviewShape) + { + case PreviewShape.Circle: + { + EditorGUI.BeginChangeCheck(); + + inspector.m_PreviewArcHandle.angle = 360.0f; + inspector.m_PreviewArcHandle.radius = s_PreviewShapeSize; + inspector.m_PreviewArcHandle.SetColorWithRadiusHandle(Color.white, 0f); + inspector.m_PreviewArcHandle.radiusHandleColor *= gizmoEditingColor; + inspector.m_PreviewArcHandle.angleHandleColor = Color.clear; + + using (new Handles.DrawingScope(Handles.matrix * s_ArcHandleOffsetMatrix)) + inspector.m_PreviewArcHandle.DrawHandle(); + + if (EditorGUI.EndChangeCheck()) + s_PreviewShapeSize = ClampPreviewSize(tr, inspector.m_PreviewArcHandle.radius); + } + break; + case PreviewShape.Square: + { + EditorGUI.BeginChangeCheck(); + float size = DoSimpleSquareHandle(s_PreviewShapeSize, allowGizmoEditing); + if (EditorGUI.EndChangeCheck()) + s_PreviewShapeSize = ClampPreviewSize(tr, size); + } + break; + case PreviewShape.Line: + { + EditorGUI.BeginChangeCheck(); + float radius = Handles.DoSimpleEdgeHandle(Quaternion.identity, Vector3.zero, s_PreviewShapeSize, allowGizmoEditing); + if (EditorGUI.EndChangeCheck()) + s_PreviewShapeSize = ClampPreviewSize(tr, radius); + } + break; + case PreviewShape.SineWave: + { + EditorGUI.BeginChangeCheck(); + float radius = DoSimpleSineWaveHandle(s_PreviewShapeSize, allowGizmoEditing, k_SineWaveHeightMultiplier, k_SineWaveRepeat); + if (EditorGUI.EndChangeCheck()) + s_PreviewShapeSize = ClampPreviewSize(tr, radius); + } + break; + case PreviewShape.Spring: + { + EditorGUI.BeginChangeCheck(); + float radius = DoSimpleSpringHandle(s_PreviewShapeSize, allowGizmoEditing, k_SpringRotations); + if (EditorGUI.EndChangeCheck()) + s_PreviewShapeSize = ClampPreviewSize(tr, radius); + } + break; + } + } + + Handles.color = origCol; + Handles.matrix = orgMatrix; + } + + private static float DoSimpleSquareHandle(float size, bool editable) + { + if (Event.current.alt) + editable = false; + + Vector3 right = Vector3.right; + Vector3 up = Vector3.up; + + if (editable) + { + // Size handles at edges + EditorGUI.BeginChangeCheck(); + size = Handles.SizeSlider(Vector3.zero, up, size); + size = Handles.SizeSlider(Vector3.zero, -up, size); + size = Handles.SizeSlider(Vector3.zero, right, size); + size = Handles.SizeSlider(Vector3.zero, -right, size); + if (EditorGUI.EndChangeCheck()) + size = Mathf.Max(0.0f, size); + } + + // Draw gizmo + if (size > 0) + { + Vector3[] points = new Vector3[5]; + + points[0] = up * size + right * size; + points[1] = -up * size + right * size; + points[2] = -up * size - right * size; + points[3] = up * size - right * size; + points[4] = points[0]; + + Handles.DrawPolyLine(points); + } + + return size; + } + + private static float DoSimpleSineWaveHandle(float radius, bool editable, float heightMultiplier, float repeatCount) + { + if (Event.current.alt) + editable = false; + + Vector3 right = Vector3.right; + Vector3 up = Vector3.up; + + if (editable) + { + // Radius handles at ends + EditorGUI.BeginChangeCheck(); + radius = Handles.SizeSlider(Vector3.zero, right, radius); + radius = Handles.SizeSlider(Vector3.zero, -right, radius); + if (EditorGUI.EndChangeCheck()) + radius = Mathf.Max(0.0f, radius); + } + + // Draw gizmo + if (radius > 0) + { + Vector3 start = -right * radius; + + Vector3[] points = new Vector3[128]; + for (int i = 0; i < points.Length; i++) + { + float percent = ((float)i / (points.Length - 1)); + Vector3 sine = start + right * (radius * percent) * 2.0f; + sine += up * Mathf.Sin(percent * Mathf.PI * 2.0f * repeatCount) * radius * heightMultiplier; + points[i] = sine; + } + + Handles.DrawPolyLine(points); + } + + return radius; + } + + private static float DoSimpleSpringHandle(float radius, bool editable, float rotationCount) + { + if (Event.current.alt) + editable = false; + + Vector3 right = Vector3.right; + Vector3 up = Vector3.up; + + if (editable) + { + // Radius handles at ends + EditorGUI.BeginChangeCheck(); + + Vector3 start = new Vector3(0.0f, 0.0f, -0.5f * rotationCount) * radius; + radius = Handles.SizeSlider(start, up, radius); + + Vector3 end = new Vector3(0.0f, 0.0f, 0.5f * rotationCount) * radius; + radius = Handles.SizeSlider(end, up, radius); + + if (EditorGUI.EndChangeCheck()) + radius = Mathf.Max(0.0f, radius); + } + + // Draw gizmo + if (radius > 0) + { + Vector3[] points = new Vector3[128]; + for (int i = 0; i < points.Length; i++) + { + float percent = ((float)i / (points.Length - 1)); + float theta = percent * Mathf.PI * 2.0f * rotationCount; + Vector3 sine = new Vector3(Mathf.Sin(theta), Mathf.Cos(theta), Mathf.Lerp(-0.5f, 0.5f, percent) * rotationCount); + points[i] = sine * radius; + } + + Handles.DrawPolyLine(points); + } + + return radius; + } + } } } diff --git a/Editor/Mono/Inspector/TransformRotationGUI.cs b/Editor/Mono/Inspector/TransformRotationGUI.cs index fbd9afd381..18bd2d534b 100644 --- a/Editor/Mono/Inspector/TransformRotationGUI.cs +++ b/Editor/Mono/Inspector/TransformRotationGUI.cs @@ -96,6 +96,22 @@ public void RotationField(bool disabled) EditorGUI.FloatField(nr, s_XYZLabels[i], ref m_EulerFloats[i]); if (EditorGUI.EndChangeCheck() && m_EulerFloats[i].hasResult) eulerChangedMask |= 1 << i; + + if (Event.current.type == EventType.ContextClick && nr.Contains(Event.current.mousePosition)) + { + var childProperty = m_Rotation.Copy(); + childProperty.Next(true); + int childPropertyIndex = i; + while (childPropertyIndex > 0) + { + childProperty.Next(false); + childPropertyIndex--; + } + + EditorGUI.DoPropertyContextMenu(childProperty); + Event.current.Use(); + } + nr.x += w + EditorGUI.kSpacingSubLabel; } EditorGUIUtility.labelWidth = prevWidth; diff --git a/Editor/Mono/Inspector/TransformUtils.cs b/Editor/Mono/Inspector/TransformUtils.cs index 6238ea9153..a7af1fe1f4 100644 --- a/Editor/Mono/Inspector/TransformUtils.cs +++ b/Editor/Mono/Inspector/TransformUtils.cs @@ -2,6 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; using UnityEngine; using UnityEditor; @@ -18,5 +19,31 @@ public static void SetInspectorRotation(Transform t, Vector3 r) { t.SetLocalEulerAngles(r, t.rotationOrder); } + + public static bool GetConstrainProportions(Transform transform) + { + return GetConstrainProportions(new []{transform}); + } + + public static bool GetConstrainProportions(Transform[] transforms) + { + return Selection.DoAllGOsHaveConstrainProportionsEnabled(transforms); + } + + public static void SetConstrainProportions(Transform transform, bool enabled) + { + SetConstrainProportions(new[] { transform }, enabled); + } + + public static void SetConstrainProportions(Transform[] transforms, bool enabled) + { + foreach (var t in transforms) + { + if (t == null) + throw new ArgumentNullException("transform", "One or more transforms are null"); + } + + ConstrainProportionsTransformScale.SetConstrainProportions(transforms, enabled); + } } } diff --git a/Editor/Mono/Inspector/UnityEventDrawer.cs b/Editor/Mono/Inspector/UnityEventDrawer.cs index dca303a963..690bcfb341 100644 --- a/Editor/Mono/Inspector/UnityEventDrawer.cs +++ b/Editor/Mono/Inspector/UnityEventDrawer.cs @@ -9,6 +9,7 @@ using System.Text; using UnityEngine; using UnityEditor; +using UnityEditor.UIElements; using UnityEngine.Events; using Object = UnityEngine.Object; using UnityEngine.Pool; @@ -155,22 +156,21 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten private ListView CreateListView(SerializedProperty property) { - var listView = EditorUIService.instance.CreateListViewBinding(property.serializedObject); - listView.showAddRemoveFooter = true; - listView.reorderMode = ListViewReorderMode.Animated; - listView.showBorder = true; - listView.showFoldoutHeader = false; - listView.showBoundCollectionSize = false; - listView.showAlternatingRowBackgrounds = AlternatingRowBackground.None; - listView.fixedItemHeight = GetElementHeight(); + var listView = new ListView + { + showAddRemoveFooter = true, + reorderMode = ListViewReorderMode.Animated, + showBorder = true, + showFoldoutHeader = false, + showBoundCollectionSize = false, + showAlternatingRowBackgrounds = AlternatingRowBackground.None, + fixedItemHeight = GetElementHeight() + }; var propertyRelative = property.FindPropertyRelative(kCallsPath); listView.bindingPath = propertyRelative.propertyPath; - listView.makeItem += () => - { - return EditorUIService.instance.CreateUnityEventItem(); - }; + listView.makeItem += () => new UnityEventItem(); listView.bindItem += (VisualElement element, int i) => { @@ -208,7 +208,8 @@ private ListView CreateListView(SerializedProperty property) return GetArgument(pListener); }; - EditorUIService.instance.BindUnityEventItem(element, propertyData, createMenuCallback, formatSelectedValueCallback, getArgumentCallback); + var eventItem = element as UnityEventItem; + eventItem.BindFields(propertyData, createMenuCallback, formatSelectedValueCallback, getArgumentCallback); }; return listView; @@ -663,6 +664,9 @@ internal static GenericMenu BuildPopupList(Object target, UnityEventBase dummyEv // Collect all the names and record how many times the same name is used. foreach (Component comp in comps) { + if (comp == null) + continue; + var duplicateIndex = 0; if (duplicateNames.TryGetValue(comp.GetType().Name, out duplicateIndex)) duplicateIndex++; diff --git a/Editor/Mono/Inspector/VisualElements/MinMaxGradientField.cs b/Editor/Mono/Inspector/VisualElements/MinMaxGradientField.cs index ea530d83e4..fc3961be32 100644 --- a/Editor/Mono/Inspector/VisualElements/MinMaxGradientField.cs +++ b/Editor/Mono/Inspector/VisualElements/MinMaxGradientField.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Linq; using UnityEditorInternal; using UnityEngine; using UnityEngine.UIElements; @@ -11,6 +12,8 @@ namespace UnityEditor.UIElements { internal class MinMaxGradientField : BaseField { + public new class UxmlFactory : UxmlFactory { } + public new static readonly string ussClassName = "unity-min-max-gradient-field"; public static readonly string visualInputUssClass = ussClassName + "__visual-input"; public static readonly string dropdownFieldUssClass = ussClassName + "__dropdown-field"; @@ -28,56 +31,86 @@ internal class MinMaxGradientField : BaseField L10n.Tr("Random Color") }; - VisualElement m_ColorMin; - VisualElement m_ColorMax; - VisualElement m_GradientMin; - VisualElement m_GradientMax; - VisualElement m_ModeDropdown; + PropertyField m_ColorMin; + PropertyField m_ColorMax; + PropertyField m_GradientMin; + PropertyField m_GradientMax; + DropdownField m_ModeDropdown; VisualElement m_GradientsContainer; - VisualElement m_MixedValueTypeLabel; + Label m_MixedValueTypeLabel; public MinMaxGradientField() : this(null, null) { } public MinMaxGradientField(MinMaxGradientPropertyDrawer.PropertyData propertyData, string label) : base(label, null) { - m_ColorMin = EditorUIService.instance.CreatePropertyField(propertyData.colorMin, ""); - m_ColorMin.AddToClassList(colorFieldUssClass); - m_ColorMax = EditorUIService.instance.CreatePropertyField(propertyData.colorMax, ""); - m_ColorMax.AddToClassList(colorFieldUssClass); - m_GradientMin = EditorUIService.instance.CreatePropertyField(propertyData.gradientMin, ""); - m_GradientMax = EditorUIService.instance.CreatePropertyField(propertyData.gradientMax, ""); - m_ModeDropdown = EditorUIService.instance.CreateDropdownField(stringModes, () => propertyData.mode.intValue); - m_ModeDropdown.AddToClassList(dropdownFieldUssClass); - m_MixedValueTypeLabel = new Label("\u2014"); - m_MixedValueTypeLabel.AddToClassList(multipleValuesLabelUssClass); - - var dropdownInput = m_ModeDropdown.Q(null, "unity-popup-field__input"); - dropdownInput.AddToClassList(dropdownInputUssClass); - - m_GradientsContainer = new VisualElement(); - m_GradientsContainer.AddToClassList(gradientContainerUssClass); - m_GradientsContainer.Add(m_GradientMin); - m_GradientsContainer.Add(m_GradientMax); - - visualInput.AddToClassList(visualInputUssClass); - visualInput.Add(m_ColorMin); - visualInput.Add(m_ColorMax); - visualInput.Add(m_GradientsContainer); - visualInput.Add(m_MixedValueTypeLabel); - visualInput.Add(m_ModeDropdown); - - m_ModeDropdown.RegisterCallback>(e => + if (propertyData != null) { - var index = Array.IndexOf(stringModes, e.newValue); - var mode = (MinMaxGradientState)index; + m_ColorMin = new PropertyField(propertyData.colorMin, ""); + m_ColorMin.AddToClassList(colorFieldUssClass); + m_ColorMax = new PropertyField(propertyData.colorMax, ""); + m_ColorMax.AddToClassList(colorFieldUssClass); + m_GradientMin = new PropertyField(propertyData.gradientMin, ""); + m_GradientMax = new PropertyField(propertyData.gradientMax, ""); + m_ModeDropdown = new DropdownField + { + choices = stringModes.ToList() + }; + m_ModeDropdown.createMenuCallback = () => + { + var osMenu = new GenericOSMenu(); + + for (int i = 0; i < stringModes.Length; i++) + { + var option = stringModes[i]; + var isValueSelected = propertyData.mode.intValue == i; + + osMenu.AddItem(option, isValueSelected, () => + { + m_ModeDropdown.value = option; + }); + } + + return osMenu; + }; + m_ModeDropdown.formatSelectedValueCallback = (value) => + { + // Don't show label for this dropdown + return ""; + }; + + m_ModeDropdown.index = propertyData.mode.intValue; + m_ModeDropdown.AddToClassList(dropdownFieldUssClass); + m_MixedValueTypeLabel = new Label("\u2014"); + m_MixedValueTypeLabel.AddToClassList(multipleValuesLabelUssClass); + + var dropdownInput = m_ModeDropdown.Q(null, "unity-popup-field__input"); + dropdownInput.AddToClassList(dropdownInputUssClass); + + m_GradientsContainer = new VisualElement(); + m_GradientsContainer.AddToClassList(gradientContainerUssClass); + m_GradientsContainer.Add(m_GradientMin); + m_GradientsContainer.Add(m_GradientMax); + + visualInput.AddToClassList(visualInputUssClass); + visualInput.Add(m_ColorMin); + visualInput.Add(m_ColorMax); + visualInput.Add(m_GradientsContainer); + visualInput.Add(m_MixedValueTypeLabel); + visualInput.Add(m_ModeDropdown); + + m_ModeDropdown.RegisterCallback>(e => + { + var index = Array.IndexOf(stringModes, e.newValue); + var mode = (MinMaxGradientState)index; + + propertyData.mode.intValue = index; + propertyData.mode.serializedObject.ApplyModifiedProperties(); + UpdateFieldsDisplay(propertyData.mode); + }); - propertyData.mode.intValue = index; - propertyData.mode.serializedObject.ApplyModifiedProperties(); UpdateFieldsDisplay(propertyData.mode); - }); - - UpdateFieldsDisplay(propertyData.mode); + } } private void UpdateFieldsDisplay(SerializedProperty mode) diff --git a/Editor/Mono/InspectorUtility.cs b/Editor/Mono/InspectorUtility.cs new file mode 100644 index 0000000000..aa8cff31f2 --- /dev/null +++ b/Editor/Mono/InspectorUtility.cs @@ -0,0 +1,161 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using UnityEngine.Bindings; +using System; +using Object = UnityEngine.Object; +using System.Collections.Generic; +using UnityEngine.Scripting; + +namespace UnityEditor +{ + internal class InspectorUtility + { + internal delegate void LivePropertyOverrideCallback(SerializedObject serializedObject, bool isLiveUpdate); + internal delegate bool LivePropertyChangedCallback(Object[] unityObject); + + internal static Dictionary s_LivePropertyOverrideCallbacks = new Dictionary(); + internal static Dictionary s_LivePropertyChangedCallbacks = new Dictionary(); + + static SerializedObject s_CachedSerializedObject = new SerializedObject(IntPtr.Zero); + + internal static void SetLivePropertyOverride(Type type, LivePropertyOverrideCallback callback) + { + if (s_LivePropertyOverrideCallbacks.TryGetValue(type, out var del)) + { + del += callback; + } + else + { + s_LivePropertyOverrideCallbacks[type] = callback; + } + } + + internal static void RemoveLivePropertyOverride(Type type, LivePropertyOverrideCallback callback) + { + s_LivePropertyOverrideCallbacks[type] -= callback; + if (s_LivePropertyOverrideCallbacks[type] == null) + s_LivePropertyOverrideCallbacks.Remove(type); + } + + internal static void SetLivePropertyChanged(Type type, LivePropertyChangedCallback callback) + { + if (s_LivePropertyChangedCallbacks.TryGetValue(type, out var del)) + { + del += callback; + } + else + { + s_LivePropertyChangedCallbacks[type] = callback; + } + } + + internal static void RemoveLivePropertyChanged(Type type, LivePropertyChangedCallback callback) + { + s_LivePropertyChangedCallbacks[type] -= callback; + if (s_LivePropertyChangedCallbacks[type] == null) + s_LivePropertyChangedCallbacks.Remove(type); + } + + static bool IsLivePropertyChanged(Editor editor) + { + var target = editor.target; + if (target == null) + return false; + + var targetType = target.GetType(); + + if (s_LivePropertyChangedCallbacks.TryGetValue(targetType, out var cb)) + { + if (cb.Invoke(editor.targets)) + return true; + } + + return false; + } + + internal static void DirtyLivePropertyChanges(ActiveEditorTracker tracker) + { + if (!EditorApplication.isPlaying || s_LivePropertyOverrideCallbacks.Count <= 0) + return; + + var editors = tracker.activeEditors; + for (var i = 0; i != editors.Length;i++) + { + if (tracker.GetVisible(i) == 0) + continue; + + // Callback + var editor = editors[i]; + if (IsLivePropertyChanged(editor)) + editor.isInspectorDirty = true; + } + } + + // Check if current serialized object contains any live properties. + // If so, enable live property feature. + [RequiredByNativeCode] + internal static bool Internal_HasLiveProperties(IntPtr nativeObjectPtr) + { + if (!EditorApplication.isPlaying || s_LivePropertyOverrideCallbacks.Count <= 0) + return false; + + if (s_CachedSerializedObject.m_NativeObjectPtr != IntPtr.Zero) + throw new ArgumentException("Recursive EnableLivePropertyFeature are not allowed."); + + var livePropertyFeatureEnabled = false; + + try + { + s_CachedSerializedObject.m_NativeObjectPtr = nativeObjectPtr; + var target = s_CachedSerializedObject.targetObject; + if (target == null) + return false; + + var targetType = target.GetType(); + + if (s_LivePropertyOverrideCallbacks.ContainsKey(targetType)) + { + livePropertyFeatureEnabled = true; + } + } + finally + { + // This serialized object is just a temp reference to pass around, it doesn't own the pointer + s_CachedSerializedObject.m_NativeObjectPtr = IntPtr.Zero; + } + + return livePropertyFeatureEnabled; + } + + // Invoke override callback for live property. + [RequiredByNativeCode] + internal static void Internal_CallPropertyOverrideCallback(IntPtr nativeObjectPtr, bool isLiveUpdate) + { + if (s_CachedSerializedObject.m_NativeObjectPtr != IntPtr.Zero && s_CachedSerializedObject.m_NativeObjectPtr != nativeObjectPtr) + throw new ArgumentException("Recursive SetLivePropertyOverrides are not allowed."); + + try + { + if (s_CachedSerializedObject.m_NativeObjectPtr == IntPtr.Zero) + { + s_CachedSerializedObject.m_NativeObjectPtr = nativeObjectPtr; + if (s_CachedSerializedObject.targetObject == null) + return; + } + + var targetType = s_CachedSerializedObject.targetObject.GetType(); + if (s_LivePropertyOverrideCallbacks.TryGetValue(targetType, out var cb)) + { + cb.Invoke(s_CachedSerializedObject, isLiveUpdate); + } + } + finally + { + // This serialized object is just a temp reference to pass around, it doesn't own the pointer + s_CachedSerializedObject.m_NativeObjectPtr = IntPtr.Zero; + } + } + } +} diff --git a/Editor/Mono/InteractionContext.bindings.cs b/Editor/Mono/InteractionContext.bindings.cs new file mode 100644 index 0000000000..3777ca822b --- /dev/null +++ b/Editor/Mono/InteractionContext.bindings.cs @@ -0,0 +1,90 @@ +// 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; +using UnityEngine.Assertions; + +namespace UnityEditor +{ + [NativeType(Header = "Editor/Src/InteractionContext.h")] + internal partial class InteractionContext + { + [Flags] + public enum Flags + { + DisableNone = 0, + DisableUndo = 1, + DisableDialogs = 2, + + DisableUndoAndDialogs = DisableUndo | DisableDialogs, + } + + internal IntPtr m_NativePtr; + + public InteractionContext(Flags flags) + { + m_NativePtr = Internal_Create((int)flags); + } + + ~InteractionContext() + { + Internal_Destroy(m_NativePtr); + } + + public extern bool IsUndoEnabled(); + public extern bool WasAnyUndoOperationRegisteredSinceCreation(); + + public extern bool AreDialogsEnabled(); + public extern bool HasUnusedDialogResponses(); + public extern bool IsCurrentDialogResponse(string dialogTitle); + public extern string GetCurrentDialogResponse(); + public extern string GetCurrentDialogResponseAndAvance(); + public extern void AppendDialogResponse(string dialogTitle, string dialogResponse); + + public extern string GetErrors(); + + [FreeFunction("CreateInteractionContext", IsThreadSafe = false)] + private static extern IntPtr Internal_Create(int flags); + [FreeFunction("DestroyInteractionContext")] + private static extern void Internal_Destroy(IntPtr m_NativePtr); + + public static InteractionContext UserAction = new InteractionContext(Flags.DisableNone); + } + + internal class GlobalInteractionContext : InteractionContext, IDisposable + { + public GlobalInteractionContext(InteractionContext.Flags flags) + : base(flags) + { + Assert.IsNull(GetGlobalInteractionContext()); + SetGlobalInteractionContext(this); + } + + public void Dispose() + { + try + { + Assert.IsFalse(HasUnusedDialogResponses()); + if (!IsUndoEnabled()) + { + Assert.IsFalse(WasAnyUndoOperationRegisteredSinceCreation()); + } + } + finally + { + ClearGlobalInteractionContext(); + } + } + + [FreeFunction("SetGlobalInteractionContext")] + private static extern void SetGlobalInteractionContext(InteractionContext interactionContext); + + [FreeFunction("GetGlobalInteractionContext")] + private static extern InteractionContext GetGlobalInteractionContext(); + + [FreeFunction("ClearGlobalInteractionContext")] + private static extern void ClearGlobalInteractionContext(); + } +} diff --git a/Editor/Mono/Internal/MonoScripts.cs b/Editor/Mono/Internal/MonoScripts.cs index bde203fa3d..697ad65d47 100644 --- a/Editor/Mono/Internal/MonoScripts.cs +++ b/Editor/Mono/Internal/MonoScripts.cs @@ -15,7 +15,11 @@ public static class MonoScripts public static MonoScript CreateMonoScript(string scriptContents, string className, string nameSpace, string assemblyName, bool isEditorScript) { var script = new MonoScript(); - script.Init(scriptContents, className, nameSpace, assemblyName, isEditorScript); + if (!string.IsNullOrEmpty(scriptContents)) + { + Debug.LogWarning($"MonoScript {className} was initialized with a non-empty script. This has never worked and should not be attempted. The script contents will be ignored"); + } + script.Init(className, nameSpace, assemblyName, isEditorScript); return script; } } diff --git a/Editor/Mono/InternalEditorUtility.bindings.cs b/Editor/Mono/InternalEditorUtility.bindings.cs index c52fcafe82..3830ed411e 100644 --- a/Editor/Mono/InternalEditorUtility.bindings.cs +++ b/Editor/Mono/InternalEditorUtility.bindings.cs @@ -507,6 +507,7 @@ public static Version GetUnityVersion() [FreeFunction("InternalEditorUtilityBindings::GetLicenseInfoText")] extern public static string GetLicenseInfo(); + [Obsolete("GetLicenseFlags is no longer supported", error: true)] [FreeFunction("InternalEditorUtilityBindings::GetLicenseFlags")] extern public static int[] GetLicenseFlags(); @@ -836,10 +837,6 @@ internal static bool RunningUnderWindows8(bool orHigher = true) [FreeFunction("ScriptingManager::GetClasslibsProfile")] extern internal static string GetEditorProfile(); - [StaticAccessor("UnityExtensions::Get()", StaticAccessorType.Dot)] - [NativeMethod("IsInitialized")] - extern internal static bool IsUnityExtensionsInitialized(); - [FreeFunction("UnityExtensions::IsValidExtensionPath")] extern internal static bool IsValidUnityExtensionPath(string path); @@ -891,9 +888,6 @@ internal static PrecompiledAssembly[] GetUnityAssemblies(bool buildingForEditor, [FreeFunction("GetUnityAssembliesManaged")] extern private static PrecompiledAssembly[] GetUnityAssembliesInternal(bool buildingForEditor, BuildTarget target); - [FreeFunction("GetPrecompiledAssembliesManaged")] - extern internal static PrecompiledAssembly[] GetPrecompiledAssemblies(bool buildingForEditor, BuildTargetGroup buildTargetGroup, BuildTarget target, string[] extraScriptingDefines = null); - [FreeFunction("GetPrecompiledAssemblyPathsManaged")] extern internal static string[] GetPrecompiledAssemblyPaths(); diff --git a/Editor/Mono/InternalEditorUtility.cs b/Editor/Mono/InternalEditorUtility.cs index 5cedce2850..9748ad749f 100644 --- a/Editor/Mono/InternalEditorUtility.cs +++ b/Editor/Mono/InternalEditorUtility.cs @@ -13,6 +13,7 @@ using UnityEditor.Scripting.ScriptCompilation; using UnityEngine.UIElements; using UnityEngine.Video; +using UnityEditor.Build; namespace UnityEditorInternal { @@ -619,7 +620,6 @@ internal static bool IsScriptOrAssembly(string filename) switch (System.IO.Path.GetExtension(filename).ToLower()) { case ".cs": - case ".js": case ".boo": return true; case ".dll": @@ -647,7 +647,7 @@ internal static string GetMonolithicEngineAssemblyPath() internal static string[] GetCompilationDefines(EditorScriptCompilationOptions options, BuildTargetGroup targetGroup, BuildTarget target) { - return GetCompilationDefines(options, targetGroup, target, PlayerSettings.GetApiCompatibilityLevel(targetGroup)); + return GetCompilationDefines(options, targetGroup, target, PlayerSettings.GetApiCompatibilityLevel(NamedBuildTarget.FromActiveSettings(target))); } public static void SetShowGizmos(bool value) @@ -662,5 +662,26 @@ public static void SetShowGizmos(bool value) view.SetShowGizmos(value); } + + private static Material blitSceneViewCaptureMat; + public static bool CaptureSceneView(SceneView sv, RenderTexture rt) + { + if (!sv.hasFocus) + return false; + + if (blitSceneViewCaptureMat == null) + blitSceneViewCaptureMat = (Material)EditorGUIUtility.LoadRequired("SceneView/BlitSceneViewCapture.mat"); + + // Grab SceneView framebuffer into a temporary RT. + RenderTexture tmp = RenderTexture.GetTemporary(rt.descriptor); + Rect rect = new Rect(0, 0, sv.position.width, sv.position.height); + sv.m_Parent.GrabPixels(tmp, rect); + + // Blit it into the target RT, it will be flipped by the shader if necessary. + Graphics.Blit(tmp, rt, blitSceneViewCaptureMat); + RenderTexture.ReleaseTemporary(tmp); + + return true; + } } } diff --git a/Modules/UIServiceEditor/EditorToolbar/ToolbarElements/MainToolbarImguiContainer.cs b/Editor/Mono/MainToolbarImguiContainer.cs similarity index 100% rename from Modules/UIServiceEditor/EditorToolbar/ToolbarElements/MainToolbarImguiContainer.cs rename to Editor/Mono/MainToolbarImguiContainer.cs diff --git a/Editor/Mono/MaterialProperty.cs b/Editor/Mono/MaterialProperty.cs index baa88178d4..69995c776c 100644 --- a/Editor/Mono/MaterialProperty.cs +++ b/Editor/Mono/MaterialProperty.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Collections.Generic; using System.Runtime.InteropServices; using UnityEngine; using Object = UnityEngine.Object; @@ -213,5 +214,588 @@ private void ApplyProperty(object previousValue, int changedPropertyMask) if (!didApply) ShaderUtil.ApplyProperty(this, changedPropertyMask, "Modify " + displayName + " of " + targetTitle); } + + // -------- helper functions to handle material variant overrides + // It displays the override bar on the left, the lock icon, and the bold font + // It also creates the context menu when left clicking a property + + private static class Styles + { + public static string revertMultiText = L10n.Tr("Revert on {0} Material(s)"); + public static string applyToMaterialText = L10n.Tr("Apply to Material '{0}'"); + public static string applyToVariantText = L10n.Tr("Apply as Override in Variant '{0}'"); + + static Color overrideLineColor_l = new Color32(0x09, 0x09, 0x09, 0xFF); + static Color overrideLineColor_d = new Color32(0xC4, 0xC4, 0xC4, 0xFF); + public static Color overrideLineColor { get { return EditorGUIUtility.isProSkin ? overrideLineColor_d : overrideLineColor_l; } } + + public static readonly GUIContent revertContent = EditorGUIUtility.TrTextContent("Revert"); + public static readonly GUIContent revertAllContent = EditorGUIUtility.TrTextContent("Revert all Overrides"); + public static readonly GUIContent lockContent = EditorGUIUtility.TrTextContent("Lock in children"); + public static readonly GUIContent lockOriginContent = EditorGUIUtility.TrTextContent("See lock origin"); + + public static readonly GUIContent resetContent = EditorGUIUtility.TrTextContent("Reset"); + public static readonly GUIContent copyContent = EditorGUIUtility.TrTextContent("Copy"); + public static readonly GUIContent pasteContent = EditorGUIUtility.TrTextContent("Paste"); + + static readonly Texture lockInChildrenIcon = EditorGUIUtility.IconContent("HierarchyLock").image; + public static readonly GUIContent lockInChildrenContent = EditorGUIUtility.TrTextContent(string.Empty, "Locked properties cannot be overriden by a child.", lockInChildrenIcon); + + static readonly Texture lockedByAncestorIcon = EditorGUIUtility.IconContent("IN LockButton on").image; + public static readonly GUIContent lockedByAncestorContent = EditorGUIUtility.TrTextContent(string.Empty, "This property is set and locked by an ancestor.", lockedByAncestorIcon); + + public static readonly GUIStyle centered = new GUIStyle(EditorStyles.label) { alignment = TextAnchor.MiddleLeft }; + } + + struct PropertyData + { + public MaterialProperty property; + public MaterialSerializedProperty serializedProperty; + public Object[] targets; + + public bool wasBoldDefaultFont; + public bool isLockedInChildren, isLockedByAncestor, isOverriden; + + public float startY; + public Rect position; + + private static List capturedProperties = new List(); + private static List capturedSerializedProperties = new List(); + + private bool HasMixedValues(Func getter) + { + T value = getter(targets[0] as Material); + for (int i = 1; i < targets.Length; ++i) + { + if (!EqualityComparer.Default.Equals(value, getter(targets[i] as Material))) + return true; + } + return false; + } + + public bool hasMixedValue + { + get + { + if (property != null) + return property.hasMixedValue; + + if (serializedProperty == MaterialSerializedProperty.EnableInstancingVariants) + return HasMixedValues((mat) => mat.enableInstancing); + else if (serializedProperty == MaterialSerializedProperty.LightmapFlags) + return HasMixedValues((mat) => mat.globalIlluminationFlags); + else if (serializedProperty == MaterialSerializedProperty.DoubleSidedGI) + return HasMixedValues((mat) => mat.doubleSidedGI); + else if (serializedProperty == MaterialSerializedProperty.CustomRenderQueue) + return HasMixedValues((mat) => mat.rawRenderQueue); + return false; + } + } + + public void Init() + { + isLockedInChildren = false; + isLockedByAncestor = false; + isOverriden = true; + int nameId = property != null ? Shader.PropertyToID(property.name) : -1; + foreach (Material target in targets) + { + bool l, b, o; + if (property != null) + target.GetPropertyState(nameId, out o, out l, out b); + else + target.GetPropertyState(serializedProperty, out o, out l, out b); + // When multi editing: + // 1. Show property as locked if any target is locked, to prevent bypassing the lock + // 2. Show property as overriden if all targets override it, to not show overrides on materials + isLockedInChildren |= l; + isLockedByAncestor |= b; + isOverriden &= o; + } + } + + static void MergeStack(out bool lockedInChildren, out bool lockedByAncestor, out bool overriden) + { + // We have to copy the property stack, because access from the Menu callbacks is delayed + capturedProperties.Clear(); + capturedSerializedProperties.Clear(); + + lockedInChildren = false; + lockedByAncestor = false; + overriden = false; + for (int i = 0; i < s_PropertyStack.Count; i++) + { + // When multiple properties are displayed on the same line, we *or* everything otherwise it gets confusing. + if (s_PropertyStack[i].targets == null) continue; + lockedInChildren |= s_PropertyStack[i].isLockedInChildren; + lockedByAncestor |= s_PropertyStack[i].isLockedByAncestor; + overriden |= s_PropertyStack[i].isOverriden; + + if (s_PropertyStack[i].property != null) + capturedProperties.Add(s_PropertyStack[i].property); + else + capturedSerializedProperties.Add(s_PropertyStack[i].serializedProperty); + } + } + + static string GetMultiEditingDisplayName(string multiEditSuffix) + { + int nonEmptyCount = capturedProperties.Count + capturedSerializedProperties.Count; + if (nonEmptyCount != 1) + return nonEmptyCount + " " + multiEditSuffix; + else if (capturedProperties.Count != 0) + return capturedProperties[0].displayName; + else + return capturedSerializedProperties[0].ToString(); + } + + public static void DoPropertyContextMenu(bool lockMenusOnly, Object[] targets) + { + MergeStack(out bool lockedInChildren, out bool lockedByAncestor, out bool overriden); + + GenericMenu menu = new GenericMenu(); + + if (lockedByAncestor) + { + if (targets.Length != 1) + return; + + menu.AddItem(Styles.lockOriginContent, false, () => GotoLockOriginAction(targets)); + } + else if (GUI.enabled) + { + if (!lockMenusOnly) + DoRegularMenu(menu, overriden, targets); + DoLockPropertiesMenu(menu, !lockedInChildren, targets); + } + + if (Event.current.shift && capturedProperties.Count == 1) + { + if (menu.GetItemCount() != 0) + menu.AddSeparator(""); + menu.AddItem(EditorGUIUtility.TrTextContent("Copy Property Name"), false, () => EditorGUIUtility.systemCopyBuffer = capturedProperties[0].name); + } + + if (menu.GetItemCount() == 0) + return; + + Event.current.Use(); + menu.ShowAsContext(); + } + + enum DisplayMode { Material, Variant, Mixed }; + static DisplayMode GetDisplayMode(Object[] targets) + { + int variantCount = MaterialEditor.GetVariantCount(targets); + if (variantCount == 0) + return DisplayMode.Material; + if (variantCount == targets.Length) + return DisplayMode.Variant; + return DisplayMode.Mixed; + } + + static void ResetMaterialProperties() + { + foreach (var property in capturedProperties) + { + // fetch default value from shader + var shader = (property.targets[0] as Material).shader; + int nameId = shader.FindPropertyIndex(property.name); + switch (property.type) + { + case PropType.Float: + case PropType.Range: + property.floatValue = shader.GetPropertyDefaultFloatValue(nameId); + break; + case PropType.Vector: + property.vectorValue = shader.GetPropertyDefaultVectorValue(nameId); + break; + case PropType.Color: + property.colorValue = shader.GetPropertyDefaultVectorValue(nameId); + break; + case PropType.Int: + property.intValue = shader.GetPropertyDefaultIntValue(nameId); + break; + case PropType.Texture: + Texture texture = null; + var importer = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(shader)) as ShaderImporter; + if (importer != null) + texture = importer.GetDefaultTexture(property.name); + if (texture == null) + texture = EditorMaterialUtility.GetShaderDefaultTexture(shader, property.name); + property.textureValue = texture; + property.textureScaleAndOffset = new Vector4(1, 1, 0, 0); + break; + } + } + } + + static void HandleApplyRevert(GenericMenu menu, bool singleEditing, Object[] targets) + { + // Apply + if (singleEditing) + { + Material source = (Material)targets[0]; + Material destination = (Material)targets[0]; + while (destination = destination.parent as Material) + { + if (AssetDatabase.IsForeignAsset(destination)) + continue; + + var text = destination.isVariant ? Styles.applyToVariantText : Styles.applyToMaterialText; + var applyContent = new GUIContent(string.Format(text, destination.name)); + + menu.AddItem(applyContent, false, (object dest) => { + foreach (var prop in capturedProperties) + source.ApplyPropertyOverride((Material)dest, prop.name); + foreach (var prop in capturedSerializedProperties) + source.ApplyPropertyOverride((Material)dest, prop); + }, destination); + } + } + + // Revert + var content = singleEditing ? Styles.revertContent : + EditorGUIUtility.TempContent(string.Format(Styles.revertMultiText, targets.Length)); + menu.AddItem(content, false, () => { + string displayName = GetMultiEditingDisplayName("overrides"); + string targetName = singleEditing ? targets[0].name : targets.Length + " Materials"; + Undo.RecordObjects(targets, "Revert " + displayName + " of " + targetName); + + foreach (Material target in targets) + { + foreach (var prop in capturedProperties) + target.RevertPropertyOverride(prop.name); + foreach (var prop in capturedSerializedProperties) + target.RevertPropertyOverride(prop); + } + }); + } + + static void HandleCopyPaste(GenericMenu menu) + { + GetCopyPasteAction(capturedProperties[0], out var copyAction, out var pasteAction); + + if (menu.GetItemCount() != 0) + menu.AddSeparator(""); + + if (copyAction != null) + menu.AddItem(Styles.copyContent, false, copyAction); + else + menu.AddDisabledItem(Styles.copyContent); + if (pasteAction != null) + menu.AddItem(Styles.pasteContent, false, pasteAction); + else + menu.AddDisabledItem(Styles.pasteContent); + } + + static void HandleRevertAll(GenericMenu menu, bool singleEditing, Object[] targets) + { + foreach (Material target in targets) + { + if (target.overrideCount != 0) + { + if (menu.GetItemCount() != 0) + menu.AddSeparator(""); + + menu.AddItem(Styles.revertAllContent, false, () => { + string targetName = singleEditing ? targets[0].name : targets.Length + " Materials"; + Undo.RecordObjects(targets, "Revert all overrides of " + targetName); + + foreach (Material target in targets) + target.RevertAllPropertyOverrides(); + }); + break; + } + } + } + + static void DoRegularMenu(GenericMenu menu, bool isOverriden, Object[] targets) + { + var singleEditing = targets.Length == 1; + + if (isOverriden) + HandleApplyRevert(menu, singleEditing, targets); + + if (singleEditing && capturedProperties.Count == 1) + HandleCopyPaste(menu); + + DisplayMode displayMode = GetDisplayMode(targets); + if (displayMode == DisplayMode.Material) + { + if (menu.GetItemCount() != 0) + menu.AddSeparator(""); + + menu.AddItem(Styles.resetContent, false, ResetMaterialProperties); + } + else if (displayMode == DisplayMode.Variant) + HandleRevertAll(menu, singleEditing, targets); + } + + static void GetCopyPasteAction(MaterialProperty prop, out GenericMenu.MenuFunction copyAction, out GenericMenu.MenuFunction pasteAction) + { + bool canCopy = !capturedProperties[0].hasMixedValue; + bool canPaste = GUI.enabled; + + copyAction = null; + pasteAction = null; + switch (prop.type) + { + case PropType.Float: + case PropType.Range: + if (canCopy) copyAction = () => Clipboard.floatValue = prop.floatValue; + if (canPaste && Clipboard.hasFloat) pasteAction = () => prop.floatValue = Clipboard.floatValue; + break; + case PropType.Int: + if (canCopy) copyAction = () => Clipboard.integerValue = prop.intValue; + if (canPaste && Clipboard.hasInteger) pasteAction = () => prop.intValue = Clipboard.integerValue; + break; + case PropType.Color: + if (canCopy) copyAction = () => Clipboard.colorValue = prop.colorValue; + if (canPaste && Clipboard.hasColor) pasteAction = () => prop.colorValue = Clipboard.colorValue; + break; + case PropType.Vector: + if (canCopy) copyAction = () => Clipboard.vector4Value = prop.vectorValue; + if (canPaste && Clipboard.hasVector4) pasteAction = () => prop.vectorValue = Clipboard.vector4Value; + break; + case PropType.Texture: + if (canCopy) copyAction = () => Clipboard.guidValue = AssetDatabase.GUIDFromAssetPath(AssetDatabase.GetAssetPath(prop.textureValue)); + if (canPaste && Clipboard.hasGuid) pasteAction = () => prop.textureValue = AssetDatabase.LoadMainAssetAtPath(AssetDatabase.GUIDToAssetPath(Clipboard.guidValue)) as Texture; + break; + } + } + + static void DoLockPropertiesMenu(GenericMenu menu, bool lockValue, Object[] targets) + { + if (menu.GetItemCount() != 0) + menu.AddSeparator(""); + + // Lock + menu.AddItem(Styles.lockContent, !lockValue, () => { + LockProperties(lockValue, targets); + }); + } + + static void LockProperties(bool lockValue, Object[] targets) + { + string actionName = lockValue ? "locking" : "unlocking"; + string displayName = GetMultiEditingDisplayName("properties"); + string targetName = targets.Length == 1 ? targets[0].name : targets.Length + " Materials"; + Undo.RecordObjects(targets, string.Format("{0} {1} of {2}", actionName, displayName, targetName)); + + foreach (Material target in targets) + { + foreach (var prop in capturedProperties) + target.SetPropertyLock(prop.name, lockValue); + foreach (var prop in capturedSerializedProperties) + target.SetPropertyLock(prop, lockValue); + } + } + + static public void DoLockAction(Object[] targets) + { + MergeStack(out bool lockedInChildren, out bool lockedByAncestor, out bool _); + + if (lockedByAncestor) + GotoLockOriginAction(targets); + else + LockProperties(!lockedInChildren, targets); + + Event.current.Use(); + } + + static void GotoLockOriginAction(Object[] targets) + { + // Find lock origin + Material origin = targets[0] as Material; + while ((origin = origin.parent)) + { + bool isLocked = false; + foreach (var prop in capturedProperties) + { + origin.GetPropertyState(Shader.PropertyToID(prop.name), out _, out isLocked, out _); + if (isLocked) break; + } + if (isLocked) break; + + foreach (var prop in capturedSerializedProperties) + { + origin.GetPropertyState(prop, out _, out isLocked, out _); + if (isLocked) break; + } + if (isLocked) break; + } + + if (origin) + { + int clickCount = 1; + if (Event.current != null) + { + clickCount = Event.current.clickCount; + Event.current.Use(); + } + if (clickCount == 1) + EditorGUIUtility.PingObject(origin); + else + { + Selection.SetActiveObjectWithContext(origin, null); + GUIUtility.ExitGUI(); + } + } + } + } + static List s_PropertyStack = new List(); + internal static void ClearStack() => s_PropertyStack.Clear(); + + internal static void BeginProperty(MaterialProperty prop, Object[] targets) + { + // Get the current Y coordinate before drawing the property + // We define a new empty rect in order to grab the current height even if there was nothing drawn in the block + // (GetLastRect cause issue if it was first element of block) + MaterialProperty.BeginProperty(Rect.zero, prop, 0, targets, GUILayoutUtility.GetRect(0, 0).yMax); + } + + internal static void BeginProperty(MaterialSerializedProperty prop, Object[] targets) + { + // Get the current Y coordinate before drawing the property + // We define a new empty rect in order to grab the current height even if there was nothing drawn in the block + // (GetLastRect cause issue if it was first element of block) + MaterialProperty.BeginProperty(Rect.zero, null, prop, targets, GUILayoutUtility.GetRect(0, 0).yMax); + } + + internal static void BeginProperty(Rect totalRect, MaterialProperty prop, MaterialSerializedProperty serializedProp, Object[] targets, float startY = -1) + { + if (targets == null || IsRegistered(prop, serializedProp)) + { + s_PropertyStack.Add(new PropertyData() { targets = null }); + return; + } + + PropertyData data = new PropertyData() + { + property = prop, + serializedProperty = serializedProp, + targets = targets, + + startY = startY, + position = totalRect, + wasBoldDefaultFont = EditorGUIUtility.GetBoldDefaultFont() + }; + data.Init(); + s_PropertyStack.Add(data); + + if (data.isOverriden) + EditorGUIUtility.SetBoldDefaultFont(true); + + if (data.isLockedByAncestor) + EditorGUI.BeginDisabledGroup(true); + + EditorGUI.showMixedValue = data.hasMixedValue; + } + + internal static void EndProperty() + { + if (s_PropertyStack.Count == 0) + { + Debug.LogError("MaterialProperty stack is empty"); + return; + } + var data = s_PropertyStack[s_PropertyStack.Count - 1]; + if (data.targets == null) + { + s_PropertyStack.RemoveAt(s_PropertyStack.Count - 1); + return; + } + + Rect position = data.position; + if (data.startY != -1) + { + position = GUILayoutUtility.GetLastRect(); + position.yMin = data.startY; + position.x = 1; + position.width = EditorGUIUtility.labelWidth; + } + + bool mouseOnLock = false; + if (position != Rect.zero) + { + // Display override rect + if (data.isOverriden) + EditorGUI.DrawMarginLineForRect(position, Styles.overrideLineColor); + + Rect lockRegion = position; + lockRegion.width = 14; + lockRegion.height = 14; + lockRegion.x = 11; + lockRegion.y += (position.height - lockRegion.height) * 0.5f; + mouseOnLock = lockRegion.Contains(Event.current.mousePosition); + + // Display lock icon + Rect lockRect = position; + lockRect.width = 32; + lockRect.height = Mathf.Max(lockRect.height, 20.0f); + lockRect.x = 8; + lockRect.y += (position.height - lockRect.height) * 0.5f; + + if (data.isLockedByAncestor) + { + // Make sure we draw the lock only once + bool isLastLockInStack = true; + for (int i = 0; i < s_PropertyStack.Count - 1; i++) + { + if (s_PropertyStack[i].isLockedByAncestor) + { + isLastLockInStack = false; + break; + } + } + + if (isLastLockInStack) + GUI.Label(lockRect, Styles.lockedByAncestorContent, Styles.centered); + } + else if (data.isLockedInChildren) + GUI.Label(lockRect, Styles.lockInChildrenContent, Styles.centered); + else if (GUI.enabled) + { + GUIView.current?.MarkHotRegion(GUIClip.UnclipToWindow(lockRegion)); + if (mouseOnLock) + { + EditorGUI.BeginDisabledGroup(true); + GUI.Label(lockRect, Styles.lockInChildrenContent, Styles.centered); + EditorGUI.EndDisabledGroup(); + } + } + } + + // Restore state + EditorGUI.showMixedValue = false; + + EditorGUIUtility.SetBoldDefaultFont(data.wasBoldDefaultFont); + + if (data.isLockedByAncestor) + EditorGUI.EndDisabledGroup(); + + // Context menu + if (Event.current.rawType == EventType.ContextClick && (position.Contains(Event.current.mousePosition) || mouseOnLock)) + PropertyData.DoPropertyContextMenu(mouseOnLock, data.targets); + else if (Event.current.type == EventType.MouseUp && Event.current.button == 0 && mouseOnLock) + PropertyData.DoLockAction(data.targets); + + s_PropertyStack.RemoveAt(s_PropertyStack.Count - 1); + } + + static bool IsRegistered(MaterialProperty prop, MaterialSerializedProperty serializedProp) + { + // [PerRendererData] material properties are read-only as they are meant to be set in code on a per-renderer basis. + // Don't show override UI for them + if (prop != null && (prop.flags & PropFlags.PerRendererData) != 0) + return true; + for (int i = 0; i < s_PropertyStack.Count; i++) + { + if (s_PropertyStack[i].property == prop && s_PropertyStack[i].serializedProperty == serializedProp) + return true; + } + return false; + } } } // namespace UnityEngine.Rendering diff --git a/Editor/Mono/Media/Bindings/MediaEncoder.bindings.cs b/Editor/Mono/Media/Bindings/MediaEncoder.bindings.cs index 37c373658d..988acc0d9c 100644 --- a/Editor/Mono/Media/Bindings/MediaEncoder.bindings.cs +++ b/Editor/Mono/Media/Bindings/MediaEncoder.bindings.cs @@ -44,7 +44,7 @@ public void Set(int numerator, int denominator = 1) public static explicit operator double(MediaRational r) { - return (r.denominator == 0) ? 0.0 : (r.numerator / r.denominator); + return (r.denominator == 0) ? 0.0 : ((double)r.numerator / r.denominator); } public MediaRational inverse diff --git a/Editor/Mono/MeshUtility.bindings.cs b/Editor/Mono/MeshUtility.bindings.cs index 0aa6958109..67cffc6fb6 100644 --- a/Editor/Mono/MeshUtility.bindings.cs +++ b/Editor/Mono/MeshUtility.bindings.cs @@ -24,13 +24,16 @@ public static void Optimize(Mesh mesh) OptimizeReorderVertexBuffer(mesh); } - extern public static void SetMeshCompression(Mesh mesh, ModelImporterMeshCompression compression); - extern public static ModelImporterMeshCompression GetMeshCompression(Mesh mesh); + extern public static void SetMeshCompression([NotNull] Mesh mesh, ModelImporterMeshCompression compression); + extern public static ModelImporterMeshCompression GetMeshCompression([NotNull] Mesh mesh); [NativeName("SetPerTriangleUV2")] static extern bool SetPerTriangleUV2NoCheck(Mesh src, Vector2[] triUV); public static bool SetPerTriangleUV2(Mesh src, Vector2[] triUV) { + if (src == null) + throw new ArgumentNullException("src"); + if (triUV == null) throw new ArgumentNullException("triUV"); diff --git a/Editor/Mono/Modules/BeeBuildPostprocessor.cs b/Editor/Mono/Modules/BeeBuildPostprocessor.cs index a0db0aae5b..64648bd98a 100644 --- a/Editor/Mono/Modules/BeeBuildPostprocessor.cs +++ b/Editor/Mono/Modules/BeeBuildPostprocessor.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; using Bee.BeeDriver; @@ -119,8 +120,8 @@ private IEnumerable GetPluginsFor(BuildTarget target) LinkerConfig LinkerConfigFor(BuildPostProcessArgs args) { - var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(args.target); - var strippingLevel = PlayerSettings.GetManagedStrippingLevel(buildTargetGroup); + var namedBuildTarget = GetNamedBuildTarget(args); + var strippingLevel = PlayerSettings.GetManagedStrippingLevel(namedBuildTarget); // IL2CPP does not support a managed stripping level of disabled. If the player settings // do try this (which should not be possible from the editor), use Low instead. @@ -162,7 +163,7 @@ LinkerConfig LinkerConfigFor(BuildPostProcessArgs args) .ToArray(), Runtime = GetUseIl2Cpp(args) ? "il2cpp" : "mono", Profile = IL2CPPUtils.ApiCompatibilityLevelToDotNetProfileArgument( - PlayerSettings.GetApiCompatibilityLevel(buildTargetGroup), args.target), + PlayerSettings.GetApiCompatibilityLevel(namedBuildTarget), args.target), Ruleset = strippingLevel switch { ManagedStrippingLevel.Minimal => "Minimal", @@ -173,7 +174,7 @@ LinkerConfig LinkerConfigFor(BuildPostProcessArgs args) }, AdditionalArgs = additionalArgs.ToArray(), ModulesAssetPath = $"{BuildPipeline.GetPlaybackEngineDirectory(args.target, 0)}/modules.asset", - AllowDebugging = (args.report.summary.options & BuildOptions.AllowDebugging) == BuildOptions.AllowDebugging, + AllowDebugging = GetAllowDebugging(args), PerformEngineStripping = PlayerSettings.stripEngineCode, }; } @@ -185,8 +186,7 @@ LinkerConfig LinkerConfigFor(BuildPostProcessArgs args) protected virtual string Il2CppBuildConfigurationNameFor(BuildPostProcessArgs args) { - var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(args.target); - return Il2CppNativeCodeBuilderUtils.GetConfigurationName(PlayerSettings.GetIl2CppCompilerConfiguration(buildTargetGroup)); + return Il2CppNativeCodeBuilderUtils.GetConfigurationName(PlayerSettings.GetIl2CppCompilerConfiguration(GetNamedBuildTarget(args))); } protected virtual IEnumerable AdditionalIl2CppArgsFor(BuildPostProcessArgs args) @@ -194,6 +194,46 @@ protected virtual IEnumerable AdditionalIl2CppArgsFor(BuildPostProcessAr yield break; } + IEnumerable SplitArgs(string args) + { + int startIndex = 0; + bool inQuotes = false; + int i = 0; + for (; i < args.Length; i++) + { + if (args[i] == '"') + inQuotes = !inQuotes; + if (args[i] == ' ' && !inQuotes) + { + if (i - startIndex > 0) + yield return args.Substring(startIndex, i - startIndex); + startIndex = i + 1; + } + } + if (i - startIndex > 0) + yield return args.Substring(startIndex, i - startIndex); + } + + protected virtual string Il2CppSysrootPathFor(BuildPostProcessArgs args) + { + return null; + } + + protected virtual string Il2CppToolchainPathFor(BuildPostProcessArgs args) + { + return null; + } + + protected virtual string Il2CppCompilerFlagsFor(BuildPostProcessArgs args) + { + return null; + } + + protected virtual string Il2CppLinkerFlagsFor(BuildPostProcessArgs args) + { + return null; + } + Il2CppConfig Il2CppConfigFor(BuildPostProcessArgs args) { if (!GetUseIl2Cpp(args)) @@ -203,27 +243,34 @@ Il2CppConfig Il2CppConfigFor(BuildPostProcessArgs args) var diagArgs = Debug.GetDiagnosticSwitch("VMIl2CppAdditionalArgs").value as string; if (!string.IsNullOrEmpty(diagArgs)) - additionalArgs.Add(diagArgs.Trim('\'')); + additionalArgs.AddRange(SplitArgs(diagArgs.Trim('\''))); var playerSettingsArgs = PlayerSettings.GetAdditionalIl2CppArgs(); if (!string.IsNullOrEmpty(playerSettingsArgs)) - additionalArgs.Add(playerSettingsArgs); + additionalArgs.AddRange(SplitArgs(playerSettingsArgs)); + + var sysrootPath = Il2CppSysrootPathFor(args); + var toolchainPath = Il2CppToolchainPathFor(args); + var compilerFlags = Il2CppCompilerFlagsFor(args); + var linkerFlags = Il2CppLinkerFlagsFor(args); if (CrashReportingSettings.enabled) additionalArgs.Add("--emit-source-mapping"); - var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(args.target); - var apiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(buildTargetGroup); - var namedBuildTarget = NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup); + var namedBuildTarget = GetNamedBuildTarget(args); + var apiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(namedBuildTarget); var il2cppCodeGeneration = PlayerSettings.GetIl2CppCodeGeneration(namedBuildTarget); var platformHasIncrementalGC = BuildPipeline.IsFeatureSupported("ENABLE_SCRIPTING_GC_WBARRIERS", args.target); + var allowDebugging = GetAllowDebugging(args); + return new Il2CppConfig { EnableDeepProfilingSupport = GetDevelopment(args) && IsBuildOptionSet(args.report.summary.options, BuildOptions.EnableDeepProfilingSupport), EnableFullGenericSharing = il2cppCodeGeneration == Il2CppCodeGeneration.OptimizeSize, - Profile = IL2CPPUtils.ApiCompatibilityLevelToDotNetProfileArgument(PlayerSettings.GetApiCompatibilityLevel(buildTargetGroup), args.target), + Profile = IL2CPPUtils.ApiCompatibilityLevelToDotNetProfileArgument(PlayerSettings.GetApiCompatibilityLevel(namedBuildTarget), args.target), + Defines = string.Join(";", IL2CPPUtils.GetBuilderDefinedDefines(args.target, apiCompatibilityLevel, allowDebugging)), ConfigurationName = Il2CppBuildConfigurationNameFor(args), GcWBarrierValidation = platformHasIncrementalGC && PlayerSettings.gcWBarrierValidation, GcIncremental = platformHasIncrementalGC && PlayerSettings.gcIncremental && @@ -237,7 +284,11 @@ Il2CppConfig Il2CppConfigFor(BuildPostProcessArgs args) .Select(imp => imp.assetPath) .ToArray(), AdditionalArgs = additionalArgs.ToArray(), - AllowDebugging = (args.report.summary.options & BuildOptions.AllowDebugging) == BuildOptions.AllowDebugging, + AllowDebugging = allowDebugging, + CompilerFlags = compilerFlags, + LinkerFlags = linkerFlags, + SysRootPath = sysrootPath, + ToolChainPath = toolchainPath, }; } @@ -258,7 +309,7 @@ static bool IsNewInputSystemEnabled() CompanyName = args.companyName, ProductName = Paths.MakeValidFileName(args.productName), PlayerPackage = args.playerPackage, - ApplicationIdentifier = PlayerSettings.GetApplicationIdentifier(BuildPipeline.GetBuildTargetGroup(args.target)), + ApplicationIdentifier = PlayerSettings.GetApplicationIdentifier(GetNamedBuildTarget(args)), InstallIntoBuildsFolder = GetInstallingIntoBuildsFolder(args), GenerateIdeProject = GetCreateSolution(args), Development = (args.report.summary.options & BuildOptions.Development) == BuildOptions.Development, @@ -307,12 +358,22 @@ protected string GetDataFolderFor(BuildPostProcessArgs args) private SystemProcessRunnableProgram MakePlayerBuildProgram(BuildPostProcessArgs args) { var buildProgramAssembly = new NPath($"{args.playerPackage}/{GetPlatformNameForBuildProgram(args)}PlayerBuildProgram.exe"); + NPath buildPipelineFolder = $"{EditorApplication.applicationContentsPath}/Tools/BuildPipeline"; + NPath beePlatformFolder = $"{args.playerPackage}/Bee"; + var searchPaths = $"{beePlatformFolder}{Path.PathSeparator}"; + if (IL2CPPUtils.UsingDevelopmentBuild()) + { + NPath il2cppPath = IL2CPPUtils.GetExePath("il2cpp").ToNPath().Parent; + searchPaths = $"{il2cppPath}{Path.PathSeparator}"; + } + return new SystemProcessRunnableProgram(NetCoreRunProgram.NetCoreRunPath, new[] { buildProgramAssembly.InQuotes(SlashMode.Native), - $"\"{EditorApplication.applicationContentsPath}/Tools/BuildPipeline\"" - }); + $"\"{searchPaths}{buildPipelineFolder}\"" + }, + new () {{ "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "1" }}); } class PlayerBuildProgressAPI : ProgressAPI @@ -371,11 +432,11 @@ protected virtual RunnableProgram BeeBackendProgram(BuildPostProcessArgs args) return null; } - void SetupBeeDriver(BuildPostProcessArgs args) + void SetupBeeDriver(BuildPostProcessArgs args, ILPostProcessingProgram ilpp) { RunnableProgram buildProgram = MakePlayerBuildProgram(args); progressAPI = new PlayerBuildProgressAPI($"Building {args.productName}"); - Driver = UnityBeeDriver.Make(buildProgram, DagName(args), DagDirectory.ToString(), false, "", progressAPI, BeeBackendProgram(args)); + Driver = UnityBeeDriver.Make(buildProgram, DagName(args), DagDirectory.ToString(), false, "", ilpp, UnityBeeDriver.StdOutModeForPlayerBuilds, progressAPI, BeeBackendProgram(args)); foreach (var o in GetDataForBuildProgramFor(args)) { @@ -473,13 +534,29 @@ public override string PrepareForBuild(BuildOptions options, BuildTarget target) if ((options & BuildOptions.CleanBuildCache) == BuildOptions.CleanBuildCache) EditorCompilation.CleanCache(); + 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); } protected virtual void CleanBuildOutput(BuildPostProcessArgs args) { - new NPath(args.installPath).DeleteIfExists(DeleteMode.Soft); - new NPath(GetIl2CppDataBackupFolderName(args)).DeleteIfExists(DeleteMode.Soft); + if (!GetInstallingIntoBuildsFolder(args)) + { + new NPath(args.installPath).DeleteIfExists(DeleteMode.Soft); + new NPath(GetIl2CppDataBackupFolderName(args)).DeleteIfExists(DeleteMode.Soft); + } } public override void PostProcess(BuildPostProcessArgs args) @@ -491,7 +568,7 @@ public override void PostProcess(BuildPostProcessArgs args) var buildStep = args.report.BeginBuildStep("Setup incremental player build"); - SetupBeeDriver(args); + SetupBeeDriver(args, new ILPostProcessingProgram()); args.report.EndBuildStep(buildStep); // Remove any previous file entries in the build report. @@ -552,7 +629,7 @@ public override void PostProcessCompletedBuild(BuildPostProcessArgs args) { base.PostProcessCompletedBuild(args); - if (PlayerSettings.GetManagedStrippingLevel(BuildPipeline.GetBuildTargetGroup(args.target)) == ManagedStrippingLevel.Disabled) + if (PlayerSettings.GetManagedStrippingLevel(GetNamedBuildTarget(args)) == ManagedStrippingLevel.Disabled) return; var strippingInfo = GetStrippingInfoFromBuild(args); @@ -576,6 +653,19 @@ protected virtual bool IsPluginCompatibleWithCurrentBuild(BuildTarget buildTarge return !string.Equals(cpu, "None", StringComparison.OrdinalIgnoreCase); } + protected NamedBuildTarget GetNamedBuildTarget(BuildPostProcessArgs args) + { + var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(args.target); + + if (buildTargetGroup == BuildTargetGroup.Standalone) + { + return (StandaloneBuildSubtarget)args.subtarget == StandaloneBuildSubtarget.Server + ? NamedBuildTarget.Server : NamedBuildTarget.Standalone; + } + + return NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup); + } + protected bool GetDevelopment(BuildPostProcessArgs args) => IsBuildOptionSet(args.options, BuildOptions.Development); @@ -586,6 +676,13 @@ protected bool ShouldAppendBuild(BuildPostProcessArgs args) => IsBuildOptionSet(args.options, BuildOptions.AcceptExternalModificationsToPlayer); protected virtual bool GetUseIl2Cpp(BuildPostProcessArgs args) => - PlayerSettings.GetScriptingBackend(BuildPipeline.GetBuildTargetGroup(args.target)) == ScriptingImplementation.IL2CPP; + PlayerSettings.GetScriptingBackend(GetNamedBuildTarget(args)) == ScriptingImplementation.IL2CPP; + + protected virtual bool GetAllowDebugging(BuildPostProcessArgs args) => (args.report.summary.options & BuildOptions.AllowDebugging) == BuildOptions.AllowDebugging; + + #pragma warning disable 618 + protected virtual bool GetUseCoreCLR(BuildPostProcessArgs args) => + PlayerSettings.GetScriptingBackend(GetNamedBuildTarget(args)) == ScriptingImplementation.CoreCLR; + } } diff --git a/Editor/Mono/Modules/DefaultBuildPostprocessor.cs b/Editor/Mono/Modules/DefaultBuildPostprocessor.cs index c943cb27d9..e0ff8a0768 100644 --- a/Editor/Mono/Modules/DefaultBuildPostprocessor.cs +++ b/Editor/Mono/Modules/DefaultBuildPostprocessor.cs @@ -73,14 +73,8 @@ public virtual void UpdateBootConfig(BuildTarget target, BootConfigData config, config.Set("wait-for-native-debugger", "0"); if (config.Get("player-connection-debug") == "1") { - if (EditorUserBuildSettings.waitForManagedDebugger) - { - config.Set("wait-for-managed-debugger", "1"); - } - else - { - config.Set("wait-for-managed-debugger", "0"); - } + config.Set("wait-for-managed-debugger", EditorUserBuildSettings.waitForManagedDebugger ? "1" : "0"); + config.Set("managed-debugger-fixed-port", EditorUserBuildSettings.managedDebuggerFixedPort.ToString()); } config.Set("hdr-display-enabled", PlayerSettings.useHDRDisplay ? "1" : "0"); diff --git a/Editor/Mono/Modules/DefaultBuildWindowExtension.cs b/Editor/Mono/Modules/DefaultBuildWindowExtension.cs index f967f28d62..b0a917144b 100644 --- a/Editor/Mono/Modules/DefaultBuildWindowExtension.cs +++ b/Editor/Mono/Modules/DefaultBuildWindowExtension.cs @@ -52,6 +52,7 @@ public virtual bool AskForBuildLocation() public virtual bool ShouldDrawExplicitArrayBoundsCheckbox() { return false; } public virtual bool ShouldDrawForceOptimizeScriptsCheckbox() { return false; } public virtual bool ShouldDrawWaitForManagedDebugger() { return false; } + public virtual bool ShouldDrawManagedDebuggerFixedPort() { return false; } public virtual bool ShouldDisableManagedDebuggerCheckboxes() { return false; } public virtual void DoScriptsOnlyGUI() diff --git a/Editor/Mono/Modules/DefaultPluginImporterExtension.cs b/Editor/Mono/Modules/DefaultPluginImporterExtension.cs index e80811c52a..700c1893f9 100644 --- a/Editor/Mono/Modules/DefaultPluginImporterExtension.cs +++ b/Editor/Mono/Modules/DefaultPluginImporterExtension.cs @@ -9,6 +9,8 @@ using System.Linq; using System.Text; using UnityEditor.Compilation; +using UnityEditor.Scripting.ScriptCompilation; +using UnityEditorInternal; using UnityEngine; namespace UnityEditor.Modules @@ -151,38 +153,43 @@ public virtual string CalculateFinalPluginPath(string platformName, PluginImport return Path.GetFileName(imp.assetPath); } - protected Dictionary> GetCompatiblePlugins(string buildTargetName) + private static bool IsPluginCompatible(PluginImporter pluginImporter, string buildTargetName, string[] defines) { - var assemblies = CompilationPipeline.GetAssemblies(); - var assemblyDefines = new Dictionary(assemblies.Length); - foreach (var assembly in assemblies) - { - assemblyDefines.Add(assembly.name, assembly.defines); - } + var defineConstraints = pluginImporter.DefineConstraints; + var isCompatibleWithPlatform = pluginImporter.GetCompatibleWithPlatformOrAnyPlatformBuildTarget(buildTargetName); + return isCompatibleWithPlatform + && DefineConstraintsHelper.IsDefineConstraintsCompatible(defines, defineConstraints); + } - IEnumerable plugins = PluginImporter.GetAllImporters().Where(imp => imp.GetCompatibleWithPlatformOrAnyPlatformBuildTarget(buildTargetName) && imp.IsCompatibleWithDefines(assemblyDefines.ContainsKey(imp.name) ? assemblyDefines[imp.name] : null)); - Dictionary> matchingPlugins = new Dictionary>(); + protected Dictionary> GetCompatiblePlugins(string buildTargetName, string[] defines) + { + var pluginImporters = PluginImporter.GetAllImporters(); + var plugins = pluginImporters + .Where(pluginImporter => IsPluginCompatible(pluginImporter, buildTargetName, defines)); + + plugins = PluginImporter.FilterAssembliesByAssemblyVersion(plugins); + var matchingPlugins = new Dictionary>(); foreach (var plugin in plugins) { string finalPluginPath = CalculateFinalPluginPath(buildTargetName, plugin); if (string.IsNullOrEmpty(finalPluginPath)) continue; - List temp = null; - if (matchingPlugins.TryGetValue(finalPluginPath, out temp) == false) + if (!matchingPlugins.TryGetValue(finalPluginPath, out var pluginsList)) { - temp = new List(); - matchingPlugins[finalPluginPath] = temp; + pluginsList = new List(); + matchingPlugins[finalPluginPath] = pluginsList; } - temp.Add(plugin); + pluginsList.Add(plugin); } + return matchingPlugins; } - public virtual bool CheckFileCollisions(string buildTargetName) + public virtual bool CheckFileCollisions(string buildTargetName, string[] defineConstraints) { - Dictionary> matchingPlugins = GetCompatiblePlugins(buildTargetName); + Dictionary> matchingPlugins = GetCompatiblePlugins(buildTargetName, defineConstraints); bool foundCollisions = false; diff --git a/Editor/Mono/Modules/ModuleManager.cs b/Editor/Mono/Modules/ModuleManager.cs index eff6836d2e..0937538dec 100644 --- a/Editor/Mono/Modules/ModuleManager.cs +++ b/Editor/Mono/Modules/ModuleManager.cs @@ -485,8 +485,13 @@ internal static bool ShouldShowMultiDisplayOption() { GUIContent[] platformDisplayNames = Modules.ModuleManager.GetDisplayNames(EditorUserBuildSettings.activeBuildTarget.ToString()); BuildTargetGroup curPlatform = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget); - return curPlatform == BuildTargetGroup.Standalone || curPlatform == BuildTargetGroup.WSA || curPlatform == BuildTargetGroup.iOS || curPlatform == BuildTargetGroup.Android - || platformDisplayNames != null; + return curPlatform == BuildTargetGroup.Standalone + || curPlatform == BuildTargetGroup.WSA + || curPlatform == BuildTargetGroup.iOS + || curPlatform == BuildTargetGroup.Android + || curPlatform == BuildTargetGroup.EmbeddedLinux + || platformDisplayNames != null + ; } internal static GUIContent[] GetDisplayNames(string target) diff --git a/Editor/Mono/Modules/PlatformSupportModule.cs b/Editor/Mono/Modules/PlatformSupportModule.cs index 3c2c10d722..0868a1ca10 100644 --- a/Editor/Mono/Modules/PlatformSupportModule.cs +++ b/Editor/Mono/Modules/PlatformSupportModule.cs @@ -171,12 +171,26 @@ internal class DefaultScriptingImplementations : IScriptingImplementations { public virtual ScriptingImplementation[] Supported() { - return new[] { ScriptingImplementation.Mono2x, ScriptingImplementation.IL2CPP }; + if (Unsupported.IsSourceBuild()) + { + return new[] + { + ScriptingImplementation.Mono2x, + ScriptingImplementation.IL2CPP, + #pragma warning disable 618 + ScriptingImplementation.CoreCLR + }; + } + return new[] + { + ScriptingImplementation.Mono2x, + ScriptingImplementation.IL2CPP, + }; } public virtual ScriptingImplementation[] Enabled() { - return new[] { ScriptingImplementation.Mono2x, ScriptingImplementation.IL2CPP }; + return Supported(); } public virtual bool AllowIL2CPPCompilerConfigurationSelection() @@ -303,6 +317,8 @@ internal interface IBuildWindowExtension // Enables a dialog "Wait For Managed debugger", which halts program execution until managed debugger is connected bool ShouldDrawWaitForManagedDebugger(); + bool ShouldDrawManagedDebuggerFixedPort(); + // Grays out managed debugger options bool ShouldDisableManagedDebuggerCheckboxes(); @@ -367,7 +383,7 @@ internal interface IPluginImporterExtension // Called before building the player, checks if plugins don't overwrite each other string CalculateFinalPluginPath(string buildTargetName, PluginImporter imp); - bool CheckFileCollisions(string buildTargetName); + bool CheckFileCollisions(string buildTargetName, string[] buildDefineConstraints); } internal struct BuildLaunchPlayerArgs diff --git a/Editor/Mono/MonoScript.bindings.cs b/Editor/Mono/MonoScript.bindings.cs index 24fd0f7836..2d3b3ec9cd 100644 --- a/Editor/Mono/MonoScript.bindings.cs +++ b/Editor/Mono/MonoScript.bindings.cs @@ -50,9 +50,9 @@ public MonoScript() : base(TextAsset.CreateOptions.None, null) Init_Internal(this); } - internal void Init(string scriptContents, string className, string nameSpace, string assemblyName, bool isEditorScript) + internal void Init(string className, string nameSpace, string assemblyName, bool isEditorScript) { - Init(this, scriptContents, className, nameSpace, assemblyName, isEditorScript); + Init(this, className, nameSpace, assemblyName, isEditorScript); } [FreeFunction("MonoScript_Init_Internal")] @@ -60,7 +60,7 @@ internal void Init(string scriptContents, string className, string nameSpace, st // *undocumented* [FreeFunction("MonoScript_Init")] - private static extern void Init([NotNull("NullExceptionObject")] MonoScript self, string scriptContents, string className, string nameSpace, string assemblyName, bool isEditorScript); + private static extern void Init([NotNull("NullExceptionObject")] MonoScript self, string className, string nameSpace, string assemblyName, bool isEditorScript); // *undocumented* [NativeName("GetAssemblyName")] diff --git a/Editor/Mono/ObjectListArea.cs b/Editor/Mono/ObjectListArea.cs index 960f17ed59..bc50f31383 100644 --- a/Editor/Mono/ObjectListArea.cs +++ b/Editor/Mono/ObjectListArea.cs @@ -247,7 +247,8 @@ internal void InitForSearch(Rect rect, HierarchyType hierarchyType, SearchFilter requiredTypeNames = searchFilter.classNames, requiredTypes = searchFilter.classNames.Select(name => TypeCache.GetTypesDerivedFrom() - .FirstOrDefault(t => name == t.FullName || name == t.Name)) + .FirstOrDefault(t => name == t.FullName || name == t.Name)), + searchFilter = searchFilter }; }, searchSessionOptions); m_SearchSessionHandler.BeginSearch(searchQuery); @@ -341,7 +342,7 @@ internal float GetVisibleWidth() public float m_RightMargin = 10f; public float m_LeftMargin = 10f; - public virtual void OnGUI(Rect position, int keyboardControlID) + public void OnGUI(Rect position, int keyboardControlID) { s_VCEnabled = VersionControlUtils.isVersionControlConnected; @@ -643,7 +644,7 @@ public void SelectAll() SetSelection(instanceIDs.ToArray(), false); } - protected void SetSelection(int[] selectedInstanceIDs, bool doubleClicked) + void SetSelection(int[] selectedInstanceIDs, bool doubleClicked) { InitSelection(selectedInstanceIDs); diff --git a/Editor/Mono/ObjectListLocalGroup.cs b/Editor/Mono/ObjectListLocalGroup.cs index c28d3d2cfb..3b8c3468ba 100644 --- a/Editor/Mono/ObjectListLocalGroup.cs +++ b/Editor/Mono/ObjectListLocalGroup.cs @@ -662,12 +662,6 @@ void DrawSubAssetBackground(int beginIndex, int endIndex, float yOffset) } } - internal void DrawItem(Rect itemRect, ExtraItem extraItem) - { - GUI.Label(itemRect, GUIContent.none, Styles.iconAreaBg); - DrawItem(itemRect, null, extraItem, false); - } - void DrawItem(Rect position, FilteredHierarchy.FilterResult filterItem, BuiltinResource builtinResource, bool isFolderBrowsing) { System.Diagnostics.Debug.Assert((filterItem != null && builtinResource == null) || @@ -1426,7 +1420,7 @@ public virtual void StartDrag(int draggedInstanceID, List selectedInstanceI public DragAndDropVisualMode DoDrag(int dragToInstanceID, bool perform) { - return DragAndDrop.Drop(DragAndDropWindowTarget.projectBrowser, dragToInstanceID, AssetDatabase.GetAssetPath(dragToInstanceID), perform); + return DragAndDrop.DropOnProjectBrowserWindow(dragToInstanceID, AssetDatabase.GetAssetPath(dragToInstanceID), perform); } static internal int GetControlIDFromInstanceID(int instanceID) diff --git a/Editor/Mono/ObjectSelector.cs b/Editor/Mono/ObjectSelector.cs index ed3a210d12..d87b386539 100644 --- a/Editor/Mono/ObjectSelector.cs +++ b/Editor/Mono/ObjectSelector.cs @@ -79,6 +79,7 @@ static class Styles UnityObject m_ObjectBeingEdited; SerializedProperty m_EditedProperty; + bool m_SelectionCancelled; int m_LastSelectedInstanceId = 0; readonly SearchService.ObjectSelectorSearchSessionHandler m_SearchSessionHandler = new SearchService.ObjectSelectorSearchSessionHandler(); readonly SearchSessionOptions m_LegacySearchSessionOptions = new SearchSessionOptions { legacyOnly = true }; @@ -428,6 +429,7 @@ internal void Show(UnityObject obj, Type[] requiredTypes, UnityObject objectBein m_AllowedIDs = allowedInstanceIDs; m_ObjectBeingEdited = objectBeingEdited; m_LastSelectedInstanceId = obj?.GetInstanceID() ?? 0; + m_SelectionCancelled = false; m_OnObjectSelectorClosed = onObjectSelectorClosed; m_OnObjectSelectorUpdated = onObjectSelectedUpdated; @@ -502,6 +504,7 @@ internal void Show(UnityObject obj, Type[] requiredTypes, UnityObject objectBein // Undo changes we have done in the ObjectSelector Undo.RevertAllDownToGroup(m_ModalUndoGroup); m_LastSelectedInstanceId = 0; + m_SelectionCancelled = true; } else { @@ -515,6 +518,8 @@ internal void Show(UnityObject obj, Type[] requiredTypes, UnityObject objectBein if (m_SearchSessionHandler.SelectObject(onSelectorClosed, onSelectionChanged)) return; + else + m_SearchSessionHandler.EndSession(); } // Freeze to prevent flicker on OSX. @@ -522,6 +527,7 @@ internal void Show(UnityObject obj, Type[] requiredTypes, UnityObject objectBein // SetFreezeDisplay(false) further down. ContainerWindow.SetFreezeDisplay(true); + 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++) @@ -529,6 +535,11 @@ internal void Show(UnityObject obj, Type[] requiredTypes, UnityObject objectBein titleContent = EditorGUIUtility.TrTextContent(text); // Deal with window size + if (shouldRepositionWindow) + { + m_Parent.window.LoadInCurrentMousePosition(); + m_Parent.window.FitWindowToScreen(true); + } Rect p = m_Parent == null ? new Rect(0, 0, 1, 1) : m_Parent.window.position; p.width = EditorPrefs.GetFloat("ObjectSelectorWidth", 200); p.height = EditorPrefs.GetFloat("ObjectSelectorHeight", 390); @@ -616,6 +627,11 @@ void InitIfNeeded() } } + public static bool SelectionCanceled() + { + return ObjectSelector.get.m_SelectionCancelled; + } + public static UnityObject GetCurrentObject() { return EditorUtility.InstanceIDToObject(ObjectSelector.get.GetSelectedInstanceID()); @@ -945,6 +961,7 @@ void Cancel() m_ListArea?.InitSelection(new int[0]); m_ObjectTreeWithSearch.Clear(); m_LastSelectedInstanceId = 0; + m_SelectionCancelled = true; m_EditedProperty = null; SendEvent(ObjectSelectorCanceledCommand, false); diff --git a/Editor/Mono/OrderedCallbackCollection.cs b/Editor/Mono/OrderedCallbackCollection.cs new file mode 100644 index 0000000000..39660d2e5d --- /dev/null +++ b/Editor/Mono/OrderedCallbackCollection.cs @@ -0,0 +1,378 @@ +// 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 System.Text; +using UnityEngine; +using UnityEngine.Pool; + +namespace UnityEditor.Callbacks +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class RunAfterClassAttribute : Attribute + { + public Type classType { get; } + + public RunAfterClassAttribute(Type type) => classType = type; + + public RunAfterClassAttribute(string assemblyQualifiedName) => classType = Type.GetType(assemblyQualifiedName, false); + } + + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class RunBeforeClassAttribute : Attribute + { + public Type classType { get; } + + public RunBeforeClassAttribute(Type type) => classType = type; + + public RunBeforeClassAttribute(string assemblyQualifiedName) => classType = Type.GetType(assemblyQualifiedName, false); + } + + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class RunAfterAssemblyAttribute : Attribute + { + public string assemblyName { get; } + + public RunAfterAssemblyAttribute(string assemblyName) => this.assemblyName = assemblyName; + } + + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class RunBeforeAssemblyAttribute : Attribute + { + public string assemblyName { get; } + + public RunBeforeAssemblyAttribute(string assemblyName) => this.assemblyName = assemblyName; + } + + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class RunAfterPackageAttribute : Attribute + { + public string packageName { get; } + + public RunAfterPackageAttribute(string packageName) => this.packageName = packageName; + } + + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class RunBeforePackageAttribute : Attribute + { + public string packageName { get; } + + public RunBeforePackageAttribute(string packageName) => this.packageName = packageName; + } + + abstract class OrderedCallbackCollection + { + public abstract class Callback : IComparable + { + string m_PackageName; + + public abstract Type classType { get; } + + public abstract string name { get; } + + public string packageName + { + get + { + if (m_PackageName == null) + { + var pkg = PackageManager.PackageInfo.FindForAssembly(classType.Assembly); + m_PackageName = pkg != null ? pkg.name : string.Empty; + } + return m_PackageName; + } + } + + public HashSet outgoing { get; } = new HashSet(); + public HashSet incoming { get; } = new HashSet(); + + public abstract IEnumerable GetCustomAttributes() where T : Attribute; + + public void AddIncomingConnection(Callback method) + { + incoming.Add(method); + method.outgoing.Add(this); + } + + public void AddIncomingConnections(IList methods) + { + foreach(var m in methods) + { + AddIncomingConnection(m); + } + } + + public void AddOutgoingConnection(Callback method) + { + outgoing.Add(method); + method.incoming.Add(this); + } + + public void AddOutgoingConnections(IList methods) + { + foreach (var m in methods) + { + AddOutgoingConnection(m); + } + } + + public int CompareTo(object obj) + { + if (obj is Callback other) + return classType.FullName.CompareTo(other.classType.FullName); + return 0; + } + } + + List m_SortedCallbacks; + + public abstract string name { get; } + + public List sortedCallbacks + { + get + { + if (m_SortedCallbacks == null) + m_SortedCallbacks = GenerateSortedCallbacks(); + return m_SortedCallbacks; + } + } + + public abstract List GetCallbacks(); + + public List GenerateDependencyGraph() + { + var callbacks = GetCallbacks(); + var packageLookup = DictionaryPool>.Get(); + var assemblyLookup = DictionaryPool>.Get(); + var classLookup = DictionaryPool.Get(); + + // First generate our lookups for class, assembly and package. + foreach (var cb in callbacks) + { + classLookup[cb.classType] = cb; + + var assemblyName = cb.classType.Assembly.GetName().Name; + if (!assemblyLookup.TryGetValue(assemblyName, out var assemblies)) + { + assemblies = new List(); + assemblyLookup[assemblyName] = assemblies; + } + assemblies.Add(cb); + + var package = cb.packageName; + if(package != null) + { + if (!packageLookup.TryGetValue(package, out var packages)) + { + packages = new List(); + packageLookup[package] = packages; + } + packages.Add(cb); + } + } + + // Sort the methods so that the output order is deterministic. + callbacks.Sort(); + + // Now connect the dependency graph nodes + foreach (var dependency in callbacks) + { + // Dependency by class + foreach (var runAfter in dependency.GetCustomAttributes()) + { + // Ignore classes that may not exist in the project + if (runAfter.classType == null) + continue; + + if (classLookup.TryGetValue(runAfter.classType, out var runAfterMethodInfo)) + { + dependency.AddIncomingConnection(runAfterMethodInfo); + } + } + foreach (var runBefore in dependency.GetCustomAttributes()) + { + // Ignore classes that may not exist in the project + if (runBefore.classType == null) + continue; + + if (classLookup.TryGetValue(runBefore.classType, out var runBeforeMethodInfo)) + { + dependency.AddOutgoingConnection(runBeforeMethodInfo); + } + } + + // Dependency by package + foreach (var runAfter in dependency.GetCustomAttributes()) + { + if (packageLookup.TryGetValue(runAfter.packageName, out var runAfterMethodInfos)) + { + dependency.AddIncomingConnections(runAfterMethodInfos); + } + } + foreach (var runBefore in dependency.GetCustomAttributes()) + { + if (packageLookup.TryGetValue(runBefore.packageName, out var runBeforeMethodInfos)) + { + dependency.AddOutgoingConnections(runBeforeMethodInfos); + } + } + + // Dependency by Assembly + foreach (var runAfter in dependency.GetCustomAttributes()) + { + if (assemblyLookup.TryGetValue(runAfter.assemblyName, out var runAfterMethodInfos)) + { + dependency.AddIncomingConnections(runAfterMethodInfos); + } + } + foreach (var runBefore in dependency.GetCustomAttributes()) + { + if (assemblyLookup.TryGetValue(runBefore.assemblyName, out var runBeforeMethodInfos)) + { + dependency.AddOutgoingConnections(runBeforeMethodInfos); + } + } + } + + DictionaryPool>.Release(packageLookup); + DictionaryPool>.Release(assemblyLookup); + DictionaryPool.Release(classLookup); + + return callbacks; + } + + public List GenerateSortedCallbacks() => PerformTopologicalSortingKahnAlgorithm(GenerateDependencyGraph()); + + List PerformTopologicalSortingKahnAlgorithm(List dependencyGraph, HashSet cyclicNodes = null) + { + int n = dependencyGraph.Count; + var ordered = new List(n); + var q = new Queue(); + + // Find nodes which do not need to run after anything(no incoming) + foreach (var node in dependencyGraph) + { + if (node.incoming.Count == 0) + q.Enqueue(node); + } + + while (q.Count != 0) + { + var at = q.Dequeue(); + ordered.Add(at); + + foreach (var o in at.outgoing) + { + o.incoming.Remove(at); + if (o.incoming.Count == 0) + q.Enqueue(o); + } + at.outgoing.Clear(); + } + + // Graph contains a cycle + if (ordered.Count != dependencyGraph.Count) + { + var sb = new StringBuilder(); + sb.Append($"Found cycles in callback dependency graph for {name}.\nThe following nodes could not be added:\n"); + + var visited = cyclicNodes ?? new HashSet(); + foreach (var node in dependencyGraph) + { + if (node.incoming.Count == 0) + continue; + + PrintChildren(node, sb, visited, 0); + } + Debug.LogError(sb.ToString()); + } + + return ordered; + } + + static void PrintChildren(Callback callback, StringBuilder stringBuilder, HashSet visited, int depth) + { + if (visited.Contains(callback.name)) + { + if (depth != 0) + { + // We have a cycle. Abort here + stringBuilder.Append(new string('-', depth)); + stringBuilder.AppendLine($"{callback.name}"); + } + + return; + } + + visited.Add(callback.name); + + if (depth != 0) + { + stringBuilder.Append(new string('-', depth)); + } + + stringBuilder.AppendLine(callback.name); + + foreach (var node in callback.outgoing) + { + PrintChildren(node, stringBuilder, visited, depth + 1); + } + } + + /// + /// This can aid with debugging and understanding the dependencies. + /// It will generate a Graphviz dot diagram to show the dependencies, cyclic issues and generated callback order. + /// + /// Where to save the generated dot diagram. + public void GenerateDependencyDiagram(string path = "DependenciesDiagram.dot") + { + var dependencyGraph = GenerateDependencyGraph(); + var cyclicNodes = new HashSet(); + var sortedCallbacks = PerformTopologicalSortingKahnAlgorithm(GenerateDependencyGraph(), cyclicNodes); + + var graphvizDiagram = new StringBuilder(); + graphvizDiagram.AppendLine("digraph DependenciesDiagram {"); + graphvizDiagram.AppendLine("\tnode [style=\"filled, rounded\", shape=box, fillcolor=\"#FCE5EC\" color=\"#EB417A\"]"); + foreach(var node in cyclicNodes) + { + graphvizDiagram.AppendLine($"\t<{node}>"); + } + + graphvizDiagram.AppendLine(); + graphvizDiagram.AppendLine("\tnode [style=\"filled, rounded\", shape=box, fillcolor=\"#DAEDFD\" color=\"#2196F3\"]"); + graphvizDiagram.AppendLine("\tedge [penwidth=1.5, color=\"#2196F3\"]"); + foreach (var node in dependencyGraph) + { + if (node.outgoing.Count > 0) + { + graphvizDiagram.Append($"\t<{node.name}> -> "); + + var enumerator = node.outgoing.GetEnumerator(); + enumerator.MoveNext(); + graphvizDiagram.Append($"<{enumerator.Current.name}>"); + while (enumerator.MoveNext()) + { + graphvizDiagram.Append($", <{enumerator.Current.name}>"); + } + graphvizDiagram.AppendLine(); + } + } + graphvizDiagram.AppendLine(); + + // Sorted results + graphvizDiagram.AppendLine("\tedge [penwidth=1.5, color=\"#67BC6B\"]"); + for (var i = 0; i < sortedCallbacks.Count - 1; ++i) + { + graphvizDiagram.AppendLine($"\t<{sortedCallbacks[i].name}> -> <{sortedCallbacks[i + 1].name}>"); + } + graphvizDiagram.AppendLine("}"); + + File.WriteAllText(path, graphvizDiagram.ToString()); + EditorUtility.OpenWithDefaultApp(path); + } + } +} diff --git a/Editor/Mono/Overlays/IMGUIOverlay.cs b/Editor/Mono/Overlays/IMGUIOverlay.cs index 6fc79aa42d..9daf39149d 100644 --- a/Editor/Mono/Overlays/IMGUIOverlay.cs +++ b/Editor/Mono/Overlays/IMGUIOverlay.cs @@ -28,24 +28,25 @@ abstract class TransientSceneViewOverlay : IMGUIOverlay, ITransientOverlay public abstract class IMGUIOverlay : Overlay { - internal IMGUIContainer drawingContainer { get; private set; } + internal IMGUIContainer imguiContainer { get; private set; } public sealed override VisualElement CreatePanelContent() { rootVisualElement.pickingMode = PickingMode.Position; - var imgui = new IMGUIContainer(); - imgui.onGUIHandler = () => OnPanelGUIHandler(imgui); - return imgui; + imguiContainer = new IMGUIContainer(); + imguiContainer.onGUIHandler = OnPanelGUIHandler; + OnContentRebuild(); + return imguiContainer; } - void OnPanelGUIHandler(IMGUIContainer container) + internal virtual void OnContentRebuild() { } + + void OnPanelGUIHandler() { if (!displayed) return; - drawingContainer = container; OnGUI(); - drawingContainer = null; if (Event.current.isMouse) Event.current.Use(); diff --git a/Editor/Mono/Overlays/Overlay.cs b/Editor/Mono/Overlays/Overlay.cs index 929fba01fc..4248911aaf 100644 --- a/Editor/Mono/Overlays/Overlay.cs +++ b/Editor/Mono/Overlays/Overlay.cs @@ -48,6 +48,13 @@ public abstract partial class Overlay Vector2 m_FloatingSnapOffset; internal Vector2 m_SnapOffsetDelta = Vector2.zero; + //Min and Max being 0 means the resizing is disabled for that axis without enforcing an actual size + //Resizing is disabled by default + Vector2 m_MinSize = Vector2.zero; + Vector2 m_MaxSize = Vector2.zero; + Vector2 m_Size; + bool m_SizeOverridden; + // Temporary Variables bool m_LockAnchor = false; bool m_ContentsChanged = true; @@ -62,6 +69,8 @@ public abstract partial class Overlay VisualElement m_CollapsedContent; OverlayPopup m_ModalPopup; // collapsed popup root VisualElement m_RootVisualElement; + VisualElement m_ResizeTarget; + internal VisualElement resizeTarget => m_ResizeTarget; OverlayDropZone m_BeforeDropZone; OverlayDropZone m_AfterDropZone; @@ -70,6 +79,11 @@ public abstract partial class Overlay public event Action layoutChanged; public event Action collapsedChanged; public event Action displayedChanged; + internal event Action containerChanged; + internal event Action minSizeChanged; + internal event Action maxSizeChanged; + internal event Action sizeOverridenChanged; + // Invoked in partial class OverlayPlacement.cs #pragma warning disable 67 public event Action floatingChanged; @@ -121,6 +135,7 @@ internal set { if (m_Layout == value) return; + m_Layout = value; RebuildContent(); } @@ -164,12 +179,19 @@ public string displayName internal OverlayContainer container { get => m_Container; - set => m_Container = value; + set + { + if (m_Container == value) + return; + + m_Container = value; + containerChanged?.Invoke(m_Container); + } } internal DockZone dockZone => floating ? DockZone.Floating : OverlayCanvas.GetDockZone(container); - internal DockPosition dockPosition => container?.GetDockPosition(this) ?? DockPosition.Bottom; + internal DockPosition dockPosition => container.GetSection(OverlayContainerSection.BeforeSpacer).Contains(this) ? DockPosition.Top : DockPosition.Bottom; internal static VisualTreeAsset treeAsset { @@ -181,11 +203,6 @@ internal static VisualTreeAsset treeAsset } } - internal void SetDisplayedNoCallback(bool value) - { - rootVisualElement.style.display = value ? DisplayStyle.Flex : DisplayStyle.None; - } - public bool displayed { get => rootVisualElement.style.display == DisplayStyle.Flex; @@ -249,6 +266,10 @@ internal VisualElement rootVisualElement m_RootVisualElement.Add(m_AfterDropZone = new OverlayDropZone(this, OverlayDropZone.Placement.After)); m_RootVisualElement.tooltip = L10n.Tr(displayName); + m_ResizeTarget = m_RootVisualElement.Q("unity-overlay"); + m_ResizeTarget.style.overflow = Overflow.Hidden; + m_ResizeTarget.Add(new OverlayResizerGroup(this)); + return m_RootVisualElement; } } @@ -266,7 +287,80 @@ internal VisualElement contentRoot } } - public bool isInToolbar => !floating && container is ToolbarOverlayContainer; + public bool isInToolbar => container is ToolbarOverlayContainer; + internal bool sizeOverridden + { + get => m_SizeOverridden; + private set + { + if (m_SizeOverridden == value) + return; + + m_SizeOverridden = value; + sizeOverridenChanged?.Invoke(); + } + } + + internal Vector2 sizeToSave => m_Size; + + public Vector2 size + { + get + { + if (!ShouldUseSizeValue()) + return m_ResizeTarget.layout.size; + + return new Vector2( + IsSizeAuto(m_MinSize.x, m_MaxSize.x) ? m_ResizeTarget.layout.width : m_Size.x, + IsSizeAuto(m_MinSize.y, m_MaxSize.y) ? m_ResizeTarget.layout.height : m_Size.y); + } + set + { + if (m_Size == value && sizeOverridden) + return; + + m_Size = value; + sizeOverridden = true; + UpdateSize(); + } + } + + public Vector2 minSize + { + get => m_MinSize; + set + { + if (m_MinSize == value) + return; + + m_MinSize = value; + minSizeChanged?.Invoke(); + UpdateSize(); + } + } + + public Vector2 maxSize + { + get => m_MaxSize; + set + { + if (m_MaxSize == value) + return; + + m_MaxSize = value; + maxSizeChanged?.Invoke(); + UpdateSize(); + } + } + + internal void ResetSize() + { + if (!sizeOverridden) + return; + + sizeOverridden = false; + UpdateSize(); + } bool CanCreateRequestedLayout(Layout requested) { @@ -302,7 +396,7 @@ internal void RebuildContent() // We need to invoke a callback if the collapsed state changes (either from user request or invalid layout) bool wasCollapsed = collapsedContent.parent == contentRoot; - var prevLayout = layout; + var prevLayout = m_ActiveLayout; m_ActiveLayout = GetBestLayoutForState(); // Clear any existing contents. @@ -315,8 +409,8 @@ internal void RebuildContent() // An Overlay can collapsed by request, or by necessity. If collapsed due to invalid layout/container match, // the collapsed property is not modified. The next time a content rebuild is requested we'll try again to - // create the contents with the st ored state. - bool isCollapsed = m_Collapsed || m_ActiveLayout == 0; + // create the contents with the stored state. + bool isCollapsed = m_Collapsed || activeLayout == 0; if (isCollapsed) { @@ -357,7 +451,7 @@ internal void RebuildContent() m_BeforeDropZone.style.display = dropZonesDisplay; m_AfterDropZone.style.display = dropZonesDisplay; - container?.UpdateIsVisibleInContainer(this); + UpdateSize(); // Invoke callbacks after content is created and styling has been applied if(wasCollapsed != isCollapsed) @@ -367,6 +461,40 @@ internal void RebuildContent() layoutChanged?.Invoke(m_ActiveLayout); } + bool ShouldUseSizeValue() + { + return sizeOverridden && layout == Layout.Panel && !collapsed && !(container is ToolbarOverlayContainer); + } + + bool IsSizeAuto(float min, float max) + { + return Mathf.Approximately(min, 0) && Mathf.Approximately(max, 0); + } + + void UpdateSize() + { + m_Size.x = Mathf.Clamp(m_Size.x, m_MinSize.x, m_MaxSize.x); + m_Size.y = Mathf.Clamp(m_Size.y, m_MinSize.y, m_MaxSize.y); + + if (m_ResizeTarget == null) + return; + + if (!ShouldUseSizeValue()) + { + m_ResizeTarget.style.width = new StyleLength(StyleKeyword.Auto); + m_ResizeTarget.style.height = new StyleLength(StyleKeyword.Auto); + } + else + { + //If both min-max for one axis are 0, disable resizing for that axis + m_ResizeTarget.style.width = IsSizeAuto(m_MinSize.x, m_MaxSize.x) ? new StyleLength(StyleKeyword.Auto) : m_Size.x; + m_ResizeTarget.style.height = IsSizeAuto(m_MinSize.y, m_MaxSize.y) ? new StyleLength(StyleKeyword.Auto) : m_Size.y; + } + + var position = canvas.ClampToOverlayWindow(new Rect(floatingPosition, m_Size)).position; + UpdateSnapping(position); + } + // CreateContent always returns a new VisualElement tree with the Overlay contents. It does not modify the // m_CurrentContent or m_ContentRoot properties. Use RebuildContent() to update an Overlay contents. // CreateContent will try to return content in the requested layout, regardless of whether the container @@ -450,7 +578,7 @@ void ToggleCollapsedPopup() m_ModalPopup.RegisterCallback(evt => { - if (evt.relatedTarget is VisualElement target && m_ModalPopup.Contains(target)) + if (evt.relatedTarget is VisualElement target && (m_ModalPopup == target || m_ModalPopup.Contains(target))) return; // When the new focus is an embedded IMGUIContainer or popup window, give focus back to the modal @@ -494,6 +622,8 @@ void BuildContextMenu(ContextualMenuPopulateEvent evt) if (!isInToolbar) { + menu.AppendAction(L10n.Tr("Reset Size"), action => ResetSize()); + menu.AppendSeparator(); var layouts = supportedLayouts; @@ -531,6 +661,8 @@ internal void ApplySaveData(SaveData data) m_Layout = data.layout; m_FloatingSnapOffset = data.snapOffset; m_SnapOffsetDelta = data.snapOffsetDelta; + m_SizeOverridden = data.sizeOverriden; + m_Size = data.size; } } } diff --git a/Editor/Mono/Overlays/OverlayAttribute.cs b/Editor/Mono/Overlays/OverlayAttribute.cs index 69768cbfea..3803de4130 100644 --- a/Editor/Mono/Overlays/OverlayAttribute.cs +++ b/Editor/Mono/Overlays/OverlayAttribute.cs @@ -87,7 +87,7 @@ public OverlayAttribute() if (string.IsNullOrEmpty(m_UssName)) m_UssName = m_Id; } - public OverlayAttribute(Type editorWindowType, string id, string displayName, string ussName, bool defaultDisplay = false) + public OverlayAttribute(Type editorWindowType, string id, string displayName, string ussName, bool defaultDisplay = false):this() { this.editorWindowType = editorWindowType; this.displayName = displayName; diff --git a/Editor/Mono/Overlays/OverlayCanvas.cs b/Editor/Mono/Overlays/OverlayCanvas.cs index 35a588c83e..9ed79d551c 100644 --- a/Editor/Mono/Overlays/OverlayCanvas.cs +++ b/Editor/Mono/Overlays/OverlayCanvas.cs @@ -28,6 +28,8 @@ class SaveData : IEquatable public string id; public int index = k_InvalidIndex; public Layout layout = Layout.Panel; + public Vector2 size; + public bool sizeOverriden; public SaveData() { } @@ -44,12 +46,14 @@ public SaveData(SaveData other) id = other.id; index = other.index; layout = other.layout; + size = other.size; + sizeOverriden = other.sizeOverriden; } public SaveData(Overlay overlay, int indexInContainer = k_InvalidIndex) { var container = overlay.container != null ? overlay.container.name : ""; - var dock = overlay.container != null && overlay.container.topOverlays.Contains(overlay) + var dock = overlay.container != null && overlay.container.ContainsOverlay(overlay, OverlayContainerSection.BeforeSpacer) ? DockPosition.Top : DockPosition.Bottom; @@ -181,7 +185,7 @@ public sealed class OverlayCanvas : ISerializationCallbackReceiver "overlay-toolbar__bottom", "overlay-container--left", "overlay-container--right", - k_DefaultContainer + "Floating" }; internal static DockZone GetDockZone(OverlayContainer container) @@ -214,20 +218,22 @@ internal OverlayContainer GetDockZoneContainer(DockZone zone) VisualElement m_RootVisualElement; internal EditorWindow containerWindow { get; set; } - internal VisualElement floatingContainer { get; set; } + + internal FloatingOverlayContainer floatingContainer => m_FloatingOverlayContainer ??= new FloatingOverlayContainer {canvas = this}; + + FloatingOverlayContainer m_FloatingOverlayContainer; Overlay m_HoveredOverlay; OverlayMenu menu => m_Menu ??= new OverlayMenu(this); internal VisualElement rootVisualElement => m_RootVisualElement ??= CreateRoot(); - Vector2 localMousePosition { get; set; } + internal Vector2 localMousePosition { get; set; } internal Overlay hoveredOverlay => m_HoveredOverlay; OverlayContainer hoveredOverlayContainer { get; set; } OverlayContainer defaultContainer { get; set; } OverlayContainer defaultToolbarContainer { get; set; } - List containers; - List toolbarZones { get; set; } + List containers { get; set; } readonly Dictionary m_OverlaysByVE = new Dictionary(); @@ -253,9 +259,6 @@ internal void SetOverlaysEnabled(bool visible) foreach (var container in containers) container.style.display = visible ? DisplayStyle.Flex : DisplayStyle.None; - foreach (var toolbar in toolbarZones) - toolbar.style.display = visible ? DisplayStyle.Flex : DisplayStyle.None; - floatingContainer.style.display = visible ? DisplayStyle.Flex : DisplayStyle.None; overlaysEnabledChanged?.Invoke(visible); } @@ -286,22 +289,13 @@ VisualElement CreateRoot() if (s_DropZoneTreeAsset == null) s_DropZoneTreeAsset = EditorGUIUtility.Load(k_UxmlPathDropZone) as VisualTreeAsset; - var toolbarZonesContainer = ve.Q("overlay-drop-zones__toolbars"); - - if (s_DropZoneTreeAsset != null) - { - s_DropZoneTreeAsset.CloneTree(toolbarZonesContainer); - - toolbarZones = toolbarZonesContainer.Query(null, k_ToolbarZone).ToList(); - - foreach (var visualElement in toolbarZones) - { - visualElement.style.position = Position.Absolute; - } - } - ve.name = ussClassName; ve.style.flexGrow = 1; + + ve.Add(floatingContainer); + floatingContainer.AddToClassList(k_FloatingContainer); + floatingContainer.name = "Floating"; + containers = ve.Query().ToList(); foreach (var container in containers) @@ -316,19 +310,12 @@ VisualElement CreateRoot() } } - floatingContainer = ve.Q(k_FloatingContainer); - floatingContainer.style.position = Position.Absolute; - floatingContainer.style.top = 0; - floatingContainer.style.left = 0; - floatingContainer.style.right = 0; - floatingContainer.style.bottom = 0; - m_OriginGhost = new VisualElement { name = "origin-ghost"}; m_OriginGhost.AddToClassList(k_GhostClassName); m_OriginGhost.AddToClassList(k_DropTargetClassName); destinationMarker = new OverlayDestinationMarker { name = "dest-marker"}; - ve.Q("overlay-drop-zones").Add(destinationMarker); + ve.Add(destinationMarker); SetPickingMode(ve, PickingMode.Ignore); @@ -517,7 +504,9 @@ internal void UpdateGhostHover(bool hovered) void WriteOrReplaceSaveData(Overlay overlay, int containerIndex = -1) { if (containerIndex < 0) - containerIndex = overlay.container?.FindIndex(overlay) ?? SaveData.k_InvalidIndex; + if (overlay.container == null || !overlay.container.GetOverlayIndex(overlay, out _, out containerIndex)) + containerIndex = SaveData.k_InvalidIndex; + var saveData = new SaveData(overlay, containerIndex); int existing = m_SaveData.FindIndex(x => x.id == overlay.id); @@ -536,19 +525,19 @@ public void OnBeforeSerialize() { if (container != null) { - var top = container.topOverlays; - var bot = container.bottomOverlays; + var before = container.GetSection(OverlayContainerSection.BeforeSpacer); + var after = container.GetSection(OverlayContainerSection.AfterSpacer); - for (int i = 0, c = top.Count; i < c; ++i) + for (int i = 0, c = before.Count; i < c; ++i) { - if (!top[i].dontSaveInLayout) - WriteOrReplaceSaveData(top[i], i); + if (!before[i].dontSaveInLayout) + WriteOrReplaceSaveData(before[i], i); } - for (int i = 0, c = bot.Count; i < c; ++i) + for (int i = 0, c = after.Count; i < c; ++i) { - if (!bot[i].dontSaveInLayout) - WriteOrReplaceSaveData(bot[i], i); + if (!after[i].dontSaveInLayout) + WriteOrReplaceSaveData(after[i], i); } } } @@ -587,11 +576,16 @@ internal void ApplySaveData(SaveData[] saveData) internal void Move(Overlay overlay, DockZone zone, DockPosition position = DockPosition.Bottom) { var container = GetDockZoneContainer(zone); - if(position == DockPosition.Bottom) - container.AddToBottom(overlay); + if (position == DockPosition.Bottom) + overlay.DockAt(container, OverlayContainerSection.AfterSpacer); else - container.AddToTop(overlay); - overlay.RebuildContent(); + overlay.DockAt(container, OverlayContainerSection.BeforeSpacer); + } + + internal void Rebuild() + { + OnBeforeSerialize(); + RestoreOverlays(); } public void Add(Overlay overlay, bool show = true) @@ -680,6 +674,7 @@ SaveData FindSaveData(Overlay overlay) data.dockPosition = attrib.defaultDockPosition; data.floating = attrib.defaultDockZone == DockZone.Floating; data.layout = attrib.defaultLayout; + data.displayed = attrib.defaultDisplay; } } @@ -704,18 +699,21 @@ void RestoreOverlay(Overlay overlay, SaveData data = null) // Overlays are sorted by their index in containers so we can directly add them to top or bottom without // thinking of order - if(data.dockPosition == DockPosition.Top) - container.AddToTop(overlay); + if (data.dockPosition == DockPosition.Top) + overlay.DockAt(container, OverlayContainerSection.BeforeSpacer, container.GetSectionCount(OverlayContainerSection.BeforeSpacer)); else if (data.dockPosition == DockPosition.Bottom) - container.AddToBottom(overlay); + overlay.DockAt(container, OverlayContainerSection.AfterSpacer, container.GetSectionCount(OverlayContainerSection.AfterSpacer)); else throw new Exception("data.dockPosition is not Top or Bottom, did someone add a new one?"); if(overlay.floating) floatingContainer.Add(overlay.rootVisualElement); - overlay.SetDisplayedNoCallback(data.displayed); - overlay.RebuildContent(); + if(overlay.displayed != data.displayed) + overlay.displayed = data.displayed; + else + overlay.RebuildContent(); + overlay.UpdateAbsolutePosition(); } diff --git a/Editor/Mono/Overlays/OverlayContainer.cs b/Editor/Mono/Overlays/OverlayContainer.cs index 5e197727a9..a31d7c8b43 100644 --- a/Editor/Mono/Overlays/OverlayContainer.cs +++ b/Editor/Mono/Overlays/OverlayContainer.cs @@ -2,107 +2,37 @@ // 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.Collections.ObjectModel; +using System.ComponentModel; using UnityEngine; using UnityEngine.UIElements; namespace UnityEditor.Overlays { - class ToolbarOverlayContainer : OverlayContainer + enum OverlayContainerSection { - public new class UxmlFactory : UxmlFactory {} - public new class UxmlTraits : OverlayContainer.UxmlTraits {} - - const string k_ToolbarClassName = "overlay-toolbar-area"; - - readonly OverlayDropZoneBase m_NoElementDropZone; - - public ToolbarOverlayContainer() - { - AddToClassList(k_ToolbarClassName); - m_NoElementDropZone = new HiddenToolbarDropZone(this) {name = "NoElementToolbarDropZone"}; - Add(m_NoElementDropZone); - } - - protected override bool InitSpacer() - { - if (base.InitSpacer()) - { - m_NoElementDropZone.style.display = DisplayStyle.Flex; - beforeSpacerDropZone.style.display = DisplayStyle.None; - afterSpacerDropZone.style.display = DisplayStyle.None; - return true; - } - - return false; - } - - protected override void OnStateUnlocked() - { - base.OnStateUnlocked(); - UpdateDropZones(); - } - - protected override void OnOverlayBecomeVisibleInContainer(Overlay overlay) - { - InitSpacer(); - base.OnOverlayBecomeVisibleInContainer(overlay); - - UpdateDropZones(); - } - - protected override void OnOverlayBecomeInvisibleInContainer(Overlay overlay) - { - InitSpacer(); - base.OnOverlayBecomeInvisibleInContainer(overlay); - - UpdateDropZones(); - } - - void UpdateDropZones() - { - if (stateLocked) - return; - - if (visibleOverlayCount >= 1) - { - m_NoElementDropZone.style.display = DisplayStyle.None; - beforeSpacerDropZone.style.display = DisplayStyle.Flex; - afterSpacerDropZone.style.display = DisplayStyle.Flex; - } - else - { - m_NoElementDropZone.style.display = DisplayStyle.Flex; - beforeSpacerDropZone.style.display = DisplayStyle.None; - afterSpacerDropZone.style.display = DisplayStyle.None; - } - } - - public override Layout preferredLayout => isHorizontal ? Layout.HorizontalToolbar : Layout.VerticalToolbar; - - public override bool IsOverlayLayoutSupported(Layout requested) - { - if (isHorizontal) - return (requested & Layout.HorizontalToolbar) > 0; - return (requested & Layout.VerticalToolbar) > 0; - } + BeforeSpacer, + AfterSpacer } class OverlayContainer : VisualElement { - public new class UxmlFactory : UxmlFactory {} + public new class UxmlFactory : UxmlFactory { } public new class UxmlTraits : VisualElement.UxmlTraits { - readonly UxmlBoolAttributeDescription m_IsHorizontal = new UxmlBoolAttributeDescription { name = "horizontal", defaultValue = false }; - readonly UxmlStringAttributeDescription m_SupportedLayout = new UxmlStringAttributeDescription {name = "supported-overlay-layout", defaultValue = ""}; + readonly UxmlBoolAttributeDescription m_IsHorizontal = new UxmlBoolAttributeDescription + {name = "horizontal", defaultValue = false}; + + readonly UxmlStringAttributeDescription m_SupportedLayout = new UxmlStringAttributeDescription + {name = "supported-overlay-layout", defaultValue = ""}; public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc) { base.Init(ve, bag, cc); - var container = ((OverlayContainer)ve); + var container = ((OverlayContainer) ve); container.isHorizontal = m_IsHorizontal.GetValueFromBag(bag, cc); container.m_SupportedOverlayLayouts = Layout.Panel; @@ -122,14 +52,24 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext } } - List m_TopOverlays = new List(); - List m_BottomOverlaysOverlays = new List(); - public List topOverlays => m_TopOverlays; - public List bottomOverlays => m_BottomOverlaysOverlays; + public const string className = "unity-overlay-container"; + public const string spacerClassName = "overlay-container__spacer"; + const string k_HorizontalClassName = className + "-horizontal"; + const string k_VerticalClassName = className + "-vertical"; + public static readonly Overlay spacerMarker = null; + + readonly List m_BeforeOverlays = new List(); + readonly List m_AfterOverlays = new List(); + readonly VisualElement m_Spacer; + + // This is set by querying the stylesheet for 'vertical' and 'horizontal' + Layout m_SupportedOverlayLayouts = 0; //Used as a flag in this case + bool m_IsHorizontal; + + public OverlayCanvas canvas { get; internal set; } - public int visibleOverlayCount => m_VisibleInContainer.Count; - protected OverlayDropZoneBase beforeSpacerDropZone { get; private set; } - protected OverlayDropZoneBase afterSpacerDropZone { get; private set; } + public int overlayCount => m_BeforeOverlays.Count + m_AfterOverlays.Count; + public virtual Layout preferredLayout => Layout.Panel; public bool isHorizontal { @@ -147,261 +87,241 @@ public bool isHorizontal } } - // This is set by querying the stylesheet for 'vertical' and 'horizontal' - Layout m_SupportedOverlayLayouts = 0; - - public const string className = "unity-overlay-container"; - public const string spacerClassName = "overlay-container__spacer"; - const string k_HorizontalClassName = className + "-horizontal"; - const string k_VerticalClassName = className + "-vertical"; - - bool m_IsHorizontal; - VisualElement m_Spacer; - VisualElement m_Canvas; - bool m_StateLocked; - readonly HashSet m_VisibleInContainer = new HashSet(); + protected VisualElement spacer => m_Spacer; + public bool isSpacerVisible => !Mathf.Approximately(spacer.rect.width, 0) && !Mathf.Approximately(spacer.rect.height, 0); public OverlayContainer() { AddToClassList(className); name = className; - RegisterCallback(OnAttachedToPanel); + Add(m_Spacer = new VisualElement()); + m_Spacer.AddToClassList(spacerClassName); + + m_Spacer.Add(new OverlayContainerDropZone(this, OverlayContainerDropZone.Placement.Start)); + var dropZoneSpacer = new VisualElement { name = "DropZonesSpacer" }; + m_Spacer.Add(dropZoneSpacer); + m_Spacer.Add(new OverlayContainerDropZone(this, OverlayContainerDropZone.Placement.End)); + SetVertical(); } - void SetHorizontal() + protected virtual void SetHorizontal() { EnableInClassList(k_HorizontalClassName, true); EnableInClassList(k_VerticalClassName, false); } - void SetVertical() + protected virtual void SetVertical() { EnableInClassList(k_HorizontalClassName, false); EnableInClassList(k_VerticalClassName, true); } - public virtual Layout preferredLayout => Layout.Panel; - - public virtual bool IsOverlayLayoutSupported(Layout requested) + public bool ContainsOverlay(Overlay overlay, OverlayContainerSection section) { - return (m_SupportedOverlayLayouts & requested) > 0; + return GetSectionInternal(section).Contains(overlay); } - void OnAttachedToPanel(AttachToPanelEvent evt) + public bool ContainsOverlay(Overlay overlay) { - m_Canvas = parent; - while (m_Canvas != null && !m_Canvas.ClassListContains(OverlayCanvas.ussClassName)) - { - m_Canvas = m_Canvas.parent; - } - - InitSpacer(); + return ContainsOverlay(overlay, OverlayContainerSection.BeforeSpacer) + || ContainsOverlay(overlay, OverlayContainerSection.AfterSpacer); } - public VisualElement spacer + public void InsertOverlay(Overlay overlay, OverlayContainerSection section, int index) { - get - { - InitSpacer(); - return m_Spacer; - } - } + if (overlay == null) + return; - public bool stateLocked - { - get => m_StateLocked; - set + var list = GetSectionInternal(section); + + int realIndex = -1; + + //Insert relative to another element in case other visual elements are added to hierarchy + if (index < list.Count) { - if (m_StateLocked == value) - return; + realIndex = IndexOf(list[index].rootVisualElement); + + // Section after spacer is listed from bottom to spacer instead of spacer to bottom. + // So before an overlay after the spacer is actually after the overlay element in the container hierarchy. + if (section == OverlayContainerSection.AfterSpacer) + ++realIndex; - m_StateLocked = value; - if (m_StateLocked) - OnStateLocked(); - else - OnStateUnlocked(); } - } - protected virtual bool InitSpacer() - { - if (m_Spacer == null) + if (realIndex < 0) { - m_Spacer = this.Q(null, spacerClassName); - if (m_Spacer == null) - return false; - - m_Spacer.Add(beforeSpacerDropZone = new OverlayContainerDropZone(this, OverlayContainerDropZone.Placement.Start)); - var dropZoneSpacer = new VisualElement { name = "DropZonesSpacer" }; - m_Spacer.Add(dropZoneSpacer); - m_Spacer.Add(afterSpacerDropZone = new OverlayContainerDropZone(this, OverlayContainerDropZone.Placement.End)); - return true; + switch (section) + { + case OverlayContainerSection.BeforeSpacer: realIndex = IndexOf(m_Spacer); break; + case OverlayContainerSection.AfterSpacer: realIndex = IndexOf(m_Spacer) + 1; break; + } } - return false; - } - - public void RemoveOverlay(Overlay overlay) - { - if (!topOverlays.Remove(overlay) && !bottomOverlays.Remove(overlay)) - return; - overlay.rootVisualElement.RemoveFromHierarchy(); - OnOverlayRemoved(overlay); + Insert(realIndex, overlay.rootVisualElement); + list.Insert(index, overlay); } - public void InsertBefore(Overlay overlay, Overlay targetOverlay) + public bool RemoveOverlay(Overlay overlay) { - var index = topOverlays.IndexOf(targetOverlay); - if (index >= 0) - { - if (topOverlays.Contains(overlay)) return; - topOverlays.Insert(index, overlay); - Insert(IndexOf(targetOverlay.rootVisualElement), overlay.rootVisualElement); - OnOverlayAdded(overlay); - return; - } + if (overlay == spacerMarker) + return false; - index = bottomOverlays.IndexOf(targetOverlay); - if (index >= 0) - { - if (bottomOverlays.Contains(overlay)) return; - bottomOverlays.Insert(index, overlay); - Insert(IndexOf(targetOverlay.rootVisualElement), overlay.rootVisualElement); - OnOverlayAdded(overlay); - } + bool found = m_BeforeOverlays.Remove(overlay); + found |= m_AfterOverlays.Remove(overlay); + if (found) + overlay.rootVisualElement.RemoveFromHierarchy(); + + return found; } - public void AddAfter(Overlay overlay, Overlay targetOverlay) + public bool GetOverlayIndex(Overlay overlay, out OverlayContainerSection section, out int index) { - var index = topOverlays.IndexOf(targetOverlay); + index = m_BeforeOverlays.IndexOf(overlay); if (index >= 0) { - if (topOverlays.Contains(overlay)) return; - topOverlays.Insert(index + 1, overlay); - Insert(IndexOf(targetOverlay.rootVisualElement) + 1, overlay.rootVisualElement); - OnOverlayAdded(overlay); - return; + section = OverlayContainerSection.BeforeSpacer; + return true; } - index = bottomOverlays.IndexOf(targetOverlay); + index = m_AfterOverlays.IndexOf(overlay); if (index >= 0) { - if (bottomOverlays.Contains(overlay)) return; - bottomOverlays.Insert(index + 1, overlay); - Insert(IndexOf(targetOverlay.rootVisualElement) + 1, overlay.rootVisualElement); - OnOverlayAdded(overlay); + section = OverlayContainerSection.AfterSpacer; + return true; } - } - - public void AddToTop(Overlay overlay) - { - if (topOverlays.Contains(overlay)) return; - topOverlays.Add(overlay); - Insert(IndexOf(spacer), overlay.rootVisualElement); - OnOverlayAdded(overlay); - } - public void AddToBottom(Overlay overlay) - { - if (bottomOverlays.Contains(overlay)) return; - bottomOverlays.Add(overlay); - Add(overlay.rootVisualElement); - OnOverlayAdded(overlay); + section = (OverlayContainerSection)(-1); + index = -1; + return false; } - void OnOverlayAdded(Overlay overlay) + public bool HasVisibleOverlays() { - overlay.container = this; - UpdateIsVisibleInContainer(overlay); - } + if (GetFirstVisible(OverlayContainerSection.BeforeSpacer) != null) + return true; - protected void OnOverlayRemoved(Overlay overlay) - { - overlay.container = null; - if (m_VisibleInContainer.Remove(overlay)) - OnOverlayBecomeInvisibleInContainer(overlay); - } + if (GetFirstVisible(OverlayContainerSection.AfterSpacer) != null) + return true; - internal Overlay FirstTopOverlay() - { - return FirstValidOverlay(topOverlays); + return false; } - internal Overlay LastTopOverlay() + public int GetSectionCount(OverlayContainerSection section) { - return LastValidOverlay(topOverlays); + return GetSectionInternal(section).Count; } - internal Overlay FirstBottomOverlay() + public ReadOnlyCollection GetSection(OverlayContainerSection section) { - return FirstValidOverlay(bottomOverlays); + return GetSectionInternal(section).AsReadOnly(); } - internal Overlay LastBottomOverlay() + List GetSectionInternal(OverlayContainerSection section) { - return LastValidOverlay(bottomOverlays); + switch (section) + { + case OverlayContainerSection.BeforeSpacer: return m_BeforeOverlays; + case OverlayContainerSection.AfterSpacer: return m_AfterOverlays; + default: + throw new InvalidEnumArgumentException(); + } } - Overlay FirstValidOverlay(List overlays) + public Overlay GetFirstVisible(OverlayContainerSection section) { - for (int i = 0; i < overlays.Count; ++i) + List overlays = GetSectionInternal(section); + foreach (var overlay in overlays) { - if (IsOverlayVisibleInContainer(overlays[i])) - return overlays[i]; + if (overlay != null && overlay.displayed) + return overlay; } + return null; } - Overlay LastValidOverlay(List overlays) + public Overlay GetLastVisible(OverlayContainerSection section) { + List overlays = GetSectionInternal(section); for (int i = overlays.Count - 1; i >= 0; --i) { - if (IsOverlayVisibleInContainer(overlays[i])) - return overlays[i]; + var overlay = overlays[i]; + if (overlay != null && overlay.displayed) + return overlay; } + return null; } - internal bool IsOverlayVisibleInContainer(Overlay overlay) + public virtual bool IsOverlayLayoutSupported(Layout requested) { - return !overlay.floating && overlay.displayed; + return (m_SupportedOverlayLayouts & requested) > 0; } + } - protected virtual void OnOverlayBecomeVisibleInContainer(Overlay overlay) {} - protected virtual void OnOverlayBecomeInvisibleInContainer(Overlay overlay) {} - protected virtual void OnStateLocked() {} - protected virtual void OnStateUnlocked() {} + class FloatingOverlayContainer : OverlayContainer + { + public FloatingOverlayContainer() + { + this.StretchToParentSize(); + } - internal void UpdateIsVisibleInContainer(Overlay overlay) + public override bool IsOverlayLayoutSupported(Layout requested) { - if (overlay.displayed && !overlay.floating) - { - if (m_VisibleInContainer.Add(overlay)) - OnOverlayBecomeVisibleInContainer(overlay); - } - else - { - if (m_VisibleInContainer.Remove(overlay)) - OnOverlayBecomeInvisibleInContainer(overlay); - } + return true; + } + } + + class ToolbarOverlayContainer : OverlayContainer + { + public new class UxmlFactory : UxmlFactory { } + public new class UxmlTraits : OverlayContainer.UxmlTraits { } + + const string k_ToolbarClassName = "overlay-toolbar-area"; + + readonly OverlayDropZoneBase m_NoElementDropZone; + readonly VisualElement m_ContentContainer; + + public override VisualElement contentContainer => m_ContentContainer ?? base.contentContainer; + + public override Layout preferredLayout => isHorizontal ? Layout.HorizontalToolbar : Layout.VerticalToolbar; + + public ToolbarOverlayContainer() + { + AddToClassList(k_ToolbarClassName); + m_NoElementDropZone = new HiddenToolbarDropZone(this) { name = "NoElementToolbarDropZone" }; + hierarchy.Add(m_NoElementDropZone); + + hierarchy.Add(m_ContentContainer = new VisualElement()); + m_ContentContainer.style.flexGrow = 1; + m_ContentContainer.style.flexDirection = isHorizontal ? FlexDirection.Row : FlexDirection.Column; + m_ContentContainer.pickingMode = PickingMode.Ignore; + m_ContentContainer.Add(spacer); + } + + protected override void SetHorizontal() + { + base.SetHorizontal(); + + if (contentContainer != null) + contentContainer.style.flexDirection = FlexDirection.Row; } - internal int FindIndex(Overlay overlay) + protected override void SetVertical() { - var top = topOverlays.IndexOf(overlay); - if (top >= 0) - return top; - return bottomOverlays.IndexOf(overlay); + base.SetVertical(); + + if (contentContainer != null) + contentContainer.style.flexDirection = FlexDirection.Column; } - internal DockPosition GetDockPosition(Overlay overlay) + public override bool IsOverlayLayoutSupported(Layout requested) { - if (topOverlays.Contains(overlay)) - return DockPosition.Top; - return DockPosition.Bottom; + if (isHorizontal) + return (requested & Layout.HorizontalToolbar) > 0; + return (requested & Layout.VerticalToolbar) > 0; } } } diff --git a/Editor/Mono/Overlays/OverlayDragger.cs b/Editor/Mono/Overlays/OverlayDragger.cs index e5c7c17d90..fbfde935fa 100644 --- a/Editor/Mono/Overlays/OverlayDragger.cs +++ b/Editor/Mono/Overlays/OverlayDragger.cs @@ -11,8 +11,6 @@ namespace UnityEditor.Overlays { sealed class OverlayDragger : MouseManipulator { - internal const string k_DragAreaHovered = "unity-overlay-drag-area-hovered"; - public static event Action dragStarted; public static event Action dragEnded; @@ -27,9 +25,10 @@ sealed class OverlayDragger : MouseManipulator Vector2 m_StartMousePosition; readonly Overlay m_Overlay; int m_InitialIndex; + OverlayContainerSection m_InitialSection; OverlayCanvas canvas => m_Overlay.canvas; - VisualElement floatingContainer => canvas.floatingContainer; + FloatingOverlayContainer floatingContainer => canvas.floatingContainer; VisualElement canvasRoot => canvas.rootVisualElement; public OverlayDragger(Overlay overlay) @@ -43,10 +42,9 @@ public OverlayDragger(Overlay overlay) protected override void RegisterCallbacksOnTarget() { target.RegisterCallback(OnMouseDown); - target.RegisterCallback(OnMouseMove, TrickleDown.TrickleDown); target.RegisterCallback(OnMouseUp); target.RegisterCallback(OnKeyDown); - target.RegisterCallback(OnMouseLeave); + target.RegisterCallback(OnPointerCaptureOut); } protected override void UnregisterCallbacksFromTarget() @@ -55,7 +53,7 @@ protected override void UnregisterCallbacksFromTarget() target.UnregisterCallback(OnMouseMove); target.UnregisterCallback(OnMouseUp); target.UnregisterCallback(OnKeyDown); - target.UnregisterCallback(OnMouseLeave); + target.UnregisterCallback(OnPointerCaptureOut); } OverlayDropZoneBase GetOverlayDropZone(Vector2 mousePosition, Overlay ignoreTarget) @@ -109,34 +107,31 @@ void OnMouseDown(MouseDownEvent e) m_InitialLayoutPosition = floatingContainer.WorldToLocal(m_Overlay.rootVisualElement.worldBound.position); + dragStarted?.Invoke(m_Overlay); + //if docked, convert to floating if (!m_Overlay.floating) { - m_Overlay.container.stateLocked = true; - m_InitialIndex = m_Overlay.container.IndexOf(m_Overlay.rootVisualElement); + m_Overlay.container.GetOverlayIndex(m_Overlay, out m_InitialSection, out m_InitialIndex); canvas.ShowOriginGhost(m_Overlay); - m_Overlay.floatingPosition = m_InitialLayoutPosition; m_Overlay.Undock(); - } - else - { - //make sure overlay is on top - m_Overlay.rootVisualElement.BringToFront(); + m_Overlay.floatingPosition = m_InitialLayoutPosition; + m_Overlay.UpdateAbsolutePosition(); } + m_Overlay.BringToFront(); + m_Active = true; + target.RegisterCallback(OnMouseMove, TrickleDown.TrickleDown); target.CaptureMouse(); e.StopPropagation(); - UpdateHovered(e.mousePosition); dragStarted?.Invoke(m_Overlay); } void OnMouseMove(MouseMoveEvent e) { - UpdateHovered(e.mousePosition); - if (!m_Active) return; @@ -182,20 +177,11 @@ void OnMouseUp(MouseUpEvent e) OnDragEnd(e.mousePosition); } - void OnMouseLeave(MouseLeaveEvent evt) - { - //No need to consider the event position in case of a mouse leave event - UpdateHovered(false); - } - - void UpdateHovered(Vector2 mousePosition) - { - UpdateHovered(m_Active || IsInDraggableArea(mousePosition)); - } - - void UpdateHovered(bool hoverStatus) + void OnPointerCaptureOut(PointerCaptureOutEvent evt) { - m_Overlay.rootVisualElement.EnableInClassList(k_DragAreaHovered, hoverStatus); + if (!m_Active) + return; + CancelDrag(evt.originalMousePosition); } void OnKeyDown(KeyDownEvent evt) @@ -222,8 +208,7 @@ void CancelDrag(Vector2 mousePosition) } else { - m_Overlay.floating = false; - m_Overlay.container.Insert(m_InitialIndex, m_Overlay.rootVisualElement); + m_Overlay.DockAt(m_StartContainer, m_InitialSection, m_InitialIndex); } OnDragEnd(mousePosition); @@ -236,8 +221,7 @@ void OnDragEnd(Vector2 mousePosition) canvas.HideOriginGhost(); canvas.destinationMarker.SetTarget(null); - m_StartContainer.stateLocked = false; - UpdateHovered(mousePosition); + target.UnregisterCallback(OnMouseMove); dragEnded?.Invoke(m_Overlay); } diff --git a/Editor/Mono/Overlays/OverlayDropZone.cs b/Editor/Mono/Overlays/OverlayDropZone.cs index 661b87de3d..87f5db1458 100644 --- a/Editor/Mono/Overlays/OverlayDropZone.cs +++ b/Editor/Mono/Overlays/OverlayDropZone.cs @@ -94,92 +94,56 @@ protected override void OnDropZoneActivated(Overlay draggedOverlay) base.OnDropZoneActivated(draggedOverlay); //Disable if the drop zone is linked to the overlay being dragged currently - if (m_Placement == Placement.Start && m_Container.LastTopOverlay() == draggedOverlay - || m_Placement == Placement.End && m_Container.FirstBottomOverlay() == draggedOverlay) + if (m_Placement == Placement.Start && m_Container.GetLastVisible(OverlayContainerSection.BeforeSpacer) == draggedOverlay + || m_Placement == Placement.End && m_Container.GetLastVisible(OverlayContainerSection.AfterSpacer) == draggedOverlay) { SetVisualMode(VisualMode.Disabled); return; } - if (m_Container is ToolbarOverlayContainer) + if (m_Container is FloatingOverlayContainer) { - //Disable if drop zone is reduced to 0 size. Toolbar have container drop zone with flex-grow = 1 - if (Mathf.Approximately(rect.width, 0) || Mathf.Approximately(rect.height, 0)) + SetVisualMode(VisualMode.Disabled); + } + else + { + if (m_Container is ToolbarOverlayContainer && !m_Container.HasVisibleOverlays()) { SetVisualMode(VisualMode.Disabled); - return; } - - switch (m_Placement) + else if (!m_Container.isSpacerVisible) { - case Placement.Start: - SetVisualMode(VisualMode.AddToStart); - break; - case Placement.End: - SetVisualMode(VisualMode.AddToEnd); - break; - default: - SetVisualMode(VisualMode.Disabled); - break; + SetVisualMode(VisualMode.Insert); } - } - else - { - switch (m_Placement) + else { - case Placement.Start: - SetVisualMode(OverlayVisibleInContainerCount(m_Container, m_Container.topOverlays) == 0 - ? VisualMode.AddToStart - : VisualMode.Insert); - break; - case Placement.End: - SetVisualMode(OverlayVisibleInContainerCount(m_Container, m_Container.bottomOverlays) == 0 - ? VisualMode.AddToEnd - : VisualMode.Insert); - break; - default: - SetVisualMode(VisualMode.Insert); - break; + switch (m_Placement) + { + case Placement.Start: + SetVisualMode(VisualMode.AddToStart); + break; + case Placement.End: + SetVisualMode(VisualMode.AddToEnd); + break; + default: + SetVisualMode(VisualMode.Disabled); + break; + } } } } - static int OverlayVisibleInContainerCount(OverlayContainer container, List overlays) - { - int count = 0; - foreach (var overlay in overlays) - { - if (container.IsOverlayVisibleInContainer(overlay)) - ++count; - } - return count; - } - public override void DropOverlay(Overlay overlay) { switch (m_Placement) { case Placement.Start: - { - var target = m_Container.LastTopOverlay(); - if (target != null) - m_Container.AddAfter(overlay, target); - else - m_Container.AddToTop(overlay); - + overlay.DockAt(m_Container, OverlayContainerSection.BeforeSpacer, m_Container.GetSectionCount(OverlayContainerSection.BeforeSpacer)); break; - } case Placement.End: - { - var target = m_Container.FirstBottomOverlay(); - if (target != null) - m_Container.InsertBefore(overlay, target); - else - m_Container.AddToBottom(overlay); - + overlay.DockAt(m_Container, OverlayContainerSection.AfterSpacer, m_Container.GetSectionCount(OverlayContainerSection.AfterSpacer)); break; - } } overlay.floating = false; @@ -217,8 +181,13 @@ protected override void OnDropZoneActivated(Overlay draggedOverlay) { base.OnDropZoneActivated(draggedOverlay); + var container = m_TargetOverlay.container; + + //If floating, current overlay or this dropzone is next to a spacer if (m_TargetOverlay.floating - || m_TargetOverlay == draggedOverlay) + || m_TargetOverlay == draggedOverlay + || m_Placement == Placement.After && container.GetLastVisible(OverlayContainerSection.BeforeSpacer) == m_TargetOverlay + || m_Placement == Placement.Before && container.GetLastVisible(OverlayContainerSection.AfterSpacer) == m_TargetOverlay) { SetVisualMode(VisualMode.Disabled); } @@ -227,14 +196,14 @@ protected override void OnDropZoneActivated(Overlay draggedOverlay) switch (m_Placement) { case Placement.Before: - if (m_TargetOverlay.container.FirstTopOverlay() != m_TargetOverlay) + if (container.GetFirstVisible(OverlayContainerSection.BeforeSpacer) != m_TargetOverlay) goto default; SetVisualMode(VisualMode.AddToStart); break; case Placement.After: - if (m_TargetOverlay.container.LastBottomOverlay() != m_TargetOverlay) + if (container.GetFirstVisible(OverlayContainerSection.AfterSpacer) != m_TargetOverlay) goto default; SetVisualMode(VisualMode.AddToEnd); @@ -254,17 +223,17 @@ public override bool CanAcceptTarget(Overlay overlay) public override void DropOverlay(Overlay overlay) { - var container = m_TargetOverlay.container; - - switch (m_Placement) + // The drop zone before the element should place after the next overlay when after the spacer. + // Overlay after the spacer are listed from bottom to spacer instead of spacer to bottom. + m_TargetOverlay.container.GetOverlayIndex(m_TargetOverlay, out var section, out _); + if (section == OverlayContainerSection.BeforeSpacer && m_Placement == Placement.After + || section == OverlayContainerSection.AfterSpacer && m_Placement == Placement.Before) { - case Placement.Before: - container.InsertBefore(overlay, m_TargetOverlay); - break; - - case Placement.After: - container.AddAfter(overlay, m_TargetOverlay); - break; + overlay.DockAfter(m_TargetOverlay); + } + else + { + overlay.DockBefore(m_TargetOverlay); } overlay.floating = false; @@ -283,11 +252,13 @@ sealed class HiddenToolbarDropZone : OverlayContainerDropZone { const string k_NoElementClassName = className + "-no-element"; const string k_DestinationClassName = OverlayDestinationMarker.className + "--no-element"; + OverlayContainer m_OverlayContainer; public HiddenToolbarDropZone(OverlayContainer container) : base(container, Placement.Start) { AddToClassList(k_NoElementClassName); - + m_OverlayContainer = container; + SetVisualMode(VisualMode.Disabled); priority = 1; } @@ -297,6 +268,18 @@ public override void PopulateDestMarkerClassList(IList classes) classes.Add(k_DestinationClassName); } + + protected override void OnDropZoneActivated(Overlay draggedOverlay) + { + base.OnDropZoneActivated(draggedOverlay); + + SetVisualMode(m_OverlayContainer.HasVisibleOverlays() ? VisualMode.Disabled : VisualMode.Custom); + } + + protected override void OnDropZoneDeactivated(Overlay draggedOverlay) + { + SetVisualMode(VisualMode.Disabled); + } } abstract class OverlayDropZoneBase : VisualElement diff --git a/Editor/Mono/Overlays/OverlayMenu.cs b/Editor/Mono/Overlays/OverlayMenu.cs index 0777ed68ab..41c842fe78 100644 --- a/Editor/Mono/Overlays/OverlayMenu.cs +++ b/Editor/Mono/Overlays/OverlayMenu.cs @@ -137,14 +137,19 @@ public void Show(IEnumerable overlays, Rect bounds, Vector2 mousePositi size.x = Mathf.Min(parentBounds.width, size.x); size.y = Mathf.Min(parentBounds.height, size.y); - if (menuRect.xMax > parentBounds.width) + //Show has been triggered by a menu entry and not by a shortcut key + //Place the menu at the center of the parent Bounds + if(mousePosition.Equals(Vector2.negativeInfinity)) + menuRect = new Rect(parentBounds.center - size / 2f, size); + + if(menuRect.xMax > parentBounds.width) menuRect.x -= menuRect.xMax - parentBounds.width; - if (menuRect.xMin < 0) + if(menuRect.xMin < 0) menuRect.x = 0; - if (menuRect.yMax > parentBounds.height) + if(menuRect.yMax > parentBounds.height) menuRect.y -= menuRect.yMax - parentBounds.height; - if (menuRect.yMin < 0) + if(menuRect.yMin < 0) menuRect.y = 0; style.top = menuRect.y; diff --git a/Editor/Mono/Overlays/OverlayPlacement.cs b/Editor/Mono/Overlays/OverlayPlacement.cs index 228eaba800..182f78018f 100644 --- a/Editor/Mono/Overlays/OverlayPlacement.cs +++ b/Editor/Mono/Overlays/OverlayPlacement.cs @@ -68,21 +68,84 @@ internal set void OnFloatingChanged(bool floating) { - RebuildContent(); - if (floating) UpdateAbsolutePosition(); - container?.UpdateIsVisibleInContainer(this); floatingChanged?.Invoke(floating); } + internal bool DockAt(OverlayContainer container, OverlayContainerSection section) + { + return DockAt(container, section, container.GetSectionCount(section)); + } + + internal bool DockAt(OverlayContainer container, OverlayContainerSection section, int index) + { + //If the overlay is staying in the same container + if (container.GetOverlayIndex(this, out var originSection, out var originIndex)) + { + if (originSection == section && originIndex == index) + return true; + + //If the overlay was before the index, removing it will change the size of the container + if (originSection == section && originIndex < index) + --index; + } + + this.container?.RemoveOverlay(this); + + this.container = container; + this.container.InsertOverlay(this, section, index); + + floating = container is FloatingOverlayContainer; + + RebuildContent(); + + return true; + } + + internal bool DockBefore(Overlay target) + { + if (target.container == null) + throw new ArgumentException("Target overlay has an invalid container", nameof(target)); + + var container = target.container; + container.GetOverlayIndex(target, out var section, out var index); + return DockAt(container, section, index); + } + + internal bool DockAfter(Overlay target) + { + if (target.container == null) + throw new ArgumentException("Target overlay has an invalid container", nameof(target)); + + var container = target.container; + container.GetOverlayIndex(target, out var section, out var index); + return DockAt(container, section, index + 1); + } + public void Undock() { if (floating) return; - canvas.floatingContainer.Add(rootVisualElement); - floating = true; + + DockAt(canvas.floatingContainer, OverlayContainerSection.BeforeSpacer, canvas.floatingContainer.GetSectionCount(OverlayContainerSection.BeforeSpacer)); + } + + internal void BringToFront() + { + if (!(container is FloatingOverlayContainer)) + return; + + DockAt(container, OverlayContainerSection.BeforeSpacer, container.GetSectionCount(OverlayContainerSection.BeforeSpacer)); + } + + internal void SetSnappingOffset(Vector2 snapOffset, Vector2 snapOffsetDelta) + { + m_FloatingSnapOffset = snapOffset; + m_SnapOffsetDelta = snapOffsetDelta; + UpdateAbsolutePosition(); + floatingPositionChanged?.Invoke(floatingPosition); } Vector2 SnapToFloatingPosition(SnapCorner corner, Vector2 snapPosition) @@ -186,6 +249,10 @@ internal void OnGeometryChanged(GeometryChangedEvent evt) void UpdateSnapping(Vector2 position) { + //Protect against an update to the position while the canvas hasn't had its first geometry pass + if (float.IsNaN(position.x) || float.IsNaN(position.y)) + return; + if (m_LockAnchor) { //Anchor and position are locked, we only update the offsetDelta diff --git a/Editor/Mono/Overlays/OverlayResizer.cs b/Editor/Mono/Overlays/OverlayResizer.cs new file mode 100644 index 0000000000..9bb742c3a2 --- /dev/null +++ b/Editor/Mono/Overlays/OverlayResizer.cs @@ -0,0 +1,280 @@ +// 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; +using Cursor = UnityEngine.UIElements.Cursor; + +namespace UnityEditor.Overlays +{ + class OverlayResizerGroup : VisualElement + { + const float k_CornerSize = 8; + const float k_SideSize = 6; + + [Flags] + enum Direction + { + Left = 1 << 0, + Bottom = 1 << 1, + Top = 1 << 2, + Right = 1 << 3, + + TopLeft = Top | Left, + TopRight = Top | Right, + BottomLeft = Bottom | Left, + BottomRight = Bottom | Right, + } + + class OverlayResizer : VisualElement + { + readonly Direction m_Direction; + readonly Overlay m_Overlay; + readonly bool m_ModifyPosition; + + Vector2 m_OriginalMousePosition; + Rect m_OriginalRect; + Rect m_ContainerRect; + Vector2 m_MinSize; + Vector2 m_MaxSize; + bool m_Active; + + public OverlayResizer(Overlay overlay, Direction direction) + { + m_Overlay = overlay; + m_Direction = direction; + + style.position = Position.Absolute; + + bool hasLeft = HasDirection(Direction.Left); + bool hasRight = HasDirection(Direction.Right); + bool hasTop = HasDirection(Direction.Top); + bool hasBottom = HasDirection(Direction.Bottom); + bool isCorner = HasMultipleDirections(); + + var size = isCorner ? k_CornerSize : k_SideSize; + + if (hasLeft) style.left = -size * .5f; + if (hasRight) style.right = -size * .5f; + if (hasTop) style.top = -size * .5f; + if (hasBottom) style.bottom = -size * .5f; + + style.width = hasLeft || hasRight ? size : new Length(100, LengthUnit.Percent); + style.height = hasTop || hasBottom ? size : new Length(100, LengthUnit.Percent); + + m_ModifyPosition = HasDirection(Direction.Left) || HasDirection(Direction.Top); + + style.cursor = new Cursor { defaultCursorId = (int)GetCursor(direction) }; + + RegisterCallback(OnMouseDown); + RegisterCallback(OnMouseMove); + RegisterCallback(OnMouseUp); + } + + void OnMouseDown(MouseDownEvent evt) + { + m_OriginalRect = new Rect( + m_Overlay.floating ? m_Overlay.floatingPosition : m_Overlay.rootVisualElement.layout.position, + m_Overlay.size); + + m_ContainerRect = m_Overlay.rootVisualElement.parent.rect; + m_OriginalMousePosition = evt.mousePosition; + m_MaxSize = m_Overlay.maxSize; + m_MinSize = m_Overlay.minSize; + + this.CaptureMouse(); + evt.StopPropagation(); + m_Active = true; + } + + void OnMouseMove(MouseMoveEvent evt) + { + if (m_Active) + { + var translation = evt.mousePosition - m_OriginalMousePosition; + var rect = ResizeRect(m_OriginalRect, translation); + + m_Overlay.size = rect.size; + + if (m_ModifyPosition && m_Overlay.floating) + m_Overlay.floatingPosition = rect.position; + + evt.StopPropagation(); + } + } + + void OnMouseUp(MouseUpEvent evt) + { + if (m_Active) + { + evt.StopPropagation(); + this.ReleaseMouse(); + m_Active = false; + } + } + + Rect ResizeRect(Rect rect, Vector2 delta) + { + delta = ClampDeltaToMinMax(delta, rect); + if (HasDirection(Direction.Left)) + rect.xMin = Mathf.Max(m_ContainerRect.xMin, rect.xMin + delta.x); + else if (HasDirection(Direction.Right)) + rect.xMax = Mathf.Min(m_ContainerRect.xMax, rect.xMax + delta.x); + + if (HasDirection(Direction.Top)) + rect.yMin = Mathf.Max(m_ContainerRect.yMin, rect.yMin + delta.y); + else if (HasDirection(Direction.Bottom)) + rect.yMax = Mathf.Min(m_ContainerRect.yMax, rect.yMax + delta.y); + + return rect; + } + + Vector2 ClampDeltaToMinMax(Vector2 delta, Rect rect) + { + if (HasDirection(Direction.Left)) + delta.x = rect.width - Mathf.Clamp(rect.width - delta.x, m_MinSize.x, m_MaxSize.x); + else if (HasDirection(Direction.Right)) + delta.x = Mathf.Clamp(rect.width + delta.x, m_MinSize.x, m_MaxSize.x) - rect.width; + + if (HasDirection(Direction.Top)) + delta.y = rect.height - Mathf.Clamp(rect.height - delta.y, m_MinSize.y, m_MaxSize.y); + else if (HasDirection(Direction.Bottom)) + delta.y = Mathf.Clamp(rect.height + delta.y, m_MinSize.y, m_MaxSize.y) - rect.height; + + return delta; + } + + static MouseCursor GetCursor(Direction direction) + { + MouseCursor cursorStyle = MouseCursor.Arrow; + + switch (direction) + { + case Direction.TopLeft: + case Direction.BottomRight: + cursorStyle = MouseCursor.ResizeUpLeft; + break; + case Direction.TopRight: + case Direction.BottomLeft: + cursorStyle = MouseCursor.ResizeUpRight; + break; + case Direction.Left: + case Direction.Right: + cursorStyle = MouseCursor.ResizeHorizontal; + break; + case Direction.Top: + case Direction.Bottom: + cursorStyle = MouseCursor.ResizeVertical; + break; + + } + return cursorStyle; + } + + public bool HasDirection(Direction target) + { + return (m_Direction & target) == target; + } + + public bool HasMultipleDirections() + { + return (m_Direction & (m_Direction - 1)) != 0; + } + } + + readonly Overlay m_Overlay; + readonly OverlayResizer[] m_Resizers; + + public OverlayResizerGroup(Overlay overlay) + { + this.StretchToParentSize(); + pickingMode = PickingMode.Ignore; + + m_Resizers = new [] + { + new OverlayResizer(overlay, Direction.Top) { name = "ResizerTop" }, + new OverlayResizer(overlay, Direction.Bottom) { name = "ResizerBottom" }, + new OverlayResizer(overlay, Direction.Left) { name = "ResizerLeft" }, + new OverlayResizer(overlay, Direction.Right) { name = "ResizerRight" }, + new OverlayResizer(overlay, Direction.TopLeft) { name = "ResizerTopLeft" }, + new OverlayResizer(overlay, Direction.TopRight) { name = "ResizerTopRight" }, + new OverlayResizer(overlay, Direction.BottomLeft) { name = "ResizerBottomLeft" }, + new OverlayResizer(overlay, Direction.BottomRight) { name = "ResizerBottomRight" }, + }; + + m_Overlay = overlay; + foreach (var resizer in m_Resizers) + Add(resizer); + + overlay.containerChanged += OnOverlayContainerChanged; + overlay.layoutChanged += OnOverlayLayoutChanged; + UpdateResizerVisibility(); + } + + void OnOverlayLayoutChanged(Layout layout) + { + UpdateResizerVisibility(); + } + + void OnOverlayContainerChanged(OverlayContainer container) + { + UpdateResizerVisibility(); + } + + bool ContainerCanShowResizer(OverlayResizer resizer) + { + var container = m_Overlay.container; + if (container == null) + return false; + + if (container is FloatingOverlayContainer) + return true; + + if (container is ToolbarOverlayContainer) + return false; + + var alignment = container.resolvedStyle.alignItems; + bool hide = false; + + // Check the opposite direction. If the content is align to one side, hide resizer on that side + switch (alignment) + { + case Align.FlexStart: + hide |= resizer.HasDirection(container.isHorizontal ? Direction.Top : Direction.Left); + break; + + case Align.FlexEnd: + hide |= resizer.HasDirection(container.isHorizontal ? Direction.Bottom : Direction.Right); + break; + } + + return !hide; + } + + void UpdateResizerVisibility() + { + bool globalHide = m_Overlay.layout != Layout.Panel; + foreach (var resizer in m_Resizers) + { + bool hide = globalHide || !ContainerCanShowResizer(resizer); + + if (resizer.HasMultipleDirections()) + { + hide |= m_Overlay.minSize == m_Overlay.maxSize; + } + else + { + if (resizer.HasDirection(Direction.Left) || resizer.HasDirection(Direction.Right)) + hide |= Mathf.Approximately(m_Overlay.minSize.x, m_Overlay.maxSize.x); + + if (resizer.HasDirection(Direction.Top) || resizer.HasDirection(Direction.Bottom)) + hide |= Mathf.Approximately(m_Overlay.minSize.y, m_Overlay.maxSize.y); + } + + resizer.style.display = hide ? DisplayStyle.None : DisplayStyle.Flex; + } + } + } +} diff --git a/Editor/Mono/Overlays/ToolbarOverlay.cs b/Editor/Mono/Overlays/ToolbarOverlay.cs index 54d631d81b..789bd8b8c4 100644 --- a/Editor/Mono/Overlays/ToolbarOverlay.cs +++ b/Editor/Mono/Overlays/ToolbarOverlay.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using UnityEditor.Toolbars; +using UnityEngine; using UnityEngine.UIElements; namespace UnityEditor.Overlays diff --git a/Editor/Mono/PerformanceTools/FrameDebugger.cs b/Editor/Mono/PerformanceTools/FrameDebugger.cs index 8d29d53ab0..eb64165626 100644 --- a/Editor/Mono/PerformanceTools/FrameDebugger.cs +++ b/Editor/Mono/PerformanceTools/FrameDebugger.cs @@ -4,508 +4,183 @@ using System; using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; + using UnityEngine; -using UnityEngine.Rendering; -using UnityEditor.Rendering; +using UnityEngine.Profiling; +using UnityEngine.Networking.PlayerConnection; + using UnityEditorInternal; -using System.Runtime.InteropServices; using UnityEditor.IMGUI.Controls; -using UnityEngine.Networking.PlayerConnection; using UnityEditor.Networking.PlayerConnection; -using UnityEngine.Experimental.Rendering; - -namespace UnityEditorInternal -{ - // match enum FrameEventType on C++ side! - // match kFrameEventTypeNames names array! - internal enum FrameEventType - { - // ReSharper disable InconsistentNaming - ClearNone = 0, - ClearColor, - ClearDepth, - ClearColorDepth, - ClearStencil, - ClearColorStencil, - ClearDepthStencil, - ClearAll, - SetRenderTarget, - ResolveRT, - ResolveDepth, - GrabIntoRT, - StaticBatch, - DynamicBatch, - Mesh, - DynamicGeometry, - GLDraw, - SkinOnGPU, - DrawProcedural, - DrawProceduralIndirect, - DrawProceduralIndexed, - DrawProceduralIndexedIndirect, - ComputeDispatch, - RayTracingDispatch, - PluginEvent, - InstancedMesh, - BeginSubpass, - SRPBatch, - HierarchyLevelBreak, - HybridBatch - // ReSharper restore InconsistentNaming - } - - internal enum ShowAdditionalInfo - { - Preview, - ShaderProperties - } - - // Match C++ ScriptingShaderFloatInfo memory layout! - [StructLayout(LayoutKind.Sequential)] - internal struct ShaderFloatInfo - { - public string name; - public int flags; - public float value; - } - - // Match C++ ScriptingShaderIntInfo memory layout! - [StructLayout(LayoutKind.Sequential)] - internal struct ShaderIntInfo - { - public string name; - public int flags; - public int value; - } - - // Match C++ ScriptingShaderVectorInfo memory layout! - [StructLayout(LayoutKind.Sequential)] - internal struct ShaderVectorInfo - { - public string name; - public int flags; - public Vector4 value; - } - - // Match C++ ScriptingShaderMatrixInfo memory layout! - [StructLayout(LayoutKind.Sequential)] - internal struct ShaderMatrixInfo - { - public string name; - public int flags; - public Matrix4x4 value; - } - - // Match C++ ScriptingShaderTextureInfo memory layout! - [StructLayout(LayoutKind.Sequential)] - internal struct ShaderTextureInfo - { - public string name; - public int flags; - public string textureName; - public Texture value; - } - - // Match C++ ScriptingShaderBufferInfo memory layout! - [StructLayout(LayoutKind.Sequential)] - internal struct ShaderBufferInfo - { - public string name; - public int flags; - } - - // Match C++ ScriptingShaderBufferInfo memory layout! - [StructLayout(LayoutKind.Sequential)] - internal struct ShaderConstantBufferInfo - { - public string name; - public int flags; - } - // Match C++ ScriptingShaderProperties memory layout! - [StructLayout(LayoutKind.Sequential)] - internal struct ShaderProperties - { - public ShaderFloatInfo[] floats; - public ShaderIntInfo[] ints; - public ShaderVectorInfo[] vectors; - public ShaderMatrixInfo[] matrices; - public ShaderTextureInfo[] textures; - public ShaderBufferInfo[] buffers; - public ShaderConstantBufferInfo[] 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 string shaderKeywords; - public int componentInstanceID; - public Mesh mesh; - public int meshInstanceID; - public int meshSubset; - public int[] 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; - - // 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; - - // 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; - - // shader state - public FrameDebuggerBlendState blendState; - public FrameDebuggerRasterState rasterState; - public FrameDebuggerDepthState depthState; - public FrameDebuggerStencilState stencilState; - public int stencilRef; - - // clear event data - public float clearColorR; - public float clearColorG; - public float clearColorB; - public float clearColorA; - public float clearDepth; - public uint clearStencil; - - // shader properties - public int batchBreakCause; - public ShaderProperties shaderProperties; - } - - // Match C++ MonoFrameDebuggerEvent memory layout! - [StructLayout(LayoutKind.Sequential)] - internal struct FrameDebuggerEvent - { - public FrameEventType type; - public UnityEngine.Object 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; - } - - // 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; - } - - // Match C++ ScriptingFrameDebuggerDepthState memory layout! - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] - internal struct FrameDebuggerDepthState - { - public int depthWrite; - public CompareFunction 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; - } -} namespace UnityEditor { internal class FrameDebuggerWindow : EditorWindow { - // match enum FrameEventType on C++ side! - static public readonly string[] s_FrameEventTypeNames = new[] - { - "Clear (nothing)", - "Clear (color)", - "Clear (Z)", - "Clear (color+Z)", - "Clear (stencil)", - "Clear (color+stencil)", - "Clear (Z+stencil)", - "Clear (color+Z+stencil)", - "SetRenderTarget", - "Resolve Color", - "Resolve Depth", - "Grab RenderTexture", - "Static Batch", - "Dynamic Batch", - "Draw Mesh", - "Draw Dynamic", - "Draw GL", - "GPU Skinning", - "Draw Procedural", - "Draw Procedural Indirect", - "Draw Procedural Indexed", - "Draw Procedural Indexed Indirect", - "Compute", - "Ray Tracing Dispatch", - "Plugin Event", - "Draw Mesh (instanced)", - "Begin Subpass", - "SRP Batch", - "", // on purpose empty string for kFrameEventHierarchyLevelBreak - "Hybrid Batch Group" - }; - - // Cached strings built from FrameDebuggerEventData. - // Only need to rebuild them when event data actually changes. - private struct EventDataStrings - { - public string drawCallCount; - public string drawInstancedCallCount; - - public string shader; - public string pass; - - public string stencilRef; - public string stencilReadMask; - public string stencilWriteMask; - public string stencilComp; - public string stencilPass; - public string stencilFail; - public string stencilZFail; - - public string[] texturePropertyTooltips; - } - - const float kResizerWidth = 5f; - const float kMinListWidth = 200f; - const float kMinDetailsWidth = 200f; - const float kDetailsMargin = 0f; - const float kMinPreviewSize = 64f; - - const string kIntFormat = "d"; - const string kFloatFormat = "g2"; - const string kFloatDetailedFormat = "g7"; + // Serialized + [SerializeField] private float m_TreeWidth = FrameDebuggerStyles.Window.k_MinTreeWidth; + [SerializeField] private TreeViewState m_TreeViewState; + + // Private + private int m_RepaintFrames = k_NeedToRepaintFrames; + private int m_FrameEventsHash; + private Rect m_SearchRect; + private string m_SearchString = String.Empty; + private IConnectionState m_AttachToPlayerState; + private FrameDebuggerTreeView m_TreeView; + private FrameDebuggerEventDetailsView m_EventDetailsView; + private FrameDebuggerToolbarView m_Toolbar; - const float kShaderPropertiesIndention = 15.0f; - const float kNameFieldWidth = 250.0f; - const float kValueFieldWidth = 300.0f; - const float kArrayValuePopupBtnWidth = 25.0f; + // Statics + private static List s_FrameDebuggers = new List(); - // See the comments for BaseParamInfo in FrameDebuggerInternal.h - const int kShaderTypeBits = (int)ShaderType.Count; - const int kArraySizeBitMask = 0x3FF; + // Constants // Sometimes when disabling the frame debugger, the UI does not update automatically - // the repaint happens, but we still think there's zero events present // (on Mac at least). Haven't figured out why, so whenever changing the // enable/limit state, just repaint a couple of times. Yeah... - const int kNeedToRepaintFrames = 4; + private const int k_NeedToRepaintFrames = 4; + // Properties + private bool IsDisabledInEditor => !FrameDebugger.enabled && m_AttachToPlayerState.connectedToTarget == ConnectionTarget.Editor; + private bool HasEventHashChanged => FrameDebuggerUtility.eventsHash != m_FrameEventsHash; - [SerializeField] - float m_ListWidth = kMinListWidth * 1.5f; - private int m_RepaintFrames = kNeedToRepaintFrames; + [MenuItem("Window/Analysis/Frame Debugger", false, 10)] + public static FrameDebuggerWindow ShowFrameDebuggerWindow() + { + var wnd = GetWindow(typeof(FrameDebuggerWindow)) as FrameDebuggerWindow; + wnd.titleContent = EditorGUIUtility.TrTextContent("Frame Debug"); + return wnd; + } - // Mesh preview - PreviewRenderUtility m_PreviewUtility; + private void OnGUI() + { + FrameDebuggerEvent[] descs = FrameDebuggerUtility.GetFrameEvents(); + Initialize(descs); - // Texture preview - GUIContent m_TextureGUIContent = new GUIContent(); + int oldLimit = FrameDebuggerUtility.limit; - // Frame events tree view - [SerializeField] - TreeViewState m_TreeViewState; - [NonSerialized] - FrameDebuggerTreeView m_Tree; - [NonSerialized] private int m_FrameEventsHash; + Profiler.BeginSample("DrawToolbar"); + bool repaint = m_Toolbar.DrawToolbar(this, m_AttachToPlayerState); + Profiler.EndSample(); - // Render target view options - [NonSerialized] int m_RTIndex; - [NonSerialized] int m_RTIndexLastSet = int.MaxValue; - [NonSerialized] int m_RTChannel; + if (IsDisabledInEditor) + { + GUI.enabled = true; + if (!FrameDebuggerUtility.locallySupported) + { + string warningMessage = (FrameDebuggerHelper.IsOnLinuxOpenGL) ? FrameDebuggerStyles.EventDetails.warningLinuxOpenGLMsg : FrameDebuggerStyles.EventDetails.warningMultiThreadedMsg; + EditorGUILayout.HelpBox(warningMessage, MessageType.Warning, true); + } - [NonSerialized] private float m_RTBlackLevel; - [NonSerialized] private float m_RTWhiteLevel = 1.0f; + EditorGUILayout.HelpBox(FrameDebuggerStyles.EventDetails.descriptionString, MessageType.Info, true); + } + else + { + if (FrameDebugger.IsLocalEnabled()) + { + PlayModeView playModeView = PlayModeView.GetMainPlayModeView(); + if (playModeView) + playModeView.ShowTab(); + } - private int m_PrevEventsLimit = 0; - private int m_PrevEventsCount = 0; + // captured frame event contents have changed, rebuild the tree data + if (HasEventHashChanged) + { + m_TreeView.m_DataSource.SetEvents(descs); + m_FrameEventsHash = FrameDebuggerUtility.eventsHash; + } - private FrameDebuggerEventData m_CurEventData; - private uint m_CurEventDataHash = 0; - private EventDataStrings m_CurEventDataStrings; + float toolbarHeight = EditorStyles.toolbar.fixedHeight; - // Shader Properties - private Vector2 m_ScrollViewShaderProps = Vector2.zero; + 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; - private ShowAdditionalInfo m_AdditionalInfo = ShowAdditionalInfo.ShaderProperties; - private GUIContent[] m_AdditionalInfoGuiContents = Enum.GetNames(typeof(ShowAdditionalInfo)).Select(m => new GUIContent(m)).ToArray(); + // Search area + m_SearchRect = EditorGUILayout.GetControlRect(); + m_SearchRect.width = m_TreeWidth - 5; + DrawSearchField(m_SearchString); - static List s_FrameDebuggers = new List(); + Rect listRect = new Rect( + 0, + toolbarHeight + m_SearchRect.y, + m_TreeWidth, + position.height - toolbarHeight - m_SearchRect.height - 5 + ); - private IConnectionState m_AttachToPlayerState; + Rect currentEventRect = new Rect( + m_TreeWidth, + toolbarHeight, + position.width - m_TreeWidth, + position.height - toolbarHeight + ); - MeshPreview.Settings m_Settings; + Profiler.BeginSample("DrawTree"); + m_TreeView.m_TreeView.searchString = m_SearchString; + m_TreeView.DrawTree(listRect); + Profiler.EndSample(); - [MenuItem("Window/Analysis/Frame Debugger", false, 10)] - public static FrameDebuggerWindow ShowFrameDebuggerWindow() - { - var wnd = GetWindow(typeof(FrameDebuggerWindow)) as FrameDebuggerWindow; - if (wnd != null) - { - wnd.titleContent = EditorGUIUtility.TrTextContent("Frame Debug"); - } - return wnd; - } + EditorGUIUtility.DrawHorizontalSplitter(dragRect); - internal static void RepaintAll() - { - foreach (var fd in s_FrameDebuggers) - { - fd.Repaint(); + Profiler.BeginSample("DrawEvent"); + m_EventDetailsView.DrawEvent(currentEventRect, descs, m_AttachToPlayerState.connectedToTarget == ConnectionTarget.Editor); + Profiler.EndSample(); } - } - - public FrameDebuggerWindow() - { - position = new Rect(50, 50, (kMinListWidth + kMinDetailsWidth) * 1.5f, 350f); - minSize = new Vector2(kMinListWidth + kMinDetailsWidth, 200); - } - internal void ChangeFrameEventLimit(int newLimit) - { - if (newLimit <= 0 || newLimit > FrameDebuggerUtility.count) - { - return; - } + if (repaint || oldLimit != FrameDebuggerUtility.limit) + RepaintOnLimitChange(); - if (newLimit != FrameDebuggerUtility.limit && newLimit > 0) + if (m_RepaintFrames > 0) { - var obj = FrameDebuggerUtility.GetFrameEventObject(newLimit - 1); - if (obj != null) - EditorGUIUtility.PingObject(obj); + m_TreeView.SelectFrameEventIndex(FrameDebuggerUtility.limit); + RepaintAllNeededThings(); + --m_RepaintFrames; } - - FrameDebuggerUtility.limit = newLimit; - if (m_Tree != null) - m_Tree.SelectFrameEventIndex(newLimit); } - static void DisableFrameDebugger() + internal void DrawSearchField(string str) { - if (FrameDebugger.IsLocalEnabled()) - { - // if it was true before, we disabled and ask the game scene to repaint - EditorApplication.SetSceneRepaintDirty(); - } - - FrameDebuggerUtility.SetEnabled(false, FrameDebuggerUtility.GetRemotePlayerGUID()); + m_SearchString = EditorGUI.ToolbarSearchField(m_SearchRect, str, false); } - internal void OnDidOpenScene() + private void OnDidOpenScene() { DisableFrameDebugger(); } - void OnPauseStateChanged(PauseState state) + private void OnPauseStateChanged(PauseState state) { RepaintOnLimitChange(); } - void OnPlayModeStateChanged(PlayModeStateChange state) + private void OnPlayModeStateChanged(PlayModeStateChange state) { RepaintOnLimitChange(); } - internal void OnEnable() + private void OnEnable() { if (m_AttachToPlayerState == null) m_AttachToPlayerState = PlayerConnectionGUIUtility.GetConnectionState(this); + wantsLessLayoutEvents = true; autoRepaintOnSceneChange = true; s_FrameDebuggers.Add(this); EditorApplication.pauseStateChanged += OnPauseStateChanged; EditorApplication.playModeStateChanged += OnPlayModeStateChanged; - m_RepaintFrames = kNeedToRepaintFrames; - - m_PreviewUtility = new PreviewRenderUtility(); - m_PreviewUtility.camera.fieldOfView = 30.0f; - m_Settings = new MeshPreview.Settings(); - m_Settings.previewDir = new Vector2(120, -20); + m_RepaintFrames = k_NeedToRepaintFrames; } - internal void OnDisable() + private void OnDisable() { - m_PreviewUtility?.Cleanup(); - m_Settings?.Dispose(); + if (m_EventDetailsView != null) + m_EventDetailsView.OnDisable(); + + FrameDebuggerStyles.OnDisable(); m_AttachToPlayerState?.Dispose(); m_AttachToPlayerState = null; @@ -525,33 +200,91 @@ internal override void OnResized() base.OnResized(); } - public void EnableIfNeeded() + private void Initialize(FrameDebuggerEvent[] descs) + { + if (m_Toolbar == null) + m_Toolbar = new FrameDebuggerToolbarView(); + + if (m_TreeViewState == null) + m_TreeViewState = new TreeViewState(); + + 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); + + // 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_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); + } + + bool hasChosenParent = false; + FrameDebuggerTreeView.FrameDebuggerTreeViewItem parentItem = null; + + internal void ChangeFrameEventLimit(int newLimit, FrameDebuggerTreeView.FrameDebuggerTreeViewItem originalItem) + { + if (newLimit <= 0 || newLimit > FrameDebuggerUtility.count) + 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_RTChannel = 0; - m_RTIndex = 0; - m_RTBlackLevel = 0.0f; - m_RTWhiteLevel = 1.0f; + + m_EventDetailsView.Reset(); ClickEnableFrameDebugger(); RepaintOnLimitChange(); } - private void ClickEnableFrameDebugger() + 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) - { - // pause play mode if needed if (EditorApplication.isPlaying && !EditorApplication.isPaused) EditorApplication.isPaused = true; - } if (!isEnabled) FrameDebuggerUtility.SetEnabled(true, ProfilerDriver.connectedProfiler); @@ -561,1301 +294,34 @@ private void ClickEnableFrameDebugger() // Make sure game view is visible when enabling frame debugger locally if (FrameDebugger.IsLocalEnabled()) { - var playModeView = PlayModeView.GetMainPlayModeView(); + PlayModeView playModeView = PlayModeView.GetMainPlayModeView(); if (playModeView) - { playModeView.ShowTab(); - } - } - - m_PrevEventsLimit = FrameDebuggerUtility.limit; - m_PrevEventsCount = FrameDebuggerUtility.count; - } - - // Only call this when m_CurEventData changes. - void BuildCurEventDataStrings() - { - // we will show that only if drawcall count is bigger than one so update unconditionally - m_CurEventDataStrings.drawCallCount = string.Format("{0}", m_CurEventData.drawCallCount); - m_CurEventDataStrings.drawInstancedCallCount = string.Format("{0}", m_CurEventData.instanceCount); - - // shader name & subshader index - m_CurEventDataStrings.shader = string.Format("{0}, SubShader #{1}", m_CurEventData.shaderName, m_CurEventData.subShaderIndex.ToString()); - - // pass name & LightMode tag - string passName = string.IsNullOrEmpty(m_CurEventData.passName) ? "#" + m_CurEventData.shaderPassIndex : m_CurEventData.passName; - string lightMode = string.IsNullOrEmpty(m_CurEventData.passLightMode) ? "" : string.Format(" ({0})", m_CurEventData.passLightMode); - m_CurEventDataStrings.pass = passName + lightMode; - - // stencil states - if (m_CurEventData.stencilState.stencilEnable) - { - m_CurEventDataStrings.stencilRef = GetStencilString(m_CurEventData.stencilRef); - - if (m_CurEventData.stencilState.readMask != 255) - m_CurEventDataStrings.stencilReadMask = GetStencilString(m_CurEventData.stencilState.readMask); - - if (m_CurEventData.stencilState.writeMask != 255) - m_CurEventDataStrings.stencilWriteMask = GetStencilString(m_CurEventData.stencilState.writeMask); - - // Only show *Front states when CullMode is set to Back. - // Only show *Back states when CullMode is set to Front. - // Show both *Front and *Back states for two-sided geometry. - if (m_CurEventData.rasterState.cullMode == CullMode.Back) - { - m_CurEventDataStrings.stencilComp = m_CurEventData.stencilState.stencilFuncFront.ToString(); - m_CurEventDataStrings.stencilPass = m_CurEventData.stencilState.stencilPassOpFront.ToString(); - m_CurEventDataStrings.stencilFail = m_CurEventData.stencilState.stencilFailOpFront.ToString(); - m_CurEventDataStrings.stencilZFail = m_CurEventData.stencilState.stencilZFailOpFront.ToString(); - } - else if (m_CurEventData.rasterState.cullMode == CullMode.Front) - { - m_CurEventDataStrings.stencilComp = m_CurEventData.stencilState.stencilFuncBack.ToString(); - m_CurEventDataStrings.stencilPass = m_CurEventData.stencilState.stencilPassOpBack.ToString(); - m_CurEventDataStrings.stencilFail = m_CurEventData.stencilState.stencilFailOpBack.ToString(); - m_CurEventDataStrings.stencilZFail = m_CurEventData.stencilState.stencilZFailOpBack.ToString(); - } - else - { - m_CurEventDataStrings.stencilComp = - string.Format("{0} {1}", m_CurEventData.stencilState.stencilFuncFront.ToString(), m_CurEventData.stencilState.stencilFuncBack.ToString()); - - m_CurEventDataStrings.stencilPass = - string.Format("{0} {1}", m_CurEventData.stencilState.stencilPassOpFront.ToString(), m_CurEventData.stencilState.stencilPassOpBack.ToString()); - - m_CurEventDataStrings.stencilFail = - string.Format("{0} {1}", m_CurEventData.stencilState.stencilFailOpFront.ToString(), m_CurEventData.stencilState.stencilFailOpBack.ToString()); - - m_CurEventDataStrings.stencilZFail = - string.Format("{0} {1}", m_CurEventData.stencilState.stencilZFailOpFront.ToString(), m_CurEventData.stencilState.stencilZFailOpBack.ToString()); - } - } - - // texture property tooltips - ShaderTextureInfo[] textureInfoArray = m_CurEventData.shaderProperties.textures; - m_CurEventDataStrings.texturePropertyTooltips = new string[textureInfoArray.Length]; - StringBuilder tooltip = new StringBuilder(); - - for (int i = 0; i < textureInfoArray.Length; ++i) - { - Texture texture = textureInfoArray[i].value; - if (texture == null) - continue; - - tooltip.Clear(); - int depth = 0; - if (texture.dimension == TextureDimension.Tex3D) - { - if (texture is Texture3D) - depth = (texture as Texture3D).depth; - else if (texture is RenderTexture) - depth = (texture as RenderTexture).volumeDepth; - } - - if (depth == 0) - tooltip.AppendFormat("Size: {0} x {1}", texture.width.ToString(), texture.height.ToString()); - else - tooltip.AppendFormat("Size: {0} x {1} x {2}", texture.width.ToString(), texture.height.ToString(), depth); - - tooltip.AppendFormat("\nDimension: {0}", texture.dimension.ToString()); - - string formatFormat = "\nFormat: {0}"; - string depthFormat = "\nDepth: {0}"; - - if (texture is Texture2D) - tooltip.AppendFormat(formatFormat, (texture as Texture2D).format.ToString()); - else if (texture is Cubemap) - tooltip.AppendFormat(formatFormat, (texture as Cubemap).format.ToString()); - else if (texture is Texture2DArray) - { - tooltip.AppendFormat(formatFormat, (texture as Texture2DArray).format.ToString()); - tooltip.AppendFormat(depthFormat, (texture as Texture2DArray).depth.ToString()); - } - else if (texture is Texture3D) - { - tooltip.AppendFormat(formatFormat, (texture as Texture3D).format.ToString()); - tooltip.AppendFormat(depthFormat, (texture as Texture3D).depth.ToString()); - } - else if (texture is CubemapArray) - { - tooltip.AppendFormat(formatFormat, (texture as CubemapArray).format.ToString()); - tooltip.AppendFormat("\nCubemap Count: {0}", (texture as CubemapArray).cubemapCount.ToString()); - } - else if (texture is RenderTexture) - { - tooltip.AppendFormat("\nRT Format: {0}", (texture as RenderTexture).graphicsFormat.ToString()); - } - - tooltip.Append("\n\nCtrl or Shift + Click to show preview"); - - m_CurEventDataStrings.texturePropertyTooltips[i] = tooltip.ToString(); } } - // Return true if should repaint - private bool DrawToolbar(FrameDebuggerEvent[] descs) + internal static void RepaintAll() { - bool repaint = false; - - bool isSupported = m_AttachToPlayerState.connectedToTarget != ConnectionTarget.Editor || FrameDebuggerUtility.locallySupported; - - GUILayout.BeginHorizontal(EditorStyles.toolbar); - // enable toggle - EditorGUI.BeginChangeCheck(); - using (new EditorGUI.DisabledScope(!isSupported)) - { - GUILayout.Toggle(FrameDebugger.enabled, styles.recordButton, EditorStyles.toolbarButtonLeft, GUILayout.MinWidth(80)); - } - if (EditorGUI.EndChangeCheck()) - { - ClickEnableFrameDebugger(); - repaint = true; - } - - if (FrameDebugger.IsLocalEnabled()) - { - styles.recordButton.text = L10n.Tr("Disable"); - styles.recordButton.tooltip = L10n.Tr("Disable Frame Debugging"); - } - else - { - styles.recordButton.text = L10n.Tr("Enable"); - styles.recordButton.tooltip = L10n.Tr("Enable Frame Debugging"); - } - - PlayerConnectionGUILayout.ConnectionTargetSelectionDropdown(m_AttachToPlayerState, EditorStyles.toolbarDropDown); - - bool isAnyEnabled = FrameDebugger.enabled; - if (isAnyEnabled && ProfilerDriver.connectedProfiler != FrameDebuggerUtility.GetRemotePlayerGUID()) - { - // Switch from local to remote debugger or vice versa - FrameDebuggerUtility.SetEnabled(false, FrameDebuggerUtility.GetRemotePlayerGUID()); - FrameDebuggerUtility.SetEnabled(true, ProfilerDriver.connectedProfiler); - } - - GUI.enabled = isAnyEnabled; - - // event limit slider - EditorGUI.BeginChangeCheck(); - int newLimit; - using (new EditorGUI.DisabledScope(FrameDebuggerUtility.count <= 1)) - { - newLimit = EditorGUILayout.IntSlider(FrameDebuggerUtility.limit, 1, FrameDebuggerUtility.count, 1, - EditorStyles.toolbarSlider); - } - if (EditorGUI.EndChangeCheck()) - { - ChangeFrameEventLimit(newLimit); - } - GUILayout.Label("of " + FrameDebuggerUtility.count, EditorStyles.toolbarLabel); - // prev/next buttons - using (new EditorGUI.DisabledScope(newLimit <= 1)) - { - if (GUILayout.Button(styles.prevFrame, EditorStyles.toolbarButton)) - { - ChangeFrameEventLimit(newLimit - 1); - } - } - using (new EditorGUI.DisabledScope(newLimit >= FrameDebuggerUtility.count)) - { - if (GUILayout.Button(styles.nextFrame, EditorStyles.toolbarButtonRight)) - { - 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) - { - ChangeFrameEventLimit(FrameDebuggerUtility.count); - } - } - m_PrevEventsLimit = FrameDebuggerUtility.limit; - m_PrevEventsCount = FrameDebuggerUtility.count; - } - - GUILayout.EndHorizontal(); - - return repaint; + foreach (var fd in s_FrameDebuggers) + fd.Repaint(); } - void DrawMeshPreview(Rect previewRect, Rect meshInfoRect, Mesh mesh, int meshSubset) + private void RepaintOnLimitChange() { - m_PreviewUtility.BeginPreview(previewRect, "preBackground"); - MeshPreview.RenderMeshPreview(mesh, m_PreviewUtility, m_Settings, meshSubset); - m_PreviewUtility.EndAndDrawPreview(previewRect); - - // mesh info - string meshName = mesh.name; - if (string.IsNullOrEmpty(meshName)) - meshName = ""; - string info = meshName + " subset " + meshSubset + "\n" + m_CurEventData.vertexCount + " verts, " + m_CurEventData.indexCount + " indices"; - if (m_CurEventData.instanceCount > 1) - info += ", " + m_CurEventData.instanceCount + " instances"; - - EditorGUI.DropShadowLabel(meshInfoRect, info); + m_RepaintFrames = k_NeedToRepaintFrames; + RepaintAllNeededThings(); } - // Draw any mesh associated with the draw event - // Return false if no mesh. - private bool DrawEventMesh() + internal void RepaintAllNeededThings() { - Mesh mesh = m_CurEventData.mesh; - if (mesh == null) - return false; + // indicate that editor needs a redraw (mostly to get offscreen cameras rendered) + EditorApplication.SetSceneRepaintDirty(); - Rect previewRect = GUILayoutUtility.GetRect(10, 10, GUILayout.ExpandHeight(true)); - if (previewRect.width < kMinPreviewSize || previewRect.height < kMinPreviewSize) - return true; - - UnityEngine.Object obj = FrameDebuggerUtility.GetFrameEventObject(m_CurEventData.frameEventIndex); - - // Info display at bottom (and pings object when clicked) - Rect meshInfoRect = previewRect; - meshInfoRect.yMin = meshInfoRect.yMax - EditorGUIUtility.singleLineHeight * 2; - Rect goInfoRect = meshInfoRect; - - meshInfoRect.xMin = meshInfoRect.center.x; - goInfoRect.xMax = goInfoRect.center.x; - var evt = Event.current; - if (evt.type == EventType.MouseDown) - { - if (meshInfoRect.Contains(evt.mousePosition)) - { - EditorGUIUtility.PingObject(m_CurEventData.mesh); - evt.Use(); - } - if (obj != null && goInfoRect.Contains(evt.mousePosition)) - { - EditorGUIUtility.PingObject(obj.GetInstanceID()); - evt.Use(); - } - } - - m_Settings.previewDir = PreviewGUI.Drag2D(m_Settings.previewDir, previewRect); - - if (evt.type == EventType.Repaint) - { - int meshSubset = m_CurEventData.meshSubset; - DrawMeshPreview(previewRect, meshInfoRect, mesh, meshSubset); - if (obj != null) - { - EditorGUI.DropShadowLabel(goInfoRect, obj.name); - } - } - - return true; - } - - private void DrawRenderTargetControls() - { - FrameDebuggerEventData cur = m_CurEventData; - - if (cur.rtWidth <= 0 || cur.rtHeight <= 0) - return; - - var isDepthOnlyRT = GraphicsFormatUtility.IsDepthFormat((GraphicsFormat)cur.rtFormat); - var hasShowableDepth = (cur.rtHasDepthTexture != 0); - var showableRTCount = cur.rtCount; - if (hasShowableDepth) - showableRTCount++; - - GUILayout.BeginHorizontal(EditorStyles.toolbar); - - EditorGUILayout.LabelField("RenderTarget", cur.rtName); - - GUILayout.EndHorizontal(); - - GUILayout.BeginHorizontal(EditorStyles.toolbar); - - // MRT to show - EditorGUI.BeginChangeCheck(); - using (new EditorGUI.DisabledScope(showableRTCount <= 1)) - { - var rtNames = new GUIContent[showableRTCount]; - for (var i = 0; i < cur.rtCount; ++i) - { - rtNames[i] = Styles.mrtLabels[i]; - } - if (hasShowableDepth) - rtNames[cur.rtCount] = Styles.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 >= cur.rtCount) - m_RTIndex = 0; - - m_RTIndex = EditorGUILayout.Popup(m_RTIndex, rtNames, EditorStyles.toolbarPopupLeft, GUILayout.Width(70)); - } - - // color channels - using (new EditorGUI.DisabledScope(isDepthOnlyRT)) - { - GUILayout.Label(Styles.channelHeader, EditorStyles.toolbarLabel); - m_RTChannel = GUILayout.Toolbar(m_RTChannel, Styles.channelLabels, EditorStyles.toolbarButton); - } - - // levels - GUILayout.BeginHorizontal(styles.toolbarLabelSliderGroup); - GUILayout.Label(Styles.levelsHeader); - EditorGUILayout.MinMaxSlider(ref m_RTBlackLevel, ref m_RTWhiteLevel, 0.0f, 1.0f, GUILayout.MaxWidth(200.0f)); - - int rtIndexToSet = m_RTIndex; - if (hasShowableDepth && rtIndexToSet == (showableRTCount - 1)) - rtIndexToSet = -1; - - if (EditorGUI.EndChangeCheck() || rtIndexToSet != m_RTIndexLastSet) - { - Vector4 mask = Vector4.zero; - if (m_RTChannel == 1) - mask.x = 1f; - else if (m_RTChannel == 2) - mask.y = 1f; - else if (m_RTChannel == 3) - mask.z = 1f; - else if (m_RTChannel == 4) - mask.w = 1f; - else - mask = Vector4.one; - - FrameDebuggerUtility.SetRenderTargetDisplayOptions(rtIndexToSet, mask, m_RTBlackLevel, m_RTWhiteLevel); - m_RTIndexLastSet = rtIndexToSet; - RepaintAllNeededThings(); - } - - GUILayout.EndHorizontal(); - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - - string attachmentInfo = ""; - if (cur.rtLoadAction >= 0 || cur.rtStoreAction >= 0 || cur.rtMemoryless != 0 || cur.rtDepthLoadAction != 0 || cur.rtDepthStoreAction != 0) - { - var sl = new List(); - if (cur.rtLoadAction >= 0) - sl.Add(String.Format("LoadAction: {0}", (RenderBufferLoadAction)cur.rtLoadAction)); - if (cur.rtStoreAction >= 0) - sl.Add(String.Format("StoreAction: {0}", (RenderBufferStoreAction)cur.rtStoreAction)); - if (cur.rtDepthLoadAction >= 0) - sl.Add(String.Format("Depth LoadAction: {0}", (RenderBufferLoadAction)cur.rtDepthLoadAction)); - if (cur.rtDepthStoreAction >= 0) - sl.Add(String.Format("Depth StoreAction: {0}", (RenderBufferStoreAction)cur.rtDepthStoreAction)); - if (cur.rtMemoryless != 0) - sl.Add("memoryless"); - - attachmentInfo = String.Format("({0})", String.Join(", ", sl.ToArray())); - } - - GUILayout.Label(string.Format("{0}x{1} {2} {3}", cur.rtWidth, cur.rtHeight, (GraphicsFormat)cur.rtFormat, attachmentInfo)); - - if (cur.rtLoadAction == (int)RenderBufferLoadAction.Clear) - { - bool isDepthFormat = GraphicsFormatUtility.IsDepthFormat((GraphicsFormat)cur.rtFormat); - bool isStencilFormat = GraphicsFormatUtility.IsStencilFormat((GraphicsFormat)cur.rtFormat); - if (!isDepthFormat && !isStencilFormat) - { - GUILayout.Label(string.Format("Clear Color ({0}, {1}, {2}, {3})", cur.rtClearColorR, cur.rtClearColorG, cur.rtClearColorB, cur.rtClearColorA)); - } - else - { - if (isDepthFormat) - GUILayout.Label($"Clear Depth {cur.rtClearDepth}"); - if (isStencilFormat) - GUILayout.Label($"Clear Stencil {GetStencilString((int)cur.rtClearStencil)}"); - } - } - - if (cur.rtDim == (int)UnityEngine.Rendering.TextureDimension.Cube) - GUILayout.Label("Rendering into cubemap"); - } - - void DrawKeywords(string shaderName) - { - if (string.IsNullOrEmpty(m_CurEventData.shaderKeywords)) - return; - EditorGUILayout.LabelField("Keywords", m_CurEventData.shaderKeywords); - if (GUI.Button(GUILayoutUtility.GetLastRect(), Styles.copyToClipboardTooltip, GUI.skin.label)) - EditorGUIUtility.systemCopyBuffer = shaderName + Environment.NewLine + m_CurEventData.shaderKeywords; - } - - private void DrawEventDrawCallInfo() - { - // shader, pass & keyword information - EditorGUILayout.LabelField("Shader", m_CurEventDataStrings.shader); - - if (GUI.Button(GUILayoutUtility.GetLastRect(), Styles.selectShaderTooltip, GUI.skin.label)) - { - EditorGUIUtility.PingObject(m_CurEventData.shaderInstanceID); - Event.current.Use(); - } - - EditorGUILayout.LabelField("Pass", m_CurEventDataStrings.pass); - DrawKeywords(m_CurEventDataStrings.shader); - - DrawStates(); - - // Show why this draw call can't batch with the previous one. - if (m_CurEventData.batchBreakCause > 1) // Valid batch break cause enum value on the C++ side starts at 2. - { - GUILayout.Space(10.0f); - GUILayout.Label(Styles.causeOfNewDrawCallLabel, EditorStyles.boldLabel); - GUILayout.Label(styles.batchBreakCauses[m_CurEventData.batchBreakCause], EditorStyles.wordWrappedLabel); - } - - GUILayout.Space(15.0f); - - // preview / properties - EditorGUILayout.BeginHorizontal(); - m_AdditionalInfo = (ShowAdditionalInfo)GUILayout.Toolbar((int)m_AdditionalInfo, m_AdditionalInfoGuiContents, "LargeButton", GUI.ToolbarButtonSize.FitToContents); - GUILayout.FlexibleSpace(); - EditorGUILayout.EndHorizontal(); - switch (m_AdditionalInfo) - { - case ShowAdditionalInfo.Preview: - // Show mesh preview if possible - if (!DrawEventMesh()) - { - // If no mesh preview, then show vertex/index count at least - EditorGUILayout.LabelField("Vertices", m_CurEventData.vertexCount.ToString()); - EditorGUILayout.LabelField("Indices", m_CurEventData.indexCount.ToString()); - - if (m_CurEventData.meshInstanceIDs != null) - { - EditorGUILayout.LabelField("Meshes", m_CurEventData.meshInstanceIDs.Length.ToString()); - ++EditorGUI.indentLevel; - foreach (var id in m_CurEventData.meshInstanceIDs) - { - var mesh = EditorUtility.InstanceIDToObject(id) as Mesh; - if (mesh != null) - EditorGUILayout.LabelField(mesh.name); - } - --EditorGUI.indentLevel; - } - } - break; - case ShowAdditionalInfo.ShaderProperties: - DrawShaderProperties(m_CurEventData.shaderProperties); - break; - } - } - - void DrawEventClearInfo(FrameEventType type) - { - if (((int)type & 1) != 0) - EditorGUILayout.LabelField("Color", $"{m_CurEventData.clearColorR:F3},{m_CurEventData.clearColorG:F3},{m_CurEventData.clearColorB:F3},{m_CurEventData.clearColorA:F3}"); - if (((int)type & 2) != 0) - EditorGUILayout.LabelField("Depth", m_CurEventData.clearDepth.ToString("f3")); - if (((int)type & 4) != 0) - EditorGUILayout.LabelField("Stencil", GetStencilString((int)m_CurEventData.clearStencil)); - } - - private void DrawEventComputeDispatchInfo() - { - // compute shader & kernel information - EditorGUILayout.LabelField("Compute Shader", m_CurEventData.csName); - if (GUI.Button(GUILayoutUtility.GetLastRect(), GUIContent.none, GUI.skin.label)) - { - EditorGUIUtility.PingObject(m_CurEventData.csInstanceID); - Event.current.Use(); - } - - EditorGUILayout.LabelField("Kernel", m_CurEventData.csKernel); - DrawKeywords(m_CurEventData.csKernel); - - // dispatch size - string threadGroupsText; - if (m_CurEventData.csThreadGroupsX != 0 || m_CurEventData.csThreadGroupsY != 0 || m_CurEventData.csThreadGroupsZ != 0) - threadGroupsText = $"{m_CurEventData.csThreadGroupsX}x{m_CurEventData.csThreadGroupsY}x{m_CurEventData.csThreadGroupsZ}"; - else - threadGroupsText = "indirect dispatch"; - - // thread group size - if (m_CurEventData.csGroupSizeX > 0) - threadGroupsText += $", group size {m_CurEventData.csGroupSizeX}x{m_CurEventData.csGroupSizeY}x{m_CurEventData.csGroupSizeZ}"; - - EditorGUILayout.LabelField("Thread Groups", threadGroupsText); - - // properties - DrawShaderProperties(m_CurEventData.shaderProperties); - } - - private void DrawEventRayTracingDispatchInfo() - { - // ray tracing shader information - EditorGUILayout.LabelField("Ray Tracing Shader", m_CurEventData.rtsName); - if (GUI.Button(GUILayoutUtility.GetLastRect(), Styles.selectShaderTooltip, GUI.skin.label)) - { - EditorGUIUtility.PingObject(m_CurEventData.rtsInstanceID); - Event.current.Use(); - } - EditorGUILayout.LabelField("Ray Generation Shader", m_CurEventData.rtsRayGenShaderName); - EditorGUILayout.LabelField("SubShader Pass", m_CurEventData.rtsShaderPassName); - - // max. recursion depth - string maxRecursionDepthText = string.Format("{0}", m_CurEventData.rtsMaxRecursionDepth); - EditorGUILayout.LabelField("Max. Recursion Depth", maxRecursionDepthText); - - // dispatch size - string dispatchSizeText = string.Format("{0} x {1} x {2}", m_CurEventData.rtsWidth, m_CurEventData.rtsHeight, m_CurEventData.rtsDepth); - EditorGUILayout.LabelField("Dispatch Size", dispatchSizeText); - - if (m_CurEventData.rtsAccelerationStructureName.Length > 0) - EditorGUILayout.LabelField("Acceleration Structure", m_CurEventData.rtsAccelerationStructureName); - - EditorGUILayout.LabelField("Miss Shader Count", string.Format("{0}", m_CurEventData.rtsMissShaderCount)); - EditorGUILayout.LabelField("Callable Shader Count", string.Format("{0}", m_CurEventData.rtsCallableShaderCount)); - - // properties - DrawShaderProperties(m_CurEventData.shaderProperties); - } - - private void DrawCurrentEvent(Rect rect, FrameDebuggerEvent[] descs) - { - int curEventIndex = FrameDebuggerUtility.limit - 1; - if (curEventIndex < 0 || curEventIndex >= descs.Length) - return; - - if (m_CurEventData == null) - m_CurEventData = new FrameDebuggerEventData(); - - GUILayout.BeginArea(rect); - - uint eventDataHash = FrameDebuggerUtility.eventDataHash; - bool isFrameEventDataValid = curEventIndex == m_CurEventData.frameEventIndex; - - if (eventDataHash != 0 && m_CurEventDataHash != eventDataHash) - { - isFrameEventDataValid = FrameDebuggerUtility.GetFrameEventData(curEventIndex, m_CurEventData); - m_CurEventDataHash = eventDataHash; - BuildCurEventDataStrings(); - } - - // event type and draw call info - FrameDebuggerEvent cur = descs[curEventIndex]; - - // render target - if (isFrameEventDataValid && cur.type != FrameEventType.RayTracingDispatch) - DrawRenderTargetControls(); - - // event type - GUILayout.BeginHorizontal(); - GUILayout.Label($"Event #{curEventIndex + 1}: {s_FrameEventTypeNames[(int) cur.type]}", EditorStyles.boldLabel); - if (isFrameEventDataValid) - { - if (m_CurEventData.instanceCount > 1) - GUILayout.Label($" ({m_CurEventDataStrings.drawCallCount} draw calls, {m_CurEventDataStrings.drawInstancedCallCount} instances)"); - else if (m_CurEventData.drawCallCount > 1) - GUILayout.Label($" ({m_CurEventDataStrings.drawCallCount} draw calls)"); - GUILayout.FlexibleSpace(); - } - GUILayout.EndHorizontal(); - - if (FrameDebugger.IsRemoteEnabled() && FrameDebuggerUtility.receivingRemoteFrameEventData) - { - GUILayout.Label("Receiving frame event data..."); - } - else if (isFrameEventDataValid) // Is this a draw call? - { - if (m_CurEventData.vertexCount > 0 || m_CurEventData.indexCount > 0 || cur.type == FrameEventType.DrawProceduralIndirect || cur.type == FrameEventType.DrawProceduralIndexedIndirect) - { - // a draw call, display extra info - DrawEventDrawCallInfo(); - } - else if (cur.type == FrameEventType.ComputeDispatch) - { - // a compute dispatch, display extra info - DrawEventComputeDispatchInfo(); - } - else if (cur.type == FrameEventType.RayTracingDispatch) - { - // a ray tracing dispatch - DrawEventRayTracingDispatchInfo(); - } - else if (cur.type >= FrameEventType.ClearNone && cur.type <= FrameEventType.ClearAll) - { - // a render target clear - DrawEventClearInfo(cur.type); - } - } - - GUILayout.EndArea(); - } - - void DrawShaderPropertyFlags(int flags) - { - // lowest bits of flags are set for each shader stage that property is used in; matching ShaderType C++ enum - var str = string.Empty; - if ((flags & (1 << 1)) != 0) - str += 'v'; - if ((flags & (1 << 2)) != 0) - str += 'f'; - if ((flags & (1 << 3)) != 0) - str += 'g'; - if ((flags & (1 << 4)) != 0) - str += 'h'; - if ((flags & (1 << 5)) != 0) - str += 'd'; - - GUILayout.Label(str, EditorStyles.miniLabel, GUILayout.MinWidth(20.0f)); - } - - void ShaderPropertyCopyValueMenu(Rect valueRect, System.Object value) - { - var e = Event.current; - if (e.type == EventType.ContextClick && valueRect.Contains(e.mousePosition)) - { - e.Use(); - var menu = new GenericMenu(); - menu.AddItem(EditorGUIUtility.TrTextContent("Copy value"), false, delegate - { - var str = string.Empty; - if (value is Vector4) - str = ((Vector4)value).ToString(kFloatDetailedFormat); - else if (value is Matrix4x4) - str = ((Matrix4x4)value).ToString(kFloatDetailedFormat); - else if (value is System.Single) - str = ((System.Single)value).ToString(kFloatDetailedFormat); - else - str = value.ToString(); - EditorGUIUtility.systemCopyBuffer = str; - }); - menu.ShowAsContext(); - } - } - - private void OnGUIShaderPropFloats(ShaderFloatInfo[] floats, int startIndex, int numValues) - { - GUILayout.BeginHorizontal(); - GUILayout.Space(kShaderPropertiesIndention); - - ShaderFloatInfo t = floats[startIndex]; - - if (numValues == 1) - { - GUILayout.Label(t.name, EditorStyles.miniLabel, GUILayout.MinWidth(kNameFieldWidth)); - DrawShaderPropertyFlags(t.flags); - GUILayout.Label(t.value.ToString(kFloatFormat, CultureInfo.InvariantCulture.NumberFormat), EditorStyles.miniLabel, GUILayout.MinWidth(kValueFieldWidth)); - ShaderPropertyCopyValueMenu(GUILayoutUtility.GetLastRect(), t.value); - } - else - { - string arrayName = String.Format("{0} [{1}]", t.name, numValues); - GUILayout.Label(arrayName, EditorStyles.miniLabel, GUILayout.MinWidth(kNameFieldWidth)); - DrawShaderPropertyFlags(t.flags); - - Rect buttonRect = GUILayoutUtility.GetRect(Styles.arrayValuePopupButton, GUI.skin.button, GUILayout.MinWidth(kValueFieldWidth)); - buttonRect.width = kArrayValuePopupBtnWidth; - if (GUI.Button(buttonRect, Styles.arrayValuePopupButton)) - { - ArrayValuePopup.GetValueStringDelegate getValueString = - (int index, bool highPrecision) => floats[index].value.ToString(highPrecision ? kFloatDetailedFormat : kFloatFormat, CultureInfo.InvariantCulture.NumberFormat); - - PopupWindowWithoutFocus.Show( - buttonRect, - new ArrayValuePopup(startIndex, numValues, 1, 100.0f, getValueString), - new[] { PopupLocation.Left, PopupLocation.Below, PopupLocation.Right }); - } - } - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - } - - private void OnGUIShaderPropInts(ShaderIntInfo[] ints, int startIndex, int numValues) - { - GUILayout.BeginHorizontal(); - GUILayout.Space(kShaderPropertiesIndention); - - ShaderIntInfo t = ints[startIndex]; - - if (numValues == 1) - { - GUILayout.Label(t.name, EditorStyles.miniLabel, GUILayout.MinWidth(kNameFieldWidth)); - DrawShaderPropertyFlags(t.flags); - GUILayout.Label(t.value.ToString(kIntFormat, CultureInfo.InvariantCulture.NumberFormat), EditorStyles.miniLabel, GUILayout.MinWidth(kValueFieldWidth)); - ShaderPropertyCopyValueMenu(GUILayoutUtility.GetLastRect(), t.value); - } - else - { - string arrayName = String.Format("{0} [{1}]", t.name, numValues); - GUILayout.Label(arrayName, EditorStyles.miniLabel, GUILayout.MinWidth(kNameFieldWidth)); - DrawShaderPropertyFlags(t.flags); - - Rect buttonRect = GUILayoutUtility.GetRect(Styles.arrayValuePopupButton, GUI.skin.button, GUILayout.MinWidth(kValueFieldWidth)); - buttonRect.width = kArrayValuePopupBtnWidth; - if (GUI.Button(buttonRect, Styles.arrayValuePopupButton)) - { - ArrayValuePopup.GetValueStringDelegate getValueString = - (int index, bool highPrecision) => ints[index].value.ToString(kIntFormat, CultureInfo.InvariantCulture.NumberFormat); - - PopupWindowWithoutFocus.Show( - buttonRect, - new ArrayValuePopup(startIndex, numValues, 1, 100.0f, getValueString), - new[] { PopupLocation.Left, PopupLocation.Below, PopupLocation.Right }); - } - } - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - } - - private void OnGUIShaderPropVectors(ShaderVectorInfo[] vectors, int startIndex, int numValues) - { - GUILayout.BeginHorizontal(); - GUILayout.Space(kShaderPropertiesIndention); - - ShaderVectorInfo t = vectors[startIndex]; - - if (numValues == 1) - { - GUILayout.Label(t.name, EditorStyles.miniLabel, GUILayout.MinWidth(kNameFieldWidth)); - DrawShaderPropertyFlags(t.flags); - GUILayout.Label(t.value.ToString(kFloatDetailedFormat), EditorStyles.miniLabel, GUILayout.MinWidth(kValueFieldWidth)); - ShaderPropertyCopyValueMenu(GUILayoutUtility.GetLastRect(), t.value); - } - else - { - string arrayName = String.Format("{0} [{1}]", t.name, numValues); - GUILayout.Label(arrayName, EditorStyles.miniLabel, GUILayout.MinWidth(kNameFieldWidth)); - DrawShaderPropertyFlags(t.flags); - - Rect buttonRect = GUILayoutUtility.GetRect(Styles.arrayValuePopupButton, GUI.skin.button, GUILayout.MinWidth(kValueFieldWidth)); - buttonRect.width = kArrayValuePopupBtnWidth; - if (GUI.Button(buttonRect, Styles.arrayValuePopupButton)) - { - ArrayValuePopup.GetValueStringDelegate getValueString = - (int index, bool highPrecision) => vectors[index].value.ToString(highPrecision ? kFloatDetailedFormat : kFloatFormat); - - PopupWindowWithoutFocus.Show( - buttonRect, - new ArrayValuePopup(startIndex, numValues, 1, 200.0f, getValueString), - new[] { PopupLocation.Left, PopupLocation.Below, PopupLocation.Right }); - } - } - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - } - - private void OnGUIShaderPropMatrices(ShaderMatrixInfo[] matrices, int startIndex, int numValues) - { - GUILayout.BeginHorizontal(); - GUILayout.Space(kShaderPropertiesIndention); - - ShaderMatrixInfo t = matrices[startIndex]; - - if (numValues == 1) - { - GUILayout.Label(t.name, EditorStyles.miniLabel, GUILayout.MinWidth(kNameFieldWidth)); - DrawShaderPropertyFlags(t.flags); - GUILayout.Label(t.value.ToString(kFloatFormat), EditorStyles.miniLabel, GUILayout.MinWidth(kValueFieldWidth)); - ShaderPropertyCopyValueMenu(GUILayoutUtility.GetLastRect(), t.value); - } - else - { - string arrayName = String.Format("{0} [{1}]", t.name, numValues); - GUILayout.Label(arrayName, EditorStyles.miniLabel, GUILayout.MinWidth(kNameFieldWidth)); - DrawShaderPropertyFlags(t.flags); - - Rect buttonRect = GUILayoutUtility.GetRect(Styles.arrayValuePopupButton, GUI.skin.button, GUILayout.MinWidth(kValueFieldWidth)); - buttonRect.width = kArrayValuePopupBtnWidth; - if (GUI.Button(buttonRect, Styles.arrayValuePopupButton)) - { - ArrayValuePopup.GetValueStringDelegate getValueString = - (int index, bool highPrecision) => '\n' + matrices[index].value.ToString(highPrecision ? kFloatDetailedFormat : kFloatFormat); - - PopupWindowWithoutFocus.Show( - buttonRect, - new ArrayValuePopup(startIndex, numValues, 5, 200.0f, getValueString), - new[] { PopupLocation.Left, PopupLocation.Below, PopupLocation.Right }); - } - } - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - } - - private void OnGUIShaderPropBuffer(ShaderBufferInfo t) - { - GUILayout.BeginHorizontal(); - GUILayout.Space(kShaderPropertiesIndention); - - GUILayout.Label(t.name, EditorStyles.miniLabel, GUILayout.MinWidth(kNameFieldWidth)); - DrawShaderPropertyFlags(t.flags); - GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.miniLabel, GUILayout.MinWidth(kValueFieldWidth)); - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - } - - private void OnGUIShaderPropConstantBuffer(ShaderConstantBufferInfo t) - { - GUILayout.BeginHorizontal(); - GUILayout.Space(kShaderPropertiesIndention); - - GUILayout.Label(t.name, EditorStyles.miniLabel, GUILayout.MinWidth(kNameFieldWidth)); - DrawShaderPropertyFlags(t.flags); - GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.miniLabel, GUILayout.MinWidth(kValueFieldWidth)); - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - } - - private void OnGUIShaderPropTexture(int idx, ShaderTextureInfo t) - { - GUILayout.BeginHorizontal(); - GUILayout.Space(kShaderPropertiesIndention); - - GUILayout.Label(t.name, EditorStyles.miniLabel, GUILayout.MinWidth(kNameFieldWidth)); - DrawShaderPropertyFlags(t.flags); - - Rect valueRect = GUILayoutUtility.GetRect(GUIContent.none, GUI.skin.label, GUILayout.MinWidth(30)); - - // display texture similar to ObjectField, just without much of interaction or styling: - Event evt = Event.current; - Rect previewRect = valueRect; - previewRect.width = previewRect.height; - - // Tooltip - if (t.value != null && previewRect.Contains(evt.mousePosition)) - GUI.Label(previewRect, GUIContent.Temp(string.Empty, m_CurEventDataStrings.texturePropertyTooltips[idx])); - - if (t.value != null) - { - // 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 - Texture previewTexture = t.value; - if (previewTexture.dimension != TextureDimension.Tex2D) - previewTexture = AssetPreview.GetMiniThumbnail(previewTexture); - EditorGUI.DrawPreviewTexture(previewRect, previewTexture); - - // Adding the path as a tooltip - m_TextureGUIContent.text = t.value.name; - m_TextureGUIContent.tooltip = AssetDatabase.GetAssetPath(t.value); - } - else - { - m_TextureGUIContent.text = t.textureName; - m_TextureGUIContent.tooltip = ""; - } - - GUILayout.Label(m_TextureGUIContent, GUILayout.ExpandWidth(true)); - - if (evt.type == EventType.MouseDown) - { - // ping or show preview of texture when clicked - if (valueRect.Contains(evt.mousePosition)) - { - EditorGUI.PingObjectOrShowPreviewOnClick(t.value, valueRect); - evt.Use(); - } - } - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - } - - void DrawShaderProperties(ShaderProperties props) - { - m_ScrollViewShaderProps = GUILayout.BeginScrollView(m_ScrollViewShaderProps); - - if (props.textures.Length > 0) - { - GUILayout.Label("Textures", EditorStyles.boldLabel); - - for (int i = 0; i < props.textures.Length; ++i) - { - OnGUIShaderPropTexture(i, props.textures[i]); - } - } - - if (props.floats.Length > 0) - { - GUILayout.Label("Floats", EditorStyles.boldLabel); - - for (int i = 0; i < props.floats.Length;) - { - int arraySize = (props.floats[i].flags >> kShaderTypeBits) & kArraySizeBitMask; - OnGUIShaderPropFloats(props.floats, i, arraySize); - i += arraySize; - } - } - - if (props.ints.Length > 0) - { - GUILayout.Label("Integers", EditorStyles.boldLabel); - - for (int i = 0; i < props.ints.Length;) - { - int arraySize = (props.ints[i].flags >> kShaderTypeBits) & kArraySizeBitMask; - OnGUIShaderPropInts(props.ints, i, arraySize); - i += arraySize; - } - } - - if (props.vectors.Length > 0) - { - GUILayout.Label("Vectors", EditorStyles.boldLabel); - - for (int i = 0; i < props.vectors.Length;) - { - int arraySize = (props.vectors[i].flags >> kShaderTypeBits) & kArraySizeBitMask; - OnGUIShaderPropVectors(props.vectors, i, arraySize); - i += arraySize; - } - } - - if (props.matrices.Length > 0) - { - GUILayout.Label("Matrices", EditorStyles.boldLabel); - - for (int i = 0; i < props.matrices.Length;) - { - int arraySize = (props.matrices[i].flags >> kShaderTypeBits) & kArraySizeBitMask; - OnGUIShaderPropMatrices(props.matrices, i, arraySize); - i += arraySize; - } - } - - if (props.buffers.Length > 0) - { - GUILayout.Label("Buffers", EditorStyles.boldLabel); - - foreach (var d in props.buffers) - { - OnGUIShaderPropBuffer(d); - } - } - - if (props.cbuffers.Length > 0) - { - GUILayout.Label("Constant Buffers", EditorStyles.boldLabel); - - foreach (var d in props.cbuffers) - { - OnGUIShaderPropConstantBuffer(d); - } - } - - GUILayout.EndScrollView(); - } - - void DrawStates() - { - FrameDebuggerBlendState blendState = m_CurEventData.blendState; - FrameDebuggerRasterState rasterState = m_CurEventData.rasterState; - FrameDebuggerDepthState depthState = m_CurEventData.depthState; - - // blend state - string blendText = string.Format("{0} {1}", blendState.srcBlend, blendState.dstBlend); - // only add alpha blend mode if different from RGB one - if (blendState.srcBlendAlpha != blendState.srcBlend || blendState.dstBlendAlpha != blendState.dstBlend) - blendText += string.Format(", {0} {1}", blendState.srcBlendAlpha, blendState.dstBlendAlpha); - - EditorGUILayout.LabelField("Blend", blendText); - - // only add blend op if non-Add - if (blendState.blendOp != BlendOp.Add || blendState.blendOpAlpha != BlendOp.Add) - { - string blendOpText; - if (blendState.blendOp == blendState.blendOpAlpha) - blendOpText = blendState.blendOp.ToString(); - else - blendOpText = string.Format("{0}, {1}", blendState.blendOp, blendState.blendOpAlpha); - - EditorGUILayout.LabelField("BlendOp", blendOpText); - } - - // only add color mask if non-RGBA - if (blendState.writeMask != 15) - { - string colorMaskText = ""; - if (blendState.writeMask == 0) - colorMaskText += '0'; - else - { - if ((blendState.writeMask & 8) != 0) - colorMaskText += 'R'; - if ((blendState.writeMask & 4) != 0) - colorMaskText += 'G'; - if ((blendState.writeMask & 2) != 0) - colorMaskText += 'B'; - if ((blendState.writeMask & 1) != 0) - colorMaskText += 'A'; - } - - EditorGUILayout.LabelField("ColorMask", colorMaskText); - } - - // depth state - EditorGUILayout.LabelField("ZClip", rasterState.depthClip.ToString()); - EditorGUILayout.LabelField("ZTest", depthState.depthFunc.ToString()); - EditorGUILayout.LabelField("ZWrite", depthState.depthWrite == 0 ? "Off" : "On"); - EditorGUILayout.LabelField("Cull", rasterState.cullMode.ToString()); - EditorGUILayout.LabelField("Conservative", rasterState.conservative.ToString()); - - // only add depth offset if non zero - if (rasterState.slopeScaledDepthBias != 0 || rasterState.depthBias != 0) - { - string offsetText = UnityString.Format("{0}, {1}", rasterState.slopeScaledDepthBias, rasterState.depthBias); - EditorGUILayout.LabelField("Offset", offsetText); - } - - // Stencil state - if (m_CurEventData.stencilState.stencilEnable) - { - EditorGUILayout.LabelField("Stencil Ref", m_CurEventDataStrings.stencilRef); - - if (m_CurEventData.stencilState.readMask != 255) - EditorGUILayout.LabelField("Stencil ReadMask", m_CurEventDataStrings.stencilReadMask); - - if (m_CurEventData.stencilState.writeMask != 255) - EditorGUILayout.LabelField("Stencil WriteMask", m_CurEventDataStrings.stencilWriteMask); - - EditorGUILayout.LabelField("Stencil Comp", m_CurEventDataStrings.stencilComp); - EditorGUILayout.LabelField("Stencil Pass", m_CurEventDataStrings.stencilPass); - EditorGUILayout.LabelField("Stencil Fail", m_CurEventDataStrings.stencilFail); - EditorGUILayout.LabelField("Stencil ZFail", m_CurEventDataStrings.stencilZFail); - } - } - - static string GetStencilString(int stencil) - { - return $"{stencil} (0b{Convert.ToString(stencil, 2)})"; - } - - internal void OnGUI() - { - FrameDebuggerEvent[] descs = FrameDebuggerUtility.GetFrameEvents(); - if (m_TreeViewState == null) - m_TreeViewState = new TreeViewState(); - if (m_Tree == null) - { - m_Tree = new FrameDebuggerTreeView(descs, m_TreeViewState, this, new Rect()); - m_FrameEventsHash = FrameDebuggerUtility.eventsHash; - m_Tree.m_DataSource.SetExpanded(m_Tree.m_DataSource.root, true); - - // Expand root's children only - foreach (var treeViewItem in m_Tree.m_DataSource.root.children) - { - if (treeViewItem != null) - m_Tree.m_DataSource.SetExpanded(treeViewItem, true); - } - } - - // captured frame event contents have changed, rebuild the tree data - if (FrameDebuggerUtility.eventsHash != m_FrameEventsHash) - { - m_Tree.m_DataSource.SetEvents(descs); - m_FrameEventsHash = FrameDebuggerUtility.eventsHash; - } - - - int oldLimit = FrameDebuggerUtility.limit; - bool repaint = DrawToolbar(descs); - - if (!FrameDebugger.enabled && m_AttachToPlayerState.connectedToTarget == ConnectionTarget.Editor) - { - GUI.enabled = true; - - if (!FrameDebuggerUtility.locallySupported) - { - var supportMessage = "The Frame Debugger requires multi-threaded renderer. If this error persists, try starting the Editor with -force-gfx-mt command line argument."; - - if (Application.platform == RuntimePlatform.LinuxEditor && SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLCore) - supportMessage += " On Linux, the editor does not support a multi-threaded renderer when using OpenGL."; - - EditorGUILayout.HelpBox(supportMessage, MessageType.Warning, true); - } - - // info box - EditorGUILayout.HelpBox("Frame Debugger lets you step through draw calls and see how exactly frame is rendered. Click Enable!", MessageType.Info, true); - } - else - { - float toolbarHeight = EditorStyles.toolbar.fixedHeight; - - var dragRect = new Rect(m_ListWidth, toolbarHeight, kResizerWidth, position.height - toolbarHeight); - dragRect = EditorGUIUtility.HandleHorizontalSplitter(dragRect, position.width, kMinListWidth, kMinDetailsWidth); - m_ListWidth = dragRect.x; - - var listRect = new Rect( - 0, - toolbarHeight, - m_ListWidth, - position.height - toolbarHeight); - var currentEventRect = new Rect( - m_ListWidth + kDetailsMargin, - toolbarHeight + kDetailsMargin, - position.width - m_ListWidth - kDetailsMargin * 2, - position.height - toolbarHeight - kDetailsMargin * 2); - - - DrawEventsTree(listRect); - EditorGUIUtility.DrawHorizontalSplitter(dragRect); - DrawCurrentEvent(currentEventRect, descs); - } - - if (repaint || oldLimit != FrameDebuggerUtility.limit) - RepaintOnLimitChange(); - - if (m_RepaintFrames > 0) - { - m_Tree.SelectFrameEventIndex(FrameDebuggerUtility.limit); - RepaintAllNeededThings(); - --m_RepaintFrames; - } - } - - private void RepaintOnLimitChange() - { - m_RepaintFrames = kNeedToRepaintFrames; - RepaintAllNeededThings(); - } - - private 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(); } - - void DrawEventsTree(Rect rect) - { - m_Tree.OnGUI(rect); - } - - internal class Styles - { - public GUIStyle rowText = "OL Label"; - public GUIStyle rowTextRight = "OL RightLabel"; - - public GUIStyle miniLabel = new GUIStyle(EditorStyles.miniLabel) - { - margin = new RectOffset(4, 4, 1, 1) - }; - - public GUIStyle toolbarLabelSliderGroup = new GUIStyle(EditorStyles.toolbarButton) - { - margin = new RectOffset(4, 4, 0, 0), - padding = new RectOffset(4, 4, 0, 0) - }; - - public GUIContent recordButton = new GUIContent(EditorGUIUtility.TrTextContent("Record", "Record profiling information")); - public GUIContent prevFrame = new GUIContent(EditorGUIUtility.TrIconContent("Profiler.PrevFrame", "Go back one frame")); - public GUIContent nextFrame = new GUIContent(EditorGUIUtility.TrIconContent("Profiler.NextFrame", "Go one frame forwards")); - - public GUIContent[] headerContent; - public readonly string[] batchBreakCauses; - - public static readonly string[] s_ColumnNames = new[] { "#", "Type", "Vertices", "Indices" }; - public static readonly GUIContent[] mrtLabels = new[] - { - EditorGUIUtility.TrTextContent("RT 0", "Show render target #0"), - EditorGUIUtility.TrTextContent("RT 1", "Show render target #1"), - EditorGUIUtility.TrTextContent("RT 2", "Show render target #2"), - EditorGUIUtility.TrTextContent("RT 3", "Show render target #3"), - EditorGUIUtility.TrTextContent("RT 4", "Show render target #4"), - EditorGUIUtility.TrTextContent("RT 5", "Show render target #5"), - EditorGUIUtility.TrTextContent("RT 6", "Show render target #6"), - EditorGUIUtility.TrTextContent("RT 7", "Show render target #7") - }; - public static readonly GUIContent depthLabel = EditorGUIUtility.TrTextContent("Depth", "Show depth buffer"); - public static readonly GUIContent[] channelLabels = new[] - { - EditorGUIUtility.TrTextContent("All", "Show all (RGB) color channels"), - EditorGUIUtility.TrTextContent("R", "Show red channel only"), - EditorGUIUtility.TrTextContent("G", "Show green channel only"), - EditorGUIUtility.TrTextContent("B", "Show blue channel only"), - EditorGUIUtility.TrTextContent("A", "Show alpha channel only") - }; - public static readonly GUIContent channelHeader = EditorGUIUtility.TrTextContent("Channels", "Which render target color channels to show"); - public static readonly GUIContent levelsHeader = EditorGUIUtility.TrTextContent("Levels", "Render target display black/white intensity levels"); - public static readonly GUIContent causeOfNewDrawCallLabel = EditorGUIUtility.TrTextContent("Why this draw call can't be batched with the previous one"); - public static readonly GUIContent selectShaderTooltip = EditorGUIUtility.TrTextContent("", "Click to select shader"); - public static readonly GUIContent copyToClipboardTooltip = EditorGUIUtility.TrTextContent("", "Click to copy shader and keywords text to clipboard."); - public static readonly GUIContent arrayValuePopupButton = EditorGUIUtility.TrTextContent("..."); - - public Styles() - { - recordButton.text = "Enable"; - recordButton.tooltip = "Enable Frame Debugging"; - prevFrame.tooltip = "Previous event"; - nextFrame.tooltip = "Next event"; - headerContent = new GUIContent[s_ColumnNames.Length]; - for (int i = 0; i < headerContent.Length; i++) - headerContent[i] = EditorGUIUtility.TextContent(s_ColumnNames[i]); - - batchBreakCauses = FrameDebuggerUtility.GetBatchBreakCauseStrings(); - } - } - - private static Styles ms_Styles; - public static Styles styles - { - get { return ms_Styles ?? (ms_Styles = new Styles()); } - } - - private class ArrayValuePopup : PopupWindowContent - { - 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 = String.Format("[{0}]\t{1}", i, 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 += String.Format("[{0}]\t{1}\n", i, GetValueString(m_StartIndex + i, true)); - } - - var menu = new GenericMenu(); - menu.AddItem(EditorGUIUtility.TrTextContent("Copy value"), false, delegate - { - EditorGUIUtility.systemCopyBuffer = allText; - }); - menu.ShowAsContext(); - } - } - } } } diff --git a/Editor/Mono/PerformanceTools/FrameDebuggerData.cs b/Editor/Mono/PerformanceTools/FrameDebuggerData.cs new file mode 100644 index 0000000000..553614df59 --- /dev/null +++ b/Editor/Mono/PerformanceTools/FrameDebuggerData.cs @@ -0,0 +1,312 @@ +// 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.Rendering; +using System.Runtime.InteropServices; + +namespace UnityEditorInternal +{ + // match enum FrameEventType on C++ side! + // match kFrameEventTypeNames names array! + internal enum FrameEventType + { + // ReSharper disable InconsistentNaming + ClearNone = 0, + ClearColor, + ClearDepth, + ClearColorDepth, + ClearStencil, + ClearColorStencil, + ClearDepthStencil, + ClearAll, + SetRenderTarget, + ResolveRT, + ResolveDepth, + GrabIntoRT, + StaticBatch, + DynamicBatch, + Mesh, + DynamicGeometry, + GLDraw, + SkinOnGPU, + DrawProcedural, + DrawProceduralIndirect, + DrawProceduralIndexed, + DrawProceduralIndexedIndirect, + ComputeDispatch, + RayTracingDispatch, + PluginEvent, + InstancedMesh, + BeginSubpass, + SRPBatch, + HierarchyLevelBreak, + HybridBatch + // ReSharper restore InconsistentNaming + } + + internal enum ShowAdditionalInfo + { + ShaderProperties = 0, + Preview = 1, + } + + // Match C++ ScriptingShaderFloatInfo memory layout! + [StructLayout(LayoutKind.Sequential)] + internal struct ShaderFloatInfo : IComparable + { + public string name; + public int flags; + public float value; + + public int CompareTo(ShaderFloatInfo other) + { + return string.Compare(name, other.name); + } + } + + // Match C++ ScriptingShaderIntInfo memory layout! + [StructLayout(LayoutKind.Sequential)] + internal struct ShaderIntInfo : IComparable + { + public string name; + public int flags; + public int value; + + public int CompareTo(ShaderIntInfo other) + { + return string.Compare(name, other.name); + } + } + + // Match C++ ScriptingShaderVectorInfo memory layout! + [StructLayout(LayoutKind.Sequential)] + internal struct ShaderVectorInfo : IComparable + { + public string name; + public int flags; + public Vector4 value; + + public int CompareTo(ShaderVectorInfo other) + { + return string.Compare(name, other.name); + } + } + + // Match C++ ScriptingShaderMatrixInfo memory layout! + [StructLayout(LayoutKind.Sequential)] + internal struct ShaderMatrixInfo : IComparable + { + public string name; + public int flags; + public Matrix4x4 value; + + public int CompareTo(ShaderMatrixInfo other) + { + return string.Compare(name, other.name); + } + } + + // Match C++ ScriptingShaderTextureInfo memory layout! + [StructLayout(LayoutKind.Sequential)] + internal struct ShaderTextureInfo : IComparable + { + public string name; + public int flags; + public string textureName; + public Texture value; + + public int CompareTo(ShaderTextureInfo other) + { + return string.Compare(name, other.name); + } + } + + // Match C++ ScriptingShaderBufferInfo memory layout! + [StructLayout(LayoutKind.Sequential)] + internal struct ShaderBufferInfo : IComparable + { + public string name; + public int flags; + + public int CompareTo(ShaderBufferInfo other) + { + return string.Compare(name, other.name); + } + } + + // Match C++ ScriptingShaderBufferInfo memory layout! + [StructLayout(LayoutKind.Sequential)] + internal struct ShaderConstantBufferInfo : IComparable + { + public string name; + public int flags; + + public int CompareTo(ShaderConstantBufferInfo other) + { + return string.Compare(name, other.name); + } + } + + // Match C++ ScriptingShaderProperties memory layout! + [StructLayout(LayoutKind.Sequential)] + internal struct ShaderProperties + { + public ShaderFloatInfo[] floats; + public ShaderIntInfo[] ints; + public ShaderVectorInfo[] vectors; + public ShaderMatrixInfo[] matrices; + public ShaderTextureInfo[] textures; + public ShaderBufferInfo[] buffers; + public ShaderConstantBufferInfo[] 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 string shaderKeywords; + public int componentInstanceID; + public Mesh mesh; + public int meshInstanceID; + public int meshSubset; + public int[] 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; + + // 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; + + // 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; + + // shader state + public FrameDebuggerBlendState blendState; + public FrameDebuggerRasterState rasterState; + public FrameDebuggerDepthState depthState; + public FrameDebuggerStencilState stencilState; + public int stencilRef; + + // clear event data + public float clearColorR; + public float clearColorG; + public float clearColorB; + public float clearColorA; + public float clearDepth; + public uint clearStencil; + + // shader properties + public int batchBreakCause; + public ShaderProperties shaderProperties; + } + + // Match C++ MonoFrameDebuggerEvent memory layout! + [StructLayout(LayoutKind.Sequential)] + internal struct FrameDebuggerEvent + { + public FrameEventType type; + public UnityEngine.Object 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; + } + + // 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; + } + + // Match C++ ScriptingFrameDebuggerDepthState memory layout! + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] + internal struct FrameDebuggerDepthState + { + public int depthWrite; + public CompareFunction 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; + } +} diff --git a/Editor/Mono/PerformanceTools/FrameDebuggerEventDetailsView.cs b/Editor/Mono/PerformanceTools/FrameDebuggerEventDetailsView.cs new file mode 100644 index 0000000000..09de61f797 --- /dev/null +++ b/Editor/Mono/PerformanceTools/FrameDebuggerEventDetailsView.cs @@ -0,0 +1,1658 @@ +// 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.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 +{ + 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_RTSelectedChannel; + [NonSerialized] private float m_RTBlackLevel; + [NonSerialized] private float m_RTWhiteLevel = 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 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 FrameDebuggerWindow m_FrameDebugger = null; + private Lazy m_CurEventData = new Lazy(() => new FrameDebuggerEventData()); + + // Constants + private const int k_NumberGUISections = 10; + private const int k_ArraySizeBitMask = 0x3FF; + private const int k_ShaderTypeBits = (int)ShaderType.Count; + + // Properties + private FrameDebuggerEvent curEvent { get; set; } + private FrameDebuggerEventData curEventData => m_CurEventData.Value; + + // 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 + { + 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; + } + + // Public functions + public FrameDebuggerEventDetailsView(FrameDebuggerWindow frameDebugger) + { + m_FrameDebugger = frameDebugger; + + m_LastEventData = new EventDisplayData(); + m_LastEventData.keywords = string.Empty; + } + + public void Reset() + { + m_RTSelectedChannel = 0; + m_SelectedColorChannel = 0; + m_RTIndex = 0; + m_RTBlackLevel = 0.0f; + m_RTWhiteLevel = 1.0f; + } + + MeshPreview m_Preview; + public void OnNewFrameEventSelected() + { + m_KeywordsList.Clear(); + + m_Preview?.Dispose(); + m_Preview = null; + } + + public void OnDisable() + { + m_Preview?.Dispose(); + m_Preview = null; + } + + // Here is the major performance bottleneck! + public void DrawEvent(Rect rect, FrameDebuggerEvent[] descs, bool isDebuggingEditor) + { + int curEventIndex = FrameDebuggerUtility.limit - 1; + if (!FrameDebuggerHelper.IsAValidFrame(curEventIndex, descs.Length)) + return; + + Initialize(curEventIndex, descs, out bool isReceivingFrameEventData, out bool isFrameEventDataValid); + + GUILayout.BeginArea(rect); + m_ScrollViewVector = GUILayout.BeginScrollView(m_ScrollViewVector); + + // Toolbar + Profiler.BeginSample("DrawToolbar"); + { + DrawRenderTargetToolbar(); + } + Profiler.EndSample(); + + // Title + Profiler.BeginSample("DrawTitle"); + { + GUILayout.BeginHorizontal(FrameDebuggerStyles.EventDetails.titleHorizontalStyle); + EditorGUILayout.LabelField(m_LastEventData.title, FrameDebuggerStyles.EventDetails.titleStyle); + GUILayout.EndHorizontal(); + } + Profiler.EndSample(); + + // Output & Mesh + // We disable Output and Mesh for Compute and Ray Tracing events + bool shouldDrawOutputAndMesh = !m_LastEventData.isComputeEvent && !m_LastEventData.isRayTracingEvent; + Profiler.BeginSample("DrawOutputAndMesh"); + { + DrawOutputAndMesh(rect, shouldDrawOutputAndMesh, isDebuggingEditor); + } + Profiler.EndSample(); + + // Event Details + Profiler.BeginSample("DrawEventDetails"); + { + DrawEventDetails(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"); + { + DrawKeywords(shouldDisplayProperties); + } + Profiler.EndSample(); + + // Properties... + Profiler.BeginSample("DrawProperties"); + { + DrawProperties(shouldDisplayProperties); + } + Profiler.EndSample(); + + GUILayout.EndScrollView(); + GUILayout.EndArea(); + } + + /////////////////////////////////////////////// + // PRIVATE + /////////////////////////////////////////////// + + private void Initialize(int curEventIndex, FrameDebuggerEvent[] descs, out bool isReceivingFrameEventData, out bool isFrameEventDataValid) + { + uint eventDataHash = FrameDebuggerUtility.eventDataHash; + isReceivingFrameEventData = FrameDebugger.IsRemoteEnabled() && FrameDebuggerUtility.receivingRemoteFrameEventData; + isFrameEventDataValid = curEventIndex == curEventData.frameEventIndex; + + if (!isFrameEventDataValid || (eventDataHash != 0 && (eventDataHash != m_LastEventData.hash || m_ForceRebuildStrings))) + { + isFrameEventDataValid = FrameDebuggerUtility.GetFrameEventData(curEventIndex, curEventData); + m_LastEventData.hash = eventDataHash; + m_LastEventData.isValid = false; + m_ForceRebuildStrings = false; + } + + // event type and draw call info + curEvent = descs[curEventIndex]; + FrameEventType eventType = curEvent.type; + + // Rebuild strings... + if (isFrameEventDataValid) + if (!m_LastEventData.isValid || m_LastEventData.index != curEventIndex || m_LastEventData.type != eventType) + BuildCurEventDataStrings(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); + } + + // 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); + }*/ + } + + private void DrawRenderTargetToolbar() + { + if (m_LastEventData.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; + + if (hasShowableDepth) + showableRTCount++; + + GUILayout.BeginHorizontal(FrameDebuggerStyles.EventToolbar.toolbarHorizontalStyle); + + // 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]; + + if (hasShowableDepth) + rtNames[curEventData.rtCount] = FrameDebuggerStyles.EventToolbar.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) + m_RTIndex = 0; + + m_RTIndex = EditorGUILayout.Popup(m_RTIndex, rtNames, FrameDebuggerStyles.EventToolbar.popupLeftStyle, GUILayout.Width(70)); + + + GUI.enabled = !isBackBuffer && !isDepthOnlyRT; + + // color channels + EditorGUILayout.Space(5f); + GUILayout.Label(FrameDebuggerStyles.EventToolbar.channelHeader, FrameDebuggerStyles.EventToolbar.channelHeaderStyle); + EditorGUILayout.Space(5f); + + int channelToDisplay = 0; + bool forceUpdate = false; + bool shouldDisableChannelButtons = isDepthOnlyRT || isClearAction || isBackBuffer; + UInt32 componentCount = GraphicsFormatUtility.GetComponentCount((GraphicsFormat)curEventData.rtFormat); + GUILayout.BeginHorizontal(); + { + GUI.enabled = !shouldDisableChannelButtons && m_SelectedColorChannel != 0; + if (GUILayout.Button(FrameDebuggerStyles.EventToolbar.channelAll, FrameDebuggerStyles.EventToolbar.channelAllStyle)) { m_RTSelectedChannel = 0; } + + GUI.enabled = !shouldDisableChannelButtons && componentCount > 0 && m_SelectedColorChannel != 1; + if (GUILayout.Button(FrameDebuggerStyles.EventToolbar.channelR, FrameDebuggerStyles.EventToolbar.channelStyle)) { m_RTSelectedChannel = 1; } + + GUI.enabled = !shouldDisableChannelButtons && componentCount > 1 && m_SelectedColorChannel != 2; + if (GUILayout.Button(FrameDebuggerStyles.EventToolbar.channelG, FrameDebuggerStyles.EventToolbar.channelStyle)) { m_RTSelectedChannel = 2; } + + GUI.enabled = !shouldDisableChannelButtons && componentCount > 2 && m_SelectedColorChannel != 3; + if (GUILayout.Button(FrameDebuggerStyles.EventToolbar.channelB, FrameDebuggerStyles.EventToolbar.channelStyle)) { m_RTSelectedChannel = 3; } + + GUI.enabled = !shouldDisableChannelButtons && componentCount > 3 && m_SelectedColorChannel != 4; + if (GUILayout.Button(FrameDebuggerStyles.EventToolbar.channelA, FrameDebuggerStyles.EventToolbar.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)); + channelToDisplay = shouldForceAll ? 0 : m_RTSelectedChannel; + + if (channelToDisplay != m_SelectedColorChannel) + { + forceUpdate = true; + m_SelectedColorChannel = channelToDisplay; + } + + GUI.enabled = true; + } + GUILayout.EndHorizontal(); + + GUI.enabled = !isBackBuffer; + + // levels + GUILayout.BeginHorizontal(FrameDebuggerStyles.EventToolbar.levelsHorizontalStyle); + GUILayout.Label(FrameDebuggerStyles.EventToolbar.levelsHeader); + + EditorGUILayout.MinMaxSlider(ref m_RTBlackLevel, ref m_RTWhiteLevel, 0.0f, 1.0f, GUILayout.MaxWidth(200.0f)); + + int rtIndexToSet = m_RTIndex; + if (hasShowableDepth && rtIndexToSet == (showableRTCount - 1)) + rtIndexToSet = -1; + + if (EditorGUI.EndChangeCheck() || rtIndexToSet != m_RTIndexLastSet || forceUpdate) + { + m_SelectedMask = Vector4.zero; + switch (channelToDisplay) + { + case 1: m_SelectedMask.x = 1f; break; + case 2: m_SelectedMask.y = 1f; break; + case 3: m_SelectedMask.z = 1f; break; + case 4: m_SelectedMask.w = 1f; break; + case 5: m_SelectedMask = Vector4.zero; break; + default: m_SelectedMask = Vector4.one; break; + } + + FrameDebuggerUtility.SetRenderTargetDisplayOptions(rtIndexToSet, m_SelectedMask, m_RTBlackLevel, m_RTWhiteLevel); + m_FrameDebugger.RepaintAllNeededThings(); + m_RTIndexLastSet = rtIndexToSet; + m_ForceRebuildStrings = true; + } + + GUILayout.EndHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + GUI.enabled = true; + } + + private void DrawOutputAndMesh(Rect rect, bool shouldDrawOutputAndMesh, bool isDebuggingEditor) + { + if (BeginFoldoutBox(0, shouldDrawOutputAndMesh, FrameDebuggerStyles.EventDetails.foldoutOutputOrMeshText, out float fadePercent)) + { + if (shouldDrawOutputAndMesh) + { + EditorGUILayout.BeginVertical(); + { + float viewportWidth = Mathf.Max(850, rect.width) - 20f; + if (viewportWidth < FrameDebuggerStyles.EventDetails.k_MaxViewportWidth) + return; + + float viewportHeightFaded = FrameDebuggerStyles.EventDetails.k_MaxViewportHeight * fadePercent; + float texWidth = viewportWidth; + float texHeight = viewportHeightFaded; + if (curEventData.rtOutput != null) + { + 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; + } + } + + EditorGUILayout.BeginHorizontal(); + { + m_OutputMeshTabs = (ShowAdditionalInfo)GUILayout.Toolbar((int)m_OutputMeshTabs, m_OutputMeshTabsGuiContents, FrameDebuggerStyles.EventDetails.outputMeshTabStyle); + } + EditorGUILayout.EndHorizontal(); + + if (m_OutputMeshTabs == 0) + { + DrawTargetTexture(viewportWidth, viewportHeightFaded, texWidth, texHeight, fadePercent, isDebuggingEditor); + } + else + { + DrawEventMesh(viewportWidth, viewportHeightFaded, texWidth); + } + } + GUILayout.EndVertical(); + } + } + EndFoldoutBox(); + } + + private void DrawTargetTexture(float viewportWidth, float viewportHeight, float texWidth, float texHeight, float fadePercent, bool isDebuggingEditor) + { + if (fadePercent < 1f) + viewportHeight *= fadePercent; + + EditorGUILayout.BeginHorizontal(FrameDebuggerStyles.EventDetails.outputMeshTextureStyle); + 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) + { + 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); + } + + m_TargetTextureMaterial.SetTexture(ShaderPropertyIDs._MainTex, curEventData.rtOutput); + m_TargetTextureMaterial.SetPass(0); + + if (viewportWidth > texWidth) + textureRect.x += (viewportWidth - texWidth) * 0.5f; + + if (viewportHeight > texHeight) + textureRect.y += (viewportHeight - texHeight) * 0.5f; + + // Remember currently active render texture + RenderTexture currentActiveRT = RenderTexture.active; + + // Blit to the Render Texture + RenderTexture targetRenderTexture = RenderTexture.GetTemporary((int)texWidth, (int)previewRect.height); + Graphics.Blit(null, targetRenderTexture, m_TargetTextureMaterial, 0); + + // Restore previously active render texture + RenderTexture.active = currentActiveRT; + + // Draw the texture to the screen + GUI.DrawTexture(textureRect, targetRenderTexture, ScaleMode.ScaleAndCrop, false, (texWidth / texHeight)); + + // Release + RenderTexture.ReleaseTemporary(targetRenderTexture); + } + 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 (mesh == null) + { + // Draw the background + EditorGUILayout.BeginHorizontal(FrameDebuggerStyles.EventDetails.outputMeshTextureStyle, GUILayout.Width(viewportWidth)); + GUILayoutUtility.GetRect(viewportWidth, viewportHeight); + EditorGUILayout.EndHorizontal(); + return; + } + + if (m_Preview == null) + m_Preview = new MeshPreview(mesh); + else + m_Preview.mesh = mesh; + + // We need this rect called here to push the control buttons below the Mesh... + 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)); + { + GUILayout.FlexibleSpace(); + + GUIContent meshName = new GUIContent(mesh.name); + float meshNameWidth = EditorStyles.label.CalcSize(meshName).x + 10f; + Rect meshNameRect = EditorGUILayout.GetControlRect(GUILayout.Width(meshNameWidth)); + meshNameRect.y -= 1; + meshNameRect.x = 10; + + GUI.Label(meshNameRect, meshName, FrameDebuggerStyles.EventDetails.meshPreToolbarLabelStyle); + + if (FrameDebuggerHelper.IsCurrentEventMouseDown() && FrameDebuggerHelper.IsClickingRect(meshNameRect)) + { + EditorGUIUtility.PingObject(mesh); + Event.current.Use(); + } + + m_Preview.OnPreviewSettings(); + } + EditorGUILayout.EndHorizontal(); + + m_Preview?.OnPreviewGUI(previewRect, EditorStyles.helpBox); + } + + private void DrawEventDetails(Rect rect) + { + if (BeginFoldoutBox(1, true, FrameDebuggerStyles.EventDetails.foldoutEventDetailsText, out float fadePercent)) + { + // 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(); + } + EndFoldoutBox(); + } + + private void DrawKeywords(bool shouldDisplayProperties) + { + 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(); + } + + private void DrawProperties(bool shouldDisplayProperties) + { + ShaderProperties props = curEventData.shaderProperties; + + Profiler.BeginSample("DrawTextureProperties"); + { + DrawTextureProperties(3, shouldDisplayProperties && props.textures.Length != 0, props.textures); + } + Profiler.EndSample(); + + 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(); + } + + private void DrawTextureProperties(int num, bool shouldDrawProperties, ShaderTextureInfo[] textures) + { + if (BeginFoldoutBox(num, shouldDrawProperties, FrameDebuggerStyles.EventDetails.foldoutTexturesText, out float fadePercent)) + { + 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(); + } + EndFoldoutBox(); + } + + private void DrawIntProperties(int num, bool shouldDrawProperties, ShaderIntInfo[] ints) + { + if (BeginFoldoutBox(num, shouldDrawProperties, FrameDebuggerStyles.EventDetails.foldoutIntsText, out float fadePercent)) + { + 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(); + } + + 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(); + + i += numValues; + } + } + GUILayout.EndVertical(); + } + EndFoldoutBox(); + } + + private void DrawVectorProperties(int num, bool shouldDrawProperties, ShaderVectorInfo[] vectors) + { + if (BeginFoldoutBox(num, shouldDrawProperties, FrameDebuggerStyles.EventDetails.foldoutVectorsText, out float fadePercent)) + { + GUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.propertiesVerticalStyle); + { + 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; + } + } + 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(); + } + + private void DrawBufferProperties(int num, bool shouldDrawProperties, ShaderBufferInfo[] buffers) + { + if (BeginFoldoutBox(num, shouldDrawProperties, FrameDebuggerStyles.EventDetails.foldoutBuffersText, out float fadePercent)) + { + GUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.propertiesVerticalStyle); + { + if (shouldDrawProperties) + { + foreach (var t in buffers) + { + GUILayout.BeginHorizontal(); + + DrawPropName(t.name); + DrawShaderPropertyFlags(t.flags); + + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + } + } + } + GUILayout.EndVertical(); + } + 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) + { + 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); + + m_TempSB1.AppendLine("BlendOp Color"); + m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); + + 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 + { + 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); + } + + m_LastEventData.detailsLabelsLeftColumn = m_TempSB1.ToString(); + m_LastEventData.detailsValuesLeftColumn = m_TempSB2.ToString(); + + m_TempSB1.Clear(); + m_TempSB2.Clear(); + + m_LastEventData.firstMeshName = null; + m_LastEventData.listOfMeshesString = null; + if (curEventData.meshInstanceIDs != null && curEventData.meshInstanceIDs.Length > 0) + { + int numOfMeshesAdded = 0; + m_LastEventData.meshTitle = curEventData.meshInstanceIDs.Length < 2 ? "Mesh" : "Meshes"; + for (int i = 0; i < curEventData.meshInstanceIDs.Length; i++) + { + int id = curEventData.meshInstanceIDs[i]; + Mesh mesh = EditorUtility.InstanceIDToObject(id) as Mesh; + if (mesh != null) + { + if (m_LastEventData.firstMeshName == null) + m_LastEventData.firstMeshName = mesh.name; + else + m_TempSB2.AppendLine(" " + mesh.name); + numOfMeshesAdded++; + } + } + + // 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}"); + } + } + 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); + + 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); + } + } + + 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"; + + // 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; + } + } + + private bool BeginFoldoutBox(int nr, bool hasData, GUIContent header, out float fadePercent) + { + GUI.enabled = hasData; + + EditorGUILayout.BeginVertical(FrameDebuggerStyles.EventDetails.foldoutCategoryBoxStyle); + Rect r = GUILayoutUtility.GetRect(2, 21); + + EditorGUI.BeginChangeCheck(); + bool expanded = EditorGUI.FoldoutTitlebar(r, header, m_FoldoutAnimators[nr].target, true, EditorStyles.inspectorTitlebarFlat, EditorStyles.inspectorTitlebarText); + if (EditorGUI.EndChangeCheck()) + { + m_FoldoutAnimators[nr].target = !m_FoldoutAnimators[nr].target; + } + + GUI.enabled = true; + EditorGUI.indentLevel++; + fadePercent = m_FoldoutAnimators[nr].faded; + + return EditorGUILayout.BeginFadeGroup(m_FoldoutAnimators[nr].faded); + } + + private void EndFoldoutBox() + { + EditorGUILayout.EndFadeGroup(); + EditorGUI.indentLevel--; + EditorGUILayout.EndVertical(); + } + } +} diff --git a/Editor/Mono/PerformanceTools/FrameDebuggerHelper.cs b/Editor/Mono/PerformanceTools/FrameDebuggerHelper.cs new file mode 100644 index 0000000000..ec11596e47 --- /dev/null +++ b/Editor/Mono/PerformanceTools/FrameDebuggerHelper.cs @@ -0,0 +1,65 @@ +// 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.Rendering; + +namespace UnityEditorInternal +{ + internal class FrameDebuggerHelper + { + internal static bool IsOnLinuxOpenGL => Application.platform == RuntimePlatform.LinuxEditor && SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLCore; + 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 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 string GetStencilString(int stencil) + { + const int k_NumOfUsedStencilBits = 8; + return $"(0b{Convert.ToString(stencil, 2).PadLeft(k_NumOfUsedStencilBits, '0')}) {stencil}"; + } + + internal static string GetFormat(Texture texture) + { + if (texture == null) + { + return string.Empty; + } + + if (texture is Texture2D) + { + return (texture as Texture2D).format.ToString(); + } + else if (texture is Cubemap) + { + return (texture as Cubemap).format.ToString(); + } + else if (texture is Texture2DArray) + { + return (texture as Texture2DArray).format.ToString(); + } + else if (texture is Texture3D) + { + return (texture as Texture3D).format.ToString(); + } + else if (texture is CubemapArray) + { + return (texture as CubemapArray).format.ToString(); + } + else if (texture is RenderTexture) + { + return (texture as RenderTexture).graphicsFormat.ToString(); + } + + return string.Empty; + } + } +} diff --git a/Editor/Mono/PerformanceTools/FrameDebuggerStyles.cs b/Editor/Mono/PerformanceTools/FrameDebuggerStyles.cs new file mode 100644 index 0000000000..736daf0757 --- /dev/null +++ b/Editor/Mono/PerformanceTools/FrameDebuggerStyles.cs @@ -0,0 +1,339 @@ +// 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 UnityEditor; + +namespace UnityEditorInternal +{ + internal static class FrameDebuggerStyles + { + // match enum FrameEventType on C++ side! + public static readonly string[] frameEventTypeNames = new[] + { + "Clear (nothing)", + "Clear (color)", + "Clear (Depth)", + "Clear (color+depth)", + "Clear (stencil)", + "Clear (color+stencil)", + "Clear (depth+stencil)", + "Clear (color+depth+stencil)", + "SetRenderTarget", + "Resolve Color", + "Resolve Depth", + "Grab RenderTexture", + "Static Batch", + "Dynamic Batch", + "Draw Mesh", + "Draw Dynamic", + "Draw GL", + "GPU Skinning", + "Draw Procedural", + "Draw Procedural Indirect", + "Draw Procedural Indexed", + "Draw Procedural Indexed Indirect", + "Compute", + "Ray Tracing Dispatch", + "Plugin Event", + "Draw Mesh (instanced)", + "Begin Subpass", + "SRP Batch", + "", // on purpose empty string for kFrameEventHierarchyLevelBreak + "Hybrid Batch Group" + }; + + // General settings for the Frame Debugger Window and layout + public 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; + } + + // Tree + public struct Tree + { + public static readonly GUIStyle rowText = "OL Label"; + public static readonly GUIStyle rowTextRight = "OL RightLabel"; + public const string k_UnknownScopeString = ""; + } + + // Top Toolbar + public 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"); + } + + // Event Toolbar in the Event Details window + public struct EventToolbar + { + private const float k_ToolbarHeight = 22f; + private const float k_ToolbarButtondWidth = 30f; + + public static readonly GUIStyle toolbarHorizontalStyle = new GUIStyle(EditorStyles.toolbar) + { + fixedHeight = k_ToolbarHeight + 1f + }; + public static readonly GUIStyle channelHeaderStyle = new GUIStyle(EditorStyles.toolbarLabel) + { + fixedHeight = k_ToolbarHeight + }; + public static readonly GUIStyle channelStyle = new GUIStyle(EditorStyles.miniButtonMid) + { + fixedWidth = k_ToolbarButtondWidth, + }; + public static readonly GUIStyle channelAllStyle = new GUIStyle(EditorStyles.miniButtonLeft) + { + fixedWidth = k_ToolbarButtondWidth, + }; + public static readonly GUIStyle channelAStyle = new GUIStyle(EditorStyles.miniButtonRight) + { + fixedWidth = k_ToolbarButtondWidth, + }; + public static readonly GUIStyle popupLeftStyle = new GUIStyle(EditorStyles.toolbarPopupLeft) + { + fixedHeight = k_ToolbarHeight + }; + public static readonly GUIStyle 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[] + { + EditorGUIUtility.TrTextContent("RT 0", "Show render target #0"), + EditorGUIUtility.TrTextContent("RT 1", "Show render target #1"), + EditorGUIUtility.TrTextContent("RT 2", "Show render target #2"), + EditorGUIUtility.TrTextContent("RT 3", "Show render target #3"), + EditorGUIUtility.TrTextContent("RT 4", "Show render target #4"), + EditorGUIUtility.TrTextContent("RT 5", "Show render target #5"), + EditorGUIUtility.TrTextContent("RT 6", "Show render target #6"), + EditorGUIUtility.TrTextContent("RT 7", "Show render target #7") + }; + } + + // Event Details Window + public 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; + + public const int k_PropertyNameMaxChars = 35; + public const int k_TextureFormatMaxChars = 16; + + + public const float k_MeshBottomToolbarHeight = 21f; + public const float k_ArrayValuePopupBtnWidth = 2.0f; + + public const string k_FloatFormat = "g7"; + public const string k_IntFormat = "d"; + public const string k_NotAvailable = "-"; + + + + public static readonly GUIStyle 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) + { + margin = new RectOffset(0, 0, 0, 10), + }; + + public static readonly GUIStyle verticalLabelStyle = new GUIStyle(EditorStyles.label) + { + fixedWidth = 175, + padding = new RectOffset(0, 0, 0, 0), + margin = new RectOffset(0, 0, 0, 0), + }; + + public static readonly GUIStyle verticalValueStyle = 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") + { + 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, + }; + + public static readonly GUIStyle textureButtonStyle = new GUIStyle() + { + fixedWidth = 12f, + fixedHeight = 12f, + }; + public static readonly GUIStyle textureDimensionsStyle = new GUIStyle(EditorStyles.label) + { + fixedWidth = 65f, + }; + public static readonly GUIStyle textureSizeStyle = new GUIStyle(EditorStyles.label) + { + fixedWidth = 100f, + }; + public static readonly GUIStyle textureFormatStyle = new GUIStyle(EditorStyles.label) + { + fixedWidth = 110f, + }; + + 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(); + } + + // Constructor + static FrameDebuggerStyles() + { + float greyVal = 0.2196079f; + EventDetails.outputMeshTexture = MakeTex(1, 1, new Color(greyVal, greyVal, greyVal, 1f)); + EventDetails.outputMeshTextureStyle.normal.background = EventDetails.outputMeshTexture; + } + + 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); + result.Apply(); + + return result; + } + + public static void OnDisable() + { + UnityEngine.Object.DestroyImmediate(EventDetails.outputMeshTexture); + EventDetails.outputMeshTexture = null; + } + + public class ArrayValuePopup : PopupWindowContent + { + 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(); + } + } + } + } +} diff --git a/Editor/Mono/PerformanceTools/FrameDebuggerToolbarView.cs b/Editor/Mono/PerformanceTools/FrameDebuggerToolbarView.cs new file mode 100644 index 0000000000..b1bf0c34a0 --- /dev/null +++ b/Editor/Mono/PerformanceTools/FrameDebuggerToolbarView.cs @@ -0,0 +1,118 @@ +// 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 UnityEditorInternal; +using UnityEngine.Networking.PlayerConnection; +using UnityEditor.Networking.PlayerConnection; +using UnityEngine.Profiling; + +namespace UnityEditor +{ + internal class FrameDebuggerToolbarView + { + // Non-Serialized + [NonSerialized] private int m_PrevEventsLimit = 0; + [NonSerialized] private int m_PrevEventsCount = 0; + + // Returns true if repaint is needed + public bool DrawToolbar(FrameDebuggerWindow frameDebugger, IConnectionState m_AttachToPlayerState) + { + 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(); + + return needsRepaint; + } + + private void DrawEnableDisableButton(FrameDebuggerWindow frameDebugger, IConnectionState m_AttachToPlayerState, out bool needsRepaint) + { + needsRepaint = false; + + EditorGUI.BeginChangeCheck(); + + bool wasEnabled = GUI.enabled; + GUI.enabled = m_AttachToPlayerState.connectedToTarget != ConnectionTarget.Editor || FrameDebuggerUtility.locallySupported; + GUIContent button = (FrameDebugger.enabled) ? FrameDebuggerStyles.TopToolbar.recordButtonDisable : FrameDebuggerStyles.TopToolbar.recordButtonEnable; + GUILayout.Toggle(FrameDebugger.enabled, button, EditorStyles.toolbarButtonLeft, GUILayout.MinWidth(80)); + GUI.enabled = wasEnabled; + + if (EditorGUI.EndChangeCheck()) + { + frameDebugger.ClickEnableFrameDebugger(); + needsRepaint = true; + } + } + + private void DrawConnectionDropdown(FrameDebuggerWindow frameDebugger, 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); + } + } + + private void DrawEventLimitSlider(FrameDebuggerWindow frameDebugger, out int newLimit) + { + newLimit = 0; + + EditorGUI.BeginChangeCheck(); + + bool wasEnabled = GUI.enabled; + GUI.enabled = FrameDebuggerUtility.count > 1; + newLimit = EditorGUILayout.IntSlider(FrameDebuggerUtility.limit, 1, FrameDebuggerUtility.count, 1, EditorStyles.toolbarSlider); + GUI.enabled = wasEnabled; + + if (EditorGUI.EndChangeCheck()) + frameDebugger.ChangeFrameEventLimit(newLimit); + } + + private void DrawPrevNextButtons(FrameDebuggerWindow frameDebugger, ref int newLimit) + { + bool wasEnabled = GUI.enabled; + + GUI.enabled = newLimit > 1; + if (GUILayout.Button(FrameDebuggerStyles.TopToolbar.prevFrame, EditorStyles.toolbarButton)) + frameDebugger.ChangeFrameEventLimit(newLimit - 1); + + GUI.enabled = newLimit < FrameDebuggerUtility.count; + if (GUILayout.Button(FrameDebuggerStyles.TopToolbar.nextFrame, EditorStyles.toolbarButtonRight)) + frameDebugger.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); + + m_PrevEventsLimit = FrameDebuggerUtility.limit; + m_PrevEventsCount = FrameDebuggerUtility.count; + + GUI.enabled = wasEnabled; + } + } +} diff --git a/Editor/Mono/PerformanceTools/FrameDebuggerTreeView.cs b/Editor/Mono/PerformanceTools/FrameDebuggerTreeView.cs index 06e8da3a91..97c873656f 100644 --- a/Editor/Mono/PerformanceTools/FrameDebuggerTreeView.cs +++ b/Editor/Mono/PerformanceTools/FrameDebuggerTreeView.cs @@ -7,6 +7,7 @@ using UnityEditor; using System.Globalization; using UnityEditor.IMGUI.Controls; +using Object = UnityEngine.Object; namespace UnityEditorInternal @@ -14,29 +15,36 @@ namespace UnityEditorInternal internal class FrameDebuggerTreeView { internal readonly TreeViewController m_TreeView; - internal FDTreeViewDataSource m_DataSource; + internal FrameDebuggerTreeViewDataSource m_DataSource; private readonly FrameDebuggerWindow m_FrameDebugger; public FrameDebuggerTreeView(FrameDebuggerEvent[] frameEvents, TreeViewState treeViewState, FrameDebuggerWindow window, Rect startRect) { m_FrameDebugger = window; m_TreeView = new TreeViewController(window, treeViewState); - m_DataSource = new FDTreeViewDataSource(m_TreeView, frameEvents); - var gui = new FDTreeViewGUI(m_TreeView); + m_DataSource = new FrameDebuggerTreeViewDataSource(m_TreeView, frameEvents); + var gui = new FrameDebuggerTreeViewGUI(m_TreeView); m_TreeView.Init(startRect, m_DataSource, gui, null); m_TreeView.ReloadData(); m_TreeView.selectionChangedCallback += SelectionChanged; + m_TreeView.itemSingleClickedCallback += ItemSingleClicked; + m_TreeView.itemDoubleClickedCallback += PingFrameEventObject; + } + + void ItemSingleClicked(int selectedID) + { + if (Event.current.type == EventType.MouseDown && EditorGUI.actionKey) + PingFrameEventObject(selectedID); } void SelectionChanged(int[] selectedIDs) { if (selectedIDs.Length < 1) return; + int id = selectedIDs[0]; int eventIndex = id; - - if (PopupWindowWithoutFocus.IsVisible()) - PopupWindowWithoutFocus.Hide(); + FrameDebuggerTreeViewItem originalItem = null; // For tree hierarchy nodes, their IDs are not the frame event indices; // fetch the ID from the node itself in that case. @@ -47,14 +55,25 @@ void SelectionChanged(int[] selectedIDs) // so that rendered state corresponds to "everything up to and including this whole sub-tree". if (eventIndex <= 0) { - var item = m_TreeView.FindItem(id) as FDTreeViewItem; + originalItem = m_TreeView.FindItem(id) as FrameDebuggerTreeViewItem; + FrameDebuggerTreeViewItem item = m_TreeView.FindItem(id) as FrameDebuggerTreeViewItem; if (item != null) eventIndex = item.m_EventIndex; + + // If still has no valid ID, do nothing. + if (eventIndex <= 0) + return; } - // If still has no valid ID, do nothing. - if (eventIndex <= 0) - return; - m_FrameDebugger.ChangeFrameEventLimit(eventIndex); + + m_FrameDebugger.ChangeFrameEventLimit(eventIndex, originalItem); + } + + private void PingFrameEventObject(int selectedID) + { + Object obj = FrameDebuggerUtility.GetFrameEventObject(selectedID - 1); + if (obj != null) + EditorGUIUtility.PingObject(obj); + m_FrameDebugger.DrawSearchField(string.Empty); } public void SelectFrameEventIndex(int eventIndex) @@ -66,7 +85,7 @@ public void SelectFrameEventIndex(int eventIndex) int[] selection = m_TreeView.GetSelection(); if (selection.Length > 0) { - var item = m_TreeView.FindItem(selection[0]) as FDTreeViewItem; + FrameDebuggerTreeViewItem item = m_TreeView.FindItem(selection[0]) as FrameDebuggerTreeViewItem; if (item != null && eventIndex == item.m_EventIndex) return; } @@ -74,9 +93,9 @@ public void SelectFrameEventIndex(int eventIndex) m_TreeView.SetSelection(new[] { eventIndex }, true); } - public void OnGUI(Rect rect) + public void DrawTree(Rect rect) { - var keyboardControlID = GUIUtility.GetControlID(FocusType.Keyboard); + int keyboardControlID = GUIUtility.GetControlID(FocusType.Keyboard); m_TreeView.OnGUI(rect, keyboardControlID); } @@ -84,12 +103,13 @@ public void OnGUI(Rect rect) // ID is different for leaf nodes (actual frame events) vs hierarchy nodes (parent profiler nodes): // - leaf node IDs are frame event indices (always > 0). // - hierarchy node IDs are always negative; to get frame event index we want we need to lookup the node and get it from m_EventIndex. - private class FDTreeViewItem : TreeViewItem + public class FrameDebuggerTreeViewItem : TreeViewItem { public FrameDebuggerEvent m_FrameEvent; public int m_ChildEventCount; public int m_EventIndex; - public FDTreeViewItem(int id, int depth, FDTreeViewItem parent, string displayName) + + public FrameDebuggerTreeViewItem(int id, int depth, FrameDebuggerTreeViewItem parent, string displayName) : base(id, depth, parent, displayName) { m_EventIndex = id; @@ -97,12 +117,11 @@ public FDTreeViewItem(int id, int depth, FDTreeViewItem parent, string displayNa } // GUI for TreeView - - private class FDTreeViewGUI : TreeViewGUI + private class FrameDebuggerTreeViewGUI : TreeViewGUI { - const float kSmallMargin = 4; + private const float kSmallMargin = 4; - public FDTreeViewGUI(TreeViewController treeView) + public FrameDebuggerTreeViewGUI(TreeViewController treeView) : base(treeView) { } @@ -112,45 +131,62 @@ protected override Texture GetIconForItem(TreeViewItem item) return null; } + private int childCounter = 0; + 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; + GUIContent tempContent; + GUIStyle style; - var item = (FDTreeViewItem)itemRaw; + bool isParent = (item.hasChildren); + FontStyle fontStyle = (isParent) ? FontStyle.Bold : FontStyle.Normal; + childCounter = (isParent) ? 1 : (childCounter + 1); + + // Draw background + style = FrameDebuggerStyles.Tree.rowText; + tempContent = EditorGUIUtility.TempContent(""); + style.Draw(rect, tempContent, false, false, false, false); // indent float indent = GetContentIndent(item); rect.x += indent; rect.width -= indent; - string text; - GUIContent gc; - GUIStyle style; - // child event count - if (item.m_ChildEventCount > 0) + if (isParent) { + text = item.m_ChildEventCount.ToString(CultureInfo.InvariantCulture); + tempContent = EditorGUIUtility.TempContent(text); + + style = FrameDebuggerStyles.Tree.rowTextRight; + style.fontStyle = fontStyle; + Rect r = rect; r.width -= kSmallMargin; - text = item.m_ChildEventCount.ToString(CultureInfo.InvariantCulture); - gc = EditorGUIUtility.TempContent(text); - style = FrameDebuggerWindow.styles.rowTextRight; - style.Draw(r, gc, false, false, false, false); + style.Draw(r, tempContent, false, false, false, false); + // reduce width of available space for the name, so that it does not overlap event count - rect.width -= style.CalcSize(gc).x + kSmallMargin * 2; + rect.width -= style.CalcSize(tempContent).x + kSmallMargin * 2; } + style = FrameDebuggerStyles.Tree.rowText; + style.fontStyle = fontStyle; + // draw event name - if (item.id <= 0) - text = item.displayName; // hierarchy item - else - text = FrameDebuggerWindow.s_FrameEventTypeNames[(int)item.m_FrameEvent.type] + item.displayName; // leaf event + text = item.displayName; + if (string.IsNullOrEmpty(text)) - text = ""; - gc = EditorGUIUtility.TempContent(text); - style = FrameDebuggerWindow.styles.rowText; - style.Draw(rect, gc, false, false, false, selected && focused); + text = FrameDebuggerStyles.Tree.k_UnknownScopeString; + + tempContent = EditorGUIUtility.TempContent(text); + style.Draw(rect, tempContent, false, false, false, selected && focused); } protected override void RenameEnded() @@ -160,12 +196,11 @@ protected override void RenameEnded() // Data source for TreeView - internal class FDTreeViewDataSource : TreeViewDataSource + internal class FrameDebuggerTreeViewDataSource : TreeViewDataSource { private FrameDebuggerEvent[] m_FrameEvents; - public FDTreeViewDataSource(TreeViewController treeView, FrameDebuggerEvent[] frameEvents) - : base(treeView) + public FrameDebuggerTreeViewDataSource(TreeViewController treeView, FrameDebuggerEvent[] frameEvents) : base(treeView) { m_FrameEvents = frameEvents; rootIsCollapsable = false; @@ -174,7 +209,7 @@ public FDTreeViewDataSource(TreeViewController treeView, FrameDebuggerEvent[] fr public void SetEvents(FrameDebuggerEvent[] frameEvents) { - var wasEmpty = m_FrameEvents == null || m_FrameEvents.Length < 1; + bool wasEmpty = m_FrameEvents == null || m_FrameEvents.Length < 1; m_FrameEvents = frameEvents; m_NeedRefreshRows = true; @@ -183,16 +218,7 @@ public void SetEvents(FrameDebuggerEvent[] frameEvents) // Only expand whole events tree if it was empty before. // If we already had something in there, we want to preserve user's expanded items. if (wasEmpty) - { - SetExpanded(m_RootItem, true); - - // Expand root's children only - foreach (var treeViewItem in m_RootItem.children) - { - if (treeViewItem != null) - SetExpanded(treeViewItem, true); - } - } + SetExpandedWithChildren(m_RootItem, true); } public override bool IsRenamingItemAllowed(TreeViewItem item) @@ -206,92 +232,95 @@ public override bool CanBeMultiSelected(TreeViewItem item) } // Used while building the tree data source; represents current tree hierarchy level - private class FDTreeHierarchyLevel + private class FrameDebuggerTreeHierarchyLevel { - internal readonly FDTreeViewItem item; + internal readonly FrameDebuggerTreeViewItem item; internal readonly List children; - internal FDTreeHierarchyLevel(int depth, int id, string name, FDTreeViewItem parent) + internal FrameDebuggerTreeHierarchyLevel(int depth, int id, string name, FrameDebuggerTreeViewItem parent) { - item = new FDTreeViewItem(id, depth, parent, name); + item = new FrameDebuggerTreeViewItem(id, depth, parent, name); children = new List(); } } - private static void CloseLastHierarchyLevel(List eventStack, int prevFrameEventIndex) + + private static void CloseLastHierarchyLevel(List eventStack, int prevFrameEventIndex) { var idx = eventStack.Count - 1; eventStack[idx].item.children = eventStack[idx].children; eventStack[idx].item.m_EventIndex = prevFrameEventIndex; + if (eventStack[idx].item.parent != null) - ((FDTreeViewItem)eventStack[idx].item.parent).m_ChildEventCount += eventStack[idx].item.m_ChildEventCount; - eventStack.RemoveAt(idx); - } + ((FrameDebuggerTreeViewItem)eventStack[idx].item.parent).m_ChildEventCount += eventStack[idx].item.m_ChildEventCount; - static bool IsHiddenEventType(FrameEventType et) - { - return et == FrameEventType.BeginSubpass; + eventStack.RemoveAt(idx); } public override void FetchData() { - var rootLevel = new FDTreeHierarchyLevel(0, 0, string.Empty, null); + FrameDebuggerTreeHierarchyLevel rootLevel = new FrameDebuggerTreeHierarchyLevel(0, 0, string.Empty, null); // Hierarchy levels of a tree being built - var eventStack = new List(); + List eventStack = new List(); eventStack.Add(rootLevel); int hierarchyIDCounter = -1; - for (var i = 0; i < m_FrameEvents.Length; ++i) + for (int i = 0; i < m_FrameEvents.Length; ++i) { // This will be a slash-delimited string, e.g. Foo/Bar/Baz. // Add "/" in front to account for the single (invisible) root item // that the TreeView always has. string context = "/" + (FrameDebuggerUtility.GetFrameEventInfoName(i) ?? string.Empty); string[] names = context.Split('/'); + // find matching hierarchy level int level = 0; while (level < eventStack.Count && level < names.Length) { if (names[level] != eventStack[level].item.displayName) break; + ++level; } + // close all the further levels from previous events in the stack while (eventStack.Count > 0 && eventStack.Count > level) - { CloseLastHierarchyLevel(eventStack, i); - } - if (m_FrameEvents[i].type == FrameEventType.HierarchyLevelBreak) + if (FrameDebuggerHelper.IsAHierarchyLevelBreakEvent(m_FrameEvents[i].type)) continue; // add all further levels for current event - for (var j = level; j < names.Length; ++j) + for (int j = level; j < names.Length; ++j) { var parent = eventStack[eventStack.Count - 1]; - var newLevel = new FDTreeHierarchyLevel(eventStack.Count - 1, --hierarchyIDCounter, names[j], parent.item); + var newLevel = new FrameDebuggerTreeHierarchyLevel(eventStack.Count - 1, --hierarchyIDCounter, names[j], parent.item); parent.children.Add(newLevel.item); eventStack.Add(newLevel); } - if (IsHiddenEventType(m_FrameEvents[i].type)) + if (FrameDebuggerHelper.IsAHiddenEvent(m_FrameEvents[i].type)) continue; // add leaf event to current level var eventObj = FrameDebuggerUtility.GetFrameEventObject(i); var displayName = string.Empty; + if (eventObj) displayName = " " + eventObj.name; - FDTreeHierarchyLevel parentEvent = eventStack[eventStack.Count - 1]; - var leafEventID = i + 1; - var item = new FDTreeViewItem(leafEventID, eventStack.Count - 1, parentEvent.item, displayName); + else + displayName = FrameDebuggerStyles.frameEventTypeNames[(int)m_FrameEvents[i].type]; + + 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; } + while (eventStack.Count > 0) - { CloseLastHierarchyLevel(eventStack, m_FrameEvents.Length); - } + m_RootItem = rootLevel.item; } } diff --git a/Editor/Mono/PlayModeView/PlayModeView.cs b/Editor/Mono/PlayModeView/PlayModeView.cs index d4fbe5b3d0..4b6c9ef6d6 100644 --- a/Editor/Mono/PlayModeView/PlayModeView.cs +++ b/Editor/Mono/PlayModeView/PlayModeView.cs @@ -54,7 +54,19 @@ internal abstract class PlayModeView : EditorWindow private const int k_MaxSupportedDisplays = 8; - private Dictionary m_AvailableWindowTypes; + static Dictionary s_AvailableWindowTypes; + + internal Vector2 viewPadding + { + get; + private protected set; + } + + internal float viewMouseScale + { + get; + private protected set; + } protected string playModeViewName { @@ -257,15 +269,15 @@ protected RenderTexture RenderView(Vector2 mousePosition, bool clearTexture) } } - protected string GetWindowTitle(Type type) + protected static string GetWindowTitle(Type type) { var attributes = type.GetCustomAttributes(typeof(EditorWindowTitleAttribute), true); return attributes.Length > 0 ? ((EditorWindowTitleAttribute)attributes[0]).title : type.Name; } - protected Dictionary GetAvailableWindowTypes() + internal static Dictionary GetAvailableWindowTypes() { - return m_AvailableWindowTypes ?? (m_AvailableWindowTypes = TypeCache.GetTypesDerivedFrom(typeof(PlayModeView)).OrderBy(GetWindowTitle).ToDictionary(t => t, GetWindowTitle)); + return s_AvailableWindowTypes ?? (s_AvailableWindowTypes = TypeCache.GetTypesDerivedFrom(typeof(PlayModeView)).OrderBy(GetWindowTitle).ToDictionary(t => t, GetWindowTitle)); } private void SetSerializedViews(Dictionary serializedViews) @@ -285,7 +297,7 @@ private Dictionary ListsToDictionary(List keys, List x.Name.Contains("SimulatorWindow")); + + PlayModeWindow() + { + } + + public enum PlayModeViewTypes + { + GameView, + SimulatorView + } + + public static void GetRenderingResolution(out uint width, out uint height) + { + var view = GetOrCreateWindow(); + width = (uint)view.targetSize.x; + height = (uint)view.targetSize.y; + } + + public static void SetCustomRenderingResolution(uint width, uint height, string baseName) + { + var win = GetOrCreateWindow(); + var gameView = win as GameView; + + if (gameView != null) + { + gameView.SetCustomResolution(new Vector2(width, height), baseName); + } + else + { + Debug.LogError($"The {win.GetType().Name} does not support custom resolution"); + } + } + + public static void SetPlayModeFocused(bool focused) + { + GetOrCreateWindow().enterPlayModeBehavior = focused + ? PlayModeView.EnterPlayModeBehavior.PlayFocused + : PlayModeView.EnterPlayModeBehavior.PlayMaximized; + } + + public static bool GetPlayModeFocused() + { + return GetOrCreateWindow().enterPlayModeBehavior == PlayModeView.EnterPlayModeBehavior.PlayFocused; + } + + public static PlayModeViewTypes GetViewType() + { + var type = GetOrCreateWindow().GetType(); + if (type == typeof(GameView)) + return PlayModeViewTypes.GameView; + if (type == s_SimulatorWindowType) + return PlayModeViewTypes.SimulatorView; + + throw new Exception("Unsupported PlayModeView type"); + } + + public static void SetViewType(PlayModeViewTypes type) + { + var view = GetOrCreateWindow(); + switch (type) + { + case PlayModeViewTypes.GameView: + view.SwapMainWindow(typeof(GameView)); + return; + case PlayModeViewTypes.SimulatorView: + { + // DeviceSim is a Module that might be disabled in the future. + if (s_SimulatorWindowType == null) + { + throw new Exception("Cannot find the SimulatorWindow type."); + } + view.SwapMainWindow(s_SimulatorWindowType); + return; + } + default: + throw new Exception("Unsupported PlayModeView type"); + } + } + + static PlayModeView GetOrCreateWindow() + { + var view = PlayModeView.GetMainPlayModeView(); + if (view == null) + { + return EditorWindow.CreateWindow(); + } + + return view; + } + } +} diff --git a/Editor/Mono/PlayerSettings.bindings.cs b/Editor/Mono/PlayerSettings.bindings.cs index 4b068ba447..746342c186 100644 --- a/Editor/Mono/PlayerSettings.bindings.cs +++ b/Editor/Mono/PlayerSettings.bindings.cs @@ -32,6 +32,8 @@ public enum ScriptingImplementation Mono2x = 0, IL2CPP = 1, WinRTDotNET = 2, + [Obsolete("CoreCLR support is still a work in progress and is disabled for now.")] // Hide from intellisense while WIP + CoreCLR = 3, } // Must be in sync with Il2CppCompilerConfiguration enum in SerializationMetaFlags.h @@ -42,25 +44,6 @@ public enum Il2CppCompilerConfiguration Master = 2, } - // Must be in sync with kAspectRatioSerializeNames and kAspectRatioValues - public enum AspectRatio - { - // Undefined aspect ratios. - AspectOthers = 0, - - // 4:3 aspect ratio. - Aspect4by3 = 1, - - // 5:4 aspect ratio. - Aspect5by4 = 2, - - // 16:10 aspect ratio. - Aspect16by10 = 3, - - // 16:9 aspect ratio. - Aspect16by9 = 4, - } - // Mac fullscreen mode public enum MacFullscreenMode { @@ -242,6 +225,14 @@ internal enum LightmapEncodingQuality High = 2 } + // Keep in synch with HDRCubemapEncodingQuality enum from GfxDeviceTypes.h + internal enum HDRCubemapEncodingQuality + { + Low = 0, + Normal = 1, + High = 2 + } + // Must be in sync with ShaderPrecisionModel enum in EditorOnlyPlayerSettings.h public enum ShaderPrecisionModel { @@ -489,13 +480,6 @@ public static Guid productGUID [Obsolete("displayResolutionDialog has been removed.", false)] public static extern ResolutionDialogSetting displayResolutionDialog { get; set; } - // Returns whether or not the specified aspect ratio is enabled. - [NativeMethod("AspectRatioEnabled")] - public static extern bool HasAspectRatio(AspectRatio aspectRatio); - - // Enables the specified aspect ratio. - public static extern void SetAspectRatio(AspectRatio aspectRatio, bool enable); - // If enabled, the game will default to fullscreen mode. [Obsolete("(defaultIsFullScreen is deprecated, use fullScreenMode instead")] [StaticAccessor("PlayerSettingsBindings", StaticAccessorType.DoubleColon)] @@ -519,6 +503,9 @@ public static Guid productGUID // Use resizable window in standalone player builds. public static extern bool resizableWindow { get; set; } + // Should resolution be reset when native window size changes. Shared between iOS & Android platforms. + public static extern bool resetResolutionOnWindowResize { get; set; } + /// Bake collision meshes into the mesh asset. public static extern bool bakeCollisionMeshes { get; set; } @@ -577,6 +564,7 @@ public static bool singlePassStereoRendering public static bool protectGraphicsMemory { get { return false; } set {} } public static extern bool enableFrameTimingStats { get; set; } + public static extern bool enableOpenGLProfilerGPURecorders { get; set; } public static extern bool useHDRDisplay { get; set; } @@ -717,6 +705,12 @@ internal static string GetPlatformName(BuildTargetGroup targetGroup) [NativeMethod("SetLightmapEncodingQuality")] internal static extern void SetLightmapEncodingQualityForPlatformGroup(BuildTargetGroup platformGroup, LightmapEncodingQuality encodingQuality); + [NativeMethod("GetHDRCubemapEncodingQuality")] + internal static extern HDRCubemapEncodingQuality GetHDRCubemapEncodingQualityForPlatformGroup(BuildTargetGroup platformGroup); + + [NativeMethod("SetHDRCubemapEncodingQuality")] + internal static extern void SetHDRCubemapEncodingQualityForPlatformGroup(BuildTargetGroup platformGroup, HDRCubemapEncodingQuality encodingQuality); + [FreeFunction("GetTargetPlatformGraphicsAPIAvailability")] internal static extern UnityEngine.Rendering.GraphicsDeviceType[] GetSupportedGraphicsAPIs(BuildTarget platform); @@ -880,11 +874,11 @@ public static Il2CppCompilerConfiguration GetIl2CppCompilerConfiguration(BuildTa public static void SetIl2CppCompilerConfiguration(BuildTargetGroup targetGroup, Il2CppCompilerConfiguration configuration) => SetIl2CppCompilerConfiguration(NamedBuildTarget.FromBuildTargetGroup(targetGroup), configuration); - // [Obsolete("Use GetIncrementalIl2CppBuild(NamedBuildTarget buildTarget) instead")] + [Obsolete("GetIncrementalIl2CppBuild has no impact on the build process")] public static bool GetIncrementalIl2CppBuild(BuildTargetGroup targetGroup) => GetIncrementalIl2CppBuild(NamedBuildTarget.FromBuildTargetGroup(targetGroup)); - // [Obsolete("Use SetIncrementalIl2CppBuild(NamedBuildTarget buildTarget, bool enabled) instead")] + [Obsolete("SetIncrementalIl2CppBuild has no impact on the build process")] public static void SetIncrementalIl2CppBuild(BuildTargetGroup targetGroup, bool enabled) => SetIncrementalIl2CppBuild(NamedBuildTarget.FromBuildTargetGroup(targetGroup), enabled); @@ -1065,12 +1059,14 @@ public static void SetIl2CppCompilerConfiguration(NamedBuildTarget buildTarget, [StaticAccessor("GetPlayerSettings().GetEditorOnly()")] [NativeMethod("GetPlatformIncrementalIl2CppBuild")] private static extern bool GetIncrementalIl2CppBuildInternal(string buildTargetName); + [Obsolete("GetIncrementalIl2CppBuild has no impact on the build process")] public static bool GetIncrementalIl2CppBuild(NamedBuildTarget buildTarget) => GetIncrementalIl2CppBuildInternal(buildTarget.TargetName); [NativeThrows] [StaticAccessor("GetPlayerSettings().GetEditorOnlyForUpdate()")] [NativeMethod("SetPlatformIncrementalIl2CppBuild")] private static extern void SetIncrementalIl2CppBuildInternal(string buildTargetName, bool enabled); + [Obsolete("SetIncrementalIl2CppBuild has no impact on the build process")] public static void SetIncrementalIl2CppBuild(NamedBuildTarget buildTarget, bool enabled) => SetIncrementalIl2CppBuildInternal(buildTarget.TargetName, enabled); @@ -1137,13 +1133,11 @@ public static void SetMobileMTRendering(NamedBuildTarget buildTarget, bool enabl public static void SetNormalMapEncoding(NamedBuildTarget buildTarget, NormalMapEncoding encoding) => SetNormalMapEncodingInternal(buildTarget.TargetName, encoding); - public static extern bool assemblyVersionValidation + [Obsolete("assemblyVersionValidation has been deprecated due to the introduction of binding redirects")] + public static bool assemblyVersionValidation { - [StaticAccessor("GetPlayerSettings().GetEditorOnly()")] - get; - - [StaticAccessor("GetPlayerSettings().GetEditorOnlyForUpdate()")] - set; + get => false; + set { } } [StaticAccessor("GetPlayerSettings().GetEditorOnly().additionalIl2CppArgs")] diff --git a/Editor/Mono/PlayerSettings.deprecated.cs b/Editor/Mono/PlayerSettings.deprecated.cs index d88fa2a8d3..3d8e4f326e 100644 --- a/Editor/Mono/PlayerSettings.deprecated.cs +++ b/Editor/Mono/PlayerSettings.deprecated.cs @@ -81,6 +81,26 @@ public enum iOSSystemGestureDeferMode: uint All = TopEdge | LeftEdge | BottomEdge | RightEdge } + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + [Obsolete("AspectRatio enum has been deprecated and its functionality has been removed from Unity.", false)] + public enum AspectRatio + { + // Undefined aspect ratios. + AspectOthers = 0, + + // 4:3 aspect ratio. + Aspect4by3 = 1, + + // 5:4 aspect ratio. + Aspect5by4 = 2, + + // 16:10 aspect ratio. + Aspect16by10 = 3, + + // 16:9 aspect ratio. + Aspect16by9 = 4, + } + partial class PlayerSettings { // deprecated since forever @@ -111,6 +131,14 @@ partial class PlayerSettings [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [Obsolete("Use PlayerSettings.applicationIdentifier instead (UnityUpgradable) -> UnityEditor.PlayerSettings.applicationIdentifier", true)] public static string bundleIdentifier { get { return applicationIdentifier; } set { applicationIdentifier = value; } } + + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + [Obsolete("HasAspectRatio is deprecated, Unity supports all aspect ratios.", false)] + public static bool HasAspectRatio(AspectRatio aspectRatio) => true; + + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + [Obsolete("SetAspectRatio is deprecated, Unity supports all aspect ratios.", false)] + public static void SetAspectRatio(AspectRatio aspectRatio, bool enable) { } } partial class PlayerSettings diff --git a/Editor/Mono/PlayerSettingsIOS.bindings.cs b/Editor/Mono/PlayerSettingsIOS.bindings.cs index 03f13550c6..ff21f94e91 100644 --- a/Editor/Mono/PlayerSettingsIOS.bindings.cs +++ b/Editor/Mono/PlayerSettingsIOS.bindings.cs @@ -108,6 +108,7 @@ public enum iOSBackgroundMode: uint BluetoothPeripheral = 1 << 6, Fetch = 1 << 7, RemoteNotification = 1 << 8, + Processing = 1 << 9, } public enum iOSLaunchScreenImageType diff --git a/Editor/Mono/PlayerSettingsWSA.bindings.cs b/Editor/Mono/PlayerSettingsWSA.bindings.cs index 2f8af01e01..3daa66e699 100644 --- a/Editor/Mono/PlayerSettingsWSA.bindings.cs +++ b/Editor/Mono/PlayerSettingsWSA.bindings.cs @@ -375,6 +375,8 @@ private static extern WSASupportedFileType[] metroFTAFileTypes set; } + public static extern string vcxProjDefaultLanguage { get; set; } + public static class Declarations { public static string protocolName diff --git a/Editor/Mono/Plugins/PluginsHelper.cs b/Editor/Mono/Plugins/PluginsHelper.cs index 12ffd4c269..9e9c0b76cc 100644 --- a/Editor/Mono/Plugins/PluginsHelper.cs +++ b/Editor/Mono/Plugins/PluginsHelper.cs @@ -11,7 +11,7 @@ namespace UnityEditorInternal { internal class PluginsHelper { - public static bool CheckFileCollisions(BuildTarget buildTarget) + public static bool CheckFileCollisions(BuildTarget buildTarget, string[] defineConstraints) { // Checks that plugins don't collide with each other IPluginImporterExtension pluginImporterExtension = null; @@ -26,7 +26,7 @@ public static bool CheckFileCollisions(BuildTarget buildTarget) pluginImporterExtension = new DefaultPluginImporterExtension(null); } - if (pluginImporterExtension.CheckFileCollisions(BuildPipeline.GetBuildTargetName(buildTarget))) + if (pluginImporterExtension.CheckFileCollisions(BuildPipeline.GetBuildTargetName(buildTarget), defineConstraints)) return true; return false; diff --git a/Editor/Mono/PointEditor.cs b/Editor/Mono/PointEditor.cs index 6390ee46c6..d0c6ed7321 100644 --- a/Editor/Mono/PointEditor.cs +++ b/Editor/Mono/PointEditor.cs @@ -15,7 +15,7 @@ namespace UnityEditor /// The point editor uses this to make callbacks to your editor so no assumptions about your data structure is made. internal interface IEditablePoint { - /// Get the world-space position of a specifc point + /// Get the world-space position of a specific point Vector3 GetPosition(int idx); /// Set the world-space position of a specific point void SetPosition(int idx, Vector3 position); diff --git a/Editor/Mono/Prefabs/PrefabFamilyPopup.cs b/Editor/Mono/Prefabs/PrefabFamilyPopup.cs new file mode 100644 index 0000000000..bcb8593948 --- /dev/null +++ b/Editor/Mono/Prefabs/PrefabFamilyPopup.cs @@ -0,0 +1,748 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.Collections.Generic; +using UnityEngine; +using Object = UnityEngine.Object; +using UnityEditor.SceneManagement; +using System.Linq; +using System; +using UnityEngine.Assertions; + +namespace UnityEditor +{ + class PrefabFamilyPopup : PopupWindowContent + { + GameObject m_Target; + string m_TargetPath; + string m_TargetGUID; + + struct AncestorItem + { + public string assetPath; + public int overrideCount; + } + AncestorItem[] m_AncestorItems; + OverridesCounterForPrefabAssets m_OverridesCounter; + int numRows => m_AncestorItems.Length; + + bool m_DisplayChildren = false; + ObjectListArea m_ListArea; + ObjectListAreaState m_ListAreaState = new ObjectListAreaState() { m_GridSize = 56 }; + SavedInt m_SavedGridSize = new SavedInt("PrefabFamilyPopup.GridSize", 56); + int[] m_Results = null; + Delayer m_Debounce; + SearchFilter m_SearchFilter; + string m_SearchFilterString = ""; + Vector2 m_Scroll = Vector2.zero; + IEnumerator m_Enumerator = null; + const int k_MinIconSize = 20; + + static bool s_Open = false; + bool m_Debug = false; + + float m_WindowWidth, m_NamesWidth, m_MaxNameWidth, m_NoResultsX, m_OverridesX; + + const int k_MaxSearchIterationPerFrame = 500; + const int k_MaxTableRows = 10; + const float k_MinWindowWidth = 300f, k_MaxWindowWidth = 700f; + const float k_HeaderHeight = 28f; + const float k_EntryHeight = 20f; + const float k_SliderWidth = 55f; + const float k_SearchHeight = 150f; + const float k_Padding = 3f; + const float k_OffsetX = 6f; + const float k_SplitWidth = 1f; + readonly float k_MinNameWidth; + readonly float k_TitleWidth = k_OffsetX + 50f; + readonly float k_OverridesWidth = k_SplitWidth + EditorStyles.miniLabel.CalcSize(Styles.overridesLabel).x + 2 * k_Padding; + readonly float k_ScrollbarWidth = GUI.skin.verticalScrollbar.fixedWidth + GUI.skin.verticalScrollbar.margin.left; + readonly float k_ScrollbarHeight = GUI.skin.horizontalScrollbar.fixedHeight + GUI.skin.horizontalScrollbar.margin.top; + + + public static class Colors + { + static Color header_l = new Color32(0xDF, 0xDF, 0xDF, 0xFF); + static Color header_d = new Color(0.5f, 0.5f, 0.5f, 0.2f); + + static Color[] rows_l = new Color[2] + { + new Color32(0xC8, 0xC8, 0xC8, 0xFF), + new Color32(0xCE, 0xCE, 0xCE, 0xFF) + }; + + static Color[] rows_d = new Color[2] + { + new Color32(0x38, 0x38, 0x38, 0xFF), + new Color32(0x3E, 0x3E, 0x3E, 0xFF) + }; + + public static Color headerBackground { get { return EditorGUIUtility.isProSkin ? Colors.header_d : Colors.header_l; } } + public static Color rowBackground(int i) => EditorGUIUtility.isProSkin ? Colors.rows_d[i % 2] : Colors.rows_l[i % 2]; + } + + public static class Styles + { + public static readonly GUIContent rootLabel = EditorGUIUtility.TrTextContent("Root", "The root of the Prefab Variant hierarchy"); + public static readonly GUIContent selectedLabel = EditorGUIUtility.TrTextContent("Current", "The currently selected Prefab"); + public static readonly GUIContent titlePrefixLabel = EditorGUIUtility.TrTextContent("Variant Family of"); + public static readonly GUIContent ancestorLabel = EditorGUIUtility.TrTextContent("Ancestors"); + public static readonly GUIContent overridesLabel = EditorGUIUtility.TrTextContent("Overrides"); + public static readonly GUIContent childrenLabel = EditorGUIUtility.TrTextContent("Children"); + public static readonly GUIContent noResultsLabel = EditorGUIUtility.TrTextContent("No results"); + public static readonly GUIContent noChildrenLabel = EditorGUIUtility.TrTextContent("This Prefab doesn't have any children.\nPrefab Variants created from this Prefab\nwill be listed here."); + public static readonly GUIStyle searchBackground = new GUIStyle("ProjectBrowserIconAreaBg"); + public static readonly GUIStyle centered = new GUIStyle(EditorStyles.label) { alignment = TextAnchor.MiddleCenter }; + public static readonly GUIStyle boldRightAligned = new GUIStyle(EditorStyles.boldLabel) + { + alignment = TextAnchor.MiddleRight, + fontSize = (int)(1.1f * EditorStyles.boldLabel.fontSize) + }; + public static readonly GUIStyle boldNumber = new GUIStyle(EditorStyles.boldLabel) + { + fontSize = (int)(0.9f * EditorStyles.boldLabel.fontSize) + }; + + public static readonly GUIStyle searchFieldStyle = new GUIStyle(EditorStyles.toolbarSearchField) + { + margin = new RectOffset(5, 4, 4, 5) + }; + } + + internal PrefabFamilyPopup(GameObject target) + { + if (isOpen) + throw new InvalidOperationException("PrefabFamilyPopup is already open"); + + this.m_Target = target; + m_TargetPath = AssetDatabase.GetAssetPath(target); + m_TargetGUID = AssetDatabase.AssetPathToGUID(m_TargetPath); + + k_MinNameWidth = k_MinWindowWidth - (k_TitleWidth + k_SplitWidth + k_OverridesWidth); + + m_SearchFilter = new SearchFilter() + { + classNames = new string[] { "Prefab" }, + searchArea = SearchFilter.SearchArea.AllAssets + }; + m_Debounce = Delayer.Debounce(_ => + { + SearchFilterChanged(); + editorWindow.Repaint(); + }); + + Init(); + } + + public override void OnOpen() + { + base.OnOpen(); + s_Open = true; + + if (m_Debug) + Debug.Log("[PrefabFamilyPopup] Open"); + } + + public override void OnClose() + { + base.OnClose(); + s_Open = false; + + EditorApplication.update -= CalculateOverrideCountsTimeSliced; + if (m_ListArea != null) + m_ListArea.OnDestroy(); + + if (m_Debug) + Debug.Log("[PrefabFamilyPopup] Close"); + } + + bool isVariant { get { return PrefabUtility.GetCorrespondingObjectFromSource(m_Target) != null; } } + + public static bool isOpen => s_Open; + + public static string ObjectToGUID(Object asset) + { + Assert.IsNotNull(asset); + + string guid; + AssetDatabase.TryGetGUIDAndLocalFileIdentifier(asset, out guid, out long _); + return guid; + + } + + AncestorItem[] GetAncestorItems() + { + if (!isVariant) + throw new InvalidOperationException("GetAncestorItems() should only be called for prefab variants"); + + var items = new List(); + + var currentGUID = ObjectToGUID(m_Target); + while (!string.IsNullOrEmpty(currentGUID)) + { + var assetPath = AssetDatabase.GUIDToAssetPath(currentGUID); + Assert.IsNotNull(assetPath); + + items.Add(new AncestorItem { assetPath = assetPath, overrideCount = -1 }); + + var instanceID = AssetDatabase.GetMainAssetInstanceID(assetPath); + currentGUID = PrefabUtility.GetVariantParentGUID(instanceID); + } + + return items.ToArray(); + } + + + void Init() + { + if (isVariant) + { + m_NamesWidth = k_MinNameWidth; + m_AncestorItems = GetAncestorItems(); + foreach (var item in m_AncestorItems) + { + var prefabName = System.IO.Path.GetFileNameWithoutExtension(item.assetPath); + m_NamesWidth = Mathf.Max(GUI.skin.label.CalcSize(EditorGUIUtility.TempContent(prefabName)).x + 40, m_NamesWidth); + } + + m_OverridesCounter = new OverridesCounterForPrefabAssets(m_AncestorItems.Select(x => AssetDatabase.LoadAssetAtPath(x.assetPath)).ToList()); + EditorApplication.update += CalculateOverrideCountsTimeSliced; + + float scrollBarWidthOffset = numRows >= k_MaxTableRows ? k_ScrollbarWidth : 0; + m_MaxNameWidth = k_MaxWindowWidth - (k_TitleWidth + k_SplitWidth + k_OverridesWidth) - scrollBarWidthOffset; + + float prevWidth = m_WindowWidth; + if (m_NamesWidth <= k_MinNameWidth) + m_WindowWidth = k_MinWindowWidth; + else if (m_NamesWidth >= m_MaxNameWidth) + m_WindowWidth = k_MaxWindowWidth; + else + m_WindowWidth = k_TitleWidth + m_NamesWidth + k_SplitWidth + k_OverridesWidth; + + m_OverridesX = m_WindowWidth - k_OverridesWidth - scrollBarWidthOffset; + } + else + { + // Just showing children area + var titleWidth = GUI.skin.label.CalcSize(EditorGUIUtility.TempContent(m_Target.name)).x + GUI.skin.label.CalcSize(Styles.titlePrefixLabel).x + 60; + m_WindowWidth = Mathf.Min(k_MaxWindowWidth, Mathf.Max(titleWidth, k_MinWindowWidth)); + } + + m_NoResultsX = (m_WindowWidth - EditorStyles.label.CalcSize(Styles.noResultsLabel).x) * 0.5f; + } + + void CalculateOverrideCountsTimeSliced() + { + if (m_OverridesCounter.MoveNext()) + { + if (m_OverridesCounter.changedCount) + { + for (int i = 0; i < m_AncestorItems.Count(); ++i) + { + m_AncestorItems[i].overrideCount = m_OverridesCounter.GetCurrentOverrideCount(i); + } + + editorWindow.Repaint(); + } + } + else + { + EditorApplication.update -= CalculateOverrideCountsTimeSliced; + if (m_Debug) + Debug.Log("[PrefabFamilyPopup] Timesliced overrides calculation done"); + } + } + + public override Vector2 GetWindowSize() + { + if (m_Target == null) + { + editorWindow.Close(); + return Vector2.one; + } + + var height = k_HeaderHeight; + + if (isVariant) + { + // Horizontal scrollbar + if (m_NamesWidth > m_MaxNameWidth) + height += k_ScrollbarHeight; + + // Ancestors table + height += Mathf.Min(numRows, k_MaxTableRows) * k_EntryHeight + k_EntryHeight; + } + + // Children list + height += k_EntryHeight; + if (!isVariant || m_DisplayChildren) + height += k_SearchHeight; + + return new Vector2(m_WindowWidth, height); + } + + public override void OnGUI(Rect rect) + { + if (m_Target == null) + { + editorWindow.Close(); + return; + } + + // Escape closes the window + if (Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Escape) + { + editorWindow.Close(); + GUIUtility.ExitGUI(); + } + + DrawHeader(); + + float height = k_HeaderHeight; + if (isVariant) + height += DrawVariantHierarchy(); + height += DrawChildrenLabel(height); + + if (!isVariant || m_DisplayChildren) + DrawChildren(height); + } + + void DrawHeader() + { + Rect headerRect = GUILayoutUtility.GetRect(20, m_WindowWidth, k_HeaderHeight, k_HeaderHeight); + EditorGUI.DrawRect(headerRect, Colors.headerBackground); + + float labelSize = Styles.boldRightAligned.CalcSize(Styles.titlePrefixLabel).x; + + Rect labelRect = new Rect(k_OffsetX, headerRect.y + k_Padding, labelSize, EditorGUIUtility.singleLineHeight); + Rect contentRect = new Rect(labelRect.x + labelRect.width + k_Padding, labelRect.y, m_WindowWidth, labelRect.height); + + GUI.Label(labelRect, Styles.titlePrefixLabel, Styles.boldRightAligned); + DoObjectLabel(contentRect, AssetDatabase.GetAssetPath(m_Target), EditorStyles.boldLabel); + + labelRect.y = labelRect.height + 2 * k_Padding; + } + + float DrawVariantHierarchy() + { + // Draw table header + Rect entryRect = new Rect(0, k_HeaderHeight, m_WindowWidth, k_EntryHeight); + EditorGUI.DrawRect(entryRect, Colors.rowBackground(0)); + + var labelRect = entryRect; + labelRect.x = k_TitleWidth; + GUI.Label(labelRect, Styles.ancestorLabel, EditorStyles.miniLabel); + + labelRect.x = m_OverridesX + k_Padding; + GUI.Label(labelRect, Styles.overridesLabel, EditorStyles.miniLabel); + + float tableHeight = Mathf.Min(numRows, k_MaxTableRows) * k_EntryHeight + (m_NamesWidth > m_MaxNameWidth ? k_ScrollbarHeight : 0); + float realHeight = numRows * k_EntryHeight + (m_NamesWidth > m_MaxNameWidth ? k_ScrollbarHeight : 0); + var tableRect = new Rect(0, k_HeaderHeight + k_EntryHeight, m_WindowWidth - 1, tableHeight); + + m_Scroll.y = GUI.BeginScrollView(tableRect, m_Scroll, new Rect(tableRect) { width = tableRect.width - k_ScrollbarWidth, height = realHeight }).y; + { + // Draw overrides and locks table + for (int i = 0; i < m_AncestorItems.Length; i++) + { + entryRect.y = k_HeaderHeight + (i+1) * k_EntryHeight; + EditorGUI.DrawRect(entryRect, Colors.rowBackground(i+1)); + + DisplayOverridesAndLocks(entryRect, m_AncestorItems[m_AncestorItems.Length - 1 - i]); + } + + var scrollRect = new Rect(k_TitleWidth, k_HeaderHeight + k_EntryHeight, Mathf.Min(m_NamesWidth, m_MaxNameWidth), numRows * k_EntryHeight); + m_Scroll.x = GUI.BeginScrollView(new Rect(scrollRect) { height = scrollRect.height + k_ScrollbarHeight }, m_Scroll, new Rect(scrollRect) { width = m_NamesWidth }).x; + { + // Draw scrollable table + entryRect.x = k_TitleWidth; + for (int i = 0; i < m_AncestorItems.Length; i++) + { + entryRect.y = k_HeaderHeight + (i + 1) * k_EntryHeight; + var assetPath = m_AncestorItems[m_AncestorItems.Length - 1 - i].assetPath; + + DoObjectLabel(entryRect, assetPath); + } + } + GUI.EndScrollView(); + + + // Draw selected label + labelRect.x = k_OffsetX; + labelRect.y = k_HeaderHeight + numRows * k_EntryHeight; + labelRect.width = k_TitleWidth - labelRect.x; + GUI.Label(labelRect, Styles.selectedLabel); + + // Draw root label + if (labelRect.y != k_HeaderHeight + k_EntryHeight) + { + labelRect.y = k_HeaderHeight + k_EntryHeight; + GUI.Label(labelRect, Styles.rootLabel); + } + + // Draw vertical splits + Rect splitBar = new Rect(m_OverridesX - k_SplitWidth, k_HeaderHeight, k_SplitWidth, (numRows + 1) * k_EntryHeight); + EditorGUI.DrawRect(splitBar, Colors.headerBackground); + } + GUI.EndScrollView(); + + float height = tableHeight + k_EntryHeight; + return height; + } + + float DrawChildrenLabel(float yMin) + { + var labelRect = new Rect(k_OffsetX, yMin, 100, k_EntryHeight); + if (isVariant) + m_DisplayChildren = EditorGUI.Foldout(labelRect, m_DisplayChildren, Styles.childrenLabel, true); + else + EditorGUI.LabelField(labelRect, Styles.childrenLabel); + + if (!isVariant || m_DisplayChildren) + { + if (m_ListArea == null) + InitListArea(); + + labelRect = new Rect(labelRect.x + 70, labelRect.y + 2, k_SliderWidth, EditorGUI.kSingleLineHeight); + if (m_Results.Length != 0) + EditorGUI.LabelField(labelRect, m_Results.Length.ToString(), Styles.boldNumber); + + EditorGUI.BeginChangeCheck(); + labelRect.x = m_WindowWidth - k_OffsetX - k_SliderWidth; + var newGridSize = (int)GUI.HorizontalSlider(labelRect, m_ListArea.gridSize, m_ListArea.minGridSize, m_ListArea.maxGridSize); + if (EditorGUI.EndChangeCheck()) + { + m_ListArea.gridSize = m_SavedGridSize.value = newGridSize; + } + } + + return k_EntryHeight; + } + + void DrawChildren(float yMin) + { + int listKeyboardControlID = GUIUtility.GetControlID(FocusType.Keyboard); + + var backgroundRect = new Rect(0, yMin, m_WindowWidth, k_SearchHeight); + GUI.Label(backgroundRect, GUIContent.none, Styles.searchBackground); + + EditorGUI.BeginChangeCheck(); + var searchRect = new Rect(k_OffsetX + k_Padding, backgroundRect.y + k_Padding, m_WindowWidth - 2 * k_OffsetX - k_Padding, Styles.searchFieldStyle.fixedHeight); + m_SearchFilterString = EditorGUI.ToolbarSearchField(searchRect, m_SearchFilterString, false); + if (EditorGUI.EndChangeCheck()) + m_Debounce.Execute(); + + if (m_Enumerator != null) + Search(); + + yMin = searchRect.height + (m_ListArea.gridSize < k_MinIconSize ? 11f : 0f); + var listRect = new Rect(k_Padding, searchRect.y + yMin, m_WindowWidth - 2 * k_Padding, k_SearchHeight - yMin - k_Padding); + + m_ListArea.OnGUI(listRect, listKeyboardControlID); + + if (m_Enumerator == null && m_Results.Length == 0) + { + var labelRect = new Rect(m_NoResultsX, backgroundRect.y + 69f, m_WindowWidth, EditorGUI.kSingleLineHeight); + EditorGUI.LabelField(backgroundRect, m_SearchFilter.nameFilter.Length == 0 ? Styles.noChildrenLabel : Styles.noResultsLabel, Styles.centered); + } + } + + void InitListArea() + { + m_ListAreaState.m_GridSize = m_SavedGridSize; + m_ListArea = new ObjectListArea(m_ListAreaState, editorWindow, false) + { + allowDeselection = true, + allowMultiSelect = false, + allowRenaming = false, + allowBuiltinResources = true, + }; + + m_ListArea.itemSelectedCallback += (bool doubleClicked) => + { + if (m_ListArea.GetSelection().Length == 0) + return; + var selection = m_ListArea.GetSelection()[0]; + if (doubleClicked) + { + Selection.SetActiveObjectWithContext(EditorUtility.InstanceIDToObject(selection), null); + Event.current.Use(); + editorWindow.Close(); + GUIUtility.ExitGUI(); + } + else + { + EditorGUIUtility.PingObject(selection); + Event.current.Use(); + } + }; + + SearchFilterChanged(); + } + + static IEnumerator FindInAllAssets(SearchFilter searchFilter) + { + var rootPaths = new List(); + rootPaths.Add("Assets"); + foreach (var package in PackageManagerUtilityInternal.GetAllVisiblePackages(false)) + { + if (package.source == PackageManager.PackageSource.Local) + rootPaths.Add(package.assetPath); + } + + foreach (var rootPath in rootPaths) + { + var property = new HierarchyProperty(rootPath, false); + property.SetSearchFilter(searchFilter); + while (property.Next(null)) + yield return property; + } + } + + void SearchFilterChanged() + { + m_SearchFilter.nameFilter = m_SearchFilterString; + + var size = GetWindowSize(); + var rect = new Rect(0, size.y - k_SearchHeight, size.x, k_SearchHeight); + + m_ListArea.Init(rect, HierarchyType.Assets, new SearchFilter(), true, SearchService.SearchSessionOptions.Default); + m_Enumerator = FindInAllAssets(m_SearchFilter); + m_Results = new int[0]; + } + + void Search() + { + var newResults = new List(); + + var maxAddCount = k_MaxSearchIterationPerFrame; + while (--maxAddCount >= 0) + { + if (!m_Enumerator.MoveNext()) + { + m_Enumerator = null; + break; + } + + var currentInstanceID = m_Enumerator.Current.instanceID; + var variantParentGUID = PrefabUtility.GetVariantParentGUID(currentInstanceID); + if (variantParentGUID == m_TargetGUID) + { + newResults.Add(currentInstanceID); + } + } + + int newCount = newResults.Count; + int i = m_Results.Length; + Array.Resize(ref m_Results, m_Results.Length + newCount); + for (var j = 0; j < newCount && i < m_Results.Length; ++j, ++i) + m_Results[i] = newResults[j]; + + m_ListArea.ShowObjectsInList(m_Results); + } + + void DisplayOverridesAndLocks(Rect rect, AncestorItem item) + { + rect.x = m_OverridesX; + rect.width = k_OverridesWidth; + string text; + if (item.overrideCount == -1) + text = string.Empty; + else if (item.overrideCount == 0) + text = "-"; + else if (item.overrideCount >= 10000) + text = "+9999"; + else + text = item.overrideCount.ToString(); + GUI.Label(rect, GUIContent.Temp(text, item.overrideCount.ToString()), Styles.centered); + } + + void DoObjectLabel(Rect rect, string assetPath) + { + DoObjectLabel(rect, assetPath, GUI.skin.label); + } + + void DoObjectLabel(Rect rect, string assetPath, GUIStyle style) + { + if (Event.current.type == EventType.MouseDown && Event.current.button == 0 && rect.Contains(Event.current.mousePosition)) + { + // One click shows where the referenced object is + if (Event.current.clickCount == 1) + { + GUIUtility.keyboardControl = GUIUtility.GetControlID(FocusType.Keyboard); + + var instanceID = AssetDatabase.GetMainAssetInstanceID(assetPath); + EditorGUIUtility.PingObject(instanceID); + Event.current.Use(); + } + // Double click changes selection to referenced object + else if (Event.current.clickCount == 2) + { + var instanceID = AssetDatabase.GetMainAssetInstanceID(assetPath); + Selection.activeInstanceID = instanceID; + Event.current.Use(); + editorWindow.Close(); + GUIUtility.ExitGUI(); + } + } + + var icon = AssetDatabase.GetCachedIcon(assetPath); + var name = System.IO.Path.GetFileNameWithoutExtension(assetPath); + GUI.Label(rect, EditorGUIUtility.TempContent(name, icon), style); + } + } + + [Serializable] + class PrefabAssetInfo + { + [SerializeField] + public Hash128 prefabFileHash; + + [SerializeField] + public int overrideCount; + } + + static class PrefabAssetStateCache + { + static StateCache s_StateCache = new StateCache("Library/StateCache/PrefabAssetInfo/"); + + public static void SetState(string guid, PrefabAssetInfo obj) + { + var key = GetPrefabAssetIdentifier(guid); + s_StateCache.SetState(key, obj); + } + + public static PrefabAssetInfo GetState(string guid) + { + var id = GetPrefabAssetIdentifier(guid); + var cachedPrefabAssetInfo = s_StateCache.GetState(id); + if (cachedPrefabAssetInfo == null) + return null; // not cached yet + + var currentPrefabSourceFileHash = AssetDatabase.GetSourceAssetFileHash(guid); + if (currentPrefabSourceFileHash != cachedPrefabAssetInfo.prefabFileHash) + return null; // cache is out of sync + + return cachedPrefabAssetInfo; + } + public static Hash128 GetPrefabAssetIdentifier(string guid) + { + if (string.IsNullOrEmpty(guid)) + throw new ArgumentNullException(nameof(guid)); + + return Hash128.Compute(guid); + } + } + + class OverridesCounterForPrefabAssets + { + List m_PrefabAssetRoots; + List m_OverridesCount; + int m_CurrentAssetIndex; + int m_CurrentStep; + bool m_ChangedCount; + + bool m_Debug = false; + + public OverridesCounterForPrefabAssets(List prefabInstanceRoots) + { + m_PrefabAssetRoots = prefabInstanceRoots; + m_OverridesCount = new List(); + m_OverridesCount.AddRange(new int[prefabInstanceRoots.Count]); + } + + public bool changedCount => m_ChangedCount; + + public int GetCurrentOverrideCount(int assetIndex) + { + return m_OverridesCount[assetIndex]; + } + + bool IsDone() + { + return m_CurrentAssetIndex >= m_PrefabAssetRoots.Count -1; // No more prefabs to process (last prefab is the root prefab which does not have overrides) + } + + // Returns true if overrides count changed during the update + public bool MoveNext() + { + if (IsDone()) + return false; + + var assetRoot = m_PrefabAssetRoots[m_CurrentAssetIndex]; + var startCount = m_OverridesCount[m_CurrentAssetIndex]; + + switch (m_CurrentStep) + { + case 0: + { + // First check if we have cached overides count for the prefab + var path = AssetDatabase.GetAssetPath(assetRoot); + var guid = AssetDatabase.AssetPathToGUID(path); + var cachedPrefabAssetInfo = PrefabAssetStateCache.GetState(guid); + if (cachedPrefabAssetInfo != null) + { + m_OverridesCount[m_CurrentAssetIndex] = cachedPrefabAssetInfo.overrideCount; + m_ChangedCount = true; + m_CurrentAssetIndex++; + if (m_Debug) + Debug.Log($"[OverridesCounterForPrefabAssets] Using cached overridecount {cachedPrefabAssetInfo.overrideCount} ({path})"); + return true; + } + } + break; + case 1: + PropertyModification[] mods = PrefabUtility.GetPropertyModifications(assetRoot); + foreach (PropertyModification mod in mods) + { + if (mod.target == null) + continue; + + if (!PrefabUtility.IsDefaultOverride(mod)) + m_OverridesCount[m_CurrentAssetIndex]++; + } + break; + case 2: + m_OverridesCount[m_CurrentAssetIndex] += PrefabOverridesUtility.GetAddedComponents(assetRoot).Count; + break; + case 3: + m_OverridesCount[m_CurrentAssetIndex] += PrefabOverridesUtility.GetRemovedComponents(assetRoot).Count; + break; + case 4: + m_OverridesCount[m_CurrentAssetIndex] += PrefabOverridesUtility.GetAddedGameObjects(assetRoot).Count; + break; + default: + { + // Cache result + var path = AssetDatabase.GetAssetPath(assetRoot); + var guid = AssetDatabase.AssetPathToGUID(path); + var prefabAssetInfo = new PrefabAssetInfo(); + prefabAssetInfo.overrideCount = m_OverridesCount[m_CurrentAssetIndex]; + prefabAssetInfo.prefabFileHash = AssetDatabase.GetSourceAssetFileHash(guid); + PrefabAssetStateCache.SetState(guid, prefabAssetInfo); + if (m_Debug) + Debug.Log($"[OverridesCounterForPrefabAssets] Set cached overridecount {prefabAssetInfo.overrideCount} for {path}"); + + // Move to next asset + m_CurrentAssetIndex++; + m_CurrentStep = 0; + } + break; + } + + if (m_Debug) + Debug.Log($"[OverridesCounterForPrefabAssets] Current asset index: {m_CurrentAssetIndex}, current step: {m_CurrentStep}"); + + m_CurrentStep++; + + // Simulate heavy calculation + if (m_Debug) + System.Threading.Thread.Sleep(500); + + m_ChangedCount = m_OverridesCount[m_CurrentAssetIndex] != startCount; + + return true; + } + } +} diff --git a/Editor/Mono/Prefabs/PrefabImporterEditor.cs b/Editor/Mono/Prefabs/PrefabImporterEditor.cs index 8bfce9f342..7919ffb1ab 100644 --- a/Editor/Mono/Prefabs/PrefabImporterEditor.cs +++ b/Editor/Mono/Prefabs/PrefabImporterEditor.cs @@ -19,10 +19,12 @@ static class Styles public static GUIContent missingSerializeReferenceHelpText = EditorGUIUtility.TrTextContent("Prefab has missing SerializeReference Types. Open Prefab to fix the issue. Changing the Prefab directly will cause those types to be lost."); public static GUIContent multiSelectionMissingScriptsHelpText = EditorGUIUtility.TrTextContent("Some of the selected Prefabs have missing scripts and needs to be fixed before editing them. Click to Open Prefab to fix the issue."); public static GUIContent savingFailedHelpText = EditorGUIUtility.TrTextContent("Saving has failed. Check the Console window to get more insight into what needs to be fixed on the Prefab Asset.\n\nOpen Prefab to fix the issue."); - public static GUIContent baseContent = EditorGUIUtility.TrTextContent("Base"); + public static GUIContent variantOfText = EditorGUIUtility.TrTextContent("Variant Parent"); public static string localizedTitleMultiplePrefabs = L10n.Tr("Prefab Assets"); public static string localizedTitleSinglePrefab = L10n.Tr("Prefab Asset"); public static GUIStyle openButtonStyle = "AC Button"; + public static readonly GUIContent hierarchyIcon = EditorGUIUtility.IconContent("UnityEditor.SceneHierarchyWindow"); + public const int kHierarchyIconWidth = 44; } int m_HasMixedBaseVariants = -1; @@ -246,15 +248,15 @@ void CacheHasMixedBaseVariants() if (m_HasMixedBaseVariants >= 0) return; // already cached - var firstBaseVarient = PrefabUtility.GetCorrespondingObjectFromSource(assetTarget); - if (firstBaseVarient == null) + var firstVariantParent = PrefabUtility.GetCorrespondingObjectFromSource(assetTarget); + if (firstVariantParent == null) return; m_HasMixedBaseVariants = 0; foreach (var t in assetTargets) { - var variantBase = PrefabUtility.GetCorrespondingObjectFromSource(t); - if (variantBase != firstBaseVarient) + var variantParent = PrefabUtility.GetCorrespondingObjectFromSource(t); + if (variantParent != firstVariantParent) { m_HasMixedBaseVariants = 1; break; @@ -275,24 +277,60 @@ internal override string targetTitle } } - public override void OnInspectorGUI() + void PrefabFamilyButton() { - if (assetTarget is DefaultAsset) + if (EditorGUILayout.DropdownButton(GUIContent.none, FocusType.Passive, GUILayout.MaxWidth(Styles.kHierarchyIconWidth))) { - return; + if (!PrefabFamilyPopup.isOpen) + PopupWindow.Show(GUILayoutUtility.topLevel.GetLast(), new PrefabFamilyPopup((GameObject)assetTarget)); + GUIUtility.ExitGUI(); + } + var rect = new Rect(GUILayoutUtility.topLevel.GetLast()); + rect.x += 6; + EditorGUI.LabelField(rect, Styles.hierarchyIcon); + } + + internal override void OnHeaderControlsGUI() + { + GUILayout.FlexibleSpace(); + + using (new EditorGUI.DisabledScope(targets.Length != 1)) + { + PrefabFamilyButton(); + } + + if (!ShouldHideOpenButton()) + { + var assets = assetTargets; + ShowOpenButton(assets, assetTarget != null); } - var variantBase = PrefabUtility.GetCorrespondingObjectFromSource(assetTarget); - if (variantBase != null) + var variantParent = PrefabUtility.GetCorrespondingObjectFromSource(assetTarget) as GameObject; + if (variantParent != null) { + // OnHeaderControlsGUI() is called within a BeginHorizontal() scope so to create a new line + // we end and start a new BeginHorizontal(). + EditorGUILayout.EndHorizontal(); + EditorGUILayout.BeginHorizontal(); using (new EditorGUI.DisabledScope(true)) { CacheHasMixedBaseVariants(); EditorGUI.showMixedValue = m_HasMixedBaseVariants == 1; - EditorGUILayout.ObjectField(Styles.baseContent, variantBase, typeof(GameObject), false); + var oldLabelWidth = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth = 90; + EditorGUILayout.ObjectField(Styles.variantOfText, variantParent, typeof(GameObject), false); + EditorGUIUtility.labelWidth = oldLabelWidth; EditorGUI.showMixedValue = false; } } + } + + public override void OnInspectorGUI() + { + if (assetTarget is DefaultAsset) + { + return; + } if (hasMissingScripts) { diff --git a/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesUtility.cs b/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesUtility.cs index 560493719b..137884a4d5 100644 --- a/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesUtility.cs +++ b/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesUtility.cs @@ -171,6 +171,10 @@ static bool CheckForRemovedComponents(Transform transform, object userData) // Find asset objects that no instance objects are referencing foreach (var assetComponent in s_AssetComponentList) { + // This is possible if there's a component with a missing script. + if (assetComponent == null) + continue; + bool found = false; foreach (var instanceComponent in s_ComponentList) { diff --git a/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesWindow.cs b/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesWindow.cs index 387308d12f..b20f39a601 100644 --- a/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesWindow.cs +++ b/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesWindow.cs @@ -22,9 +22,8 @@ internal class PrefabOverridesWindow : PopupWindowContent const float k_HeaderLeftMargin = 6; const float k_NoOverridesLabelHeight = 26f; const float k_UnusedOverridesButtonHeight = 26f; - const float k_ApplyButtonHeight = 36f; - const float k_HelpBoxHeight = 40f; const float k_RowPadding = 6; + float m_ApplyButtonHeight = 0; GameObject[] m_SelectedGameObjects = null; // TreeView not used when there are multiple Prefabs. @@ -53,8 +52,8 @@ static class Styles public static GUIContent revertSelectedContent = EditorGUIUtility.TrTextContent("Revert Selected", "Revert selected overrides."); public static GUIContent applyAllContent = EditorGUIUtility.TrTextContent("Apply All", "Apply all overrides to Prefab source '{0}'."); public static GUIContent applySelectedContent = EditorGUIUtility.TrTextContent("Apply Selected", "Apply selected overrides to Prefab source '{0}'."); - public static GUIContent applyAllToBaseContent = EditorGUIUtility.TrTextContent("Apply All to Base", "Apply all overrides to base Prefab source '{0}'."); - public static GUIContent applySelectedToBaseContent = EditorGUIUtility.TrTextContent("Apply Selected to Base", "Apply selected overrides to base Prefab source '{0}'."); + public static GUIContent applyAllToBaseContent = EditorGUIUtility.TrTextContent("Apply All to Prefab Variant parent", "Apply all overrides to Prefab Variant parent '{0}'."); + public static GUIContent applySelectedToBaseContent = EditorGUIUtility.TrTextContent("Apply Selected to Prefab Variant parent", "Apply selected overrides to Prefab Variant parent '{0}'."); public static GUIContent titleLabelDefault = EditorGUIUtility.TrTextContent("Review, Revert or Apply Overrides"); public static GUIContent titleLabelNoApply = EditorGUIUtility.TrTextContent("Review or Revert Overrides"); public static GUIContent noOverridesText = EditorGUIUtility.TrTextContent("No overrides"); @@ -62,7 +61,8 @@ static class Styles public static GUIContent contextLabel = EditorGUIUtility.TrTextContent("in"); public static GUIContent removeUnusedOverridesButtonContent = EditorGUIUtility.TrTextContentWithIcon("Unused overrides", EditorGUIUtility.LoadIcon("Clear")); - public static string nonApplicableTooltip = L10n.Tr("There are no overrides that can be applied to Prefab source '{0}'."); + public static string nonApplicableTooltipApply = L10n.Tr("There are no overrides that can be applied to Prefab source '{0}'."); + public static string nonApplicableTooltipRevert = L10n.Tr("There are no overrides that can be reverted."); public static GUIContent infoMultiple = EditorGUIUtility.TrTextContent("Multiple Prefabs selected. Cannot show overrides."); public static GUIContent infoMultipleNoApply = EditorGUIUtility.TrTextContent("Multiple Prefabs selected. Cannot show overrides.\nApplying is not possible for one or more Prefabs. Select individual Prefabs for details."); @@ -116,17 +116,17 @@ internal PrefabOverridesWindow(GameObject[] selectedGameObjects) public override void OnOpen() { - Undo.undoRedoPerformed += OnUndoRedoPerformed; + Undo.undoRedoEvent += OnUndoRedoPerformed; base.OnOpen(); } public override void OnClose() { - Undo.undoRedoPerformed -= OnUndoRedoPerformed; + Undo.undoRedoEvent -= OnUndoRedoPerformed; base.OnClose(); } - void OnUndoRedoPerformed() + void OnUndoRedoPerformed(in UndoRedoInfo info) { RefreshStatus(); } @@ -160,15 +160,6 @@ internal void RefreshStatus(bool reloadTreeView = true) else UpdateTextMultiple(); - // There are a few cases where the Tree View reports no overrides even though - // PrefabUtility.HasPrefabInstanceAnyOverrides says there are; for example if - // a component has been removed on an instance, but also removed on the Asset. - // In these cases we want to make the UI not show apply/revert buttons, - // since it's confusing and inconsistent to have those when the view says - // "No overrides". Case 1197800. - if (m_TreeView != null && !m_TreeView.hasModifications) - m_AnyOverrides = false; - m_UnusedOverridesExist = PrefabUtility.HavePrefabInstancesUnusedOverrides(m_SelectedGameObjects); } @@ -211,11 +202,6 @@ bool HasMultiSelection() return m_SelectedGameObjects.Length > 1; } - bool DisplayingTreeView() - { - return m_AnyOverrides && !HasMultiSelection(); - } - bool IsShowingApplyWarning() { return @@ -226,52 +212,86 @@ bool IsShowingApplyWarning() } public override Vector2 GetWindowSize() + { + return CalculateWindowSize(); + } + + Vector2 CalculateWindowSize() { const float k_MaxAllowedTreeViewWidth = 1800f; const float k_MaxAllowedTreeViewHeight = 1000f; var width = 300f; var height = k_HeaderHeight; + // Match the call order as in OnGUI() to ensure the correct height is calculated + if (!IsShowingActionButton()) { - if (!IsShowingUnusedOverridesButton()) + if (IsShowingUnusedOverridesButton()) + height += k_UnusedOverridesButtonHeight; + else height += k_NoOverridesLabelHeight; + + return new Vector2(width, height); + } + + if (HasMultiSelection()) + { + if (m_InvalidComponentOnAsset || m_HasManagedReferencesWithMissingTypesOnAsset || m_InvalidComponentOnInstance || m_ModelPrefab || m_Immutable) + height += CalcHeightForHelpBox(Styles.infoMultipleNoApply, MessageType.Info, width); + else + height += CalcHeightForHelpBox(Styles.infoMultiple, MessageType.Info, width); + + if (m_UnusedOverridesExist) + height += k_UnusedOverridesButtonHeight + k_RowPadding; + else + height += 2; } else { - if (DisplayingTreeView()) + height += k_TreeViewPadding.top; + if (m_TreeView.hasModifications) { - height += k_TreeViewPadding.top + Mathf.Min(k_MaxAllowedTreeViewHeight, m_TreeView.totalHeight) + k_TreeViewPadding.bottom; + height += Mathf.Min(k_MaxAllowedTreeViewHeight, m_TreeView.totalHeight); width = Mathf.Max(Mathf.Min(m_TreeView.maxItemWidth, k_MaxAllowedTreeViewWidth), width); } - else + + if (m_AnyOverrides) { - if (!m_AnyOverrides) - height += k_NoOverridesLabelHeight; - else if (IsShowingUnusedOverridesButton()) - height += k_HelpBoxHeight - 8; - else - height += k_HelpBoxHeight - 6; - } + if (m_UnusedOverridesExist) + height += k_UnusedOverridesButtonHeight + k_RowPadding; - height += k_ApplyButtonHeight; + if (m_ModelPrefab) + height += CalcHeightForHelpBox(Styles.infoModel, MessageType.Info, width); + } if (IsShowingApplyWarning()) - height += k_HelpBoxHeight; // A second help box in this case. + { + if (m_InvalidComponentOnAsset) + height += CalcHeightForHelpBox(Styles.warningInvalidAsset, MessageType.Warning, width); + else if (m_InvalidComponentOnInstance) + height += CalcHeightForHelpBox(Styles.warningInvalidInstance, MessageType.Warning, width); + else if (m_HasManagedReferencesWithMissingTypesOnAsset) + height += CalcHeightForHelpBox(Styles.warningHasManagedReferencesWithMissingTypes, MessageType.Warning, width); + else if (m_Immutable) + height += CalcHeightForHelpBox(Styles.warningImmutable, MessageType.Warning, width); + } } - if (IsShowingUnusedOverridesButton()) - { - height += k_UnusedOverridesButtonHeight; - if (IsShowingActionButton()) - height += k_RowPadding; - } + if (m_ApplyButtonHeight == 0) + m_ApplyButtonHeight = GUI.skin.button.CalcHeight(Styles.applySelectedContent, width) + GUI.skin.button.margin.top + GUI.skin.button.margin.bottom; + + height += m_ApplyButtonHeight + k_RowPadding; - // Width should be no smaller than minimum width, but we could potentially improve - // width handling by making it expand if needed based on tree view content. return new Vector2(width, height); } + float CalcHeightForHelpBox(GUIContent content, MessageType messageType, float width) + { + var tempContent = EditorGUIUtility.TempContent(content.text, EditorGUIUtility.GetHelpIcon(messageType)); + return EditorStyles.helpBox.CalcHeight(tempContent, width) + EditorStyles.helpBox.margin.top + EditorStyles.helpBox.margin.bottom; + } + Color headerBgColor { get { return EditorGUIUtility.isProSkin ? new Color(0.5f, 0.5f, 0.5f, 0.2f) : new Color(0.9f, 0.9f, 0.9f, 0.6f); } } Color horizontalLineColor = new Color(0.5f, 0.5f, 0.5f, 0.3f); public override void OnGUI(Rect rect) @@ -356,11 +376,14 @@ public override void OnGUI(Rect rect) { GUILayout.Space(k_TreeViewPadding.top); - if (m_AnyOverrides) + if (m_TreeView.hasModifications) { Rect treeViewRect = GUILayoutUtility.GetRect(100, 10000, 0, 10000); m_TreeView.OnGUI(treeViewRect); + } + if (m_AnyOverrides) + { if (m_UnusedOverridesExist) { DrawUnusedOverridesButton(); @@ -380,9 +403,9 @@ public override void OnGUI(Rect rect) EditorGUILayout.HelpBox(Styles.warningInvalidAsset.text, MessageType.Warning); else if (m_InvalidComponentOnInstance) EditorGUILayout.HelpBox(Styles.warningInvalidInstance.text, MessageType.Warning); - if (m_HasManagedReferencesWithMissingTypesOnAsset) + else if (m_HasManagedReferencesWithMissingTypesOnAsset) EditorGUILayout.HelpBox(Styles.warningHasManagedReferencesWithMissingTypes.text, MessageType.Warning); - else + else if (m_Immutable) EditorGUILayout.HelpBox(Styles.warningImmutable.text, MessageType.Warning); } } @@ -453,6 +476,17 @@ void DrawUnusedOverridesButton() { Rect buttonRect = GUILayoutUtility.GetRect(100, 10000, k_NoOverridesLabelHeight, k_NoOverridesLabelHeight); + var isHovered = buttonRect.Contains(UnityEngine.Event.current.mousePosition); + if (isHovered) + { + GUIView.current.MarkHotRegion(GUIClip.UnclipToWindow(buttonRect)); + + using (new GUI.BackgroundColorScope(GameObjectStyles.hoveredBackgroundColor)) + { + GUI.Label(buttonRect, GUIContent.none, GameObjectStyles.hoveredItemBackgroundStyle); + } + } + if (Event.current.type == EventType.Repaint) { if (UnusedOverridesViewPopup.s_IsOpen) @@ -467,17 +501,6 @@ void DrawUnusedOverridesButton() Styles.removeOverridesButtonLineStyle.Draw(buttonRect, Styles.removeUnusedOverridesButtonContent, false, false, UnusedOverridesViewPopup.s_IsOpen, true); } - var isHovered = buttonRect.Contains(UnityEngine.Event.current.mousePosition); - if (isHovered) - { - GUIView.current.MarkHotRegion(GUIClip.UnclipToWindow(buttonRect)); - - using (new GUI.BackgroundColorScope(GameObjectStyles.hoveredBackgroundColor)) - { - GUI.Label(buttonRect, GUIContent.none, GameObjectStyles.hoveredItemBackgroundStyle); - } - } - if (GUI.Button(buttonRect, GUIContent.none, GUIStyle.none)) { PopupWindowWithoutFocus.Show(buttonRect, @@ -621,18 +644,24 @@ void UpdateText(Texture assetIcon, string assetName) m_ButtonWidth = k_ButtonWidth; var applyAllContent = new GUIContent(Styles.applyAllContent); + var revertAllContent = new GUIContent(Styles.revertAllContent); var applySelectedContent = Styles.applySelectedContent; if (stage is PrefabStage && PrefabUtility.IsPartOfVariantPrefab(AssetDatabase.LoadAssetAtPath(stage.assetPath))) { m_ButtonWidth = k_ButtonWidthVariant; applyAllContent = Styles.applyAllToBaseContent; applySelectedContent = Styles.applySelectedToBaseContent; + } if (!m_HasApplicableOverrides) - applyAllContent.tooltip = Styles.nonApplicableTooltip; + { + applyAllContent.tooltip = Styles.nonApplicableTooltipApply; + revertAllContent.tooltip = Styles.nonApplicableTooltipRevert; + } m_ApplyAllContent = new GUIContent(applyAllContent.text, string.Format(applyAllContent.tooltip, assetName)); + m_RevertAllContent = new GUIContent(revertAllContent.text, revertAllContent.tooltip); m_ApplySelectedContent.text = applySelectedContent.text; m_ApplySelectedContent.tooltip = string.Format(applySelectedContent.tooltip, assetName); } @@ -665,6 +694,8 @@ static class Styles public static GUIContent editorLogHint = EditorGUIUtility.TrTextContent("Details will be written to the Editor log."); public static GUIContent buttonContent = EditorGUIUtility.TrTextContent("Remove"); public static GUIContent headerContent = EditorGUIUtility.TrTextContent("{0} unused overrides"); + public static GUIContent unusedRemovedComponentsContentSingular = EditorGUIUtility.TrTextContent("{0} has 1 unused removed component"); + public static GUIContent unusedRemovedComponentsContentPlural = EditorGUIUtility.TrTextContent("{0} has {1} unused removed components"); public static GUIContent headerContentSingular = EditorGUIUtility.TrTextContent("1 unused override"); public static GUIContent extraOverridesContent = EditorGUIUtility.TrTextContent("and {0} others"); public static GUIContent extraInstancesContent = EditorGUIUtility.TrTextContent("on {0} instances"); @@ -702,6 +733,7 @@ static Styles() int m_AffectedInstanceCount = 0; int m_UnusedOverridesCount = 0; int m_UsedOverridesCount = 0; + int m_UnusedRemovedComponentsCount = 0; public UnusedOverridesViewPopup(GameObject[] selectedGameObjects, PrefabOverridesWindow owner) { @@ -804,12 +836,13 @@ void CalculateStatistics() { foreach (PrefabUtility.InstanceOverridesInfo instanceMods in m_InstanceOverridesInfos) { - if (!instanceMods.unusedMods.Any()) + if (!instanceMods.unusedMods.Any() && instanceMods.unusedRemovedComponentCount == 0) continue; m_AffectedInstanceCount++; m_UnusedOverridesCount += instanceMods.unusedMods.Length; m_UsedOverridesCount += instanceMods.usedMods.Length; + m_UnusedRemovedComponentsCount += instanceMods.unusedRemovedComponentCount; m_SingleInstanceWithUnusedMods = instanceMods; } } @@ -818,7 +851,10 @@ void CalculateStatistics() m_AffectedInstanceCount = 1; m_UnusedOverridesCount = m_SingleInstanceWithUnusedMods.unusedMods.Length; m_UsedOverridesCount = m_SingleInstanceWithUnusedMods.usedMods.Length; + m_UnusedRemovedComponentsCount = m_SingleInstanceWithUnusedMods.unusedRemovedComponentCount; } + + m_UnusedOverridesCount += m_UnusedRemovedComponentsCount; } float BuildHeaderText() @@ -862,6 +898,27 @@ float BuildMultilineSummary() addedLines = true; } + if (instanceMods.unusedRemovedComponentCount > 0 && totalLineEntries < k_MaxEntries) + { + string itemText = string.Empty; + + if (instanceMods.unusedRemovedComponentCount > 1) + itemText = string.Format(Styles.unusedRemovedComponentsContentPlural.text, instanceMods.instance.name, instanceMods.unusedRemovedComponentCount); + else + itemText = string.Format(Styles.unusedRemovedComponentsContentSingular.text, instanceMods.instance.name); + + GUIContent lineContent = new GUIContent(itemText); + m_OverridesContent.Add(lineContent); + + float w = GetTextWidth(lineContent.text, Styles.bodyStyle); + if (w > maxLineWidth) + maxLineWidth = w; + + entriesFromThisInstance++; + totalLineEntries++; + addedLines = true; + } + if (addedLines && instanceMods.unusedMods.Length <= entriesFromThisInstance) remainingAffectedInstanceCount--; } diff --git a/Editor/Mono/Prefabs/PrefabUtility.bindings.cs b/Editor/Mono/Prefabs/PrefabUtility.bindings.cs index a112cc2919..e3ffb22ba7 100644 --- a/Editor/Mono/Prefabs/PrefabUtility.bindings.cs +++ b/Editor/Mono/Prefabs/PrefabUtility.bindings.cs @@ -56,6 +56,9 @@ public sealed partial class PrefabUtility [StaticAccessor("PrefabUtilityBindings", StaticAccessorType.DoubleColon)] extern internal static bool HasPrefabInstanceUnusedOverrides_Internal(GameObject gameObject); + [StaticAccessor("PrefabUtilityBindings", StaticAccessorType.DoubleColon)] + extern internal static int GetPrefabInstanceUnusedRemovedComponentCount_Internal(GameObject gameObject); + [StaticAccessor("PrefabUtilityBindings", StaticAccessorType.DoubleColon)] extern internal static string TryGetCurrentPropertyPathFromOldPropertyPath_Internal(GameObject gameObject, Object target, string propertyPath); @@ -108,13 +111,17 @@ public sealed partial class PrefabUtility [NativeThrows] extern private static void MergePrefabInstance_internal([NotNull] Object gameObjectOrComponent); + [StaticAccessor("PrefabUtilityBindings", StaticAccessorType.DoubleColon)] + [NativeThrows] + extern private static MergeStatus GetMergeStatus(GameObject componentOrGameObject); + [StaticAccessor("PrefabUtilityBindings", StaticAccessorType.DoubleColon)] [NativeThrows] extern private static GameObject[] FindAllInstancesOfPrefab_internal([NotNull("NullExceptionObject")] GameObject prefabRoot, int sceneHandle); [StaticAccessor("PrefabUtilityBindings", StaticAccessorType.DoubleColon)] [NativeThrows] - extern public static GameObject[] UnpackPrefabInstanceAndReturnNewOutermostRoots(GameObject instanceRoot, PrefabUnpackMode unpackMode); + extern private static GameObject[] UnpackPrefabInstanceAndReturnNewOutermostRoots_internal(GameObject instanceRoot, PrefabUnpackMode unpackMode); [StaticAccessor("PrefabUtilityBindings", StaticAccessorType.DoubleColon)] [NativeThrows] @@ -362,7 +369,13 @@ internal static void AddGameObjectsToPrefabAndConnect(GameObject[] gameObjects, [FreeFunction] extern internal static bool CheckIfAddingPrefabWouldResultInCyclicNesting(Object prefabAssetThatIsAddedTo, Object prefabAssetThatWillBeAdded); + [FreeFunction] + extern internal static bool WasCreatedAsPrefabInstancePlaceholderObject(Object componentOrGameObject); + [FreeFunction] extern internal static void ShowCyclicNestingWarningDialog(); + + [NativeMethod("PrefabUtilityBindings::GetVariantParentGUID_Internal", IsFreeFunction = true, ThrowsException = true)] + extern internal static string GetVariantParentGUID(int instanceID); } } diff --git a/Editor/Mono/Prefabs/PrefabUtility.cs b/Editor/Mono/Prefabs/PrefabUtility.cs index 23b0f0a117..9375929e47 100644 --- a/Editor/Mono/Prefabs/PrefabUtility.cs +++ b/Editor/Mono/Prefabs/PrefabUtility.cs @@ -14,6 +14,7 @@ using Object = UnityEngine.Object; using RequiredByNativeCodeAttribute = UnityEngine.Scripting.RequiredByNativeCodeAttribute; using UnityEditor.VersionControl; +using UnityEngine.Scripting; namespace UnityEditor { @@ -79,6 +80,15 @@ public enum ReplacePrefabOptions ReplaceNameBased = 2, } + // This must match C++ MergeStatus + internal enum MergeStatus + { + NotMerged, // Initial state, before trying to merge + NormalMerge, // Prefab source was found and merged successfully + MergedAsMissing, // Prefab source was missing and the Prefab couldn't be merged + MergedAsMissingWithSceneBackup // Prefab source was missing, but Prefab data was found in the scene file - no merging was done + } + internal delegate void AddApplyMenuItemDelegate(GUIContent menuItem, Object sourceObject, Object instanceOrAssetObject); public sealed partial class PrefabUtility @@ -924,6 +934,20 @@ public static void RevertObjectOverride(Object instanceComponentOrGameObject, In } } + static bool DidComponentOrderChange(Component[] originalComponentOrder, Component[] newComponentOrder) + { + if (originalComponentOrder.Length != newComponentOrder.Length) + return true; + + for (int i = 0; i < originalComponentOrder.Length; ++i) + { + if (originalComponentOrder[i] != newComponentOrder[i]) + return true; + } + + return false; + } + public static void ApplyAddedComponent(Component component, string assetPath, InteractionMode action) { DateTime startTime = DateTime.UtcNow; @@ -942,6 +966,8 @@ public static void ApplyAddedComponent(Component component, string assetPath, In if (prefabSourceGameObject == null) return; + var originalComponentOrder = component.gameObject.GetComponents(); + var actionName = "Apply Added Component"; if (action == InteractionMode.UserAction) { @@ -974,6 +1000,13 @@ public static void ApplyAddedComponent(Component component, string assetPath, In PrefabUtility.ApplyAddedComponent(coupledComponent, prefabSourceGameObject); Undo.RegisterCreatedObjectUndo(GetCorrespondingObjectFromOriginalSource(coupledComponent), actionName); } + + var postApplyComponentOrder = component.gameObject.GetComponents(); + bool orderChanged = DidComponentOrderChange(originalComponentOrder, postApplyComponentOrder); + if (orderChanged) + { + EditorUtility.DisplayDialog(L10n.Tr("Notice!"), L10n.Tr("Some component(s) changed position because of other added components in the variant/nesting chain."), L10n.Tr("OK")); + } } } catch (ArgumentException exception) @@ -1034,10 +1067,14 @@ public static void RevertAddedComponent(Component component, InteractionMode act Undo.DestroyObjectImmediate(coupledComponent); } else - Object.DestroyImmediate(component); + Object.DestroyImmediate(component, true); - // Remerge the prefab instance to make any suppressed components show up if no longer suppressed - PrefabUtility.MergePrefabInstance_internal(prefabInstanceGameObject); + // Remerge the Prefab instance to make any suppressed components show up if no longer suppressed + // For the Prefab assets this is not necessary because they will be remerged by the importer when saved + if (!PrefabUtility.IsPartOfPrefabAsset(prefabInstanceGameObject)) + { + PrefabUtility.MergePrefabInstance_internal(prefabInstanceGameObject); + } } private static bool IsPrefabInstanceObjectOf(Object instance, Object source) @@ -1067,9 +1104,9 @@ internal static void RemoveRemovedComponentOverridesWhichAreNull(Object prefabIn PrefabUtility.SetRemovedComponents(prefabInstanceObject, filteredRemovedComponents); } - // We can't use the same pattern of identifyiong the prefab asset via assetPath only, + // We can't use the same pattern of identifying the prefab asset via assetPath only, // since when the component is removed in the instance, the only way to identify which component it is, - // is via the corresponding component on the asset. Additionally supplying an assetPath would be redundant. + // is via the corresponding component on the asset. We find it by matching FileIds. Additionally supplying an assetPath would be redundant. public static void ApplyRemovedComponent(GameObject instanceGameObject, Component assetComponent, InteractionMode action) { DateTime startTime = DateTime.UtcNow; @@ -1095,20 +1132,51 @@ public static void ApplyRemovedComponent(GameObject instanceGameObject, Componen EditorUtility.DisplayDialog(L10n.Tr("Can't apply removed component"), error, L10n.Tr("OK")); return; } + } - var coupledAssetComponent = assetComponent.GetCoupledComponent(); + string assetPath = AssetDatabase.GetAssetPath(assetComponent); + GameObject assetRoot = GetRootGameObject(assetComponent); - Undo.DestroyObjectUndoable(assetComponent, actionName); - // Undo.DestroyObjectUndoable saves prefab asset internally. + Component coupledAssetComponent = null; + byte[] originalFileContent = null; + if (action == InteractionMode.UserAction) + { + coupledAssetComponent = assetComponent.GetCoupledComponent(); + if(!FileUtil.ReadFileContentBinary(assetPath, out originalFileContent, out string errorMessage)) + Debug.LogError($"No undo was registered when removing {assetComponent.name} from {assetRoot.name}. \nError: {errorMessage}", assetRoot); + } + using (var scope = new EditPrefabContentsScope(assetPath)) + { + //Search components in file that matches the FileIds of componentInAsset + void DeleteCorrespondingComponent(Component componentInAsset) + { + var assetComponentId = Unsupported.GetFileIDHint(componentInAsset); + var componentsOfTypeInAsset = scope.prefabContentsRoot.GetComponentsInChildren(componentInAsset.GetType(), true); + + foreach (var component in componentsOfTypeInAsset) + { + if (Unsupported.GetOrGenerateFileIDHint(component) == assetComponentId) + { + Object.DestroyImmediate(component); + return; + } + } + Debug.LogError($"Component {componentInAsset} could not be found and deleted from corresponding asset."); + } + + DeleteCorrespondingComponent(assetComponent); if (coupledAssetComponent != null) - Undo.DestroyObjectUndoable(coupledAssetComponent, actionName); + DeleteCorrespondingComponent(coupledAssetComponent); } - else + + if (action == InteractionMode.UserAction && originalFileContent != null) { - GameObject prefabAsset = assetComponent.transform.root.gameObject; - Object.DestroyImmediate(assetComponent, true); - SavePrefabAsset(prefabAsset); + var guid = AssetDatabase.GUIDFromAssetPath(assetPath); + if (FileUtil.ReadFileContentBinary(assetPath, out byte[] newFileContent, out string errorMessage)) + Undo.RegisterFileChangeUndo(guid, originalFileContent, newFileContent); + else + Debug.LogError($"No undo was registered when removing {assetComponent.name} from {assetRoot.name}. \nError: {errorMessage}", assetRoot); } var prefabInstanceObject = PrefabUtility.GetPrefabInstanceHandle(instanceGameObject); @@ -1581,8 +1649,15 @@ private static void SaveAsPrefabAssetArgumentCheck(GameObject instanceRoot, stri if (EditorUtility.IsPersistent(instanceRoot)) throw new ArgumentException("Can't save persistent object as a Prefab asset"); - if (IsPrefabAssetMissing(instanceRoot)) - throw new ArgumentException("Can't save Prefab instance with missing asset as a Prefab. You may unpack the instance and save the unpacked GameObjects as a Prefab."); + if (IsPartOfNonAssetPrefabInstance(instanceRoot)) + { + // A PrefabInstance with missing asset can be correctly restored only if CorrespondingObjects info is available + // CorrespondingObject info is available when a PrefabInstance with missing asset was merged before deleting the asset (kNormalMerge) or when it has a scene backup (kMergedAsMissingWithSceneBackup) + var mergeStatus = GetMergeStatus(instanceRoot); + var hasCorrespondingSourceObjectInfo = mergeStatus == MergeStatus.NormalMerge || mergeStatus == MergeStatus.MergedAsMissingWithSceneBackup; + if (IsPrefabAssetMissing(instanceRoot) && !hasCorrespondingSourceObjectInfo) + throw new ArgumentException("Can't save Prefab instance with missing asset and scene backup as a Prefab. You may unpack the instance and save the unpacked GameObjects as a Prefab."); + } var actualInstanceRoot = GetOutermostPrefabInstanceRoot(instanceRoot); if (actualInstanceRoot) @@ -1942,6 +2017,12 @@ internal enum SaveVerb Apply } + [RequiredByNativeCode] + internal static bool PromptAndCheckoutPrefabIfNeeded_Internal(string[] assetPaths, SaveVerb saveVerb) + { + return PromptAndCheckoutPrefabIfNeeded(assetPaths, saveVerb); + } + internal static bool PromptAndCheckoutPrefabIfNeeded(string assetPath, SaveVerb saveVerb) { return PromptAndCheckoutPrefabIfNeeded(new string[] { assetPath }, saveVerb); @@ -1981,8 +2062,14 @@ internal static bool PromptAndCheckoutPrefabIfNeeded(string[] assetPaths, SaveVe return result; } + public static event Action prefabInstanceUnpacking; + public static event Action prefabInstanceUnpacked; + public static void UnpackPrefabInstance(GameObject instanceRoot, PrefabUnpackMode unpackMode, InteractionMode action) { + if (instanceRoot == null) + throw new ArgumentNullException(nameof(instanceRoot)); + if (!IsPartOfNonAssetPrefabInstance(instanceRoot)) throw new ArgumentException("UnpackPrefabInstance must be called with a Prefab instance."); @@ -2007,8 +2094,23 @@ public static void UnpackPrefabInstance(GameObject instanceRoot, PrefabUnpackMod { UnpackPrefabInstanceAndReturnNewOutermostRoots(instanceRoot, unpackMode); } + } - prefabInstanceUnpacked?.Invoke(instanceRoot); + public static GameObject[] UnpackPrefabInstanceAndReturnNewOutermostRoots(GameObject instanceRoot, PrefabUnpackMode unpackMode) + { + if (instanceRoot == null) + throw new ArgumentNullException(nameof(instanceRoot)); + + prefabInstanceUnpacking?.Invoke(instanceRoot, unpackMode); + + // The user can delete the instance in the prefabInstanceUnpacking callback + if (instanceRoot == null) + throw new InvalidOperationException($"The input '{nameof(instanceRoot)}' was destroyed in the prefabInstanceUnpacking callback"); + + var newRoots = UnpackPrefabInstanceAndReturnNewOutermostRoots_internal(instanceRoot, unpackMode); + prefabInstanceUnpacked?.Invoke(instanceRoot, unpackMode); + + return newRoots; } public static void UnpackAllInstancesOfPrefab(GameObject prefabRoot, PrefabUnpackMode unpackMode, InteractionMode action) @@ -2020,7 +2122,8 @@ public static void UnpackAllInstancesOfPrefab(GameObject prefabRoot, PrefabUnpac } } - static internal event Action prefabInstanceUnpacked; + + internal static bool HasInvalidComponent(Object gameObjectOrComponent) { @@ -2627,16 +2730,18 @@ static List GetComponentsWhichThisDependsOn(Component component, List internal readonly struct InstanceOverridesInfo { - public InstanceOverridesInfo(GameObject prefabInstance, PropertyModification[] usedMods, PropertyModification[] unusedMods) + public InstanceOverridesInfo(GameObject prefabInstance, PropertyModification[] usedMods, PropertyModification[] unusedMods, int unusedRemovedComponentCount) { this.instance = prefabInstance; this.usedMods = usedMods; this.unusedMods = unusedMods; + this.unusedRemovedComponentCount = unusedRemovedComponentCount; } public GameObject instance { get; } public PropertyModification[] usedMods { get; } public PropertyModification[] unusedMods { get; } + public int unusedRemovedComponentCount { get; } } internal static bool HavePrefabInstancesUnusedOverrides(GameObject[] gameObjects) @@ -2664,11 +2769,13 @@ internal static InstanceOverridesInfo[] GetPrefabInstancesOverridesInfos(GameObj foreach (GameObject go in selectedGameObjects) { - var outerMostPrefabInstance = PrefabUtility.GetOutermostPrefabInstanceRoot(go); + var outerMostInstance = PrefabUtility.GetOutermostPrefabInstanceRoot(go); + if (outerMostInstance == null) + continue; - if (PrefabUtility.HasPrefabInstanceNonDefaultOverrides_CachedForUI(outerMostPrefabInstance) || PrefabUtility.HasPrefabInstanceUnusedOverrides(outerMostPrefabInstance)) + if (PrefabUtility.HasPrefabInstanceNonDefaultOverridesOrUnusedOverrides_CachedForUI(outerMostInstance)) { - InstanceOverridesInfo instancePropMods = GetPrefabInstanceOverridesInfo(outerMostPrefabInstance); + InstanceOverridesInfo instancePropMods = GetPrefabInstanceOverridesInfo(outerMostInstance); allInstanceMods.Add(instancePropMods); } } @@ -2730,7 +2837,9 @@ internal static InstanceOverridesInfo GetPrefabInstanceOverridesInfo(GameObject } } - return new InstanceOverridesInfo(selectedGameObject, validModifications.ToArray(), invalidModifications.ToArray()); + int unusedRemovedComponentCount = GetPrefabInstanceUnusedRemovedComponentCount_Internal(selectedGameObject); + + return new InstanceOverridesInfo(selectedGameObject, validModifications.ToArray(), invalidModifications.ToArray(), unusedRemovedComponentCount); } internal static bool DoRemovePrefabInstanceUnusedOverridesDialog(InstanceOverridesInfo[] instanceOverridesInfos) @@ -2768,11 +2877,11 @@ internal static bool DoRemovePrefabInstanceUnusedOverridesDialog(InstanceOverrid { foreach (PrefabUtility.InstanceOverridesInfo instanceMods in instanceOverridesInfos) { - if (!instanceMods.unusedMods.Any()) + if (!instanceMods.unusedMods.Any() && instanceMods.unusedRemovedComponentCount == 0) continue; affectedInstanceCount++; - unusedOverridesCount += instanceMods.unusedMods.Length; + unusedOverridesCount += instanceMods.unusedMods.Length + instanceMods.unusedRemovedComponentCount; usedOverridesCount += instanceMods.usedMods.Length; currInstanceWithUnusedMods = instanceMods; } @@ -2797,7 +2906,7 @@ internal static bool DoRemovePrefabInstanceUnusedOverridesDialog(InstanceOverrid } else// Single selection { - unusedOverridesCount = currInstanceWithUnusedMods.unusedMods.Length; + unusedOverridesCount = currInstanceWithUnusedMods.unusedMods.Length + currInstanceWithUnusedMods.unusedRemovedComponentCount; usedOverridesCount = currInstanceWithUnusedMods.usedMods.Length; if (unusedOverridesCount > 0) { @@ -2829,30 +2938,50 @@ internal static bool DoRemovePrefabInstanceUnusedOverridesDialog(InstanceOverrid internal static void RemovePrefabInstanceUnusedOverrides(InstanceOverridesInfo[] instanceOverridesInfos) { + bool updatedEditorLog = false; + foreach (InstanceOverridesInfo ipmods in instanceOverridesInfos) - PrefabUtility.RemovePrefabInstanceUnusedOverrides(ipmods); + updatedEditorLog |= PrefabUtility.RemovePrefabInstanceUnusedOverrides(ipmods); + + if (updatedEditorLog) + System.Console.WriteLine(""); } - private static void RemovePrefabInstanceUnusedOverrides(InstanceOverridesInfo iovInfo) + private static bool RemovePrefabInstanceUnusedOverrides(InstanceOverridesInfo iovInfo) { - if (iovInfo.unusedMods.Any()) + if (iovInfo.instance == null) + throw new ArgumentNullException(nameof(iovInfo), "InstanceOverridesInfo.instance was null"); + else if (iovInfo.unusedMods == null) + throw new ArgumentNullException(nameof(iovInfo), "InstanceOverridesInfo.unusedMods was null"); + else if (iovInfo.usedMods == null) + throw new ArgumentNullException(nameof(iovInfo), "InstanceOverridesInfo.usedMods was null"); + + bool updatedEditorLog = false; + if (iovInfo.unusedMods.Any() || iovInfo.unusedRemovedComponentCount > 0) { Undo.RegisterCompleteObjectUndo(iovInfo.instance, "Remove unused overrides"); - SetPropertyModifications(iovInfo.instance, iovInfo.usedMods); - PrefabUtility.LogRemovedOverrides(iovInfo.instance, iovInfo.unusedMods); + if (iovInfo.unusedMods.Any()) + { + SetPropertyModifications(iovInfo.instance, iovInfo.usedMods); + updatedEditorLog |= PrefabUtility.LogRemovedPropertyOverrides(iovInfo.instance, iovInfo.unusedMods); + } - PrefabUtility.RemoveRemovedComponentOverridesWhichAreNull(iovInfo.instance); + if (iovInfo.unusedRemovedComponentCount > 0) + { + PrefabUtility.RemoveRemovedComponentOverridesWhichAreNull(iovInfo.instance); + updatedEditorLog |= PrefabUtility.LogRemovedUnusedRemovedComponents(iovInfo.instance, iovInfo.unusedRemovedComponentCount); + } } + return updatedEditorLog; } - internal static void LogRemovedOverrides(GameObject instance, PropertyModification[] mods) + internal static bool LogRemovedPropertyOverrides(GameObject instance, PropertyModification[] mods) { if (mods.Length == 0) - return; + return false; System.Text.StringBuilder info = new System.Text.StringBuilder(); - info.AppendLine(""); if (mods.Length > 1) info.AppendLine("Removed " + mods.Length + " unused overrides from instance '" + instance.name + "':"); @@ -2867,9 +2996,46 @@ internal static void LogRemovedOverrides(GameObject instance, PropertyModificati info.AppendLine(" '" + mod.propertyPath + "' refers to a non-existent property."); } - System.Console.WriteLine(info.ToString(), instance); + System.Console.Write(info.ToString(), instance); + return true; + } + + internal static bool LogRemovedUnusedRemovedComponents(GameObject instance, int unusedRemovedComponentCount) + { + if (unusedRemovedComponentCount == 0) + return false; + + System.Text.StringBuilder info = new System.Text.StringBuilder(); + + if (unusedRemovedComponentCount > 1) + info.AppendLine("Removed " + unusedRemovedComponentCount + " unused removed components from instance '" + instance.name + "'"); + else + info.AppendLine("Removed 1 unused removed component from instance '" + instance.name + "'"); + + System.Console.Write(info.ToString(), instance); + + return true; + } + + internal static event Func allowRecordingPrefabPropertyOverridesFor; + + + [RequiredByNativeCode] + static bool AllowRecordingPrefabPropertyOverridesFor(UnityEngine.Object componentOrGameObject) + { + if (allowRecordingPrefabPropertyOverridesFor == null) + return true; + + foreach (Func deleg in allowRecordingPrefabPropertyOverridesFor.GetInvocationList()) + { + if (deleg(componentOrGameObject) == false) + return false; + } + + return true; } + internal static class Analytics { public enum ApplyScope diff --git a/Editor/Mono/PreferencesWindow/AssetPipelinePreferences.cs b/Editor/Mono/PreferencesWindow/AssetPipelinePreferences.cs index 903c3c8fd9..e02f245fdd 100644 --- a/Editor/Mono/PreferencesWindow/AssetPipelinePreferences.cs +++ b/Editor/Mono/PreferencesWindow/AssetPipelinePreferences.cs @@ -227,7 +227,9 @@ void ShowGUI() GUILayout.Label(Properties.cacheServer, EditorStyles.boldLabel); if (GUILayout.Button(Properties.cacheServerLearnMore, EditorStyles.linkLabel)) { - Application.OpenURL("https://docs.unity3d.com/Manual/UnityAccelerator.html#UsingWithAssetPipeline"); + // Known issue with Docs redirect - versioned pages might not open offline docs + var help = Help.FindHelpNamed("UnityAccelerator"); + Application.OpenURL(help); } GUILayout.EndHorizontal(); EditorGUI.BeginChangeCheck(); @@ -293,7 +295,9 @@ void DoImportWorkerCount() if (GUILayout.Button(Properties.desiredImportWorkerCountPctOfLogicalCPUsLearnMore, EditorStyles.linkLabel)) { - Application.OpenURL("https://docs.unity3d.com/Manual/ParallelImport.html"); + // Known issue with Docs redirect - versioned pages might not open offline docs + var help = Help.FindHelpNamed("ParallelImport"); + Application.OpenURL(help); } GUILayout.EndHorizontal(); @@ -310,6 +314,31 @@ void DoDirectoryMonitoring() } } + internal static bool ParseCacheServerAddress(string input, out string ip, out UInt16 port) + { + var address = input.Split(':'); + ip = address[0]; + port = 0; + if (address.Length == 2) + { + try + { + port = UInt16.Parse(address[1]); + } + catch (Exception e) + { + Debug.LogError($"{e.Message} Exception thrown attempting to parse the port '{address[1]}'. Please double check the 'Default IP Address' and try again."); + return false; + } + } + else if (address.Length > 2) + { + Debug.LogError($"Failure attempting to parse the address '{input}' as multiple ports were detected. Please double check the 'Default IP Address' and try again."); + return false; + } + return true; + } + static void CacheServerGUI() { bool changeStateBeforeControls = GUI.changed; @@ -327,13 +356,7 @@ static void CacheServerGUI() if (GUILayout.Button("Check Connection", GUILayout.Width(150))) { - var address = s_CacheServer2IPAddress.Split(':'); - var ip = address[0]; - UInt16 port = 0; - if (address.Length == 2) - port = Convert.ToUInt16(address[1]); - - if (AssetDatabase.CanConnectToCacheServer(ip, port)) + if (ParseCacheServerAddress(s_CacheServer2IPAddress, out var ip, out var port) && AssetDatabase.CanConnectToCacheServer(ip, port)) s_ConnectionState = ConnectionState.Success; else s_ConnectionState = ConnectionState.Failure; diff --git a/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs b/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs index dc1eb9741b..bdbfdbebda 100644 --- a/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs +++ b/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using UnityEngine; +using UnityEditor.Analytics; using UnityEditor.Modules; using UnityEditorInternal; using System.Collections.Generic; @@ -13,6 +14,7 @@ using Unity.CodeEditor; using UnityEngine.UIElements; using UnityEditor.Experimental; +using UnityEditor.SceneManagement; namespace UnityEditor { @@ -45,13 +47,15 @@ static Styles() class GeneralProperties { public static readonly GUIContent loadPreviousProjectOnStartup = EditorGUIUtility.TrTextContent("Load Previous Project on Startup"); - public static readonly GUIContent disableEditorAnalytics = EditorGUIUtility.TrTextContent("Disable Editor Analytics (Pro Only)"); + public static readonly GUIContent disableEditorAnalytics = EditorGUIUtility.TrTextContent("Disable Editor Analytics"); public static readonly GUIContent autoSaveScenesBeforeBuilding = EditorGUIUtility.TrTextContent("Auto-save scenes before building"); public static readonly GUIContent scriptChangesDuringPlay = EditorGUIUtility.TrTextContent("Script Changes While Playing"); public static readonly GUIContent editorFont = EditorGUIUtility.TrTextContent("Editor Font"); public static readonly GUIContent editorSkin = EditorGUIUtility.TrTextContent("Editor Theme"); public static readonly GUIContent[] editorSkinOptions = { EditorGUIUtility.TrTextContent("Light"), EditorGUIUtility.TrTextContent("Dark") }; - public static readonly GUIContent enableAlphaNumericSorting = EditorGUIUtility.TrTextContent("Enable Alphanumeric Sorting"); + public static readonly GUIContent hierarchyHeader = EditorGUIUtility.TrTextContent("Hierarchy window"); + public static readonly GUIContent enableAlphaNumericSorting = EditorGUIUtility.TrTextContent("Enable Alphanumeric Sorting", "If enabled then you can choose between Transform sorting and Alphabetical sorting in the Hierarchy."); + public static readonly GUIContent defaultPrefabMode = EditorGUIUtility.TrTextContent("Default Prefab Mode", "This mode will be used when opening Prefab Mode from a Prefab instance in the Hierarchy."); public static readonly GUIContent applicationFrameThrottling = EditorGUIUtility.TrTextContent("Frame Throttling (milliseconds)", "The number of milliseconds the Editor can idle between frames."); public static readonly GUIContent inputMaxProcessTime = EditorGUIUtility.TrTextContent("Input Throttling (milliseconds)", "The maximum number of milliseconds the Editor will take to process user inputs."); public static readonly GUIContent interactionMode = EditorGUIUtility.TrTextContent("Interaction Mode", "Specifies how long the Editor can idle before it updates."); @@ -121,7 +125,7 @@ class SceneViewProperties { public static readonly GUIContent enableFilteringWhileSearching = EditorGUIUtility.TrTextContent("Enable filtering while searching", "If enabled, searching will cause non-matching items in the scene view to be greyed out"); public static readonly GUIContent enableFilteringWhileLodGroupEditing = EditorGUIUtility.TrTextContent("Enable filtering while editing LOD groups", "If enabled, editing LOD groups will cause other objects in the scene view to be greyed out"); - public static readonly GUIContent handlesLineThickness = EditorGUIUtility.TrTextContent("Line Thickness", "Thickness of manipulator tool handle lines in UI points (0 = single pixel)"); + public static readonly GUIContent handlesLineThickness = EditorGUIUtility.TrTextContent("Line Thickness", "Thickness of manipulator tool handle lines"); 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"); @@ -137,6 +141,7 @@ class LanguageProperties class DeveloperModeProperties { public static readonly GUIContent developerMode = EditorGUIUtility.TrTextContent("Developer Mode", "Enable or disable developer mode features."); + public static readonly GUIContent generateOnPostprocessAllAssets = EditorGUIUtility.TrTextContent("Generate OnPostprocessAllAssets Dependency Diagram", "Generates a graphviz diagram to show OnPostprocessAllAssets dependencies."); public static readonly GUIContent showRepaintDots = EditorGUIUtility.TrTextContent("Show Repaint Dots", "Enable or disable the colored dots that flash when an EditorWindow repaints."); public static readonly GUIContent redirectionServer = EditorGUIUtility.TrTextContent("Documentation Server", "Select the documentation redirection server."); } @@ -187,6 +192,7 @@ private struct GICacheSettings private bool m_EnableCompilerMessagesLocalization; private bool m_AllowAlphaNumericHierarchy = false; + private PrefabStage.Mode m_DefaultPrefabModeFromHierarchy = PrefabStage.Mode.InContext; private bool m_Create3DObjectsAtOrigin = false; private float m_ProgressDialogDelay = 3.0f; private bool m_GraphSnapping; @@ -457,14 +463,15 @@ private void ShowGeneral(string searchContext) // Options m_ReopenLastUsedProjectOnStartup = EditorGUILayout.Toggle(GeneralProperties.loadPreviousProjectOnStartup, m_ReopenLastUsedProjectOnStartup); - bool pro = UnityEngine.Application.HasProLicense(); - using (new EditorGUI.DisabledScope(!pro)) + bool enableEditorAnalyticsOld = m_EnableEditorAnalytics; + + using (new EditorGUI.DisabledScope(m_AnalyticSettingChangedThisSession)) { - bool enableEditorAnalyticsOld = m_EnableEditorAnalytics; - m_EnableEditorAnalytics = !EditorGUILayout.Toggle(GeneralProperties.disableEditorAnalytics, !m_EnableEditorAnalytics) || !pro && !m_EnableEditorAnalytics; + m_EnableEditorAnalytics = !EditorGUILayout.Toggle(GeneralProperties.disableEditorAnalytics, !m_EnableEditorAnalytics); if (enableEditorAnalyticsOld != m_EnableEditorAnalytics) { m_AnalyticSettingChangedThisSession = true; + EditorAnalytics.enabled = m_EnableEditorAnalytics; } if (m_AnalyticSettingChangedThisSession) { @@ -507,9 +514,6 @@ private void ShowGeneral(string searchContext) } } - bool oldAlphaNumeric = m_AllowAlphaNumericHierarchy; - m_AllowAlphaNumericHierarchy = EditorGUILayout.Toggle(GeneralProperties.enableAlphaNumericSorting, m_AllowAlphaNumericHierarchy); - if (InternalEditorUtility.IsGpuDeviceSelectionSupported()) { // Cache gpu devices @@ -562,16 +566,27 @@ private void ShowGeneral(string searchContext) GameView.openWindowOnEnteringPlayMode = EditorGUILayout.Toggle(GeneralProperties.enterPlayModeSettingsFocusGameView, GameView.openWindowOnEnteringPlayMode); - ApplyChangesToPrefs(); - - if (oldAlphaNumeric != m_AllowAlphaNumericHierarchy) - EditorApplication.DirtyHierarchyWindowSorting(); - DrawInteractionModeOptions(); DrawPackageManagerOptions(); DrawDynamicHintsOptions(); DrawPerformBumpMapCheck(); + + EditorGUILayout.Space(); + GUILayout.Label(GeneralProperties.hierarchyHeader, EditorStyles.boldLabel); + + EditorGUI.indentLevel++; + bool oldAlphaNumeric = m_AllowAlphaNumericHierarchy; + m_AllowAlphaNumericHierarchy = EditorGUILayout.Toggle(GeneralProperties.enableAlphaNumericSorting, m_AllowAlphaNumericHierarchy); + m_DefaultPrefabModeFromHierarchy = (PrefabStage.Mode)EditorGUILayout.EnumPopup(GeneralProperties.defaultPrefabMode, m_DefaultPrefabModeFromHierarchy); + EditorGUI.indentLevel--; + + EditorGUILayout.Space(); + + ApplyChangesToPrefs(); + + if (oldAlphaNumeric != m_AllowAlphaNumericHierarchy) + EditorApplication.DirtyHierarchyWindowSorting(); } enum InteractionMode @@ -775,7 +790,7 @@ private void ShowSceneView(string searchContext) AnnotationUtility.useInspectorExpandedState = EditorGUILayout.Toggle(SceneViewProperties.useInspectorExpandedStateContent, AnnotationUtility.useInspectorExpandedState); GUILayout.Label("Handles", EditorStyles.boldLabel); - Handles.s_LineThickness.value = EditorGUILayout.IntSlider(SceneViewProperties.handlesLineThickness, (int)Handles.s_LineThickness.value, 0, 5); + Handles.s_LineThickness.value = EditorGUILayout.IntSlider(SceneViewProperties.handlesLineThickness, (int)Handles.s_LineThickness.value, 1, 5); GUILayout.Label("Search", EditorStyles.boldLabel); SceneView.s_PreferenceEnableFilteringWhileSearching.value = EditorGUILayout.Toggle(SceneViewProperties.enableFilteringWhileSearching, SceneView.s_PreferenceEnableFilteringWhileSearching); @@ -983,6 +998,11 @@ private void ShowDeveloperMode(string searchContext) { Help.docRedirectionServer = docServer; } + + if (GUILayout.Button(DeveloperModeProperties.generateOnPostprocessAllAssets)) + { + AssetPostprocessingInternal.s_OnPostprocessAllAssetsCallbacks.GenerateDependencyDiagram("OnPostprocessAllAssets.dot"); + } } if (m_DeveloperModeDirty) @@ -1054,6 +1074,7 @@ private void WritePreferences() EditorPrefs.SetBool("Editor.kEnableCompilerMessagesLocalization", m_EnableCompilerMessagesLocalization); EditorPrefs.SetBool("AllowAlphaNumericHierarchy", m_AllowAlphaNumericHierarchy); + EditorPrefs.SetInt("DefaultPrefabModeFromHierarchy", (int)m_DefaultPrefabModeFromHierarchy); EditorPrefs.SetFloat("EditorBusyProgressDialogDelay", m_ProgressDialogDelay); GOCreationCommands.s_PlaceObjectsAtWorldOrigin.value = m_Create3DObjectsAtOrigin; @@ -1121,7 +1142,7 @@ private void ReadPreferences() m_ReopenLastUsedProjectOnStartup = EditorPrefs.GetBool("ReopenLastUsedProjectOnStartup"); - m_EnableEditorAnalytics = EditorPrefs.GetBool("EnableEditorAnalytics", true); + m_EnableEditorAnalytics = EditorPrefs.GetBool("EnableEditorAnalyticsV2", EditorPrefs.GetBool("EnableEditorAnalytics", true)); m_ScriptCompilationDuringPlay = (ScriptChangesDuringPlayOptions)EditorPrefs.GetInt("ScriptCompilationDuringPlay", 0); m_DeveloperMode = Unsupported.IsDeveloperMode(); @@ -1137,6 +1158,7 @@ private void ReadPreferences() m_SelectedLanguage = EditorPrefs.GetString("Editor.kEditorLocale", LocalizationDatabase.GetDefaultEditorLanguage().ToString()); m_EnableCompilerMessagesLocalization = EditorPrefs.GetBool("Editor.kEnableCompilerMessagesLocalization", false); m_AllowAlphaNumericHierarchy = EditorPrefs.GetBool("AllowAlphaNumericHierarchy", false); + m_DefaultPrefabModeFromHierarchy = GetDefaultPrefabModeForHierarchy(); m_ProgressDialogDelay = EditorPrefs.GetFloat("EditorBusyProgressDialogDelay", 3.0f); m_Create3DObjectsAtOrigin = GOCreationCommands.s_PlaceObjectsAtWorldOrigin; @@ -1329,5 +1351,10 @@ private string[] BuildFriendlyAppNameList(string[] appPathList, Dictionary s_ProgressItems = new List(8); @@ -344,22 +354,23 @@ private static void OnOperationStateChanged(int id, Updates progressUpdates) } [RequiredByNativeCode] - private static void OnOperationsStateChanged(int[] ids, Updates[] progressUpdates) + private static void OnOperationsStateChanged(ReadOnlySpan progressUpdates) { if (!s_Initialized) RestoreProgressItems(); - if (ids.Length == 0) return; + if (progressUpdates.Length == 0) return; - var items = new Item[ids.Length]; + var items = new Item[progressUpdates.Length]; - for (var i = 0; i < ids.Length; ++i) + for (var i = 0; i < progressUpdates.Length; i++) { - var id = ids[i]; - var item = GetProgressById(id); + ref readonly var update = ref progressUpdates[i]; + var item = GetProgressById(update.id); Assert.IsNotNull(item); - item.Dirty(progressUpdates[i]); + item.Dirty(update.updates); items[i] = item; } + s_ProgressDirty = true; s_RemainingTimeDirty = true; @@ -465,6 +476,9 @@ internal static void ClearProgressItems() s_Initialized = false; s_ProgressDirty = true; s_RemainingTimeDirty = true; + s_Progress = 0f; + s_RemainingTime = TimeSpan.Zero; + s_LastRemainingTimeUpdate = DateTime.Now; } } diff --git a/Editor/Mono/ProjectBrowser.cs b/Editor/Mono/ProjectBrowser.cs index 70e0ae1008..9778cc149e 100644 --- a/Editor/Mono/ProjectBrowser.cs +++ b/Editor/Mono/ProjectBrowser.cs @@ -836,27 +836,28 @@ public void EndRenaming() m_ListArea.EndRename(true); } - string[] GetTypesDisplayNames() - { - return new[] - { - "AnimationClip", - "AudioClip", - "AudioMixer", - "ComputeShader", - "Font", - "GUISkin", - "Material", - "Mesh", - "Model", - "PhysicMaterial", - "Prefab", - "Scene", - "Script", - "Shader", - "Sprite", - "Texture", - "VideoClip", + Dictionary GetTypesDisplayNames() + { + return new Dictionary + { + { "Animation Clip", new [] { "AnimationClip" } }, + { "Audio Clip", new [] { "AudioClip"} }, + { "Audio Mixer", new [] { "AudioMixer" } }, + { "Compute Shader", new [] { "ComputeShader" } }, + { "Font", new [] { "Font" } }, + { "GUI Skin", new [] { "GUISkin" } }, + { "Material", new [] { "Material" } }, + { "Mesh", new [] { "Mesh" } }, + { "Model", new [] { "Model" } }, + { "Physic Material", new [] { "PhysicMaterial" } }, + { "Prefab", new [] { "Prefab" } }, + { "Scene", new [] { "Scene"} }, + { "Script", new [] { "Script" } }, + { "Shader", new [] { "Shader" } }, + { "Sprite", new [] { "Sprite" } }, + { "Texture", new [] { "Texture" } }, + { "Video Clip", new [] { "VideoClip" } }, + { "Visual Effect Asset", new [] { "VisualEffectAsset", "VisualEffectSubgraph" } }, // "Texture2D", // "RenderTexture", @@ -877,7 +878,7 @@ public void TypeListCallback(PopupList.ListElement element) // Toggle clicked element element.selected = !element.selected; - string[] selectedDisplayNames = (from item in m_ObjectTypes.m_ListElements where item.selected select item.text).ToArray(); + string[] selectedDisplayNames = m_ObjectTypes.m_ListElements.Where(x => x.selected).SelectMany(x => x.types).ToArray(); m_SearchFilter.classNames = selectedDisplayNames; m_SearchFieldText = m_SearchFilter.FilterToSearchFieldString(); @@ -917,13 +918,12 @@ void SetupDroplists() m_ObjectTypes.m_OnSelectCallback = TypeListCallback; m_ObjectTypes.m_SortAlphabetically = false; m_ObjectTypes.m_MaxCount = 0; - string[] types = GetTypesDisplayNames(); - for (int i = 0; i < types.Length; ++i) + var types = GetTypesDisplayNames(); + foreach (var keyPair in types) { - PopupList.ListElement element = m_ObjectTypes.NewOrMatchingElement(types[i]); - if (i == 0) - element.selected = true; + m_ObjectTypes.AddElement(keyPair.Key, keyPair.Value); } + m_ObjectTypes.m_ListElements[0].selected = true; } void SetupAssetLabelList() @@ -1199,6 +1199,11 @@ void ListAreaItemSelectedCallback(bool doubleClicked) Selection.activeObject = null; } + if (Selection.instanceIDs != m_ListArea.GetSelection()) + { + m_ListArea.InitSelection(Selection.instanceIDs); + } + m_FocusSearchField = false; if (Event.current.button == 1 && Event.current.type == EventType.MouseDown) @@ -1349,9 +1354,15 @@ void AssetTreeSelectionCallback(int[] selectedTreeViewInstanceIDs) { SetAsLastInteractedProjectBrowser(); - Selection.activeObject = null; if (selectedTreeViewInstanceIDs.Length > 0) - Selection.instanceIDs = selectedTreeViewInstanceIDs; + Selection.SetSelectionWithActiveInstanceID(selectedTreeViewInstanceIDs, selectedTreeViewInstanceIDs[0]); + else + Selection.activeInstanceID = 0; + + // 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) + m_AssetTree.SetSelection(Selection.instanceIDs, true); RefreshSelectedPath(); SetSearchFoldersFromCurrentSelection(); @@ -1896,8 +1907,6 @@ float GetListHeaderHeight() { if (!m_SearchFilter.IsSearching()) return k_ToolbarHeight; - if (SearchService.ProjectSearch.HasEngineOverride()) - return 0f; return m_SearchFilter.GetState() == SearchFilter.State.EmptySearchFilter ? 0f : k_ToolbarHeight; } @@ -2079,8 +2088,9 @@ void HandleContextClickInListArea(Rect listRect) { GUIUtility.hotControl = 0; - // Only show menu if there are instances selected - if (Selection.instanceIDs.Length > 0) + // In safe mode non-scripts assets aren't selectable and therefore if you context click a non-script + // asset, then a context menu shouldn't be displayed. + if (!EditorUtility.isInSafeMode || Selection.instanceIDs.Length > 0) { // Context click in list area EditorUtility.DisplayPopupMenu(new Rect(evt.mousePosition.x, evt.mousePosition.y, 0, 0), "Assets/", null); @@ -2568,9 +2578,6 @@ void IconSizeSlider(Rect r) void SearchAreaBar() { - if (SearchService.ProjectSearch.HasEngineOverride()) - return; - // Background GUI.Label(m_ListHeaderRect, GUIContent.none, s_Styles.topBarBg); @@ -2814,6 +2821,8 @@ internal void BeginPreimportedNameEditing(int instanceID, EndNameEditAction endA public void FrameObject(int instanceID, bool ping) { + m_LockTracker.StopPingIcon(); + bool canFrame = CanFrameAsset(instanceID); if (!canFrame) { @@ -2833,7 +2842,19 @@ public void FrameObject(int instanceID, bool ping) } } - bool frame = !m_LockTracker.isLocked && (ping || canFrame); + bool frame = ping || canFrame; + if (frame && m_LockTracker.isLocked) + { + 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)) + { + Repaint(); + m_LockTracker.PingIcon(); + } + } + FrameObjectPrivate(instanceID, frame, ping); if (s_LastInteractedProjectBrowser == this) { @@ -3038,7 +3059,8 @@ protected virtual void ShowButton(Rect r) if (s_Styles == null) s_Styles = new Styles(); - m_LockTracker.ShowButton(r, s_Styles.lockButton); + if (m_LockTracker.ShowButton(r, s_Styles.lockButton)) + Repaint(); } internal bool SelectionIsFavorite() diff --git a/Editor/Mono/ProjectBrowserPopups.cs b/Editor/Mono/ProjectBrowserPopups.cs index b02bfbc175..b999d94fac 100644 --- a/Editor/Mono/ProjectBrowserPopups.cs +++ b/Editor/Mono/ProjectBrowserPopups.cs @@ -30,8 +30,11 @@ public class ListElement private bool m_WasSelected; private bool m_PartiallySelected; private bool m_Enabled; + private string[] m_Types; - public ListElement(string text, bool selected, float score) + public ListElement(string text, bool selected, float score) : this(text, new [] { text }, selected, score) { } + + public ListElement(string text, string[] types, bool selected, float score) { m_Content = new GUIContent(text); if (!string.IsNullOrEmpty(m_Content.text)) @@ -40,6 +43,7 @@ public ListElement(string text, bool selected, float score) a[0] = char.ToUpper(a[0]); m_Content.text = new string(a); } + m_Types = types; m_Selected = selected; filterScore = score; m_PartiallySelected = false; @@ -124,6 +128,14 @@ public string text } } + public IEnumerable types + { + get + { + return m_Types; + } + } + public void ResetScore() { m_WasSelected = m_Selected || m_PartiallySelected; @@ -189,6 +201,12 @@ public int GetFilteredCount(string prefix) return res.Count(); } + public void AddElement(string label, string[] types) + { + var res = new ListElement(label, types, false, -1); + m_ListElements.Add(res); + } + public ListElement NewOrMatchingElement(string label) { foreach (var element in m_ListElements) diff --git a/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs b/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs index 72c6ff09eb..b22a068080 100644 --- a/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs +++ b/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs @@ -288,6 +288,7 @@ public static void CreateAsset(Object asset, string pathName) } // Create a folder + [ShortcutManagement.ShortcutAttribute("Project Browser/Create/Folder", typeof(ProjectBrowser), KeyCode.N, ShortcutManagement.ShortcutModifiers.Shift | ShortcutManagement.ShortcutModifiers.Action)] public static void CreateFolder() { StartNameEditingIfProjectWindowExists(0, ScriptableObject.CreateInstance(), "New Folder", EditorGUIUtility.IconContent(EditorResources.emptyFolderIconName).image as Texture2D, null); @@ -509,6 +510,8 @@ internal static string SetLineEndings(string content, LineEndingsMode lineEnding internal static Object CreateScriptAssetWithContent(string pathName, string templateContent) { + AssetModificationProcessorInternal.OnWillCreateAsset(pathName); + templateContent = SetLineEndings(templateContent, EditorSettings.lineEndingsForNewScripts); string fullPath = Path.GetFullPath(pathName); @@ -868,6 +871,83 @@ public static string[] GetBaseFolders(string[] folders) return result.ToArray(); } + static bool AnyTargetMaterialHasChildren(string[] targetPaths) + { + GUID[] guids = targetPaths.Select(path => AssetDatabase.GUIDFromAssetPath(path)).ToArray(); + + Func HasChildrenInPath = (string rootPath) => { + var property = new HierarchyProperty(rootPath, false); + property.SetSearchFilter(new SearchFilter { classNames = new string[] { "Material" }, searchArea = SearchFilter.SearchArea.AllAssets }); + while (property.Next(null)) + { + GUID parent; + var child = InternalEditorUtility.GetLoadedObjectFromInstanceID(property.GetInstanceIDIfImported()) as Material; + if (child) + { + if (AssetDatabase.IsForeignAsset(child)) + continue; + parent = AssetDatabase.GUIDFromAssetPath(AssetDatabase.GetAssetPath(child.parent)); + } + else + { + var path = AssetDatabase.GUIDToAssetPath(property.guid); + if (!path.EndsWith(".mat", StringComparison.OrdinalIgnoreCase)) + continue; + parent = EditorMaterialUtility.GetMaterialParentFromFile(path); + } + + for (int i = 0; i < guids.Length; i++) + { + if (guids[i] == parent) + return true; + } + } + return false; + }; + + if (HasChildrenInPath("Assets")) + return true; + foreach (var package in PackageManagerUtilityInternal.GetAllVisiblePackages(false)) + { + if (package.source == PackageManager.PackageSource.Local && HasChildrenInPath(package.assetPath)) + return true; + } + return false; + } + + static void ReparentMaterialChildren(string assetPath) + { + var toDelete = AssetDatabase.LoadAssetAtPath(assetPath); + var toDeleteGUID = AssetDatabase.GUIDFromAssetPath(assetPath); + var newParent = toDelete.parent; + + Action ReparentInPath = (string rootPath) => { + var property = new HierarchyProperty(rootPath, false); + property.SetSearchFilter(new SearchFilter { classNames = new string[] { "Material" }, searchArea = SearchFilter.SearchArea.AllAssets }); + while (property.Next(null)) + { + var child = InternalEditorUtility.GetLoadedObjectFromInstanceID(property.GetInstanceIDIfImported()) as Material; + if (!child) + { + // First check guid from file to avoid loading all materials in memory + string path = AssetDatabase.GUIDToAssetPath(property.guid); + if (EditorMaterialUtility.GetMaterialParentFromFile(path) != toDeleteGUID) + continue; + child = AssetDatabase.LoadAssetAtPath(path); + } + if (child != null && child.parent == toDelete && !AssetDatabase.IsForeignAsset(child)) + child.parent = newParent; + } + }; + + ReparentInPath("Assets"); + foreach (var package in PackageManagerUtilityInternal.GetAllVisiblePackages(false)) + { + if (package.source == PackageManager.PackageSource.Local) + ReparentInPath(package.assetPath); + } + } + // Deletes the assets of the instance IDs, with an optional user confirmation dialog. // Returns true if the delete operation was successfully performed on all assets. // Note: Zero input assets always returns true. @@ -884,15 +964,16 @@ internal static bool DeleteAssets(List instanceIDs, bool askIfSure) return false; } - var paths = GetMainPathsOfAssets(instanceIDs).ToList(); + bool reparentMaterials = false; + var paths = GetMainPathsOfAssets(instanceIDs).ToArray(); - if (paths.Count == 0) + if (paths.Length == 0) return false; if (askIfSure) { string title; - if (paths.Count > 1) + if (paths.Length > 1) { title = L10n.Tr("Delete selected assets?"); } @@ -901,32 +982,63 @@ internal static bool DeleteAssets(List instanceIDs, bool askIfSure) title = L10n.Tr("Delete selected asset?"); } + int maxCount = 3; + bool containsMaterial = false; + var infotext = new StringBuilder(); - int pathsCount = Mathf.Min(3, paths.Count); - for (int i = 0; i < pathsCount; ++i) + for (int i = 0; i < paths.Length; ++i) { - infotext.AppendLine(paths[i]); + if (i < maxCount) + infotext.AppendLine(paths[i]); + + if (paths[i].EndsWith(".mat", StringComparison.OrdinalIgnoreCase)) + { + containsMaterial = true; + if (i >= maxCount) + break; + } } - if (paths.Count > pathsCount) + if (paths.Length > maxCount) { infotext.AppendLine("..."); } infotext.AppendLine(""); infotext.AppendLine(L10n.Tr("You cannot undo the delete assets action.")); - if (!EditorUtility.DisplayDialog(title, infotext.ToString(), L10n.Tr("Delete"), L10n.Tr("Cancel"))) + containsMaterial &= AnyTargetMaterialHasChildren(paths); + if (containsMaterial) { - return false; + 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) + reparentMaterials = true; + else if (dialogOptionIndex == 2) + return false; } + else if (!EditorUtility.DisplayDialog(title, infotext.ToString(), L10n.Tr("Delete"), L10n.Tr("Cancel"))) + return false; } bool success = true; List failedPaths = new List(); AssetDatabase.StartAssetEditing(); - if (!AssetDatabase.MoveAssetsToTrash(paths.ToArray(), failedPaths)) + + if (reparentMaterials) + { + for (int i = 0; i < paths.Length; i++) + { + if (paths[i].EndsWith(".mat", StringComparison.OrdinalIgnoreCase)) + ReparentMaterialChildren(paths[i]); + } + } + + if (!AssetDatabase.MoveAssetsToTrash(paths, failedPaths)) success = false; + AssetDatabase.StopAssetEditing(); if (!success) diff --git a/Editor/Mono/SceneHierarchy.cs b/Editor/Mono/SceneHierarchy.cs index 6f1ca61372..dbde1ae469 100644 --- a/Editor/Mono/SceneHierarchy.cs +++ b/Editor/Mono/SceneHierarchy.cs @@ -677,6 +677,7 @@ void TreeViewItemDoubleClicked(int instanceID) else { SceneView.FrameLastActiveSceneView(); + EditorGUIUtility.ExitGUI(); } } @@ -695,17 +696,11 @@ public void SetExpandedRecursive(int id, bool expand) treeView.data.SetExpandedWithChildren(item, expand); } - void OnRowGUICallback(int instanceID, Rect rect) - { - if (EditorApplication.hierarchyWindowItemOnGUI != null) - { - // Adjust rect for the right aligned column for the prefab isolation button - rect.xMax -= - GameObjectTreeViewGUI.GameObjectStyles.rightArrow.fixedWidth + - GameObjectTreeViewGUI.GameObjectStyles.rightArrow.margin.horizontal; - EditorApplication.hierarchyWindowItemOnGUI(instanceID, rect); - } + + void OnRowGUICallback(int itemID, Rect rect) + { + GameObjectTreeViewGUI.UserCallbackRowGUI(itemID, rect); } void OnDragEndedCallback(int[] draggedInstanceIds, bool draggedItemsFromOwnTreeView) @@ -788,10 +783,13 @@ public void SetSearchFilter(string searchString, SearchableEditorWindow.SearchMo void TreeViewSelectionChanged(int[] ids) { //Last selected should be the active selected object to reflect the behavior of the scene view selection - if (ids.Length > 0) - Selection.activeInstanceID = ids[ids.Length - 1]; + int active = ids.Length > 0 ? ids[ids.Length - 1] : 0; + Selection.SetSelectionWithActiveInstanceID(ids, active); - Selection.instanceIDs = ids; + if (!IsTreeViewSelectionInSyncWithBackend()) + { + selectionSyncNeeded = true; + } m_DidSelectSearchResult = !string.IsNullOrEmpty(m_SearchFilter); } @@ -1050,13 +1048,13 @@ void ExecuteCommands() } else if (evt.commandName == EventCommandNames.Cut) { - CutCopyPasteUtility.CutGO(); + ClipboardUtility.CutGO(); GUIUtility.ExitGUI(); } else if (evt.commandName == EventCommandNames.Copy) { if (execute) - CutCopyPasteUtility.CopyGO(); + ClipboardUtility.CopyGO(); evt.Use(); GUIUtility.ExitGUI(); } @@ -1115,7 +1113,7 @@ void HandleKeyboard() if (evt.keyCode == KeyCode.Escape && CutBoard.CanGameObjectsBePasted()) { - CutCopyPasteUtility.ResetCutboardAndRepaintHierarchyWindows(); + ClipboardUtility.ResetCutboardAndRepaintHierarchyWindows(); GUIUtility.ExitGUI(); } } @@ -1125,14 +1123,14 @@ void CreateSubSceneGameObjectContextClick(GenericMenu menu, int contextClickedIt // For Sub Scenes GameObjects, have menu items for cut, paste and delete. // Not copy or duplicate, since multiple of the same Sub Scene is not supported anyway. - menu.AddItem(EditorGUIUtility.TrTextContent("Cut"), false, CutCopyPasteUtility.CutGO); + menu.AddItem(EditorGUIUtility.TrTextContent("Cut"), false, ClipboardUtility.CutGO); if (CutBoard.CanGameObjectsBePasted() || Unsupported.CanPasteGameObjectsFromPasteboard()) menu.AddItem(EditorGUIUtility.TrTextContent("Paste"), false, PasteGO); else menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Paste")); - if (CutCopyPasteUtility.CanPasteAsChild()) - menu.AddItem(EditorGUIUtility.TrTextContent("Paste As Child"), false, CutCopyPasteUtility.PasteGOAsChild); + if (ClipboardUtility.CanPasteAsChild()) + menu.AddItem(EditorGUIUtility.TrTextContent("Paste As Child"), false, ClipboardUtility.PasteGOAsChild); else menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Paste As Child")); @@ -1146,14 +1144,14 @@ void CreateSubSceneGameObjectContextClick(GenericMenu menu, int contextClickedIt void CreateGameObjectContextClick(GenericMenu menu, int contextClickedItemID) { - menu.AddItem(EditorGUIUtility.TrTextContent("Cut"), false, CutCopyPasteUtility.CutGO); - menu.AddItem(EditorGUIUtility.TrTextContent("Copy"), false, CutCopyPasteUtility.CopyGO); + menu.AddItem(EditorGUIUtility.TrTextContent("Cut"), false, ClipboardUtility.CutGO); + menu.AddItem(EditorGUIUtility.TrTextContent("Copy"), false, ClipboardUtility.CopyGO); if (CutBoard.CanGameObjectsBePasted() || Unsupported.CanPasteGameObjectsFromPasteboard()) menu.AddItem(EditorGUIUtility.TrTextContent("Paste"), false, PasteGO); else menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Paste")); - if (CutCopyPasteUtility.CanPasteAsChild()) - menu.AddItem(EditorGUIUtility.TrTextContent("Paste As Child"), false, CutCopyPasteUtility.PasteGOAsChild); + if (ClipboardUtility.CanPasteAsChild()) + menu.AddItem(EditorGUIUtility.TrTextContent("Paste As Child"), false, ClipboardUtility.PasteGOAsChild); else menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Paste As Child")); @@ -1399,7 +1397,7 @@ internal void CreateSceneHeaderContextClick(GenericMenu menu, Scene scene) menu.AddSeparator(""); } - bool isUnloadOrRemoveValid = EditorSceneManager.loadedSceneCount != GetNumLoadedScenesInSelection(); + bool isUnloadOrRemoveValid = SceneManager.loadedSceneCount != GetNumLoadedScenesInSelection(); if (!scene.isSubScene) { @@ -1583,12 +1581,12 @@ void ItemContextClick(int contextClickedItemID) void PasteGO() { - CutCopyPasteUtility.PasteGO(m_CustomParentForNewGameObjects); + ClipboardUtility.PasteGO(m_CustomParentForNewGameObjects); } void DuplicateGO() { - CutCopyPasteUtility.DuplicateGO(m_CustomParentForNewGameObjects); + ClipboardUtility.DuplicateGO(m_CustomParentForNewGameObjects); } void RenameGO() @@ -1695,7 +1693,7 @@ List GetScenePrefabInstancesWithNonDefaultOverrides(Scene scene) { visitor.VisitAll(root.transform, (transform, list) => { GameObject go = transform.gameObject; - if (PrefabUtility.IsOutermostPrefabInstanceRoot(go) && PrefabUtility.HasPrefabInstanceNonDefaultOverrides_CachedForUI(go)) + if (PrefabUtility.IsOutermostPrefabInstanceRoot(go) && PrefabUtility.HasPrefabInstanceNonDefaultOverridesOrUnusedOverrides_CachedForUI(go)) { gameObjects.Add(go); } diff --git a/Editor/Mono/SceneHierarchyWindow.cs b/Editor/Mono/SceneHierarchyWindow.cs index 9d58bd46bd..6ffe17a549 100644 --- a/Editor/Mono/SceneHierarchyWindow.cs +++ b/Editor/Mono/SceneHierarchyWindow.cs @@ -87,6 +87,12 @@ public override void OnDisable() PrefabUtility.prefabInstanceModificationCacheCleared -= OnPrefabInstanceModificationCacheCleared; } + internal override void ClickedSearchField() + { + // End renaming any GameObjects (case 1078881) + m_SceneHierarchy.treeView.EndNameEditing(true); + } + void OnDestroy() { // Set another existing hierarchy as last interacted if available @@ -322,13 +328,13 @@ internal void RebuildStageHeader() [MenuItem("Edit/Paste As Child %#V", false, 103)] static void PasteAsChild() { - CutCopyPasteUtility.PasteGOAsChild(); + ClipboardUtility.PasteGOAsChild(); } [MenuItem("Edit/Paste As Child %#V", true, 103)] static bool ValidatePasteAsChild() { - return CutCopyPasteUtility.CanPasteAsChild(); + return ClipboardUtility.CanPasteAsChild(); } internal static SceneHierarchyWindow GetSceneHierarchyWindowToFocusForNewGameObjects() diff --git a/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStage.cs b/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStage.cs index 5d82705e25..6e840b0d6a 100644 --- a/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStage.cs +++ b/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStage.cs @@ -86,6 +86,8 @@ public enum Mode Transform m_LastRootTransform; const float kDurationBeforeShowingSavingBadge = 1.0f; static ExposablePopupMenu s_ContextRenderModeSelector; + Hash128 m_InitialFileHash; + byte[] m_InitialFileContent; Hash128 m_LastPrefabSourceFileHash; bool m_NeedsReloadingWhenReturningToStage; bool m_IsAssetMissing; @@ -216,7 +218,7 @@ void ReconstructInContextStateIfNeeded() } else { - // Could not find GameObject with fileID: m_FileIdForOpenedFromInstanceObject, this can happen if inserting a base in a Variant and then discarding changes when entering in-context of the new base + // Could not find GameObject with fileID: m_FileIdForOpenedFromInstanceObject, this can happen if inserting a variant parent in a Variant and then discarding changes when entering in-context of the new base } } } @@ -236,7 +238,7 @@ protected override void OnEnable() PrefabUtility.savingPrefab += OnSavingPrefab; PrefabUtility.prefabInstanceUpdated += OnPrefabInstanceUpdated; AssetEvents.assetsChangedOnHDD += OnAssetsChangedOnHDD; - Undo.undoRedoPerformed += UndoRedoPerformed; + Undo.undoRedoEvent += UndoRedoPerformed; EditorApplication.playModeStateChanged += OnPlayModeStateChanged; m_AllPrefabStages.Add(this); @@ -248,7 +250,7 @@ protected override void OnDisable() PrefabUtility.savingPrefab -= OnSavingPrefab; PrefabUtility.prefabInstanceUpdated -= OnPrefabInstanceUpdated; AssetEvents.assetsChangedOnHDD -= OnAssetsChangedOnHDD; - Undo.undoRedoPerformed -= UndoRedoPerformed; + Undo.undoRedoEvent -= UndoRedoPerformed; EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; // Also cleanup any potential registered event handlers @@ -563,7 +565,16 @@ protected internal override bool OnOpenStage() return false; } - return OpenStage(); + bool success = OpenStage(); + if (success) + { + var guid = AssetDatabase.AssetPathToGUID(m_PrefabAssetPath); + m_InitialFileHash = AssetDatabase.GetSourceAssetFileHash(guid); + if (!FileUtil.ReadFileContentBinary(assetPath, out m_InitialFileContent, out string errorMessage)) + Debug.LogError($"No undo will be registered for {m_PrefabContentsRoot.name}. \nError: {errorMessage}"); + } + + return success; } bool OpenStage() @@ -604,7 +615,21 @@ protected override void OnCloseStage() if (isValid) prefabStageClosing?.Invoke(this); + var initialFileContent = m_InitialFileContent; + var path = m_PrefabAssetPath; + var guid = AssetDatabase.AssetPathToGUID(m_PrefabAssetPath); + var currentPrefabSourceFileHash = AssetDatabase.GetSourceAssetFileHash(guid); + CleanupBeforeClosing(); + + if (initialFileContent?.Length > 0 && currentPrefabSourceFileHash != m_InitialFileHash) + { + if (FileUtil.ReadFileContentBinary(path, out byte[] newFileContent, out string errorMessage)) + Undo.RegisterFileChangeUndo(AssetDatabase.GUIDFromAssetPath(path), initialFileContent, newFileContent); + else + Debug.LogError($"No undo will be registered for {m_PrefabContentsRoot.name}. \nError: {errorMessage}"); + + } } protected internal override void OnReturnToStage() @@ -906,7 +931,7 @@ void ApplyPatchedPropertiesToContent() StageUtility.CallAwakeFromLoadOnSubHierarchy(m_PrefabContentsRoot); } - void UndoRedoPerformed() + void UndoRedoPerformed(in UndoRedoInfo info) { if (m_Mode == Mode.InContext) { @@ -941,6 +966,8 @@ void CleanupBeforeClosing() m_OpenedFromInstanceObject = null; m_HideFlagUtility = null; m_PrefabAssetPath = null; + m_InitialFileContent = null; + m_InitialFileHash = new Hash128(); m_InitialSceneDirtyID = 0; m_StageDirtiedFired = false; m_LastSceneDirtyID = 0; @@ -1294,12 +1321,12 @@ public void ClearDirtiness() m_StageDirtiedFired = false; } - bool PromptIfMissingBasePrefabForVariant() + bool PromptIfMissingVariantParentForVariant() { if (PrefabUtility.IsPrefabAssetMissing(m_PrefabContentsRoot)) { string title = L10n.Tr("Saving Variant Failed"); - string message = L10n.Tr("Can't save the Prefab Variant when its base Prefab is missing. You have to unpack the root GameObject or recover the missing base Prefab in order to save the Prefab Variant"); + string message = L10n.Tr("Can't save the Prefab Variant when its parent Prefab is missing. You have to unpack the root GameObject or recover the missing parent Prefab in order to save the Prefab Variant"); if (autoSave) message += L10n.Tr("\n\nAuto Save has been temporarily disabled."); EditorUtility.DisplayDialog(title, message, L10n.Tr("OK")); @@ -1348,7 +1375,7 @@ internal bool SavePrefab() var startTime = EditorApplication.timeSinceStartup; - if (PromptIfMissingBasePrefabForVariant()) + if (PromptIfMissingVariantParentForVariant()) return false; // The user can have deleted required folders diff --git a/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStageUtility.cs b/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStageUtility.cs index 76b9efe80e..a7c104d8a8 100644 --- a/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStageUtility.cs +++ b/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStageUtility.cs @@ -649,22 +649,29 @@ static Canvas GetCanvasInScene(GameObject instanceRoot) internal static PrefabStage.Mode GetPrefabStageModeFromModifierKeys() { // Update GetPrefabButtonContent if this logic changes - return Event.current.alt ? PrefabStage.Mode.InIsolation : PrefabStage.Mode.InContext; + var defaultPrefabMode = PreferencesProvider.GetDefaultPrefabModeForHierarchy(); + var alternativePrefabMode = (defaultPrefabMode == PrefabStage.Mode.InContext) ? PrefabStage.Mode.InIsolation : PrefabStage.Mode.InContext; + + return Event.current.alt ? alternativePrefabMode : defaultPrefabMode; } - static Dictionary m_PrefabButtonContents = new Dictionary(); internal static GUIContent GetPrefabButtonContent(int instanceID) { GUIContent result; - if (m_PrefabButtonContents.TryGetValue(instanceID, out result)) + var defaultPrefabMode = PreferencesProvider.GetDefaultPrefabModeForHierarchy(); + switch (defaultPrefabMode) { - return result; + case PrefabStage.Mode.InContext: + result = new GUIContent("", null, $"Open Prefab Asset in context.\nPress modifier key [Alt] 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."); + break; + default: + result = new GUIContent(""); + Debug.LogError("Unhandled defaultPrefabMode enum"); + break; } - - string path = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(EditorUtility.InstanceIDToObject(instanceID) as GameObject); - string filename = System.IO.Path.GetFileNameWithoutExtension(path); - result = new GUIContent("", null, "Open Prefab Asset '" + filename + "'" + "\nPress modifier key [Alt] to open in isolation."); - m_PrefabButtonContents[instanceID] = result; return result; } } diff --git a/Editor/Mono/SceneModeWindows/DefaultLightingExplorerExtension.cs b/Editor/Mono/SceneModeWindows/DefaultLightingExplorerExtension.cs index 0d27087c07..ca1dd05b43 100644 --- a/Editor/Mono/SceneModeWindows/DefaultLightingExplorerExtension.cs +++ b/Editor/Mono/SceneModeWindows/DefaultLightingExplorerExtension.cs @@ -6,6 +6,7 @@ using System.Linq; using UnityEngine.Rendering; using UnityEngine.U2D; +using UnityEditor.SceneManagement; using Object = UnityEngine.Object; namespace UnityEditor @@ -78,14 +79,49 @@ private static bool IsEditable(Object target) return ((target.hideFlags & HideFlags.NotEditable) == 0); } - protected virtual UnityEngine.Object[] GetLights() + protected static System.Collections.Generic.IEnumerable GetObjectsForLightingExplorer() where T : UnityEngine.Component { - return Resources.FindObjectsOfTypeAll(); + var objects = Resources.FindObjectsOfTypeAll().Where((T obj) => + { + return !EditorUtility.IsPersistent(obj) && !obj.hideFlags.HasFlag(HideFlags.HideInHierarchy) && !obj.hideFlags.HasFlag(HideFlags.HideAndDontSave); + }); + + var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); + // No prefab mode. + if (prefabStage == null) + { + // Return all object instances in the scene including prefab instances, but not those that are in prefab assets. + return objects; + } + // In Context prefab mode with Normal rendering mode + else if (prefabStage.mode == PrefabStage.Mode.InContext && + StageNavigationManager.instance.contextRenderMode == StageUtility.ContextRenderMode.Normal) + { + // Return all object instances in the scene and objects in the opened prefab asset, but not objects in the opened prefab instance. + return objects.Where((T obj) => + { + return !StageUtility.IsPrefabInstanceHiddenForInContextEditing(obj.gameObject); + }); + } + // All remaining cases, e.g. In Context with Hidden or GrayedOut rendering mode, or In Isolation prefab mode. + else + { + // Return only objects in the opened prefab asset. + return objects.Where((T obj) => + { + return EditorSceneManager.IsPreviewSceneObject(obj); + }); + } } - protected virtual UnityEngine.Object[] Get2DLights() + protected internal virtual UnityEngine.Object[] GetLights() { - return Resources.FindObjectsOfTypeAll(); + return GetObjectsForLightingExplorer().ToArray(); + } + + protected internal virtual UnityEngine.Object[] Get2DLights() + { + return GetObjectsForLightingExplorer().ToArray(); } protected virtual LightingExplorerTableColumn[] Get2DLightColumns() @@ -338,9 +374,9 @@ protected virtual LightingExplorerTableColumn[] GetLightColumns() }; } - protected virtual UnityEngine.Object[] GetReflectionProbes() + protected internal virtual UnityEngine.Object[] GetReflectionProbes() { - return Resources.FindObjectsOfTypeAll(); + return GetObjectsForLightingExplorer().ToArray(); } protected virtual LightingExplorerTableColumn[] GetReflectionProbeColumns() @@ -365,9 +401,9 @@ protected virtual LightingExplorerTableColumn[] GetReflectionProbeColumns() }; } - protected virtual UnityEngine.Object[] GetLightProbes() + protected internal virtual UnityEngine.Object[] GetLightProbes() { - return Resources.FindObjectsOfTypeAll(); + return GetObjectsForLightingExplorer().ToArray(); } protected virtual LightingExplorerTableColumn[] GetLightProbeColumns() @@ -379,13 +415,16 @@ protected virtual LightingExplorerTableColumn[] GetLightProbeColumns() }; } - protected virtual UnityEngine.Object[] GetEmissives() + protected internal virtual UnityEngine.Object[] GetEmissives() { - return Resources.FindObjectsOfTypeAll().Where((MeshRenderer mr) => { - return (GameObjectUtility.AreStaticEditorFlagsSet(mr.gameObject, StaticEditorFlags.ContributeGI)); - }).SelectMany(meshRenderer => meshRenderer.sharedMaterials).Where((Material m) => { - return m != null && ((m.globalIlluminationFlags & MaterialGlobalIlluminationFlags.AnyEmissive) != 0) && m.HasProperty("_EmissionColor"); - }).Distinct().ToArray(); + return GetObjectsForLightingExplorer().Where((MeshRenderer mr) => + { + return GameObjectUtility.AreStaticEditorFlagsSet(mr.gameObject, StaticEditorFlags.ContributeGI); + }).SelectMany(meshRenderer => meshRenderer.sharedMaterials).Where((Material m) => + + { + return m != null && ((m.globalIlluminationFlags & MaterialGlobalIlluminationFlags.AnyEmissive) != 0) && m.HasProperty("_EmissionColor"); + }).Distinct().ToArray(); } protected virtual LightingExplorerTableColumn[] GetEmissivesColumns() diff --git a/Editor/Mono/SceneModeWindows/LightingWindow.cs b/Editor/Mono/SceneModeWindows/LightingWindow.cs index 14dffe7f1b..959010ad09 100644 --- a/Editor/Mono/SceneModeWindows/LightingWindow.cs +++ b/Editor/Mono/SceneModeWindows/LightingWindow.cs @@ -44,6 +44,14 @@ static class Styles public static readonly float ButtonWidth = 90; } + public interface WindowTab + { + void OnEnable(); + void OnDisable(); + void OnGUI(); + void OnSelectionChange(); + } + enum BakeMode { BakeReflectionProbes = 0, @@ -64,10 +72,7 @@ enum Mode List m_Modes = null; GUIContent[] m_ModeStrings; - LightingWindowLightingTab m_LightingSettingsTab; - LightingWindowEnvironmentTab m_EnvironmentSettingsTab; - LightingWindowLightmapPreviewTab m_RealtimeLightmapsTab; - LightingWindowLightmapPreviewTab m_BakedLightmapsTab; + Dictionary m_Tabs = new Dictionary(); SerializedObject m_LightingSettings; SerializedProperty m_WorkflowMode; @@ -106,19 +111,22 @@ internal void SetSelectedTabIndex(int index) m_SelectedModeIndex = index; } + LightingWindow() + { + m_Tabs.Add(Mode.LightingSettings, new LightingWindowLightingTab()); + m_Tabs.Add(Mode.EnvironmentSettings, new LightingWindowEnvironmentTab()); + m_Tabs.Add(Mode.RealtimeLightmaps, new LightingWindowLightmapPreviewTab(LightmapType.DynamicLightmap)); + m_Tabs.Add(Mode.BakedLightmaps, new LightingWindowLightmapPreviewTab(LightmapType.StaticLightmap)); + } + void OnEnable() { titleContent = GetLocalizedTitleContent(); - m_LightingSettingsTab = new LightingWindowLightingTab(); - m_LightingSettingsTab.OnEnable(); - m_EnvironmentSettingsTab = new LightingWindowEnvironmentTab(); - m_EnvironmentSettingsTab.OnEnable(); - - m_RealtimeLightmapsTab = new LightingWindowLightmapPreviewTab(LightmapType.DynamicLightmap); - m_BakedLightmapsTab = new LightingWindowLightmapPreviewTab(LightmapType.StaticLightmap); + foreach (var pair in m_Tabs) + pair.Value.OnEnable(); - Undo.undoRedoPerformed += Repaint; + Undo.undoRedoEvent += OnUndoRedo; Lightmapping.lightingDataUpdated += Repaint; Repaint(); @@ -126,11 +134,18 @@ void OnEnable() void OnDisable() { - m_LightingSettingsTab.OnDisable(); - Undo.undoRedoPerformed -= Repaint; + foreach (var pair in m_Tabs) + pair.Value.OnDisable(); + + Undo.undoRedoEvent -= OnUndoRedo; Lightmapping.lightingDataUpdated -= Repaint; } + private void OnUndoRedo(in UndoRedoInfo info) + { + Repaint(); + } + void OnBecameVisible() { RepaintSceneAndGameViews(); @@ -143,14 +158,14 @@ void OnBecameInvisible() void OnSelectionChange() { - if (m_RealtimeLightmapsTab == null || m_BakedLightmapsTab == null || m_Modes == null) + if (m_Modes == null) return; - if (m_Modes.Contains(Mode.RealtimeLightmaps)) - m_RealtimeLightmapsTab.UpdateActiveGameObjectSelection(); - - if (m_Modes.Contains(Mode.BakedLightmaps)) - m_BakedLightmapsTab.UpdateActiveGameObjectSelection(); + foreach (var pair in m_Tabs) + { + if (m_Modes.Contains(pair.Key)) + pair.Value.OnSelectionChange(); + } Repaint(); } @@ -178,24 +193,8 @@ void OnGUI() EditorGUILayout.Space(); - switch (selectedMode) - { - case Mode.LightingSettings: - m_LightingSettingsTab.OnGUI(); - break; - - case Mode.EnvironmentSettings: - m_EnvironmentSettingsTab.OnGUI(); - break; - - case Mode.RealtimeLightmaps: - m_RealtimeLightmapsTab.OnGUI(position); - break; - - case Mode.BakedLightmaps: - m_BakedLightmapsTab.OnGUI(position); - break; - } + if (m_Tabs.ContainsKey(selectedMode)) + m_Tabs[selectedMode].OnGUI(); Buttons(); Summary(); diff --git a/Editor/Mono/SceneModeWindows/LightingWindowEnvironmentTab.cs b/Editor/Mono/SceneModeWindows/LightingWindowEnvironmentTab.cs index d3ef1cc1df..d6dfc8b917 100644 --- a/Editor/Mono/SceneModeWindows/LightingWindowEnvironmentTab.cs +++ b/Editor/Mono/SceneModeWindows/LightingWindowEnvironmentTab.cs @@ -17,7 +17,7 @@ public virtual void OnDisable() {} public virtual void OnInspectorGUI() {} } - internal class LightingWindowEnvironmentTab + internal class LightingWindowEnvironmentTab : LightingWindow.WindowTab { class Styles { @@ -62,8 +62,7 @@ public override void OnDisable() SavedBool m_ShowOtherSettings; Object m_RenderSettings = null; Vector2 m_ScrollPosition = Vector2.zero; - - Type m_SRP = GraphicsSettings.currentRenderPipeline?.GetType(); + Type m_SRP; Object renderSettings { @@ -130,6 +129,7 @@ Editor otherRenderingEditor public void OnEnable() { + m_SRP = GraphicsSettings.currentRenderPipeline?.GetType(); m_ShowOtherSettings = new SavedBool($"LightingWindow.ShowOtherSettings", true); } @@ -172,6 +172,10 @@ public void OnGUI() EditorGUILayout.Space(); } + public void OnSelectionChange() + { + } + void OtherSettingsGUI() { if (SupportedRenderingFeatures.active.overridesFog && SupportedRenderingFeatures.active.overridesOtherLightingSettings) diff --git a/Editor/Mono/SceneModeWindows/LightingWindowLightingTab.cs b/Editor/Mono/SceneModeWindows/LightingWindowLightingTab.cs index ed4b24d130..0c6be7a1d6 100644 --- a/Editor/Mono/SceneModeWindows/LightingWindowLightingTab.cs +++ b/Editor/Mono/SceneModeWindows/LightingWindowLightingTab.cs @@ -16,7 +16,7 @@ namespace UnityEditor { - internal class LightingWindowLightingTab + internal class LightingWindowLightingTab : LightingWindow.WindowTab { class Styles { @@ -24,6 +24,7 @@ class Styles public static readonly GUIContent newLightingSettings = EditorGUIUtility.TrTextContent("New Lighting Settings"); + public static readonly GUIContent lightingSettings = EditorGUIUtility.TrTextContent("Lighting Settings"); public static readonly GUIContent workflowSettings = EditorGUIUtility.TrTextContent("Workflow Settings"); public static readonly GUIContent lightProbeVisualization = EditorGUIUtility.TrTextContent("Light Probe Visualization"); public static readonly GUIContent displayWeights = EditorGUIUtility.TrTextContent("Display Weights"); @@ -49,6 +50,7 @@ class Styles }; } + SavedBool m_ShowLightingSettings; SavedBool m_ShowWorkflowSettings; SavedBool m_ShowProbeDebugSettings; Vector2 m_ScrollPosition = Vector2.zero; @@ -78,6 +80,7 @@ public void OnEnable() m_BakeSettings = new LightingWindowBakeSettings(); m_BakeSettings.OnEnable(); + m_ShowLightingSettings = new SavedBool("LightingWindow.ShowLightingSettings", true); m_ShowWorkflowSettings = new SavedBool("LightingWindow.ShowWorkflowSettings", true); m_ShowProbeDebugSettings = new SavedBool("LightingWindow.ShowProbeDebugSettings", false); } @@ -95,23 +98,7 @@ public void OnGUI() m_ScrollPosition = EditorGUILayout.BeginScrollView(m_ScrollPosition); - EditorGUILayout.PropertyField(m_LightingSettingsAsset); - - EditorGUILayout.Space(); - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - - 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")); - } - - GUILayout.EndHorizontal(); - EditorGUILayout.Space(); + LightingSettingsGUI(); m_BakeSettings.OnGUI(); WorkflowSettingsGUI(); @@ -122,6 +109,40 @@ public void OnGUI() lightmapSettings.ApplyModifiedProperties(); } + public void OnSelectionChange() + { + } + + void LightingSettingsGUI() + { + m_ShowLightingSettings.value = EditorGUILayout.FoldoutTitlebar(m_ShowLightingSettings.value, Styles.lightingSettings, true); + + if (m_ShowLightingSettings.value) + { + ++EditorGUI.indentLevel; + + EditorGUILayout.PropertyField(m_LightingSettingsAsset, GUIContent.Temp("Lighting Settings Asset")); + + EditorGUILayout.Space(); + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + + 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")); + } + + GUILayout.EndHorizontal(); + EditorGUILayout.Space(); + + --EditorGUI.indentLevel; + } + } + void WorkflowSettingsGUI() { m_ShowWorkflowSettings.value = EditorGUILayout.FoldoutTitlebar(m_ShowWorkflowSettings.value, Styles.workflowSettings, true); diff --git a/Editor/Mono/SceneModeWindows/LightingWindowLightmapPreviewTab.cs b/Editor/Mono/SceneModeWindows/LightingWindowLightmapPreviewTab.cs index 6c53d5d201..757d5542aa 100644 --- a/Editor/Mono/SceneModeWindows/LightingWindowLightmapPreviewTab.cs +++ b/Editor/Mono/SceneModeWindows/LightingWindowLightmapPreviewTab.cs @@ -15,7 +15,7 @@ namespace UnityEditor { - internal class LightingWindowLightmapPreviewTab + internal class LightingWindowLightmapPreviewTab : LightingWindow.WindowTab { LightmapType m_LightmapType = LightmapType.NoLightmap; Vector2 m_ScrollPosition = Vector2.zero; @@ -41,8 +41,6 @@ static class Styles public LightingWindowLightmapPreviewTab(LightmapType type) { m_LightmapType = type; - - InitSettings(); } private bool isRealtimeLightmap @@ -61,7 +59,7 @@ private bool showDebugInfo } } - public void UpdateActiveGameObjectSelection() + public void OnSelectionChange() { MeshRenderer renderer; Terrain terrain = null; @@ -92,7 +90,16 @@ public void UpdateActiveGameObjectSelection() m_ShouldScrollToLightmapIndex = true; } - public void OnGUI(Rect position) + public void OnEnable() + { + InitSettings(); + } + + public void OnDisable() + { + } + + public void OnGUI() { InitSettings(); diff --git a/Editor/Mono/SceneModeWindows/NavigationWindow.cs b/Editor/Mono/SceneModeWindows/NavigationWindow.cs index a12580bf79..447f1fe15d 100644 --- a/Editor/Mono/SceneModeWindows/NavigationWindow.cs +++ b/Editor/Mono/SceneModeWindows/NavigationWindow.cs @@ -37,7 +37,7 @@ internal class NavMeshEditorWindow : EditorWindow, IHasCustomMenu SerializedProperty m_MinRegionArea; SerializedProperty m_ManualCellSize; SerializedProperty m_CellSize; - SerializedProperty m_AccuratePlacement; + SerializedProperty m_BuildHeightMesh; // Project based configuration SerializedObject m_NavMeshProjectSettingsObject; @@ -202,7 +202,7 @@ private void InitSceneBakeSettings() m_MinRegionArea = m_SettingsObject.FindProperty(kRootPath + "minRegionArea"); m_ManualCellSize = m_SettingsObject.FindProperty(kRootPath + "manualCellSize"); m_CellSize = m_SettingsObject.FindProperty(kRootPath + "cellSize"); - m_AccuratePlacement = m_SettingsObject.FindProperty(kRootPath + "accuratePlacement"); + m_BuildHeightMesh = m_SettingsObject.FindProperty(kRootPath + "buildHeightMesh"); } private void InitAreas() @@ -541,6 +541,13 @@ static void DisplayControls() NavMeshVisualizationSettings.showHeightMeshBVTree = !showHeightMeshBVTree; bRepaint = true; } + + var showHeightMaps = NavMeshVisualizationSettings.showHeightMaps; + if (showHeightMaps != EditorGUILayout.Toggle(EditorGUIUtility.TrTextContent("Show HeightMaps"), showHeightMaps)) + { + NavMeshVisualizationSettings.showHeightMaps = !showHeightMaps; + bRepaint = true; + } } if (bRepaint) @@ -971,8 +978,9 @@ private void SceneBakeSettings() EditorGUILayout.Space(); //Height mesh - var accurate = EditorGUILayout.Toggle(s_Styles.m_BakePlacementContent, m_AccuratePlacement.boolValue); - if (accurate != m_AccuratePlacement.boolValue) m_AccuratePlacement.boolValue = accurate; + var heightMesh = EditorGUILayout.Toggle(s_Styles.m_BakePlacementContent, m_BuildHeightMesh.boolValue); + if (heightMesh != m_BuildHeightMesh.boolValue) + m_BuildHeightMesh.boolValue = heightMesh; EditorGUI.indentLevel--; } diff --git a/Editor/Mono/SceneView/RectSelection.cs b/Editor/Mono/SceneView/RectSelection.cs index 5fc0f82cfa..4b1d6882b8 100644 --- a/Editor/Mono/SceneView/RectSelection.cs +++ b/Editor/Mono/SceneView/RectSelection.cs @@ -246,12 +246,8 @@ static void UpdateSelection(Object[] existingSelection, Object[] newObjects, Sel System.Array.Copy(existingSelection, newSelection, existingSelection.Length); for (int i = 0; i < newObjects.Length; i++) newSelection[existingSelection.Length + i] = newObjects[i]; - if (!isRectSelection) - Selection.activeObject = newObjects[0]; - else - Selection.activeObject = newSelection[0]; - - Selection.objects = newSelection; + Object active = isRectSelection ? newSelection[0] : newObjects[0]; + Selection.SetSelectionWithActiveObject(newSelection, active); } else { @@ -276,6 +272,7 @@ 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, diff --git a/Editor/Mono/SceneView/SceneOrientationGizmo.cs b/Editor/Mono/SceneView/SceneOrientationGizmo.cs index 432e6bbc32..1beb98ce4d 100644 --- a/Editor/Mono/SceneView/SceneOrientationGizmo.cs +++ b/Editor/Mono/SceneView/SceneOrientationGizmo.cs @@ -3,7 +3,6 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; -using System.Collections.Generic; using UnityEditor; using UnityEditor.AnimatedValues; using UnityEditor.Overlays; @@ -45,7 +44,6 @@ sealed class SceneOrientationGizmo : IMGUIOverlay new GUIContent("x"), new GUIContent("y"), new GUIContent("z") }; - bool showBackGround { get @@ -142,7 +140,7 @@ public SceneOrientationGizmo() collapsedChanged += OnCollapsedChanged; } - void OnCollapsedChanged(bool collapsed) + void OnCollapsedChanged(bool _) { UpdateHeaderAndBackground(); } @@ -216,16 +214,12 @@ public override void OnCreated() { m_ViewDirectionControlIDs = new int[kDirectionRotations.Length]; for (int i = 0; i < m_ViewDirectionControlIDs.Length; ++i) - { m_ViewDirectionControlIDs[i] = GUIUtility.GetPermanentControlID(); - } m_CenterButtonControlID = GUIUtility.GetPermanentControlID(); m_RotationLockControlID = GUIUtility.GetPermanentControlID(); m_PerspectiveIsoControlID = GUIUtility.GetPermanentControlID(); } - - UpdateHeaderAndBackground(); } public override void OnWillBeDestroyed() @@ -237,7 +231,12 @@ public override void OnWillBeDestroyed() Object.DestroyImmediate(m_Camera.gameObject); } - void AxisSelectors(SceneView view, Camera cam, float size, float sgn, GUIStyle viewAxisLabelStyle) + internal override void OnContentRebuild() + { + UpdateHeaderAndBackground(); + } + + void AxisSelectors(SceneView view, Camera cam, float size, float sgn, bool hovered) { for (int h = kDirectionRotations.Length - 1; h >= 0; h--) { @@ -289,7 +288,7 @@ void AxisSelectors(SceneView view, Camera cam, float size, float sgn, GUIStyle v // axis widget when drawn behind label if (sgn > 0 && Handles.Button(m_ViewDirectionControlIDs[h], q1 * Vector3.forward * size * -1.2f, q1, - size, size * 0.7f, Handles.ConeHandleCap)) + size, size * 0.7f, Handles.ConeHandleCap, hovered)) { if (!view.in2DMode && !view.isRotationLocked) ViewAxisDirection(view, h); @@ -311,8 +310,10 @@ void AxisSelectors(SceneView view, Camera cam, float size, float sgn, GUIStyle v } // axis widget when drawn in front of label + // Adding the hovered parameter to avoid all the axis proximity checks that are time consuming + // when the cursor is not hovering the Orientation Gizmo if (sgn < 0 && Handles.Button(m_ViewDirectionControlIDs[h], q1 * Vector3.forward * size * -1.2f, q1, - size, size * 0.7f, Handles.ConeHandleCap)) + size, size * 0.7f, Handles.ConeHandleCap, hovered)) { if (!view.in2DMode && !view.isRotationLocked) ViewAxisDirection(view, h); @@ -585,7 +586,7 @@ public override void OnGUI() GUIClip.Internal_PushParentClip(Matrix4x4.identity, GUIClip.GetParentMatrix(), gizmoRect); } - DoOrientationHandles(view, m_Camera); + DoOrientationHandles(view, m_Camera, gizmoRect.Contains(evt.mousePosition)); if (evt.type == EventType.Repaint) GUIClip.Internal_PopParentClip(); @@ -601,7 +602,7 @@ public override void OnGUI() } } - void DoOrientationHandles(SceneView view, Camera camera) + void DoOrientationHandles(SceneView view, Camera camera, bool isMouseHovering) { Handles.BeginGUI(); DrawRotationLock(view, camera.pixelRect); @@ -617,7 +618,7 @@ void DoOrientationHandles(SceneView view, Camera camera) float size = HandleUtility.GetHandleSize(Vector3.zero) * .2f; // Do axes behind the center one - AxisSelectors(view, camera, size, -1.0f, Styles.viewAxisLabelStyle); + AxisSelectors(view, camera, size, -1.0f, isMouseHovering); // Do center handle Color centerColor = Handles.centerColor; @@ -645,7 +646,7 @@ void DoOrientationHandles(SceneView view, Camera camera) } // Do axes in front of the center one - AxisSelectors(view, camera, size, 1.0f, Styles.viewAxisLabelStyle); + AxisSelectors(view, camera, size, 1.0f, isMouseHovering); GUI.enabled = true; diff --git a/Editor/Mono/SceneView/SceneView.cs b/Editor/Mono/SceneView/SceneView.cs index 1c3ebaad00..e2421bf5cf 100644 --- a/Editor/Mono/SceneView/SceneView.cs +++ b/Editor/Mono/SceneView/SceneView.cs @@ -23,6 +23,7 @@ using UnityEditor.EditorTools; using UnityEditor.Overlays; using UnityEditor.Profiling; +using UnityEditor.UIElements; using UnityEngine.Serialization; using UnityEngine.UIElements; using Component = UnityEngine.Component; @@ -183,11 +184,13 @@ internal static Transform GetDefaultParentObjectIfSet() static void OnSelectedObjectWasDestroyed(int unused) { s_ActiveEditorsDirty = true; + s_SelectionCacheDirty = true; } static void OnEditorTrackerRebuilt() { s_ActiveEditorsDirty = true; + s_SelectionCacheDirty = true; } internal static void SetActiveEditorsDirty(bool forceRepaint = false) @@ -203,6 +206,7 @@ internal static void SetActiveEditorsDirty(bool forceRepaint = false) static List s_ActiveEditors = new List(); static bool s_ActiveEditorsDirty; + static bool s_SelectionCacheDirty; internal static IEnumerable activeEditors { @@ -368,7 +372,7 @@ public bool sceneLighting private bool m_WasFocused = false; - private int[] m_CachedParentRenderersFromSelection, m_CachedChildRenderersFromSelection; + private static int[] s_CachedParentRenderersFromSelection, s_CachedChildRenderersFromSelection; [Serializable] public class SceneViewState @@ -1199,6 +1203,7 @@ public override void OnEnable() UpdateHiddenObjectCount(); + ObjectFactory.componentWasAdded += OnComponentWasAdded; EditorApplication.playModeStateChanged += OnPlayModeStateChanged; EditorApplication.modifierKeysChanged += RepaintAll; // Because we show handles on shift @@ -1235,7 +1240,7 @@ public override void OnEnable() baseRootVisualElement.styleSheets.Add(EditorGUIUtility.Load(k_StyleCommon) as StyleSheet); baseRootVisualElement.styleSheets.Add(EditorGUIUtility.Load(EditorGUIUtility.isProSkin ? k_StyleDark : k_StyleLight) as StyleSheet); - HandleUtility.FilterRendererIDs(Selection.gameObjects, out m_CachedParentRenderersFromSelection, out m_CachedChildRenderersFromSelection); + s_SelectionCacheDirty = true; } IMGUIContainer m_PrefabToolbar; @@ -1265,7 +1270,7 @@ IMGUIContainer prefabToolbar viewDataKey = name, renderHints = RenderHints.ClipWithScissors }; - EditorUIService.instance.AddDefaultEditorStyleSheets(m_PrefabToolbar); + UIElementsEditorUtility.AddDefaultEditorStyleSheets(m_PrefabToolbar); m_PrefabToolbar.style.overflow = UnityEngine.UIElements.Overflow.Hidden; } @@ -1284,7 +1289,7 @@ VisualElement CreateCameraRectVisualElement() renderHints = RenderHints.ClipWithScissors }; - EditorUIService.instance.AddDefaultEditorStyleSheets(root); + UIElementsEditorUtility.AddDefaultEditorStyleSheets(root); root.style.overflow = Overflow.Hidden; root.style.flexGrow = 1; @@ -1299,6 +1304,13 @@ VisualElement CreateCameraRectVisualElement() return root; } + void OnComponentWasAdded(Component component) + { + var renderer = component as Renderer; + if (renderer != null) + s_SelectionCacheDirty = true; + } + void GridOnGridVisibilityChanged(bool visible) { gridVisibilityChanged?.Invoke(visible); @@ -1441,6 +1453,8 @@ public override void OnDisable() if (m_StageHandling != null) m_StageHandling.OnDisable(); SceneViewMotion.DeactivateFlyModeContext(); + ObjectFactory.componentWasAdded -= OnComponentWasAdded; + base.OnDisable(); } @@ -1567,7 +1581,7 @@ void OnSelectionChange() m_WasFocused = false; - HandleUtility.FilterRendererIDs(Selection.gameObjects, out m_CachedParentRenderersFromSelection, out m_CachedChildRenderersFromSelection); + s_SelectionCacheDirty = true; Repaint(); } @@ -1917,7 +1931,7 @@ internal bool IsSceneCameraDeferred() bool usingScriptableRenderPipeline = (GraphicsSettings.currentRenderPipeline != null); if (m_Camera == null || usingScriptableRenderPipeline) return false; - if (m_Camera.actualRenderingPath == RenderingPath.DeferredLighting || m_Camera.actualRenderingPath == RenderingPath.DeferredShading) + if (m_Camera.actualRenderingPath == RenderingPath.DeferredShading) return true; return false; } @@ -2035,6 +2049,7 @@ bool SceneCameraRendersIntoRT() private void DoDrawCamera(Rect windowSpaceCameraRect, Rect groupSpaceCameraRect, out bool pushedGUIClip) { pushedGUIClip = false; + if (!m_Camera.gameObject.activeInHierarchy) return; @@ -2067,13 +2082,24 @@ private void DoDrawCamera(Rect windowSpaceCameraRect, Rect groupSpaceCameraRect, if (SceneCameraRendersIntoRT()) { 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; } Handles.DrawCameraStep1(groupSpaceCameraRect, m_Camera, m_CameraMode.drawMode, gridParam, drawGizmos, true); - if (AnnotationUtility.showSelectionOutline && evt.type == EventType.Repaint) + if (evt.type == EventType.Repaint) { - Handles.DrawOutlineInternal(kSceneViewSelectedOutline, kSceneViewSelectedChildrenOutline, 1 - alphaMultiplier, m_CachedParentRenderersFromSelection, m_CachedChildRenderersFromSelection); + if (s_SelectionCacheDirty) + { + HandleUtility.FilterInstanceIDs(Selection.gameObjects, out s_CachedParentRenderersFromSelection, out s_CachedChildRenderersFromSelection); + s_SelectionCacheDirty = false; + } + + OutlineDrawMode selectionOutlineWireFlags = 0; + if (AnnotationUtility.showSelectionOutline) selectionOutlineWireFlags |= OutlineDrawMode.SelectionOutline; + if (AnnotationUtility.showSelectionWire) selectionOutlineWireFlags |= OutlineDrawMode.SelectionWire; + if (selectionOutlineWireFlags != 0) + Handles.DrawOutlineOrWireframeInternal(kSceneViewSelectedOutline, kSceneViewSelectedChildrenOutline, 1 - alphaMultiplier, s_CachedParentRenderersFromSelection, s_CachedChildRenderersFromSelection, selectionOutlineWireFlags); } DrawRenderModeOverlay(groupSpaceCameraRect); @@ -2169,6 +2195,24 @@ void RenderFilteredScene(Rect groupSpaceCameraRect) RenderTexture.ReleaseTemporary(colorRT); RenderTexture.ReleaseTemporary(fadedRT); + OutlineDrawMode selectionDrawModeMask = 0; + if (AnnotationUtility.showSelectionOutline) + selectionDrawModeMask |= OutlineDrawMode.SelectionOutline; + if (AnnotationUtility.showSelectionWire) + selectionDrawModeMask |= OutlineDrawMode.SelectionWire; + + if (Event.current.type == EventType.Repaint && selectionDrawModeMask != 0) + { + if (s_SelectionCacheDirty) + { + HandleUtility.FilterInstanceIDs(Selection.gameObjects, out s_CachedParentRenderersFromSelection, out s_CachedChildRenderersFromSelection); + s_SelectionCacheDirty = false; + } + + Handles.DrawOutlineOrWireframeInternal(kSceneViewSelectedOutline, kSceneViewSelectedChildrenOutline, 1 - alphaMultiplier, s_CachedParentRenderersFromSelection, s_CachedChildRenderersFromSelection, selectionDrawModeMask); + Handles.Internal_FinishDrawingCamera(m_Camera, true); + } + // Reset camera m_Camera.SetReplacementShader(m_ReplacementShader, m_ReplacementString); m_Camera.renderingPath = oldRenderingPath; @@ -2401,9 +2445,14 @@ void DoOnGUI() Profiler.BeginSample("SceneView.BlitRT"); Graphics.SetRenderTarget(null); } + // If we reset the offsets pop that clip off now. if (pushedGUIClip) + { + GUIClip.Internal_PopParentClip(); GUIClip.Pop(); + } + if (evt.type == EventType.Repaint) { Graphics.DrawTexture(groupSpaceCameraRect, m_SceneTargetTexture, new Rect(0, 0, 1, 1), 0, 0, 0, 0, GUI.color, EditorGUIUtility.GUITextureBlit2SRGBMaterial); @@ -3291,7 +3340,7 @@ internal void HandleDragging(Event evt) if (DragAndDrop.HasHandler(DragAndDropWindowTarget.sceneView)) { PickObject(ref pickedObject, ref parentTransform); - DragAndDrop.visualMode = DragAndDrop.Drop(DragAndDropWindowTarget.sceneView, pickedObject, pivot, Event.current.mousePosition, parentTransform, isPerform); + DragAndDrop.visualMode = DragAndDrop.DropOnSceneWindow(pickedObject, pivot, Event.current.mousePosition, parentTransform, isPerform); dropHandled = DragAndDrop.visualMode != DragAndDropVisualMode.None; } @@ -3371,28 +3420,28 @@ void CommandsGUI() case EventCommandNames.Duplicate: if (execute) { - CutCopyPasteUtility.DuplicateGO(customParentForNewGameObjects); + ClipboardUtility.DuplicateGO(customParentForNewGameObjects); } Event.current.Use(); break; case EventCommandNames.Copy: if (execute) { - CutCopyPasteUtility.CopyGO(); + ClipboardUtility.CopyGO(); } Event.current.Use(); break; case EventCommandNames.Cut: if (execute) { - CutCopyPasteUtility.CutGO(); + ClipboardUtility.CutGO(); } Event.current.Use(); break; case EventCommandNames.Paste: if (execute) { - CutCopyPasteUtility.PasteGO(customParentForNewGameObjects); + ClipboardUtility.PasteGO(customParentForNewGameObjects); } Event.current.Use(); break; @@ -3451,7 +3500,7 @@ void CommandsGUI() // Detect if we are canceling 'Cut' operation if (Event.current.keyCode == KeyCode.Escape && CutBoard.hasCutboardData) { - CutCopyPasteUtility.ResetCutboardAndRepaintHierarchyWindows(); + ClipboardUtility.ResetCutboardAndRepaintHierarchyWindows(); Repaint(); } } @@ -3711,9 +3760,9 @@ void CallOnSceneGUI() GUIUtility.ExitGUI(); } } + EditorToolManager.InvokeOnSceneGUICustomEditorTools(); } - EditorToolManager.InvokeOnSceneGUICustomEditorTools(); if (duringSceneGui != null) { @@ -3819,12 +3868,6 @@ static void ShowCompileErrorNotification() ShowNotification("All compiler errors have to be fixed before you can enter playmode!"); } - static void ShowPrefabErrorNotification() - { - ShowNotification("All Prefab instances without a source Prefab must be fixed before you can enter playmode!"); - Debug.LogError("All Prefab instances without a source Prefab Asset must be fixed before you can enter playmode!\nYou can find them by searching for 'Missing Prefab' in the Hierarchy"); - } - internal static void ShowSceneViewPlayModeSaveWarning() { // In this case, we want to explicitly try the GameView before passing it on to whatever notificationView we have @@ -3960,7 +4003,7 @@ void CopyLastActiveSceneViewSettings() m_2DMode = view.m_2DMode; pivot = view.pivot; rotation = view.rotation; - m_Size = view.m_Size; + size = view.size; m_Ortho.value = view.orthographic; if (m_Grid == null) m_Grid = new SceneViewGrid(); diff --git a/Editor/Mono/SceneView/SceneViewMotion.cs b/Editor/Mono/SceneView/SceneViewMotion.cs index 651f12526c..92b73613f4 100644 --- a/Editor/Mono/SceneView/SceneViewMotion.cs +++ b/Editor/Mono/SceneView/SceneViewMotion.cs @@ -530,7 +530,7 @@ private static void HandleScrollWheel(SceneView view, bool zoomTowardsCenter) GUIContent cameraSpeedContent = EditorGUIUtility.TempContent(string.Format("{0}{1}", cameraSpeedDisplayValue, - s_CurrentSceneView.cameraSettings.accelerationEnabled ? "x" : "")); + view.cameraSettings.accelerationEnabled ? "x" : "")); view.ShowNotification(cameraSpeedContent, .5f); } diff --git a/Editor/Mono/SceneView/SceneViewToolbars.cs b/Editor/Mono/SceneView/SceneViewToolbars.cs index 4ab2f6a3c9..50e1277712 100644 --- a/Editor/Mono/SceneView/SceneViewToolbars.cs +++ b/Editor/Mono/SceneView/SceneViewToolbars.cs @@ -53,7 +53,7 @@ public OverlayToolbar CreateHorizontalToolbarContent() public override VisualElement CreatePanelContent() { - return CreatePanelContent(); + return CreateHorizontalToolbarContent(); } public IEnumerable toolbarElements diff --git a/Editor/Mono/SceneVisibilityManager.cs b/Editor/Mono/SceneVisibilityManager.cs index 0f09161b48..1322efa7d8 100644 --- a/Editor/Mono/SceneVisibilityManager.cs +++ b/Editor/Mono/SceneVisibilityManager.cs @@ -57,7 +57,7 @@ internal bool enableScenePicking [InitializeOnLoadMethod] private static void Initialize() { - Undo.undoRedoPerformed += UndoRedoPerformed; + Undo.undoRedoEvent += UndoRedoPerformed; EditorSceneManager.newSceneCreated += EditorSceneManagerOnNewSceneCreated; EditorSceneManager.sceneSaving += EditorSceneManagerOnSceneSaving; EditorSceneManager.sceneSaved += EditorSceneManagerOnSceneSaved; @@ -150,7 +150,7 @@ private static void EditorSceneManagerOnNewSceneCreated(Scene scene, NewSceneSet SceneVisibilityState.ClearScene(scene); } - private static void UndoRedoPerformed() + private static void UndoRedoPerformed(in UndoRedoInfo info) { SceneVisibilityState.ForceDataUpdate(); } @@ -680,46 +680,16 @@ private static void ToggleSelectionAndDescendantsVisibility() [Shortcut("Scene Picking/Toggle Picking On Selection And Descendants", typeof(ShortcutContext), KeyCode.L)] private static void ToggleSelectionAndDescendantsPicking() { - bool shouldDisablePicking = true; - foreach (var gameObject in Selection.gameObjects) - { - if (!instance.IsPickingDisabled(gameObject)) - { - break; - } - - shouldDisablePicking = false; - } - - instance.m_SelectedScenes.Clear(); - - if (shouldDisablePicking) - { - SceneHierarchyWindow.lastInteractedHierarchyWindow.GetSelectedScenes(instance.m_SelectedScenes); - - foreach (var scene in instance.m_SelectedScenes) - { - if (!instance.IsPickingDisabled(scene)) - break; - - shouldDisablePicking = false; - } - } - - Undo.RecordObject(SceneVisibilityState.GetInstance(), "Toggle Selection And Descendants Picking"); - SceneVisibilityState.SetGameObjectsPickingDisabled(Selection.gameObjects, shouldDisablePicking, true); - - foreach (var scene in instance.m_SelectedScenes) - { - if (shouldDisablePicking) - instance.DisablePicking(scene); - else - instance.EnablePicking(scene); - } + ToggleSelectionPicking(true); } [Shortcut("Scene Picking/Toggle Picking On Selection")] internal static void ToggleSelectionPickable() + { + ToggleSelectionPicking(false); + } + + private static void ToggleSelectionPicking(bool includeChildren) { bool shouldDisablePicking = true; foreach (var gameObject in Selection.gameObjects) @@ -732,7 +702,6 @@ internal static void ToggleSelectionPickable() shouldDisablePicking = false; } - instance.m_SelectedScenes.Clear(); if (shouldDisablePicking) @@ -748,9 +717,9 @@ internal static void ToggleSelectionPickable() } } - Undo.RecordObject(SceneVisibilityState.GetInstance(), "Toggle Selection Pickable"); - SceneVisibilityState.SetGameObjectsPickingDisabled(Selection.gameObjects, shouldDisablePicking, false); - + var undoName = includeChildren ? "Toggle Selection And Descendants Picking" : "Toggle Selection Pickable"; + Undo.RecordObject(SceneVisibilityState.GetInstance(), undoName); + SceneVisibilityState.SetGameObjectsPickingDisabled(Selection.gameObjects, shouldDisablePicking, includeChildren); foreach (var scene in instance.m_SelectedScenes) { @@ -759,8 +728,11 @@ internal static void ToggleSelectionPickable() else instance.EnablePicking(scene); } + + EditorApplication.RepaintHierarchyWindow(); } + [Shortcut("Scene Visibility/Exit Isolation")] private static void ExitIsolationShortcut() { diff --git a/Editor/Mono/ScriptAttributeGUI/Implementations/ExposedReferenceDrawer.cs b/Editor/Mono/ScriptAttributeGUI/Implementations/ExposedReferenceDrawer.cs index 9e3c76b032..0e3338835d 100644 --- a/Editor/Mono/ScriptAttributeGUI/Implementations/ExposedReferenceDrawer.cs +++ b/Editor/Mono/ScriptAttributeGUI/Implementations/ExposedReferenceDrawer.cs @@ -207,7 +207,14 @@ protected override void OnRenderProperty(Rect position, ExposedPropertyMode mode, IExposedPropertyTable exposedPropertyTable) { - var typeOfExposedReference = fieldInfo.FieldType.GetGenericArguments()[0]; + var propertyType = fieldInfo.FieldType; + + if (propertyType.IsArrayOrList()) + { + propertyType = propertyType.GetArrayOrListElementType(); + } + + var typeOfExposedReference = propertyType.GetGenericArguments()[0]; EditorGUI.BeginChangeCheck(); var newValue = EditorGUI.ObjectField(position, currentReferenceValue, typeOfExposedReference, exposedPropertyTable != null); diff --git a/Editor/Mono/ScriptAttributeGUI/Implementations/PropertyDrawers.cs b/Editor/Mono/ScriptAttributeGUI/Implementations/PropertyDrawers.cs index 984566716e..7f6d05b507 100644 --- a/Editor/Mono/ScriptAttributeGUI/Implementations/PropertyDrawers.cs +++ b/Editor/Mono/ScriptAttributeGUI/Implementations/PropertyDrawers.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using UnityEditor.UIElements; using UnityEngine; using UnityEngine.UIElements; @@ -121,43 +122,52 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) { if (property.type == "float") { - newField = EditorUIService.instance.CreateFloatField(property.displayName, OnValidateValue); + newField = new FloatField(property.displayName); + ((BaseField)newField).onValidateValue += OnValidateValue; } else if (property.type == "double") { - newField = EditorUIService.instance.CreateDoubleField(property.displayName, OnValidateValue); + newField = new DoubleField(property.displayName); + ((BaseField)newField).onValidateValue += OnValidateValue; } } else if (property.propertyType == SerializedPropertyType.Integer) { if (property.type == "int") { - newField = EditorUIService.instance.CreateIntField(property.displayName, OnValidateValue); + newField = new IntegerField(property.displayName); + ((BaseField)newField).onValidateValue += OnValidateValue; } else if (property.type == "long") { - newField = EditorUIService.instance.CreateLongField(property.displayName, OnValidateValue); + newField = new LongField(property.displayName); + ((BaseField)newField).onValidateValue += OnValidateValue; } } else if (property.propertyType == SerializedPropertyType.Vector2) { - newField = EditorUIService.instance.CreateVector2Field(property.displayName, OnValidateValue); + newField = new Vector2Field(property.displayName); + ((BaseField)newField).onValidateValue += OnValidateValue; } else if (property.propertyType == SerializedPropertyType.Vector2Int) { - newField = EditorUIService.instance.CreateVector2IntField(property.displayName, OnValidateValue); + newField = new Vector2IntField(property.displayName); + ((BaseField)newField).onValidateValue += OnValidateValue; } else if (property.propertyType == SerializedPropertyType.Vector3) { - newField = EditorUIService.instance.CreateVector3Field(property.displayName, OnValidateValue); + newField = new Vector3Field(property.displayName); + ((BaseField)newField).onValidateValue += OnValidateValue; } else if (property.propertyType == SerializedPropertyType.Vector3Int) { - newField = EditorUIService.instance.CreateVector3IntField(property.displayName, OnValidateValue); + newField = new Vector3IntField(property.displayName); + ((BaseField)newField).onValidateValue += OnValidateValue; } else if (property.propertyType == SerializedPropertyType.Vector4) { - newField = EditorUIService.instance.CreateVector4Field(property.displayName, OnValidateValue); + newField = new Vector4Field(property.displayName); + ((BaseField)newField).onValidateValue += OnValidateValue; } if (newField != null) @@ -248,7 +258,8 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) if (property.propertyType == SerializedPropertyType.String) { var lines = ((MultilineAttribute)attribute).lines; - var field = EditorUIService.instance.CreateTextField(property.displayName, true); + var field = new TextField(property.displayName); + field.multiline = true; field.bindingPath = property.propertyPath; field.style.height = EditorGUI.kSingleLineHeight + (lines - 1) * kLineHeight; return field; @@ -301,7 +312,8 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) var element = new VisualElement(); var label = new Label(property.displayName); var scrollView = new ScrollView(); - var textField = EditorUIService.instance.CreateTextField(null, true); + var textField = new TextField(); + textField.multiline = true; var minHeight = EditorGUI.kSingleLineHeight + (textAreaAttribute.minLines - 1) * kLineHeight; var maxHeight = minHeight; @@ -367,7 +379,9 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) if (property.propertyType == SerializedPropertyType.Color) { var colorUsage = (ColorUsageAttribute)attribute; - var field = EditorUIService.instance.CreateColorField(property.displayName, colorUsage.showAlpha, colorUsage.hdr); + var field = new ColorField(property.displayName); + field.showAlpha = colorUsage.showAlpha; + field.hdr = colorUsage.hdr; field.bindingPath = property.propertyPath; return field; } @@ -398,7 +412,9 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) if (property.propertyType == SerializedPropertyType.Gradient) { var gradientUsage = (GradientUsageAttribute)attribute; - var field = EditorUIService.instance.CreateGradientField(property.displayName, gradientUsage.hdr, gradientUsage.colorSpace); + var field = new GradientField(property.displayName); + field.hdr = gradientUsage.hdr; + field.colorSpace = gradientUsage.colorSpace; return field; } @@ -430,27 +446,32 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) { if (property.type == "float") { - newField = EditorUIService.instance.CreateFloatField(property.displayName, null, true); + newField = new FloatField(property.displayName); + ((TextInputBaseField)newField).isDelayed = true; } else if (property.type == "double") { - newField = EditorUIService.instance.CreateDoubleField(property.displayName, null, true); + newField = new DoubleField(property.displayName); + ((TextInputBaseField)newField).isDelayed = true; } } else if (property.propertyType == SerializedPropertyType.Integer) { if (property.type == "int") { - newField = EditorUIService.instance.CreateIntField(property.displayName, null, true); + newField = new IntegerField(property.displayName); + ((TextInputBaseField)newField).isDelayed = true; } else if (property.type == "long") { - newField = EditorUIService.instance.CreateLongField(property.displayName, null, true); + newField = new LongField(property.displayName); + ((TextInputBaseField)newField).isDelayed = true; } } else if (property.propertyType == SerializedPropertyType.String) { - newField = EditorUIService.instance.CreateTextField(property.displayName, false, true); + newField = new TextField(property.displayName); + ((TextInputBaseField)newField).isDelayed = true; } if (newField != null) diff --git a/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs b/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs index 8c8964fb4e..5761c9d712 100644 --- a/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs +++ b/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs @@ -37,7 +37,12 @@ internal PropertyDrawer propertyDrawer static PropertyHandler() { - Undo.undoRedoPerformed += () => ReorderableList.ClearExistingListCaches(); + Undo.undoRedoEvent += OnUndoRedo; + } + + static void OnUndoRedo(in UndoRedoInfo info) + { + ReorderableList.InvalidateExistingListCaches(); } public static void ClearCache() @@ -46,11 +51,13 @@ public static void ClearCache() s_LastInspectionTarget = 0; } - public static void ClearListCacheIncludingChildren(string propertyPath) + public static void InvalidateListCacheIncludingChildren(SerializedProperty property) { foreach (var listEntry in s_reorderableLists) { - if (listEntry.Key.Contains(propertyPath)) listEntry.Value.ClearCache(); + if (listEntry.Key.Contains(property.propertyPath) + && listEntry.Key.Contains(property.serializedObject.targetObject.GetInstanceID().ToString() + (GUIView.current?.nativeHandle.ToInt32() ?? -1))) + listEntry.Value.InvalidateCache(); } } @@ -77,9 +84,6 @@ public void HandleAttribute(SerializedProperty property, PropertyAttribute attri if (attribute is ContextMenuItemAttribute) { - // Use context menu items on array elements, not on array itself - if (propertyType.IsArrayOrList()) - return; if (contextMenuItems == null) contextMenuItems = new List(); contextMenuItems.Add(attribute as ContextMenuItemAttribute); @@ -203,11 +207,19 @@ internal bool OnGUI(Rect position, SerializedProperty property, GUIContent label // Calculate visibility rect specifically for reorderable list as when applied for the whole serialized object, // it causes collapsed out of sight array elements appear thus messing up scroll-bar experience var screenPos = GUIUtility.GUIToScreenPoint(position.position); - screenPos.y = Mathf.Clamp(screenPos.y, 0, Screen.height); - Rect listVisibility = new Rect(screenPos.x, screenPos.y, Screen.width, Screen.height); + + screenPos.y = Mathf.Clamp(screenPos.y, + GUIView.current?.screenPosition.yMin ?? 0, + GUIView.current?.screenPosition.yMax ?? Screen.height); + + Rect listVisibility = new Rect(screenPos.x, screenPos.y, + GUIView.current?.screenPosition.width ?? Screen.width, + GUIView.current?.screenPosition.height ?? Screen.height); + listVisibility = GUIUtility.ScreenToGUIRect(listVisibility); - reorderableList.Property = property; + // Copy helps with recursive list rendering + reorderableList.Property = property.Copy(); reorderableList.Draw(label, position, listVisibility, tooltip, includeChildren); return !includeChildren && property.isExpanded; } @@ -228,12 +240,21 @@ internal bool OnGUI(Rect position, SerializedProperty property, GUIContent label bool childrenAreExpanded = EditorGUI.DefaultPropertyField(position, prop, label) && EditorGUI.HasVisibleChildFields(prop); position.y += position.height + EditorGUI.kControlVerticalSpacing; + if (property.isArray) + EditorGUI.BeginIsInsideList(prop.depth); + // Loop through all child properties if (childrenAreExpanded) { SerializedProperty endProperty = prop.GetEndProperty(); while (prop.NextVisible(childrenAreExpanded) && !SerializedProperty.EqualContents(prop, endProperty)) { + if (GUI.isInsideList && prop.depth <= EditorGUI.GetInsideListDepth()) + EditorGUI.EndIsInsideList(); + + if (prop.isArray) + EditorGUI.BeginIsInsideList(prop.depth); + var handler = ScriptAttributeUtility.GetHandler(prop); EditorGUI.indentLevel = prop.depth + relIndent; position.height = handler.GetHeight(prop, null, UseReorderabelListControl(prop) && includeChildren); @@ -253,6 +274,8 @@ internal bool OnGUI(Rect position, SerializedProperty property, GUIContent label } // Restore state + if (GUI.isInsideList && property.depth <= EditorGUI.GetInsideListDepth()) + EditorGUI.EndIsInsideList(); GUI.enabled = wasEnabled; EditorGUIUtility.SetIconSize(oldIconSize); EditorGUI.indentLevel = origIndent; @@ -306,7 +329,8 @@ public float GetHeight(SerializedProperty property, GUIContent label, bool inclu s_reorderableLists[key] = reorderableList; } - reorderableList.Property = property; + // Copy helps with recursive list rendering + reorderableList.Property = property.Copy(); height += s_reorderableLists[key].GetHeight(); return height; } diff --git a/Editor/Mono/Scripting/APIUpdater/APIUpdaterAssemblyHelper.cs b/Editor/Mono/Scripting/APIUpdater/APIUpdaterAssemblyHelper.cs index 167b2f6010..ee0fe213a9 100644 --- a/Editor/Mono/Scripting/APIUpdater/APIUpdaterAssemblyHelper.cs +++ b/Editor/Mono/Scripting/APIUpdater/APIUpdaterAssemblyHelper.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using System.Diagnostics; using UnityEditor.Utils; using UnityEngine; @@ -49,13 +48,7 @@ internal static int Run(string arguments, string workingDir, out string stdOut, return assemblyUpdaterProcess.ExitCode; } - static string AssemblyUpdaterPath() - { - var unescapedAssemblyUpdaterPath = EditorApplication.applicationContentsPath + "/Tools/ScriptUpdater/AssemblyUpdater.exe"; - return Application.platform == RuntimePlatform.WindowsEditor - ? CommandLineFormatter.EscapeCharsWindows(unescapedAssemblyUpdaterPath) - : CommandLineFormatter.EscapeCharsQuote(unescapedAssemblyUpdaterPath); - } + static string AssemblyUpdaterPath() => EditorApplication.applicationContentsPath + "/Tools/ScriptUpdater/AssemblyUpdater.exe"; internal static string ArgumentsForUpdateAssembly(string assemblyPath, string tempOutputPath, IEnumerable updateConfigSourcePaths) { @@ -97,10 +90,29 @@ private static string ResolveAssemblyPath(string assemblyPath) private static string AssemblySearchPathArgument(IEnumerable configurationSourceDirectories = null) { + var req = PackageManager.Client.List(offlineMode: true, includeIndirectDependencies: true); + while (!req.IsCompleted) + System.Threading.Thread.Sleep(10); + + var packagePathsToSearchForAssemblies = new StringBuilder(); + + if (req.Status == PackageManager.StatusCode.Success) + { + foreach(var resolvedPackage in req.Result) + { + packagePathsToSearchForAssemblies.Append($"{Path.PathSeparator}+{resolvedPackage.resolvedPath.Escape(Path.PathSeparator)}"); + } + } + else + { + APIUpdaterLogger.WriteToFile(L10n.Tr($"Unable to retrieve project configured packages; AssemblyUpdater may fail to resolve assemblies from packages. Status = {req.Error?.message} ({req.Error?.errorCode})")); + } + var searchPath = NetStandardFinder.GetReferenceDirectory().Escape(Path.PathSeparator) + Path.PathSeparator + NetStandardFinder.GetNetStandardCompatShimsDirectory().Escape(Path.PathSeparator) + Path.PathSeparator + NetStandardFinder.GetDotNetFrameworkCompatShimsDirectory().Escape(Path.PathSeparator) + Path.PathSeparator - + "+" + Application.dataPath.Escape(Path.PathSeparator); + + "+" + Application.dataPath.Escape(Path.PathSeparator) + + packagePathsToSearchForAssemblies.ToString(); if (configurationSourceDirectories != null) { diff --git a/Editor/Mono/Scripting/Compilers/MicrosoftResponseFileParser.cs b/Editor/Mono/Scripting/Compilers/MicrosoftResponseFileParser.cs index a83e00c362..be1873ceb1 100644 --- a/Editor/Mono/Scripting/Compilers/MicrosoftResponseFileParser.cs +++ b/Editor/Mono/Scripting/Compilers/MicrosoftResponseFileParser.cs @@ -104,9 +104,8 @@ public static ResponseFileData ParseResponseFileFromFile( projectDirectory = Paths.ConvertSeparatorsToUnity(projectDirectory); var relativeResponseFilePath = Paths.GetPathRelativeToProjectDirectory(responseFilePath); - var responseFile = AssetDatabase.LoadAssetAtPath(relativeResponseFilePath); - if (!responseFile && File.Exists(responseFilePath)) + if (File.Exists(responseFilePath)) { var responseFileText = File.ReadAllText(responseFilePath); return ParseResponseFileText( @@ -115,8 +114,7 @@ public static ResponseFileData ParseResponseFileFromFile( projectDirectory, systemReferenceDirectories); } - - if (!responseFile) + else { var empty = new ResponseFileData { @@ -129,12 +127,6 @@ public static ResponseFileData ParseResponseFileFromFile( return empty; } - - return ParseResponseFileText( - responseFile.text, - responseFile.name, - projectDirectory, - systemReferenceDirectories); } static ResponseFileData ParseResponseFileText( diff --git a/Editor/Mono/Scripting/PragmaFixing30.cs b/Editor/Mono/Scripting/PragmaFixing30.cs deleted file mode 100644 index f4accaaab8..0000000000 --- a/Editor/Mono/Scripting/PragmaFixing30.cs +++ /dev/null @@ -1,148 +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 UnityEditor; -using UnityEditorInternal; -using System; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using System.Collections; -using System.Collections.Generic; -using RequiredByNativeCodeAttribute = UnityEngine.Scripting.RequiredByNativeCodeAttribute; - -namespace UnityEditor.Scripting -{ - internal class PragmaFixing30 - { - [RequiredByNativeCode] - static void FixJavaScriptPragmas() - { - string[] filesToFix = CollectBadFiles(); - if (filesToFix.Length == 0) - return; - - if (!InternalEditorUtility.inBatchMode) - PragmaFixingWindow.ShowWindow(filesToFix); - else - FixFiles(filesToFix); - } - - public static void FixFiles(string[] filesToFix) - { - foreach (string f in filesToFix) - { - try - { - FixPragmasInFile(f); - } - catch (Exception ex) - { - Debug.LogError("Failed to fix pragmas in file '" + f + "'.\n" + ex.Message); - } - } - } - - static bool FileNeedsPragmaFixing(string fileName) - { - return CheckOrFixPragmas(fileName, true); - } - - static void FixPragmasInFile(string fileName) - { - CheckOrFixPragmas(fileName, false); - } - - static bool CheckOrFixPragmas(string fileName, bool onlyCheck) - { - string oldText = File.ReadAllText(fileName); - StringBuilder text = new StringBuilder(oldText); - - LooseComments(text); - - Match strictMatch = PragmaMatch(text, "strict"); - - if (!strictMatch.Success) - return false; - - bool hasDowncast = PragmaMatch(text, "downcast").Success; - bool hasImplicit = PragmaMatch(text, "implicit").Success; - - if (hasDowncast && hasImplicit) - return false; - - if (!onlyCheck) - DoFixPragmasInFile(fileName, oldText, strictMatch.Index + strictMatch.Length, hasDowncast, hasImplicit); - - return true; - } - - static void DoFixPragmasInFile(string fileName, string oldText, int fixPos, bool hasDowncast, bool hasImplicit) - { - string textToAdd = string.Empty; - string lineEndings = HasWinLineEndings(oldText) ? "\r\n" : "\n"; - - if (!hasImplicit) - textToAdd += lineEndings + "#pragma implicit"; - if (!hasDowncast) - textToAdd += lineEndings + "#pragma downcast"; - - File.WriteAllText(fileName, oldText.Insert(fixPos, textToAdd)); - } - - static bool HasWinLineEndings(string text) - { - return text.IndexOf("\r\n") != -1; - } - - static IEnumerable SearchRecursive(string dir, string mask) - { - foreach (string d in Directory.GetDirectories(dir)) - foreach (string f in SearchRecursive(d, mask)) - yield return f; - foreach (string f in Directory.GetFiles(dir, mask)) - yield return f; - } - - static void LooseComments(StringBuilder sb) - { - // TODO: better comment ignoring? this one sort of does the job, it handles // - // and if it's in multiline comments, our added lines will end up commented as well - Regex r = new Regex("//"); - foreach (Match m in r.Matches(sb.ToString())) - { - int pos = m.Index; - while (pos < sb.Length && sb[pos] != '\n' && sb[pos] != '\r') - sb[pos++] = ' '; - } - } - - static Match PragmaMatch(StringBuilder sb, string pragma) - { - // unity java script, like regex, treats new line as space character as well - return new Regex(@"#\s*pragma\s*" + pragma).Match(sb.ToString()); - } - - static string[] CollectBadFiles() - { - List filesToFix = new List(); - - foreach (string f in SearchRecursive(Path.Combine(Directory.GetCurrentDirectory(), "Assets"), "*.js")) - { - try - { - if (FileNeedsPragmaFixing(f)) - filesToFix.Add(f); - } - catch (Exception ex) - { - Debug.LogError("Failed to fix pragmas in file '" + f + "'.\n" + ex.Message); - } - } - - return filesToFix.ToArray(); - } - } -} diff --git a/Editor/Mono/Scripting/ScriptCompilation/ArrayHelper.cs b/Editor/Mono/Scripting/ScriptCompilation/ArrayHelper.cs deleted file mode 100644 index a55871025b..0000000000 --- a/Editor/Mono/Scripting/ScriptCompilation/ArrayHelper.cs +++ /dev/null @@ -1,40 +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; - -namespace UnityEditor.Scripting.ScriptCompilation -{ - internal class ArrayHelper - { - public static T[] Merge(params T[][] arrays) - { - int arraySize = 0; - foreach (var array in arrays) - { - if (array == null) - { - continue; - } - - arraySize += array.Length; - } - - var result = new T[arraySize]; - int index = 0; - foreach (var array in arrays) - { - if (array == null) - { - continue; - } - - Array.Copy(array, 0, result, index, array.Length); - index += array.Length; - } - - return result; - } - } -} diff --git a/Editor/Mono/Scripting/ScriptCompilation/AssemblyBuilder.cs b/Editor/Mono/Scripting/ScriptCompilation/AssemblyBuilder.cs index d7bd8b0c26..84dcd0091a 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/AssemblyBuilder.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/AssemblyBuilder.cs @@ -113,7 +113,7 @@ internal bool Build(EditorCompilation editorCompilation) activeBeeBuild = new BeeScriptCompilationState { assemblies = new[] { assembly }, - Driver = UnityBeeDriver.Make(EditorCompilation.ScriptCompilationBuildProgram, editorCompilation, $"{(int)assembly.BuildTarget}{"AB"}", beeAssemblyBuilderDirectory), + Driver = UnityBeeDriver.Make(EditorCompilation.ScriptCompilationBuildProgram, editorCompilation, $"{(int)assembly.BuildTarget}{"AB"}", new ILPostProcessingProgram(), beeAssemblyBuilderDirectory), editorCompilation = editorCompilation, finishedCompiling = false, }; @@ -175,7 +175,7 @@ private void InvokeBuildFinished(BeeDriverResult result) try { buildFinished?.Invoke(assemblyPath, EditorCompilation.ConvertCompilerMessages(BeeScriptCompilation - .ParseAllNodeResultsIntoCompilerMessages(result.NodeResults, EditorCompilationInterface.Instance) + .ParseAllResultsIntoCompilerMessages(result.BeeDriverMessages, result.NodeResults, EditorCompilationInterface.Instance) .SelectMany(a => a).ToArray())); } catch (Exception e) diff --git a/Editor/Mono/Scripting/ScriptCompilation/AssemblyDefinitionException.cs b/Editor/Mono/Scripting/ScriptCompilation/AssemblyDefinitionException.cs index b44a0ea0a1..9bb737e4f0 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/AssemblyDefinitionException.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/AssemblyDefinitionException.cs @@ -6,30 +6,17 @@ namespace UnityEditor.Compilation { - enum AssemblyDefinitionErrorType - { - LoadError, - CyclicReferences - } - public class AssemblyDefinitionException : Exception { - internal AssemblyDefinitionErrorType errorType { get; } public string[] filePaths { get; } - internal AssemblyDefinitionException(string message, AssemblyDefinitionErrorType errorType, params string[] filePaths) : base(message) - { - this.errorType = errorType; - this.filePaths = filePaths; - } - public AssemblyDefinitionException(string message, params string[] filePaths) : base(message) { - this.errorType = AssemblyDefinitionErrorType.LoadError; this.filePaths = filePaths; } } + [Obsolete("PrecompiledAssemblyException is no longer being thrown by Unity and will be removed.")] public class PrecompiledAssemblyException : Exception { public string[] filePaths { get; } diff --git a/Editor/Mono/Scripting/ScriptCompilation/AssemblyGraphBuilder.cs b/Editor/Mono/Scripting/ScriptCompilation/AssemblyGraphBuilder.cs new file mode 100644 index 0000000000..cb89af1c19 --- /dev/null +++ b/Editor/Mono/Scripting/ScriptCompilation/AssemblyGraphBuilder.cs @@ -0,0 +1,237 @@ +// 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 System.Linq; + +namespace UnityEditor.Scripting.ScriptCompilation +{ + internal interface IAssemblyGraphBuilder + { + Dictionary> Match( + IReadOnlyCollection sources); + } + + internal class AssemblyGraphBuilder : IAssemblyGraphBuilder + { + private readonly string _projectPath; + private readonly CustomScriptAssembly _globalAssemblyDefinition; + private readonly CustomScriptAssembly _editorAssebmlyDefinition; + private readonly CustomScriptAssembly _globalFirstpassAssemblyDefinition; + private readonly CustomScriptAssembly _editorFirstpassAssemblyDefinition; + static readonly string _guidFormat = "N"; + + private readonly CustomScriptAssemblyReference[] _globalFirstpassAssemblyReferences; + + private PathMultidimensionalDivisionTree _npp = + new PathMultidimensionalDivisionTree(); + + public AssemblyGraphBuilder(string projectPath) + { + _projectPath = projectPath; + _globalAssemblyDefinition = new CustomScriptAssembly + { + Name = "Assembly-CSharp", + PathPrefix = projectPath, + FilePath = Path.Combine(projectPath, "main.asmdef"), + GUID = CreateNewUnityGuid(), + IsPredefined = true, + }; + + _editorAssebmlyDefinition = new CustomScriptAssembly + { + Name = "Assembly-CSharp-Editor", + PathPrefix = Path.Combine(projectPath, "Editor"), + FilePath = Path.Combine(projectPath, "Editor/main-editor.asmdef"), + GUID = CreateNewUnityGuid(), + IsPredefined = true, + }; + + _globalFirstpassAssemblyDefinition = new CustomScriptAssembly + { + Name = "Assembly-CSharp-firstpass", + PathPrefix = Path.Combine(projectPath, "Plugins"), + FilePath = Path.Combine(projectPath, "Plugins/firstpass.asmdef"), + GUID = CreateNewUnityGuid(), + IsPredefined = true, + }; + + _editorFirstpassAssemblyDefinition = new CustomScriptAssembly + { + Name = "Assembly-CSharp-Editor-firstpass", + PathPrefix = Path.Combine(projectPath, "Plugins/Editor"), + FilePath = Path.Combine(projectPath, "Plugins/Editor/firstpass-editor.asmdef"), + GUID = CreateNewUnityGuid(), + IsPredefined = true, + }; + + _globalFirstpassAssemblyReferences = new[] + { + CustomScriptAssemblyReference.FromPathAndReference( + Path.Combine(projectPath, "standard assets/standard assets.asmref"), + _globalFirstpassAssemblyDefinition.Name), + CustomScriptAssemblyReference.FromPathAndReference( + Path.Combine(projectPath, "pro standard assets/pro standard assets.asmref"), + _globalFirstpassAssemblyDefinition.Name), + CustomScriptAssemblyReference.FromPathAndReference( + Path.Combine(projectPath, "iphone standard assets/iphone standard assets.asmref"), + _globalFirstpassAssemblyDefinition.Name), + }; + } + + public void Initialize(IReadOnlyCollection assemblies, + IReadOnlyCollection customScriptAssemblyReferences) + { + var assemblyByNameLookup = assemblies.ToDictionary(x => x.Name, x => x); + var assemblyByGuidLookup = assemblies.ToDictionary(x => x.GUID, x => x); + + bool rootOverridden = assemblies.Any(x => AssetPath.ComparePaths(x.PathPrefix, _projectPath)); + if (!rootOverridden) + { + _npp.Insert(_globalAssemblyDefinition.PathPrefix, _globalAssemblyDefinition); + _npp.Insert(_editorAssebmlyDefinition.PathPrefix, _editorAssebmlyDefinition); + _npp.Insert(_globalFirstpassAssemblyDefinition.PathPrefix, _globalFirstpassAssemblyDefinition); + _npp.Insert(_editorFirstpassAssemblyDefinition.PathPrefix, _editorFirstpassAssemblyDefinition); + } + + foreach (var assemblyDef in assemblies) + { + _npp.Insert(assemblyDef.PathPrefix, assemblyDef); + } + + if (!rootOverridden) + { + foreach (var globalFirstpassAssemblyReference in _globalFirstpassAssemblyReferences) + { + _npp.Insert(globalFirstpassAssemblyReference.PathPrefix, _globalFirstpassAssemblyDefinition); + } + } + + foreach (var assemblyReference in customScriptAssemblyReferences) + { + CustomScriptAssembly foundAssemblyDef = null; + var foundAssemblyDefinition = GUIDReference.IsGUIDReference(assemblyReference.Reference) + ? assemblyByGuidLookup.TryGetValue(GUIDReference.GUIDReferenceToGUID(assemblyReference.Reference), + out foundAssemblyDef) + : assemblyByNameLookup.TryGetValue(assemblyReference.Reference, out foundAssemblyDef); + + if (foundAssemblyDefinition) + { + _npp.Insert(assemblyReference.PathPrefix, foundAssemblyDef); + } + else + { + Console.WriteLine( + $"Assembly reference {assemblyReference.FilePath} has no target assembly definition"); + } + } + } + + public Dictionary> Match( + IReadOnlyCollection sources) + { + var assemblyNameSources = new Dictionary>(); + + foreach (var source in sources) + { + var sourceSpan = source.AsSpan(); + var currentMatchingAssemblyDefinition = _npp.MatchClosest(sourceSpan); + currentMatchingAssemblyDefinition = + CheckAndUpdateEditorSpecialFolder(currentMatchingAssemblyDefinition, sourceSpan); + + 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."); + continue; + } + + if (!assemblyNameSources.TryGetValue(currentMatchingAssemblyDefinition, out var sourceList)) + { + sourceList = new List(); + assemblyNameSources[currentMatchingAssemblyDefinition] = sourceList; + } + + sourceList.Add(source); + } + + return assemblyNameSources; + } + + internal static string CreateNewUnityGuid() + { + return Guid.NewGuid().ToString(_guidFormat); + } + + private CustomScriptAssembly CheckAndUpdateEditorSpecialFolder( + CustomScriptAssembly currentMatchingAssemblyDefinition, ReadOnlySpan sourceSpan) + { + int offset = currentMatchingAssemblyDefinition == null + ? 0 + : currentMatchingAssemblyDefinition.PathPrefix.Length; + + if (HasEditorSpecialFolder(sourceSpan[offset..])) + { + if (currentMatchingAssemblyDefinition == null || + currentMatchingAssemblyDefinition == _globalAssemblyDefinition) + { + currentMatchingAssemblyDefinition = _editorAssebmlyDefinition; + } + else if (currentMatchingAssemblyDefinition == _globalFirstpassAssemblyDefinition) + { + currentMatchingAssemblyDefinition = _editorFirstpassAssemblyDefinition; + } + } + + return currentMatchingAssemblyDefinition; + } + + internal static bool HasEditorSpecialFolder(ReadOnlySpan remainingPath) + { + const string editorLower = "editor"; + const string editorUpper = "EDITOR"; + + if (remainingPath.Length < editorLower.Length) + { + return false; + } + + int matchOffset = 0; + for (int i = 0; i < remainingPath.Length; i++) + { + if (editorLower[matchOffset] == remainingPath[i] || editorUpper[matchOffset] == remainingPath[i]) + { + matchOffset++; + if (matchOffset < editorLower.Length) + { + continue; + } + else if (i >= remainingPath.Length || Utility.IsPathSeparator(remainingPath[i + 1])) + { + return true; + } + } + + // forward to next path separator or end + for (; i < remainingPath.Length; i++) + { + if (Utility.IsPathSeparator(remainingPath[i])) + { + break; + } + } + + matchOffset = 0; + if (remainingPath.Length - i < editorLower.Length) + { + return false; + } + } + + return matchOffset == editorLower.Length; + } + } +} diff --git a/Editor/Mono/Scripting/ScriptCompilation/AssemblyGraphBuilderFactory.cs b/Editor/Mono/Scripting/ScriptCompilation/AssemblyGraphBuilderFactory.cs new file mode 100644 index 0000000000..18c077ccaa --- /dev/null +++ b/Editor/Mono/Scripting/ScriptCompilation/AssemblyGraphBuilderFactory.cs @@ -0,0 +1,65 @@ +// 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; + +namespace UnityEditor.Scripting.ScriptCompilation +{ + internal static class AssemblyGraphBuilderFactory + { + class AssemblyGraphBuilderKey + { + public bool Equals(AssemblyGraphBuilderKey other) + { + return string.Equals(projectPath, other.projectPath, StringComparison.Ordinal) + && assemblies.SequenceEqual(other.assemblies) + && customScriptAssemblyReferences.SequenceEqual(other.customScriptAssemblyReferences); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((AssemblyGraphBuilderKey) obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(projectPath, assemblies.Count, customScriptAssemblyReferences.Count); + } + + public string projectPath; + public IReadOnlyCollection assemblies; + public IReadOnlyCollection customScriptAssemblyReferences; + } + + private static Dictionary m_AlreadyInitializedAssemblyGraphBuilder = + new Dictionary(); + + public static IAssemblyGraphBuilder GetOrCreate(string projectPath, + IReadOnlyCollection assemblies, + IReadOnlyCollection customScriptAssemblyReferences) + { + var assemblyGraphBuilderKey = new AssemblyGraphBuilderKey + { + projectPath = projectPath, + assemblies = assemblies, + customScriptAssemblyReferences = customScriptAssemblyReferences, + }; + + if (!m_AlreadyInitializedAssemblyGraphBuilder.TryGetValue(assemblyGraphBuilderKey, + out var assemblyGraphBuilder)) + { + assemblyGraphBuilder = new AssemblyGraphBuilder(projectPath); + assemblyGraphBuilder.Initialize(assemblies, customScriptAssemblyReferences); + m_AlreadyInitializedAssemblyGraphBuilder[assemblyGraphBuilderKey] = assemblyGraphBuilder; + } + + return assemblyGraphBuilder; + } + } +} diff --git a/Editor/Mono/Scripting/ScriptCompilation/AssetPath.cs b/Editor/Mono/Scripting/ScriptCompilation/AssetPath.cs index f31b68f912..35404b24cd 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/AssetPath.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/AssetPath.cs @@ -2,6 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; using UnityEditor.Utils; using File = System.IO.File; using SystemPath = System.IO.Path; @@ -13,6 +14,42 @@ static class AssetPath { public static readonly char Separator = '/'; + /// + /// Compares two paths + /// type of slashes is ignored, so different path separators is allowed + /// + /// + /// + /// + public static bool ComparePaths(string left, string right) + { + var lengthDiff = left.Length - right.Length; + if (lengthDiff > 1 || lengthDiff < -1) + return false; + + for (int i = 0; i < left.Length || i < right.Length; i++) + { + char leftCharLower = ' '; + char rightCharLower= ' '; + if (i < left.Length) + { + leftCharLower = Utility.FastToLower(left[i]); + leftCharLower = Utility.IsPathSeparator(leftCharLower) ? ' ' : leftCharLower; + } + + if (i < right.Length) + { + rightCharLower = Utility.FastToLower(right[i]); + rightCharLower = Utility.IsPathSeparator(rightCharLower) ? ' ' : rightCharLower; + } + + if (leftCharLower != rightCharLower) + return false; + } + + return true; + } + public static string GetFullPath(string path) { return ReplaceSeparators(SystemPath.GetFullPath(path.NormalizePath())); diff --git a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/BeeScriptCompilation.cs b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/BeeScriptCompilation.cs index f3b46f0366..f127250406 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/BeeScriptCompilation.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/BeeScriptCompilation.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using Bee.BeeDriver; using NiceIO; @@ -22,6 +23,7 @@ namespace UnityEditor.Scripting.ScriptCompilation internal static class BeeScriptCompilation { internal static string ExecutableExtension => Application.platform == RuntimePlatform.WindowsEditor ? ".exe" : ""; + private static string projectPath = Path.GetDirectoryName(Application.dataPath); public static void AddScriptCompilationData(BeeDriver beeDriver, EditorCompilation editorCompilation, @@ -36,14 +38,6 @@ public static void AddScriptCompilationData(BeeDriver beeDriver, // as that acts on the same ScriptAssemblies, and modifies them with different build settings. var cachedAssemblies = AssemblyDataFrom(assemblies); - AssemblyData[] codeGenAssemblies; - using (new ProfilerMarker("GetScriptAssembliesForCodeGen").Auto()) - { - codeGenAssemblies = buildingForEditor - ? null - : AssemblyDataFrom(CodeGenAssemblies(CompilationPipeline.GetScriptAssemblies(editorCompilation, AssembliesType.Editor, extraScriptingDefines))); - } - var movedFromExtractorPath = EditorApplication.applicationContentsPath + $"/Tools/ScriptUpdater/ApiUpdater.MovedFromExtractor.exe"; var dotNetSdkRoslynPath = EditorApplication.applicationContentsPath + $"/DotNetSdkRoslyn"; @@ -68,7 +62,7 @@ public static void AddScriptCompilationData(BeeDriver beeDriver, } var precompileAssemblies = editorCompilation.PrecompiledAssemblyProvider.GetPrecompiledAssembliesDictionary( - buildingForEditor, + options, BuildPipeline.GetBuildTargetGroup(buildTarget), buildTarget, extraScriptingDefines); @@ -92,7 +86,6 @@ public static void AddScriptCompilationData(BeeDriver beeDriver, DotnetRoslynPath = dotNetSdkRoslynPath, MovedFromExtractorPath = movedFromExtractorPath, Assemblies = cachedAssemblies, - CodegenAssemblies = codeGenAssemblies, Debug = debug, BuildTarget = buildTarget.ToString(), Localization = localization, @@ -104,14 +97,6 @@ public static void AddScriptCompilationData(BeeDriver beeDriver, }); } - private static ScriptAssembly[] CodeGenAssemblies(ScriptAssembly[] assemblies) => - assemblies - .Where(assembly => UnityCodeGenHelpers.IsCodeGen(FileUtil.GetPathWithoutExtension(assembly.Filename))) - .SelectMany(assembly => assembly.AllRecursiveScripAssemblyReferencesIncludingSelf()) - .Distinct() - .OrderBy(a => a.Filename) - .ToArray(); - private static AssemblyData[] AssemblyDataFrom(ScriptAssembly[] assemblies) { Array.Sort(assemblies, (a1, a2) => string.Compare(a1.Filename, a2.Filename, StringComparison.Ordinal)); @@ -127,6 +112,7 @@ private static AssemblyData AssemblyDataFrom(ScriptAssembly a, ScriptAssembly[] Array.Sort(a.Files, StringComparer.InvariantCulture); var references = a.ScriptAssemblyReferences.Select(r => Array.IndexOf(allAssemblies, r)).ToArray(); Array.Sort(references); + return new AssemblyData { Name = new NPath(a.Filename).FileNameWithoutExtension, @@ -138,13 +124,27 @@ private static AssemblyData AssemblyDataFrom(ScriptAssembly a, ScriptAssembly[] RuleSet = a.CompilerOptions.RoslynAnalyzerRulesetPath, LanguageVersion = a.CompilerOptions.LanguageVersion, Analyzers = a.CompilerOptions.RoslynAnalyzerDllPaths, + AdditionalFiles = a.CompilerOptions.RoslynAdditionalFilePaths, + AnalyzerConfigPath = a.CompilerOptions.AnalyzerConfigPath, UseDeterministicCompilation = a.CompilerOptions.UseDeterministicCompilation, SuppressCompilerWarnings = (a.Flags & AssemblyFlags.SuppressCompilerWarnings) != 0, Asmdef = a.AsmDefPath, CustomCompilerOptions = a.CompilerOptions.AdditionalCompilerArguments, BclDirectories = MonoLibraryHelpers.GetSystemReferenceDirectories(a.CompilerOptions.ApiCompatibilityLevel), DebugIndex = index, - SkipCodeGen = a.SkipCodeGen + SkipCodeGen = a.SkipCodeGen, + Path = projectPath, + }; + } + + private static CompilerMessage AsCompilerMessage(BeeDriverResult.Message message) + { + return new CompilerMessage + { + message = message.Text, + type = message.Kind == BeeDriverResult.MessageKind.Error + ? CompilerMessageType.Error + : CompilerMessageType.Warning, }; } @@ -153,24 +153,33 @@ private static AssemblyData AssemblyDataFrom(ScriptAssembly a, ScriptAssembly[] /// We return them as an array of arrays, so on the caller side you're still able to map a compilermessage to the noderesult where it originated from, /// which we need when invoking per assembly compilation callbacks. /// - public static CompilerMessage[][] ParseAllNodeResultsIntoCompilerMessages(NodeResult[] nodeResults, EditorCompilation editorCompilation) + public static CompilerMessage[][] ParseAllResultsIntoCompilerMessages(BeeDriverResult.Message[] beeDriverMessages, NodeResult[] nodeResults, EditorCompilation editorCompilation) { - var result = new CompilerMessage[nodeResults.Length][]; + // If there's any messages from the bee driver, we add one additional array to the result which contains all of the driver messages converted and augmented like the nodes messages arrays. + bool hasBeeDriverMessages = beeDriverMessages.Length > 0; + var result = new CompilerMessage[nodeResults.Length + (hasBeeDriverMessages ? 1 : 0)][]; - int totalErrors = 0; + int resultIndex = 0; + if (hasBeeDriverMessages) + { + result[resultIndex] = beeDriverMessages.Select(AsCompilerMessage).ToArray(); + ++resultIndex; + } for (int i = 0; i != nodeResults.Length; i++) { - var compilerMessages = ParseCompilerOutput(nodeResults[i]); - - //To be more kind to performance issues in situations where there are thousands of compiler messages, we're going to assume - //that after the first 10 compiler error messages, we get very little benefit from augmenting the rest with higher quality unity specific messaging. - if (totalErrors < 10) - { - UnitySpecificCompilerMessages.AugmentMessagesInCompilationErrorsWithUnitySpecificAdvice(compilerMessages, editorCompilation); - totalErrors += compilerMessages.Count(m => m.type == CompilerMessageType.Error); - } + result[resultIndex] = ParseCompilerOutput(nodeResults[i]); + ++resultIndex; + } - result[i] = compilerMessages; + //To be more kind to performance issues in situations where there are thousands of compiler messages, we're going to assume + //that after the first 10 compiler error messages, we get very little benefit from augmenting the rest with higher quality unity specific messaging. + int totalErrors = 0; + int nextResultToAugment = 0; + while (totalErrors < 10 && nextResultToAugment < result.Length) + { + UnitySpecificCompilerMessages.AugmentMessagesInCompilationErrorsWithUnitySpecificAdvice(result[nextResultToAugment], editorCompilation); + totalErrors += result[nextResultToAugment].Count(m => m.type == CompilerMessageType.Error); + ++nextResultToAugment; } return result; diff --git a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityBeeDriver.cs b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityBeeDriver.cs index 9bc55a836e..211286a372 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityBeeDriver.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityBeeDriver.cs @@ -109,13 +109,26 @@ private static void RecreateDagDirectoryIfNeeded(NPath dagDirectory) } } - public static BeeDriver Make(RunnableProgram buildProgram, EditorCompilation editorCompilation, string dagName, string dagDirectory = null, bool useScriptUpdater = true) + public static BeeDriver Make(RunnableProgram buildProgram, EditorCompilation editorCompilation, string dagName, ILPostProcessingProgram ilpp, string dagDirectory = null, bool useScriptUpdater = true) { - return Make(buildProgram, dagName, dagDirectory, useScriptUpdater, editorCompilation.projectDirectory); + return Make(buildProgram, dagName, dagDirectory, useScriptUpdater, editorCompilation.projectDirectory, ilpp, StdOutModeForScriptCompilation); } - public static BeeDriver Make(RunnableProgram buildProgram, string dagName, - string dagDirectory, bool useScriptUpdater, string projectDirectory, ProgressAPI progressAPI = null, RunnableProgram beeBackendProgram = null) + public const StdOutMode StdOutModeForScriptCompilation = + StdOutMode.LogStartArgumentsAndExitcode | StdOutMode.LogStdOutOnFinish; + public const StdOutMode StdOutModeForPlayerBuilds = + StdOutMode.LogStartArgumentsAndExitcode | StdOutMode.Stream; + + public static BeeDriver Make( + RunnableProgram buildProgram, + string dagName, + string dagDirectory, + bool useScriptUpdater, + string projectDirectory, + ILPostProcessingProgram ilpp, + StdOutMode stdoutMode, + ProgressAPI progressAPI = null, + RunnableProgram beeBackendProgram = null) { var sourceFileUpdaters = useScriptUpdater ? new[] {new UnityScriptUpdater(projectDirectory)} @@ -127,7 +140,7 @@ public static BeeDriver Make(RunnableProgram buildProgram, string dagName, RecreateDagDirectoryIfNeeded(dagDir); NPath profilerOutputFile = UnityBeeDriverProfilerSession.GetTraceEventsOutputForNewBeeDriver() ?? $"{dagDir}/fullprofile.json"; - var result = new BeeDriver(buildProgram, beeBackendProgram ?? UnityBeeBackendProgram(), projectDirectory, dagName, dagDir.ToString(), sourceFileUpdaters, processSourceFileUpdatersResult, progressAPI ?? new UnityProgressAPI("Script Compilation"), profilerOutputFile: profilerOutputFile.ToString()); + var result = new BeeDriver(buildProgram, beeBackendProgram ?? UnityBeeBackendProgram(stdoutMode), projectDirectory, dagName, dagDir.ToString(), sourceFileUpdaters, processSourceFileUpdatersResult, progressAPI ?? new UnityProgressAPI("Script Compilation"), profilerOutputFile: profilerOutputFile.ToString()); result.DataForBuildProgram.Add(new ConfigurationData { @@ -143,17 +156,18 @@ public static BeeDriver Make(RunnableProgram buildProgram, string dagName, AdvancedLicense = PlayerSettings.advancedLicense, Batchmode = InternalEditorUtility.inBatchMode, EmitDataForBeeWhy = (Debug.GetDiagnosticSwitch("EmitDataForBeeWhy").value as bool?)?? false, + NamedPipeOrUnixSocket = ilpp.NamedPipeOrUnixSocket, }); return result; } - internal static RunnableProgram UnityBeeBackendProgram() + internal static RunnableProgram UnityBeeBackendProgram(StdOutMode stdoutMode) { return new SystemProcessRunnableProgram(BeeBackendExecutable, alwaysEnvironmentVariables: new Dictionary() { { "BEE_CACHE_BEHAVIOUR", "_"}, { "CHROMETRACE_TIMEOFFSET","unixepoch"} - }, stdOutMode: StdOutMode.LogStdOutOnFinish | StdOutMode.LogStartArgumentsAndExitcode); + }, stdOutMode: stdoutMode); } } } diff --git a/Editor/Mono/Scripting/ScriptCompilation/CompilationPipeline.cs b/Editor/Mono/Scripting/ScriptCompilation/CompilationPipeline.cs index 47c3d6c6ba..09ac3ad2d5 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/CompilationPipeline.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/CompilationPipeline.cs @@ -31,6 +31,8 @@ public class ScriptCompilerOptions { public string RoslynAnalyzerRulesetPath { get; set; } public string[] RoslynAnalyzerDllPaths { get; set; } + public string[] RoslynAdditionalFilePaths { get; set; } + public string AnalyzerConfigPath {get; set;} public bool AllowUnsafeCode { get; set; } [Obsolete("Use of reference assemblies is always enabled", true)] @@ -53,6 +55,7 @@ public ScriptCompilerOptions() ApiCompatibilityLevel = ApiCompatibilityLevel.NET_Unity_4_8; ResponseFiles = new string[0]; RoslynAnalyzerDllPaths = new string[0]; + RoslynAdditionalFilePaths = new string[0]; LanguageVersion = "9.0"; } @@ -61,6 +64,8 @@ internal ScriptCompilerOptions(ScriptCompilerOptions scriptCompilerOptions) ResponseFiles = new List(scriptCompilerOptions.ResponseFiles).ToArray(); RoslynAnalyzerDllPaths = new List(scriptCompilerOptions.RoslynAnalyzerDllPaths).ToArray(); RoslynAnalyzerRulesetPath = scriptCompilerOptions.RoslynAnalyzerRulesetPath; + AnalyzerConfigPath = scriptCompilerOptions.AnalyzerConfigPath; + RoslynAdditionalFilePaths = scriptCompilerOptions.RoslynAdditionalFilePaths; UseDeterministicCompilation = scriptCompilerOptions.UseDeterministicCompilation; AllowUnsafeCode = scriptCompilerOptions.AllowUnsafeCode; CodeOptimization = scriptCompilerOptions.CodeOptimization; @@ -196,6 +201,7 @@ public static partial class CompilationPipeline public static event Action compilationFinished; [Obsolete("Use compilationStarted, compilationFinished or assemblyCompilationFinished instead. Note that using any of these functions to do time measurements is a bad idea as they run async to actual compilation.")] public static event Action assemblyCompilationStarted; + public static event Action assemblyCompilationNotRequired; public static event Action assemblyCompilationFinished; public static event Action codeOptimizationChanged; @@ -263,6 +269,18 @@ internal static void SubscribeToEvents(EditorCompilation editorCompilation) } }; + editorCompilation.assemblyCompilationNotRequired += (scriptAssembly) => + { + try + { + assemblyCompilationNotRequired?.Invoke(scriptAssembly.FullPath); + } + catch (Exception e) + { + UnityEngine.Debug.LogException(e); + } + }; + editorCompilation.assemblyCompilationFinished += (scriptAssembly, messages) => { try @@ -327,7 +345,7 @@ internal static ScriptAssembly[] GetScriptAssemblies(IEditorCompilation editorCo var buildingForEditor = (options & EditorScriptCompilationOptions.BuildingForEditor) != 0; var unityAssemblies = InternalEditorUtility.GetUnityAssemblies(buildingForEditor, @group, target); - var precompiledAssemblies = editorCompilation.PrecompiledAssemblyProvider.GetPrecompiledAssembliesDictionary(buildingForEditor, @group, target, extraScriptingDefines); + var precompiledAssemblies = editorCompilation.PrecompiledAssemblyProvider.GetPrecompiledAssembliesDictionary(options, @group, target, extraScriptingDefines); return editorCompilation.GetAllScriptAssemblies(options, unityAssemblies, precompiledAssemblies, null); } @@ -438,7 +456,10 @@ public static string[] GetPrecompiledAssemblyNames() internal static string[] GetPrecompiledAssemblyNames(PrecompiledAssemblyProviderBase precompiledAssemblyProvider) { - return precompiledAssemblyProvider.GetPrecompiledAssemblies(true, EditorUserBuildSettings.activeBuildTargetGroup, EditorUserBuildSettings.activeBuildTarget) + return precompiledAssemblyProvider.GetPrecompiledAssemblies( + EditorScriptCompilationOptions.BuildingForEditor|EditorScriptCompilationOptions.BuildingWithAsserts, + EditorUserBuildSettings.activeBuildTargetGroup, + EditorUserBuildSettings.activeBuildTarget) .Where(x => (x.Flags & sc.AssemblyFlags.UserAssembly) == sc.AssemblyFlags.UserAssembly) .Select(x => AssetPath.GetFileName(x.Path)) .ToArray(); @@ -492,7 +513,10 @@ internal static string[] GetPrecompiledAssemblyPaths(PrecompiledAssemblySources if ((precompiledAssemblySources & PrecompiledAssemblySources.UserAssembly) != 0) flags |= sc.AssemblyFlags.UserAssembly; - var precompiledAssemblies = precompiledAssemblyProvider.GetPrecompiledAssemblies(true, buildTargetGroup, buildTarget) + var precompiledAssemblies = precompiledAssemblyProvider.GetPrecompiledAssemblies( + EditorScriptCompilationOptions.BuildingForEditor | EditorScriptCompilationOptions.BuildingWithAsserts, + buildTargetGroup, + buildTarget) .Concat(precompiledAssemblyProvider.GetUnityAssemblies(true, buildTarget)); foreach (var a in precompiledAssemblies.Where(x => (x.Flags & flags) != 0)) @@ -509,7 +533,11 @@ public static string GetPrecompiledAssemblyPathFromAssemblyName(string assemblyN internal static string GetPrecompiledAssemblyPathFromAssemblyName(string assemblyName, PrecompiledAssemblyProviderBase precompiledAssemblyProvider, string[] extraScriptingDefines = null) { - var precompiledAssemblies = precompiledAssemblyProvider.GetPrecompiledAssemblies(true, EditorUserBuildSettings.activeBuildTargetGroup, EditorUserBuildSettings.activeBuildTarget, extraScriptingDefines); + var precompiledAssemblies = precompiledAssemblyProvider.GetPrecompiledAssemblies( + EditorScriptCompilationOptions.BuildingForEditor | EditorScriptCompilationOptions.BuildingWithAsserts, + EditorUserBuildSettings.activeBuildTargetGroup, + EditorUserBuildSettings.activeBuildTarget, + extraScriptingDefines); foreach (var assembly in precompiledAssemblies) { @@ -624,8 +652,7 @@ internal static string GetAssemblyRootNamespaceFromScriptPath(EditorCompilation try { var csa = editorCompilation.FindCustomScriptAssemblyFromScriptPath(sourceFilePath); - return csa != null ? csa.RootNamespace : projectRootNamespace; - } + return csa != null ? (csa.RootNamespace ?? projectRootNamespace) : projectRootNamespace; } catch (Exception) { return null; diff --git a/Editor/Mono/Scripting/ScriptCompilation/CompilationSetupErrorsTracker.cs b/Editor/Mono/Scripting/ScriptCompilation/CompilationSetupErrorsTracker.cs index 04ba48f90d..92b9a68960 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/CompilationSetupErrorsTracker.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/CompilationSetupErrorsTracker.cs @@ -27,30 +27,13 @@ interface ICompilationSetupWarningTracker static class CompilationSetupErrorsTrackerExtensions { - public static void ProcessPrecompiledAssemblyException(this ICompilationSetupErrorsTracker tracker, PrecompiledAssemblyException exception) - { - tracker.SetCompilationSetupErrors(CompilationSetupErrors.PrecompiledAssemblyError); - tracker.LogCompilationSetupErrors(CompilationSetupErrors.PrecompiledAssemblyError, exception.filePaths, exception.Message); - } - public static bool ProcessException(this ICompilationSetupErrorsTracker tracker, Exception exception) { var assemblyDefinitionException = exception as AssemblyDefinitionException; - var precompiledAssemblyException = exception as PrecompiledAssemblyException; if (assemblyDefinitionException != null && assemblyDefinitionException.filePaths.Length > 0) { - tracker.LogCompilationSetupErrors( - assemblyDefinitionException.errorType == AssemblyDefinitionErrorType.LoadError ? - CompilationSetupErrors.LoadError : CompilationSetupErrors.CyclicReferences, - assemblyDefinitionException.filePaths, assemblyDefinitionException.Message); - return true; - } - else if (precompiledAssemblyException != null) - { - // PrecompiledAssemblyException was potentially processed earlier at the GetPrecompiledAssemblies call site - // if it was called in the context of script compilation within the editor. - UnityEngine.Debug.LogException(exception); + tracker.LogCompilationSetupErrors(CompilationSetupErrors.LoadError, assemblyDefinitionException.filePaths, assemblyDefinitionException.Message); return true; } diff --git a/Editor/Mono/Scripting/ScriptCompilation/CustomScriptAssembly.cs b/Editor/Mono/Scripting/ScriptCompilation/CustomScriptAssembly.cs index ea31988a90..5254624fd3 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/CustomScriptAssembly.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/CustomScriptAssembly.cs @@ -249,6 +249,8 @@ class CustomScriptAssembly private AssemblyFlags assemblyFlags = AssemblyFlags.None; + public bool IsPredefined { get; set; } + public AssemblyFlags AssemblyFlags { get @@ -347,7 +349,9 @@ public bool IsCompatibleWith(BuildTarget buildTarget, EditorScriptCompilationOpt } if (defines != null && defines.Length == 0) + { throw new ArgumentException("Defines cannot be empty", nameof(defines)); + } // Log invalid define constraints if (DefineConstraints != null) @@ -360,8 +364,10 @@ public bool IsCompatibleWith(BuildTarget buildTarget, EditorScriptCompilationOpt } } } - var allDefines = ArrayHelper.Merge(defines, ResponseFileDefines); - if (!DefineConstraintsHelper.IsDefineConstraintsCompatible(allDefines, DefineConstraints)) + var allDefines = ResponseFileDefines != null ? + (defines != null ? defines.Concat(ResponseFileDefines) : ResponseFileDefines) + : defines; + if (!DefineConstraintsHelper.IsDefineConstraintsCompatible_Enumerable(allDefines, DefineConstraints)) { return false; } @@ -373,13 +379,19 @@ public bool IsCompatibleWith(BuildTarget buildTarget, EditorScriptCompilationOpt // Compatible with editor and all platforms. if (IncludePlatforms == null && ExcludePlatforms == null) + { return true; + } if (buildingForEditor) + { return IsCompatibleWithEditor(); + } if (ExcludePlatforms != null) + { return ExcludePlatforms.All(p => p.BuildTarget != buildTarget); + } return IncludePlatforms.Any(p => p.BuildTarget == buildTarget); } @@ -489,5 +501,42 @@ public static CustomScriptAssemblyPlatform GetPlatformFromBuildTarget(BuildTarge throw new System.ArgumentException(string.Format("No CustomScriptAssemblyPlatform setup for BuildTarget '{0}'", buildTarget)); } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CustomScriptAssembly) obj); + } + + public bool Equals(CustomScriptAssembly other) + { + return assemblyFlags == other.assemblyFlags + && string.Equals(FilePath, other.FilePath, StringComparison.Ordinal) + && string.Equals(PathPrefix, other.PathPrefix, StringComparison.Ordinal) + && string.Equals(Name, other.Name, StringComparison.Ordinal) + && string.Equals(RootNamespace, other.RootNamespace, StringComparison.Ordinal) + && GUID == other.GUID + && Equals(References, other.References) + && Equals(AdditionalPrefixes, other.AdditionalPrefixes) + && Equals(IncludePlatforms, other.IncludePlatforms) + && Equals(ExcludePlatforms, other.ExcludePlatforms) + && Equals(AssetPathMetaData, other.AssetPathMetaData) + && Equals(CompilerOptions, other.CompilerOptions) + && OverrideReferences == other.OverrideReferences + && Equals(PrecompiledReferences, other.PrecompiledReferences) + && AutoReferenced == other.AutoReferenced + && Equals(DefineConstraints, other.DefineConstraints) + && Equals(VersionDefines, other.VersionDefines) + && Equals(ResponseFileDefines, other.ResponseFileDefines) + && NoEngineReferences == other.NoEngineReferences + && IsPredefined == other.IsPredefined; + } + + public override int GetHashCode() + { + return GUID.GetHashCode(); + } } } diff --git a/Editor/Mono/Scripting/ScriptCompilation/CustomScriptAssemblyReference.cs b/Editor/Mono/Scripting/ScriptCompilation/CustomScriptAssemblyReference.cs index c0bf4a8f9b..fa676363b5 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/CustomScriptAssemblyReference.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/CustomScriptAssemblyReference.cs @@ -32,6 +32,26 @@ public static string ToJson(CustomScriptAssemblyReferenceData data) class CustomScriptAssemblyReference { + public bool Equals(CustomScriptAssemblyReference other) + { + return string.Equals(FilePath, other.FilePath, StringComparison.Ordinal) + && string.Equals(PathPrefix, other.PathPrefix, StringComparison.Ordinal) + && string.Equals(Reference, other.Reference, StringComparison.Ordinal); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CustomScriptAssemblyReference) obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(FilePath, PathPrefix, Reference); + } + public string FilePath { get; set; } public string PathPrefix { get; set; } public string Reference { get; set; } // Name or GUID @@ -51,6 +71,18 @@ public static CustomScriptAssemblyReference FromCustomScriptAssemblyReferenceDat return customScriptAssemblyReference; } + public static CustomScriptAssemblyReference FromPathAndReference(string path, string reference) + { + var pathPrefix = path.Substring(0, path.Length - AssetPath.GetFileName(path).Length); + + var customScriptAssemblyReference = new CustomScriptAssemblyReference(); + customScriptAssemblyReference.FilePath = path; + customScriptAssemblyReference.PathPrefix = pathPrefix; + customScriptAssemblyReference.Reference = reference; + + return customScriptAssemblyReference; + } + public CustomScriptAssemblyReferenceData CreateData() { return new CustomScriptAssemblyReferenceData() { reference = Reference }; diff --git a/Editor/Mono/Scripting/ScriptCompilation/DefineConstraintsHelper.cs b/Editor/Mono/Scripting/ScriptCompilation/DefineConstraintsHelper.cs index 0b1629c3a3..62530329cc 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/DefineConstraintsHelper.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/DefineConstraintsHelper.cs @@ -18,7 +18,7 @@ static class DefineConstraintsHelper // Characters that we consider valid whitespaces. public static readonly char[] k_ValidWhitespaces = { ' ', '\t' }; - static Regex s_SplitAndKeep = new Regex("(\\|\\|)", RegexOptions.Compiled); + static Regex s_SplitAndKeep = null; public enum DefineConstraintStatus { @@ -30,55 +30,60 @@ public enum DefineConstraintStatus [RequiredByNativeCode] public static bool IsDefineConstraintsCompatible(string[] defines, string[] defineConstraints) { - if (defines == null && defineConstraints == null || defineConstraints == null || defineConstraints.Length == 0) - { - return true; - } - - GetDefineConstraintsCompatibility(defines, defineConstraints, out bool[] defineConstraintsValidity); - - return defineConstraintsValidity.All(c => c); + return IsDefineConstraintsCompatible_Enumerable(defines.AsEnumerable(), defineConstraints.AsEnumerable()); } - static void GetDefineConstraintsCompatibility(string[] defines, string[] defineConstraints, out bool[] defineConstraintsValidity) + // This is not called IsDefineConstraintsCompatible because the bindings generator does not support overloads + public static bool IsDefineConstraintsCompatible_Enumerable(IEnumerable defines, IEnumerable defineConstraints) { - defineConstraintsValidity = new bool[defineConstraints.Length]; + if (defines == null && defineConstraints == null || defineConstraints == null || !defineConstraints.Any()) + { + return true; + } - for (int i = 0; i < defineConstraints.Length; ++i) + foreach (var constraint in defineConstraints) { - defineConstraintsValidity[i] = GetDefineConstraintCompatibility(defines, defineConstraints[i]) == DefineConstraintStatus.Compatible; + if (GetDefineConstraintCompatibility(defines, constraint) != DefineConstraintStatus.Compatible) + { + return false; + } } + + return true; } - internal static DefineConstraintStatus GetDefineConstraintCompatibility(string[] defines, string defineConstraints) + internal static DefineConstraintStatus GetDefineConstraintCompatibility(IEnumerable defines, string defineConstraints) { if (string.IsNullOrEmpty(defineConstraints)) { return DefineConstraintStatus.Invalid; } + if (s_SplitAndKeep == null) + s_SplitAndKeep = new Regex("(\\|\\|)", RegexOptions.Compiled); + // Split by "||" (OR) and keep it in the resulting array - var splitDefines = s_SplitAndKeep.Split(defineConstraints); + var splitDefineConstraints = s_SplitAndKeep.Split(defineConstraints); // Trim what we consider valid space characters - for (var i = 0; i < splitDefines.Length; ++i) + for (var i = 0; i < splitDefineConstraints.Length; ++i) { - splitDefines[i] = splitDefines[i].Trim(k_ValidWhitespaces); + splitDefineConstraints[i] = splitDefineConstraints[i].Trim(k_ValidWhitespaces); } // Check for consecutive OR - for (var i = 0; i < splitDefines.Length; ++i) + for (var i = 0; i < splitDefineConstraints.Length; ++i) { - if (splitDefines[i] == Or && (i < splitDefines.Length - 1 && splitDefines[i + 1] == Or)) + if (splitDefineConstraints[i] == Or && (i < splitDefineConstraints.Length - 1 && splitDefineConstraints[i + 1] == Or)) { return DefineConstraintStatus.Invalid; } } - var notExpectedDefines = new HashSet(splitDefines.Where(x => x.StartsWith(Not, StringComparison.Ordinal) && x != Or).Select(x => x.Substring(1))); - var expectedDefines = new HashSet(splitDefines.Where(x => !x.StartsWith(Not, StringComparison.Ordinal) && x != Or)); + var notExpectedDefines = new HashSet(splitDefineConstraints.Where(x => x.StartsWith(Not, StringComparison.Ordinal) && x != Or).Select(x => x.Substring(1))); + var expectedDefines = new HashSet(splitDefineConstraints.Where(x => !x.StartsWith(Not, StringComparison.Ordinal) && x != Or)); - if (defines == null) + if (defines == null || !defines.Any()) { if (expectedDefines.Count > 0) { diff --git a/Editor/Mono/Scripting/ScriptCompilation/EditorBuildRules.cs b/Editor/Mono/Scripting/ScriptCompilation/EditorBuildRules.cs index e59b81c72a..9c046f1afd 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/EditorBuildRules.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/EditorBuildRules.cs @@ -32,7 +32,7 @@ public class CompilationAssemblies public string[] RoslynAnalyzerDllPaths { get; set; } } - static readonly TargetAssembly[] predefinedTargetAssemblies; + public static Dictionary predefinedTargetAssemblies { get; private set; } private static readonly string[] s_CSharpVersionDefines = { @@ -47,12 +47,12 @@ static EditorBuildRules() public static TargetAssembly[] GetPredefinedTargetAssemblies() { - return predefinedTargetAssemblies; + return predefinedTargetAssemblies.Values.ToArray(); } public static string[] PredefinedTargetAssemblyNames { - get { return predefinedTargetAssemblies.Select(a => AssetPath.GetAssemblyNameWithoutExtension(a.Filename)).ToArray(); } + get { return predefinedTargetAssemblies.Select(a => AssetPath.GetAssemblyNameWithoutExtension(a.Key)).ToArray(); } } /// @@ -205,23 +205,6 @@ public DirtyTargetAssembly() public HashSet SourceFiles { get; set; } } - static bool ShouldUseAnalyzerForScriptAssembly(ScriptAssembly sa, TargetAssembly targetAssemblyOwningAnalyzer) - { - if (targetAssemblyOwningAnalyzer == null) - return true; - - if (sa.Filename == targetAssemblyOwningAnalyzer.Filename) - return true; - - foreach (var sar in sa.ScriptAssemblyReferences) - { - if (ShouldUseAnalyzerForScriptAssembly(sar, targetAssemblyOwningAnalyzer)) - return true; - } - - return false; - } - internal static ScriptAssembly[] ToScriptAssemblies( IDictionary targetAssemblies, ScriptAssemblySettings settings, @@ -253,7 +236,16 @@ internal static ScriptAssembly[] ToScriptAssemblies( scriptAssembly.RootNamespace = targetAssembly.Type == TargetAssemblyType.Predefined ? settings.ProjectRootNamespace : targetAssembly.RootNamespace; scriptAssembly.OutputDirectory = settings.OutputDirectory; - scriptAssembly.Defines = targetAssembly.Defines == null ? s_CSharpVersionDefines : targetAssembly.Defines.Concat(s_CSharpVersionDefines).ToArray(); + + var cSharpVersionDefines = targetAssembly.Defines == null ? s_CSharpVersionDefines.ToList() : targetAssembly.Defines.Concat(s_CSharpVersionDefines).ToList(); + + //This is used for Source Generation + if ((targetAssembly.Flags & AssemblyFlags.EditorOnly) == AssemblyFlags.EditorOnly) + { + cSharpVersionDefines.Add("UNITY_EDITOR_ONLY_COMPILATION"); + } + + scriptAssembly.Defines = cSharpVersionDefines.ToArray(); scriptAssembly.Files = dirtyTargetAssembly.SourceFiles.ToArray(); scriptAssembly.TargetAssemblyType = targetAssembly.Type; scriptAssembly.AsmDefPath = targetAssembly.AsmDefPath; @@ -263,8 +255,6 @@ internal static ScriptAssembly[] ToScriptAssemblies( else scriptAssembly.CompilerOptions = targetAssembly.CompilerOptions; - scriptAssembly.CompilerOptions.RoslynAnalyzerDllPaths = new string[0]; - scriptAssembly.CompilerOptions.RoslynAnalyzerRulesetPath = string.Empty; scriptAssembly.CompilerOptions.AdditionalCompilerArguments = settings.AdditionalCompilerArguments; var editorOnlyTargetAssembly = (targetAssembly.Flags & AssemblyFlags.EditorOnly) == AssemblyFlags.EditorOnly; @@ -315,29 +305,7 @@ internal static ScriptAssembly[] ToScriptAssemblies( } if ((settings.CompilationOptions & EditorScriptCompilationOptions.BuildingWithRoslynAnalysis) != 0) - { - var analyzers = assemblies.RoslynAnalyzerDllPaths; - foreach (var a in analyzers) - { - var targetAssemblyOwningAnalyzer = assemblies.CustomTargetAssemblies.Values - .OrderBy(c => c.PathFilter(a)).LastOrDefault(); - - if (targetAssemblyOwningAnalyzer?.PathFilter(a) <= 0) - targetAssemblyOwningAnalyzer = null; - - foreach (var scriptAssembly in scriptAssemblies) - { - if (ShouldUseAnalyzerForScriptAssembly(scriptAssembly, targetAssemblyOwningAnalyzer)) - { - scriptAssembly.CompilerOptions.RoslynAnalyzerDllPaths = scriptAssembly.CompilerOptions.RoslynAnalyzerDllPaths.Concat(new[] {a}).ToArray(); - scriptAssembly.CompilerOptions.RoslynAnalyzerRulesetPath = - scriptAssembly.TargetAssemblyType == TargetAssemblyType.Predefined - ? RuleSetFileCache.GetRuleSetFilePathInRootFolder(Path.ChangeExtension(scriptAssembly.Filename, null)) - : RuleSetFileCache.GetPathForAssembly(scriptAssembly.OriginPath); - } - } - } - } + RoslynAnalyzers.SetAnalyzers(scriptAssemblies, assemblies.CustomTargetAssemblies.Values.ToArray(), assemblies.RoslynAnalyzerDllPaths, false); return scriptAssemblies; } @@ -648,7 +616,7 @@ public static bool IsCompatibleWithPlatformAndDefines(TargetAssembly assembly, B return IsCompatibleWithPlatformAndDefines(assembly, settings); } - internal static TargetAssembly[] CreatePredefinedTargetAssemblies() + internal static Dictionary CreatePredefinedTargetAssemblies() { var runtimeFirstPassAssemblies = new List(); var runtimeAssemblies = new List(); @@ -728,7 +696,7 @@ internal static TargetAssembly[] CreatePredefinedTargetAssemblies() assembly.References.AddRange(editorFirstPassAssemblies); } - return assemblies.ToArray(); + return assemblies.ToDictionary(x => x.Filename); } internal static TargetAssembly[] GetTargetAssembliesWithScripts( @@ -772,32 +740,15 @@ internal static TargetAssembly GetTargetAssembly(string scriptPath, string assem { TargetAssembly resultAssembly; - if (customTargetAssemblies != null && + if (assemblyName != null && + customTargetAssemblies != null && customTargetAssemblies.Count > 0 && customTargetAssemblies.TryGetValue(assemblyName, out resultAssembly)) { return resultAssembly; } - // Do not compile scripts outside the Assets/ folder into predefined assemblies. - if (!Utility.IsAssetsPath(scriptPath)) - return null; - - return GetPredefinedTargetAssembly(scriptPath); - } - - internal static TargetAssembly GetTargetAssemblyLinearSearch(string scriptPath, string projectDirectory, IDictionary customTargetAssemblies) - { - TargetAssembly resultAssembly = GetCustomTargetAssembly(scriptPath, projectDirectory, customTargetAssemblies); - - if (resultAssembly != null) - return resultAssembly; - - // Do not compile scripts outside the Assets/ folder into predefined assemblies. - if (!Utility.IsAssetsPath(scriptPath)) - return null; - - return GetPredefinedTargetAssembly(scriptPath); + return GetPredefinedTargetAssembly(scriptPath, assemblyName); } static string ScriptPathToLowerCase(string scriptPath) @@ -821,62 +772,33 @@ static string ScriptPathToLowerCase(string scriptPath) return new string(chars); } - internal static TargetAssembly GetPredefinedTargetAssembly(string scriptPath) + internal static TargetAssembly GetPredefinedTargetAssembly(string scriptPath, string assemblyName = null) { - TargetAssembly resultAssembly = null; - - var lowerPath = ScriptPathToLowerCase(scriptPath); - int highestPathDepth = -1; - - foreach (var assembly in predefinedTargetAssemblies) + if (assemblyName == null) { - var pathFilter = assembly.PathFilter; - int pathDepth = -1; + TargetAssembly resultAssembly = null; + var lowerPath = ScriptPathToLowerCase(scriptPath); + int highestPathDepth = -1; + foreach (var assembly in predefinedTargetAssemblies.Values) + { + var pathDepth = assembly.PathFilter?.Invoke(lowerPath) ?? 0; + if (pathDepth <= highestPathDepth) - if (pathFilter == null) - pathDepth = 0; - else - pathDepth = pathFilter(lowerPath); + { + continue; - if (pathDepth > highestPathDepth) - { + } resultAssembly = assembly; highestPathDepth = pathDepth; } + return resultAssembly; } - return resultAssembly; - } - - internal static TargetAssembly GetCustomTargetAssembly(string scriptPath, string projectDirectory, IDictionary customTargetAssemblies) - { - if (customTargetAssemblies == null) - return null; - - int highestPathDepth = -1; - TargetAssembly resultAssembly = null; - - // CustomScriptAssembly paths are absolute, so we convert the scriptPath to an absolute path, if necessary. - bool isPathAbsolute = AssetPath.IsPathRooted(scriptPath); - var fullPath = isPathAbsolute ? AssetPath.GetFullPath(scriptPath) : AssetPath.Combine(projectDirectory, scriptPath); - - foreach (var entry in customTargetAssemblies) + if (predefinedTargetAssemblies.TryGetValue(assemblyName, out var predefined)) { - var assembly = entry.Value; - - if (assembly.MaxPathLength <= highestPathDepth) - continue; - - int pathDepth = assembly.PathFilter(fullPath); - - if (pathDepth <= highestPathDepth) - continue; - - resultAssembly = assembly; - highestPathDepth = pathDepth; + return predefined; } - - return resultAssembly; + return null; } static int FilterAssemblyInFirstpassFolder(string pathName) diff --git a/Editor/Mono/Scripting/ScriptCompilation/EditorCompilation.cs b/Editor/Mono/Scripting/ScriptCompilation/EditorCompilation.cs index cff92f9917..9b30e9c6ae 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/EditorCompilation.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/EditorCompilation.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Runtime.InteropServices; using NiceIO; @@ -76,6 +77,8 @@ public struct CustomScriptAssemblyAndReference public IVersionDefinesConsoleLogs VersionDefinesConsoleLogs { get; set; } = new VersionDefinesConsoleLogs(); public ICompilationSetupWarningTracker CompilationSetupWarningTracker { get; set; } = new CompilationSetupWarningTracker(); public ISafeModeInfo SafeModeInfo { get; set; } = new SafeModeInfo(); + + public bool EnableDiagnostics => (bool)Debug.GetDiagnosticSwitch("EnableDomainReloadTimings").value; internal string projectDirectory = string.Empty; @@ -99,6 +102,7 @@ public struct CustomScriptAssemblyAndReference public event Action compilationStarted; public event Action compilationFinished; + public event Action assemblyCompilationNotRequired; public event Action assemblyCompilationFinished; class BeeScriptCompilationState @@ -124,7 +128,7 @@ public Exception[] SetAssetPathsMetaData(AssetPathMetaData[] assetPathMetaDatas) var versionMetaDataComparer = new VersionMetaDataComparer(); - m_VersionMetaDatas = assetPathMetaDatas ? + m_VersionMetaDatas = assetPathMetaDatas? .Where(x => x.VersionMetaData != null) .Select(x => x.VersionMetaData) .Distinct(versionMetaDataComparer) @@ -155,13 +159,26 @@ public Dictionary GetVersionMetaDatas() return m_VersionMetaDatas; } - public void SetAllScripts(string[] allScripts, string[] assemblyFilenames) + private IAssemblyGraphBuilder GetAssemblyGraphBuilder() + { + return AssemblyGraphBuilderFactory.GetOrCreate(Path.Combine(projectDirectory, "Assets"), + loadingAssemblyDefinition.CustomScriptAssemblies, + loadingAssemblyDefinition.CustomScriptAssemblyReferences); + } + + public void SetAllScripts(string[] allScripts) { - this.allScripts = new Dictionary(); + Assert.IsNotNull(customTargetAssemblies); + var graphBuilder = GetAssemblyGraphBuilder(); + var graph = graphBuilder.Match(allScripts); - for (int i = 0; i < allScripts.Length; ++i) + this.allScripts.Clear(); + foreach (var assemblyScriptsPair in graph) { - this.allScripts[allScripts[i]] = assemblyFilenames[i]; + foreach (var scriptPath in assemblyScriptsPair.Value) + { + this.allScripts.Add(scriptPath, assemblyScriptsPair.Key.Name + ".dll"); + } } } @@ -210,29 +227,10 @@ public string GetCompileScriptsOutputDirectory() //Used by the TestRunner package. public PrecompiledAssembly[] GetAllPrecompiledAssemblies() { - return PrecompiledAssemblyProvider.GetPrecompiledAssemblies(true, EditorUserBuildSettings.activeBuildTargetGroup, EditorUserBuildSettings.activeBuildTarget); - } - - Dictionary GetPrecompiledAssembliesDictionaryWithSetupErrorsTracking(bool isEditor, BuildTargetGroup buildTargetGroup, BuildTarget buildTarget, string[] extraScriptingDefines = null) - { - Dictionary precompiledAssemblies; - CompilationSetupErrorsTracker.ClearCompilationSetupErrors(CompilationSetupErrors.PrecompiledAssemblyError); // this will also remove the console errors associated with the setup error flags set in the past - try - { - precompiledAssemblies = PrecompiledAssemblyProvider.GetPrecompiledAssembliesDictionary(isEditor, buildTargetGroup, buildTarget, extraScriptingDefines); - } - catch (PrecompiledAssemblyException exception) - { - CompilationSetupErrorsTracker.ProcessPrecompiledAssemblyException(exception); - throw; - } - - return precompiledAssemblies; - } - - public PrecompiledAssembly[] GetPrecompiledAssembliesWithSetupErrorsTracking(bool isEditor, BuildTargetGroup buildTargetGroup, BuildTarget buildTarget, string[] extraScriptingDefines = null) - { - return GetPrecompiledAssembliesDictionaryWithSetupErrorsTracking(isEditor, buildTargetGroup, buildTarget, extraScriptingDefines)?.Values.ToArray(); + return PrecompiledAssemblyProvider.GetPrecompiledAssemblies( + EditorScriptCompilationOptions.BuildingForEditor | EditorScriptCompilationOptions.BuildingWithAsserts, + EditorUserBuildSettings.activeBuildTargetGroup, + EditorUserBuildSettings.activeBuildTarget); } public void GetAssemblyDefinitionReferencesWithMissingAssemblies(out List referencesWithMissingAssemblies) @@ -387,105 +385,6 @@ bool VerifyReferencesIsCompiled( return removed; } - string[] CustomTargetAssembliesToFilePaths(IEnumerable targetAssemblies) - { - var customAssemblies = targetAssemblies.Select(FindCustomTargetAssemblyFromTargetAssembly); - var filePaths = customAssemblies.Select(a => a.FilePath).ToArray(); - return filePaths; - } - - string CustomTargetAssemblyToFilePath(TargetAssembly targetAssembly) - { - return FindCustomTargetAssemblyFromTargetAssembly(targetAssembly).FilePath; - } - - public struct CheckCyclicAssemblyReferencesFunctions - { - public Func ToFilePathFunc; - public Func, string[]> ToFilePathsFunc; - } - - static void CheckCyclicAssemblyReferencesDFS(TargetAssembly visitAssembly, - HashSet visited, - HashSet recursion, - CheckCyclicAssemblyReferencesFunctions functions) - { - visited.Add(visitAssembly); - recursion.Add(visitAssembly); - - foreach (var reference in visitAssembly.References) - { - if (reference.Filename == visitAssembly.Filename) - { - throw new AssemblyDefinitionException("Assembly contains a references to itself", - AssemblyDefinitionErrorType.CyclicReferences, functions.ToFilePathFunc(visitAssembly)); - } - - if (recursion.Contains(reference)) - { - throw new AssemblyDefinitionException("Assembly with cyclic references detected", - AssemblyDefinitionErrorType.CyclicReferences, functions.ToFilePathsFunc(recursion)); - } - - if (!visited.Contains(reference)) - { - CheckCyclicAssemblyReferencesDFS(reference, - visited, - recursion, - functions); - } - } - - recursion.Remove(visitAssembly); - } - - public static void CheckCyclicAssemblyReferences(IDictionary customTargetAssemblies, - CheckCyclicAssemblyReferencesFunctions functions) - { - if (customTargetAssemblies == null || customTargetAssemblies.Count < 1) - { - return; - } - - var visited = new HashSet(); - - foreach (var entry in customTargetAssemblies) - { - var assembly = entry.Value; - if (visited.Contains(assembly)) - { - continue; - } - - var recursion = new HashSet(); - CheckCyclicAssemblyReferencesDFS(assembly, - visited, - recursion, - functions); - } - } - - void CheckCyclicAssemblyReferences() - { - try - { - CheckCyclicAssemblyReferencesFunctions functions; - - functions.ToFilePathFunc = CustomTargetAssemblyToFilePath; - functions.ToFilePathsFunc = CustomTargetAssembliesToFilePaths; - - CheckCyclicAssemblyReferences(customTargetAssemblies, functions); - } - catch (AssemblyDefinitionException e) - { - if (e.errorType == AssemblyDefinitionErrorType.CyclicReferences) - { - CompilationSetupErrorsTracker.SetCompilationSetupErrors(CompilationSetupErrors.CyclicReferences); - } - throw; - } - } - public static Exception[] UpdateCustomScriptAssemblies(CustomScriptAssembly[] customScriptAssemblies, List customScriptAssemblyReferences, AssetPathMetaData[] assetPathsMetaData, @@ -592,44 +491,42 @@ Exception[] UpdateCustomTargetAssemblies() } customTargetAssemblies = EditorBuildRules.CreateTargetAssemblies(loadingAssemblyDefinition.CustomScriptAssemblies); - - CompilationSetupErrorsTracker.ClearCompilationSetupErrors(CompilationSetupErrors.CyclicReferences); - return exceptions; } public void SkipCustomScriptAssemblyGraphValidation(bool skipChecks) { + // If we have successfully compiled and reloaded all assemblies, then we can skip asmdef compilation graph checks + // for setup errors like cyclic references, self-references, duplicate assembly names, etc. + // If there is compilation errors in a Safe Mode domain or a partially loaded domain (when SafeMode is forcefully exited), + // then we need to keep the graph validation checks to rediscover potential setup errors in subsequent compilations. skipCustomScriptAssemblyGraphValidation = skipChecks; } - public Exception[] SetAllCustomScriptAssemblyReferenceJsons(string[] paths) + public IEnumerable SetAllCustomScriptAssemblyReferenceJsons(string[] paths) { return SetAllCustomScriptAssemblyReferenceJsonsContents(paths, null); } - public Exception[] SetAllCustomScriptAssemblyReferenceJsonsContents(string[] paths, string[] contents) + public IEnumerable SetAllCustomScriptAssemblyReferenceJsonsContents(string[] paths, string[] contents) { RefreshLoadingAssemblyDefinition(); loadingAssemblyDefinition.SetAllCustomScriptAssemblyReferenceJsonsContents(paths, contents); - return GetLoadingExceptions(); + var updateExceptions = UpdateCustomTargetAssemblies(); + return loadingAssemblyDefinition.Exceptions.Concat(updateExceptions); } - public Exception[] SetAllCustomScriptAssemblyJsons(string[] paths, string[] guids) + public IEnumerable SetAllCustomScriptAssemblyJsons(string[] paths, string[] guids) { return SetAllCustomScriptAssemblyJsonContents(paths, null, guids); } - public Exception[] SetAllCustomScriptAssemblyJsonContents(string[] paths, string[] contents, string[] guids) + public IEnumerable SetAllCustomScriptAssemblyJsonContents(string[] paths, string[] contents, string[] guids) { RefreshLoadingAssemblyDefinition(); loadingAssemblyDefinition.SetAllCustomScriptAssemblyJsonContents(paths, contents, guids); - return GetLoadingExceptions(); - } - - Exception[] GetLoadingExceptions() - { - return loadingAssemblyDefinition.Exceptions.Concat(UpdateCustomTargetAssemblies()).ToArray(); + var updateExceptions = UpdateCustomTargetAssemblies(); + return loadingAssemblyDefinition.Exceptions.Concat(updateExceptions); } void RefreshLoadingAssemblyDefinition() @@ -708,7 +605,7 @@ public CustomScriptAssembly FindCustomScriptAssemblyFromAssemblyName(string asse } else { - var assemblyNames = loadingAssemblyDefinition.CustomScriptAssemblies.Select(a => a.Name).ToArray(); + var assemblyNames = loadingAssemblyDefinition.CustomScriptAssemblies.Select(a => a.Name); var assemblyNamesString = string.Join(", ", assemblyNames); exceptionMessage += " Assembly names: " + assemblyNamesString; } @@ -718,22 +615,29 @@ public CustomScriptAssembly FindCustomScriptAssemblyFromAssemblyName(string asse public bool TryFindCustomScriptAssemblyFromScriptPath(string scriptPath, out CustomScriptAssembly customScriptAssembly) { - var customTargetAssembly = EditorBuildRules.GetCustomTargetAssembly(scriptPath, projectDirectory, customTargetAssemblies); - if (customTargetAssembly != null) - { - return TryFindCustomScriptAssemblyFromAssemblyName(customTargetAssembly.Filename, out customScriptAssembly); - } + var fullPath = AssetPath.IsPathRooted(scriptPath) + ? AssetPath.GetFullPath(scriptPath) + : AssetPath.Combine(projectDirectory, scriptPath); - customScriptAssembly = null; - return false; + var assemblyGraphBuilder = GetAssemblyGraphBuilder(); + var dictionary = assemblyGraphBuilder.Match(new []{fullPath}); + + customScriptAssembly = dictionary.Keys.SingleOrDefault();; + + if (customScriptAssembly is {IsPredefined: true}) + customScriptAssembly = null; + + return customScriptAssembly != null; } public CustomScriptAssembly FindCustomScriptAssemblyFromScriptPath(string scriptPath) { - var customTargetAssembly = EditorBuildRules.GetCustomTargetAssembly(scriptPath, projectDirectory, customTargetAssemblies); - var customScriptAssembly = customTargetAssembly != null ? FindCustomScriptAssemblyFromAssemblyName(customTargetAssembly.Filename) : null; + var fullPath = AssetPath.IsPathRooted(scriptPath) + ? AssetPath.GetFullPath(scriptPath) + : AssetPath.Combine(projectDirectory, scriptPath); - return customScriptAssembly; + var foundCustomScriptAssemblies = GetAssemblyGraphBuilder().Match(new []{fullPath}); + return foundCustomScriptAssemblies.SingleOrDefault().Key; } public CustomScriptAssembly FindCustomTargetAssemblyFromTargetAssembly(TargetAssembly assembly) @@ -791,20 +695,7 @@ string[] extraScriptingDefines CompileStatus compilationResult; using (new ProfilerMarker("Initiating Script Compilation").Auto()) { - try - { - compilationResult = CompileScriptsWithSettings(scriptAssemblySettings); - } - catch (PrecompiledAssemblyException) - { - // The setup errors for this exception has already been logged earlier, so there's no need to log the exception. - compilationResult = CompileStatus.CompilationFailed; - } - } - - if (compilationResult != CompileStatus.Idle) - { - WarnIfThereAreScriptsThatDoNotBelongToAnyAssembly(); + compilationResult = CompileScriptsWithSettings(scriptAssemblySettings); } return compilationResult; @@ -853,14 +744,6 @@ void CancelActiveBuild() activeBeeBuild?.Driver.CancelBuild(); } - void WarnIfThereAreScriptsThatDoNotBelongToAnyAssembly() - { - foreach (var script in GetScriptsThatDoNotBelongToAnyAssembly().OrderBy(s => s)) - { - Debug.LogWarning($"Script '{script}' will not be compiled because it exists outside the Assets folder and does not to belong to any assembly definition file."); - } - } - void WarnIfThereAreAssembliesWithoutAnyScripts(ScriptAssemblySettings scriptAssemblySettings, ScriptAssembly[] scriptAssemblies) { foreach (var targetAssembly in customTargetAssemblies.Values) @@ -910,26 +793,14 @@ public CompileStatus CompileScriptsWithSettings(ScriptAssemblySettings scriptAss PlayerSettings.EnableRoslynAnalyzers && (scriptAssemblySettings.CompilationOptions & EditorScriptCompilationOptions.BuildingWithRoslynAnalysis) != 0; - // If we have successfully compiled and reloaded all assemblies, then we can - // skip checks on the asmdef compilation graph to ensure there no - // setup errors like cyclic references, duplicate assembly names, etc. - // If there is compilation errors n Safe Mode domain or a partial domain (if SafeMode is forcefully exited), - // Then we need to keep the validation checks to rediscover potential setup errors in subsequent compilations. - if (!skipCustomScriptAssemblyGraphValidation) - { - CheckCyclicAssemblyReferences(); - } - ScriptAssembly[] scriptAssemblies; - try + if (scriptAssemblySettings.CompilationOptions.HasFlag(EditorScriptCompilationOptions.BuildingSkipCompile)) + scriptAssemblies = Array.Empty(); + else { CompilationSetupWarningTracker.ClearAssetWarnings(); - scriptAssemblies = GetAllScriptAssembliesOfType(scriptAssemblySettings, TargetAssemblyType.Undefined, CompilationSetupWarningTracker); - } - catch (PrecompiledAssemblyException) - { - // The setup errors for this exception has already been logged earlier, so there's no need to log the exception. - return CompileStatus.Idle; + scriptAssemblies = GetAllScriptAssembliesOfType(scriptAssemblySettings, + TargetAssemblyType.Undefined, CompilationSetupWarningTracker); } // Do no start compilation if there is an setup error. @@ -938,7 +809,8 @@ public CompileStatus CompileScriptsWithSettings(ScriptAssemblySettings scriptAss return CompileStatus.Idle; } - WarnIfThereAreAssembliesWithoutAnyScripts(scriptAssemblySettings, scriptAssemblies); + if ((scriptAssemblySettings.CompilationOptions & EditorScriptCompilationOptions.BuildingSkipCompile) == 0) + WarnIfThereAreAssembliesWithoutAnyScripts(scriptAssemblySettings, scriptAssemblies); var debug = scriptAssemblySettings.CodeOptimization == CodeOptimization.Debug; @@ -952,12 +824,12 @@ public CompileStatus CompileScriptsWithSettings(ScriptAssemblySettings scriptAss $"{(scriptAssemblySettings.BuildingForEditor ? "E" : "P")}" + $"{(scriptAssemblySettings.BuildingDevelopmentBuild ? "Dev" : "")}" + $"{(debug ? "Dbg" : "")}" + - ""; + $"{(scriptAssemblySettings.CompilationOptions.HasFlag(EditorScriptCompilationOptions.BuildingSkipCompile) ? "SkipCompile" : "")}"; BuildTarget buildTarget = scriptAssemblySettings.BuildTarget; activeBeeBuild = new BeeScriptCompilationState() { - Driver = UnityBeeDriver.Make(ScriptCompilationBuildProgram, this, $"{(int)buildTarget}{config}", useScriptUpdater: !scriptAssemblySettings.BuildingWithoutScriptUpdater), + Driver = UnityBeeDriver.Make(ScriptCompilationBuildProgram, this, $"{(int)buildTarget}{config}", useScriptUpdater: !scriptAssemblySettings.BuildingWithoutScriptUpdater, ilpp: new ILPostProcessingProgram()), settings = scriptAssemblySettings, assemblies = scriptAssemblies, }; @@ -988,7 +860,11 @@ public CompileStatus CompileScriptsWithSettings(ScriptAssemblySettings scriptAss static SystemProcessRunnableProgram MakeScriptCompilationBuildProgram() { var buildProgramAssembly = new NPath($"{EditorApplication.applicationContentsPath}/Tools/BuildPipeline/ScriptCompilationBuildProgram.exe"); - return new SystemProcessRunnableProgram($"{EditorApplication.applicationContentsPath}/Tools/netcorerun/netcorerun{BeeScriptCompilation.ExecutableExtension}", buildProgramAssembly.InQuotes(SlashMode.Native)); + return new SystemProcessRunnableProgram( + $"{EditorApplication.applicationContentsPath}/Tools/netcorerun/netcorerun{BeeScriptCompilation.ExecutableExtension}", + new [] { buildProgramAssembly.InQuotes(SlashMode.Native) }, + new () {{ "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "1" }} + ); } public void InvokeCompilationStarted(object context) @@ -1008,12 +884,13 @@ public bool DoesProjectFolderHaveAnyScripts() public ScriptAssemblySettings CreateScriptAssemblySettings(BuildTargetGroup buildTargetGroup, BuildTarget buildTarget, EditorScriptCompilationOptions options) { - return CreateScriptAssemblySettings(buildTargetGroup, buildTarget, options, new string[] {}); + return CreateScriptAssemblySettings(buildTargetGroup, buildTarget, options, new string[] { }); } public ScriptAssemblySettings CreateScriptAssemblySettings(BuildTargetGroup buildTargetGroup, BuildTarget buildTarget, EditorScriptCompilationOptions options, string[] extraScriptingDefines) { var predefinedAssembliesCompilerOptions = new ScriptCompilerOptions(); + var namedBuildTarget = NamedBuildTarget.FromActiveSettings(buildTarget); if ((options & EditorScriptCompilationOptions.BuildingPredefinedAssembliesAllowUnsafeCode) == EditorScriptCompilationOptions.BuildingPredefinedAssembliesAllowUnsafeCode) { @@ -1025,7 +902,7 @@ public ScriptAssemblySettings CreateScriptAssemblySettings(BuildTargetGroup buil predefinedAssembliesCompilerOptions.UseDeterministicCompilation = true; } - predefinedAssembliesCompilerOptions.ApiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup)); + predefinedAssembliesCompilerOptions.ApiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(namedBuildTarget); ICompilationExtension compilationExtension = null; if ((options & EditorScriptCompilationOptions.BuildingForEditor) == 0) @@ -1033,8 +910,7 @@ public ScriptAssemblySettings CreateScriptAssemblySettings(BuildTargetGroup buil compilationExtension = ModuleManager.FindPlatformSupportModule(ModuleManager.GetTargetStringFromBuildTarget(buildTarget))?.CreateCompilationExtension(); } - - List additionalCompilationArguments = new List(PlayerSettings.GetAdditionalCompilerArguments(NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup))); + List additionalCompilationArguments = new List(PlayerSettings.GetAdditionalCompilerArguments(namedBuildTarget)); if (PlayerSettings.suppressCommonWarnings) { @@ -1070,18 +946,6 @@ ScriptAssemblySettings CreateEditorScriptAssemblySettings(EditorScriptCompilatio { return CreateScriptAssemblySettings(EditorUserBuildSettings.activeBuildTargetGroup, EditorUserBuildSettings.activeBuildTarget, options); } - - static CompilerMessage AsCompilerMessage(BeeDriverResult.Message message) - { - return new CompilerMessage - { - message = message.Text, - type = message.Kind == BeeDriverResult.MessageKind.Error - ? CompilerMessageType.Error - : CompilerMessageType.Warning, - }; - } - //only used in for tests to peek in. public CompilerMessage[] GetCompileMessages() => _currentEditorCompilationCompilerMessages; @@ -1181,14 +1045,13 @@ public CompileStatus TickCompilationPipeline(EditorScriptCompilationOptions opti return CompileStatus.Compiling; } - var messagesForNodeResults = ProcessCompilationResult(activeBeeBuild.assemblies, result, activeBeeBuild.settings.BuildingForEditor, activeBeeBuild); + var compilerMessages = ProcessCompilationResult(activeBeeBuild.assemblies, result, activeBeeBuild.settings.BuildingForEditor, activeBeeBuild).SelectMany(m => m).ToArray(); int logIdentifier = activeBeeBuild.settings.BuildingForEditor //these numbers are "randomly picked". they are used to so that when you log a message with a certain identifier, later all messages with that identifier can be cleared. //one means "compilation error for compiling-assemblies-for-editor" the other means "compilation error for building a player". ? kLogIdentifierFor_EditorMessages : kLogIdentifierFor_PlayerMessages; - var compilerMessages = result.BeeDriverMessages.Select(AsCompilerMessage).Concat(messagesForNodeResults.SelectMany(m => m)).ToArray(); if (activeBeeBuild.settings.BuildingForEditor) { @@ -1219,17 +1082,17 @@ public void DisableLoggingEditorCompilerMessages() public CompilerMessage[][] ProcessCompilationResult(ScriptAssembly[] assemblies, BeeDriverResult result, bool buildingForEditor, object context) { - var compilerMessagesForNodeResults = BeeScriptCompilation.ParseAllNodeResultsIntoCompilerMessages(result.NodeResults, this); - InvokeAssemblyCompilationFinished(assemblies, result, buildingForEditor, compilerMessagesForNodeResults); + var compilerMessages = BeeScriptCompilation.ParseAllResultsIntoCompilerMessages(result.BeeDriverMessages, result.NodeResults, this); + InvokeAssemblyCompilationFinished(assemblies, result, buildingForEditor, compilerMessages); InvokeCompilationFinished(context); - return compilerMessagesForNodeResults; + return compilerMessages; } void InvokeAssemblyCompilationFinished(ScriptAssembly[] assemblies, BeeDriverResult beeDriverResult, bool buildingForEditor, CompilerMessage[][] compilerMessagesForNodeResults) { bool Belongs(ScriptAssembly scriptAssembly, NodeResult nodeResult) => new NPath(nodeResult.outputfile).FileName == scriptAssembly.Filename; - //we want to send callbacks for assemblies that were copied, for assemblies that failed to compile, but not for assemblies that were compiled but not copied (because they ended up identically) + // We want to send callbacks for assemblies that were copied, for assemblies that failed to compile, but not for assemblies that were compiled but not copied (because they ended up identical). bool RequiresCallbackInvocation(ScriptAssembly scriptAssembly) { var relatedNodes = beeDriverResult.NodeResults.Where(nodeResult => Belongs(scriptAssembly, nodeResult)); @@ -1240,6 +1103,8 @@ bool RequiresCallbackInvocation(ScriptAssembly scriptAssembly) { if (!RequiresCallbackInvocation(scriptAssembly)) { + // Report that an assembly was unchanged as a result of compilation. + assemblyCompilationNotRequired?.Invoke(scriptAssembly); continue; } @@ -1377,7 +1242,25 @@ public PrecompiledAssembly[] GetUnityAssemblies() public TargetAssemblyInfo GetTargetAssembly(string scriptPath) { - TargetAssembly targetAssembly = EditorBuildRules.GetTargetAssemblyLinearSearch(scriptPath, projectDirectory, customTargetAssemblies); + string path = scriptPath; + if (!Path.IsPathRooted(scriptPath)) + { + path = Path.Combine(projectDirectory, scriptPath); + } + + var matchedAssembly = GetAssemblyGraphBuilder().Match(new []{path}); + + TargetAssembly targetAssembly; + var scriptAssembly = matchedAssembly.Single().Key; + + customTargetAssemblies.TryGetValue(scriptAssembly.Name + ".dll", out targetAssembly); + + if (targetAssembly == null) + { + if (EditorBuildRules.predefinedTargetAssemblies.TryGetValue(scriptAssembly.Name + ".dll", + out var assembly)) + targetAssembly = assembly; + } TargetAssemblyInfo targetAssemblyInfo = ToTargetAssemblyInfo(targetAssembly); return targetAssemblyInfo; @@ -1385,7 +1268,8 @@ public TargetAssemblyInfo GetTargetAssembly(string scriptPath) public TargetAssembly GetTargetAssemblyDetails(string scriptPath) { - return EditorBuildRules.GetTargetAssemblyLinearSearch(scriptPath, projectDirectory, customTargetAssemblies); + var matchedAssembly = GetAssemblyGraphBuilder().Match(new []{scriptPath}); + return customTargetAssemblies[matchedAssembly.Single().Key.Name]; } public ScriptAssembly[] GetAllEditorScriptAssemblies(EditorScriptCompilationOptions additionalOptions) @@ -1400,9 +1284,8 @@ public ScriptAssembly[] GetAllEditorScriptAssemblies(EditorScriptCompilationOpti public ScriptAssembly[] GetAllScriptAssemblies(EditorScriptCompilationOptions options, string[] defines) { - var isForEditor = (options & EditorScriptCompilationOptions.BuildingForEditor) == EditorScriptCompilationOptions.BuildingForEditor; - var precompiledAssemblies = GetPrecompiledAssembliesDictionaryWithSetupErrorsTracking( - isForEditor, EditorUserBuildSettings.activeBuildTargetGroup, EditorUserBuildSettings.activeBuildTarget, defines); + var precompiledAssemblies = PrecompiledAssemblyProvider.GetPrecompiledAssembliesDictionary( + options, EditorUserBuildSettings.activeBuildTargetGroup, EditorUserBuildSettings.activeBuildTarget, defines); return GetAllScriptAssemblies(options, unityAssemblies, precompiledAssemblies, defines); } @@ -1589,7 +1472,7 @@ public ScriptAssembly[] GetAllScriptAssembliesOfType(ScriptAssemblySettings sett using (new ProfilerMarker(nameof(GetAllScriptAssembliesOfType)).Auto()) { var precompiledAssemblies = - GetPrecompiledAssembliesDictionaryWithSetupErrorsTracking(settings.BuildingForEditor, + PrecompiledAssemblyProvider.GetPrecompiledAssembliesDictionary(settings.CompilationOptions, settings.BuildTargetGroup, settings.BuildTarget, settings.ExtraGeneralDefines); UpdateAllTargetAssemblyDefines(customTargetAssemblies, EditorBuildRules.GetPredefinedTargetAssemblies(), m_VersionMetaDatas, settings); @@ -1664,6 +1547,8 @@ static EditorScriptCompilationOptions ToEditorScriptCompilationOptions(AssemblyB options |= EditorScriptCompilationOptions.BuildingForEditor; } + options |= EditorScriptCompilationOptions.BuildingWithRoslynAnalysis; + return options; } @@ -1703,11 +1588,11 @@ ScriptAssembly InitializeScriptAssemblyWithoutReferencesAndDefines(AssemblyBuild Files = scriptFiles, Filename = AssetPath.GetFileName(assemblyPath), OutputDirectory = AssetPath.GetDirectoryName(assemblyPath), - CompilerOptions = assemblyBuilder.compilerOptions, + CompilerOptions = new ScriptCompilerOptions(assemblyBuilder.compilerOptions), ScriptAssemblyReferences = new ScriptAssembly[0], RootNamespace = string.Empty }; - scriptAssembly.CompilerOptions.ApiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(assemblyBuilder.buildTargetGroup); + scriptAssembly.CompilerOptions.ApiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(NamedBuildTarget.FromActiveSettings(assemblyBuilder.buildTarget)); return scriptAssembly; } @@ -1741,6 +1626,24 @@ 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); + + // AssemblyBuilder can explicitly set analyzers and rule set + if (assemblyBuilder.compilerOptions.RoslynAnalyzerDllPaths != null) + scriptAssembly.CompilerOptions.RoslynAnalyzerDllPaths = assemblyBuilder.compilerOptions.RoslynAnalyzerDllPaths + .Concat(scriptAssembly.CompilerOptions.RoslynAnalyzerDllPaths) + .ToArray(); + + if (!string.IsNullOrEmpty(assemblyBuilder.compilerOptions.RoslynAnalyzerRulesetPath)) + scriptAssembly.CompilerOptions.RoslynAnalyzerRulesetPath = assemblyBuilder.compilerOptions.RoslynAnalyzerRulesetPath; + } + return scriptAssembly; } @@ -1754,7 +1657,7 @@ string[] GetAssemblyBuilderDefaultReferences(ScriptAssembly scriptAssembly, Edit var customReferences = EditorBuildRules.GetCompiledCustomAssembliesReferences(scriptAssembly, customTargetAssemblies, GetCompileScriptsOutputDirectory()); - var precompiledAssemblies = GetPrecompiledAssembliesWithSetupErrorsTracking(buildingForEditor, EditorUserBuildSettings.activeBuildTargetGroup, EditorUserBuildSettings.activeBuildTarget); + var precompiledAssemblies = PrecompiledAssemblyProvider.GetPrecompiledAssemblies(options, EditorUserBuildSettings.activeBuildTargetGroup, EditorUserBuildSettings.activeBuildTarget); // todo split implicit/explicit precompiled references var precompiledReferences = EditorBuildRules.GetPrecompiledReferences(scriptAssembly, TargetAssemblyType.Custom, options, EditorCompatibility.CompatibleWithEditor, precompiledAssemblies, null, null); var additionalReferences = MonoLibraryHelpers.GetSystemLibraryReferences(scriptAssembly.CompilerOptions.ApiCompatibilityLevel); diff --git a/Editor/Mono/Scripting/ScriptCompilation/EditorCompilationInterface.cs b/Editor/Mono/Scripting/ScriptCompilation/EditorCompilationInterface.cs index 813ca9986b..1d8616c039 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/EditorCompilationInterface.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/EditorCompilationInterface.cs @@ -37,9 +37,9 @@ static void LogException(Exception exception) UnityEngine.Debug.LogException(exception); } - static void EmitExceptionsAsErrors(Exception[] exceptions) + static void EmitExceptionsAsErrors(IEnumerable exceptions) { - if (exceptions == null || exceptions.Length == 0) + if (exceptions == null) return; foreach (var exception in exceptions) @@ -78,9 +78,9 @@ public static void SetAdditionalVersionMetaDatas(VersionMetaData[] versionMetaDa } [RequiredByNativeCode] - public static void SetAllScripts(string[] allScripts, string[] assemblyFilenames) + public static void SetAllScripts(string[] allScripts) { - Instance.SetAllScripts(allScripts, assemblyFilenames); + Instance.SetAllScripts(allScripts); } [RequiredByNativeCode] @@ -201,6 +201,16 @@ public static bool IsCompilationPending() return Instance.IsScriptCompilationRequested(); } + [RequiredByNativeCode] + // Unlike IsCompiling, this will only return true if compilation has actually started (and not if compilation + // is requested but has not started yet). We are using this for the BuildPlayer check (to not allow starting + // a player build if script compilation is in progress). We do allow starting a player build if compilation is + // requested (with a warning), as a common flow used in user build scripts is "Change defines -> Build Player". + public static bool IsCompilationInProgress() + { + return Instance.IsCompilationTaskCompiling() || Instance.IsAnyAssemblyBuilderCompiling(); + } + [RequiredByNativeCode] public static bool IsCompiling() { diff --git a/Editor/Mono/Scripting/ScriptCompilation/EditorScriptCompilationOptions.cs b/Editor/Mono/Scripting/ScriptCompilation/EditorScriptCompilationOptions.cs index 6e4b1c3e08..da9e57e3c3 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/EditorScriptCompilationOptions.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/EditorScriptCompilationOptions.cs @@ -23,5 +23,6 @@ enum EditorScriptCompilationOptions 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 new file mode 100644 index 0000000000..f90f3d93c4 --- /dev/null +++ b/Editor/Mono/Scripting/ScriptCompilation/ILPostProcessingProgram.cs @@ -0,0 +1,37 @@ +// 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.Diagnostics; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using UnityEditor.Scripting.Compilers; +using UnityEditor.Utils; +using UnityEngine; +using UnityEngine.Bindings; + +namespace UnityEditor.Scripting.ScriptCompilation +{ + class ILPostProcessingProgram + { + public virtual string NamedPipeOrUnixSocket + { + get + { + 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; + } + } + + [NativeHeader("Editor/Src/ScriptCompilation/ILPPExternalProcess.h")] + [FreeFunction("ILPPExternalProcess::EnsureRunningAndGetSocketOrNamedPipe", IsThreadSafe = true)] + private static extern string EnsureRunningAndGetSocketOrNamedPipe(); + } +} diff --git a/Editor/Mono/Scripting/ScriptCompilation/MonoLibraryHelpers.cs b/Editor/Mono/Scripting/ScriptCompilation/MonoLibraryHelpers.cs index 6dbca5b68c..fec1b754f0 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/MonoLibraryHelpers.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/MonoLibraryHelpers.cs @@ -7,25 +7,22 @@ using System.Linq; using UnityEditor.Scripting.Compilers; using UnityEditor.Utils; +using UnityEngine.Scripting; namespace UnityEditor.Scripting.ScriptCompilation { static class MonoLibraryHelpers { - class CachedReferences - { - public ApiCompatibilityLevel ApiCompatibilityLevel; - public string[] References; - } - - static CachedReferences cachedReferences; + static Dictionary cachedApiCompatibilityLevelReferences = new Dictionary(); + [RequiredByNativeCode] public static string[] GetSystemLibraryReferences(ApiCompatibilityLevel apiCompatibilityLevel) { return GetCachedSystemLibraryReferences(apiCompatibilityLevel); } - static string[] FindReferencesInDirectories(this string[] references, string[] directories) + + static string[] FindReferencesInDirectories(this IEnumerable references, string[] directories) { return ( from reference in references @@ -39,13 +36,13 @@ static string[] GetCachedSystemLibraryReferences(ApiCompatibilityLevel apiCompat { // We cache the references because they are computed by getting files in directories on disk, // which is very slow. - if (cachedReferences != null && cachedReferences.ApiCompatibilityLevel == apiCompatibilityLevel) + if (cachedApiCompatibilityLevelReferences.TryGetValue(apiCompatibilityLevel, out var cachedReferences)) { - return cachedReferences.References; + return cachedReferences; } var references = new List(); - var monoAssemblyDirectories = GetSystemReferenceDirectories(apiCompatibilityLevel); + if (apiCompatibilityLevel == ApiCompatibilityLevel.NET_Standard) { @@ -53,26 +50,21 @@ static string[] GetCachedSystemLibraryReferences(ApiCompatibilityLevel apiCompat } else if (apiCompatibilityLevel == ApiCompatibilityLevel.NET_Unity_4_8) { - references.AddRange(GetSystemReferences().FindReferencesInDirectories(monoAssemblyDirectories)); - references.AddRange(GetNet46SystemReferences().FindReferencesInDirectories(monoAssemblyDirectories)); - - // Look in the mono assembly directory for a facade folder and get a list of all the DLL's to be - // used later by the language compilers. + var monoAssemblyDirectories = GetSystemReferenceDirectories(apiCompatibilityLevel); + var referenceFileNames = GetSystemReferences().Concat(GetNet46SystemReferences()).Concat(GetMonoProfileNetstandardFacadeReferences()).Distinct(); + references.AddRange(referenceFileNames.FindReferencesInDirectories(monoAssemblyDirectories)); references.AddRange(Directory.GetFiles(Path.Combine(GetUnityReferenceProfileDirectory(), "Facades"), "*.dll")); } else { + var monoAssemblyDirectories = GetSystemReferenceDirectories(apiCompatibilityLevel); references.AddRange(GetSystemReferences().FindReferencesInDirectories(monoAssemblyDirectories)); } - cachedReferences = new CachedReferences - { - ApiCompatibilityLevel = apiCompatibilityLevel, - References = references.ToArray() - }; - + var apiCompatibilityLevelReference = references.ToArray(); + cachedApiCompatibilityLevelReferences[apiCompatibilityLevel] = apiCompatibilityLevelReference; - return cachedReferences.References; + return apiCompatibilityLevelReference; } static string GetSystemReference(ApiCompatibilityLevel apiCompatibilityLevel) @@ -158,5 +150,27 @@ static string[] GetNet46SystemReferences() "System.Data.dll", }; } + + static string[] GetMonoProfileNetstandardFacadeReferences() + { + return new[] + { + "mscorlib.dll", + "System.Core.dll", + "System.dll", + "System.Data.dll", + "System.Data.DataSetExtensions.dll", + "System.Drawing.dll", + "System.IO.Compression.dll", + "System.IO.Compression.FileSystem.dll", + "System.ComponentModel.Composition.dll", + "System.Net.Http.dll", + "System.Numerics.dll", + "System.Runtime.Serialization.dll", + "System.Transactions.dll", + "System.Xml.dll", + "System.Xml.Linq.dll", + }; + } } } diff --git a/Editor/Mono/Scripting/ScriptCompilation/PathMultidimensionalDivisionTree.cs b/Editor/Mono/Scripting/ScriptCompilation/PathMultidimensionalDivisionTree.cs new file mode 100644 index 0000000000..bd8eae54a4 --- /dev/null +++ b/Editor/Mono/Scripting/ScriptCompilation/PathMultidimensionalDivisionTree.cs @@ -0,0 +1,230 @@ +// 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.Assertions; + +namespace UnityEditor.Scripting.ScriptCompilation +{ + internal class PathMultidimensionalDivisionTree + { + private readonly Node _root; + + public PathMultidimensionalDivisionTree(T defaultValue = default) + { + _root = new Node(defaultValue, null); + } + + public void Insert(string key, T value) + { + if (string.IsNullOrEmpty(key)) + { + throw new ArgumentNullException(nameof(key)); + } + + var keyMemory = TrimTrailingPathSeparator(key); + _root.SplitInsert(keyMemory, value); + } + + public T MatchClosest(ReadOnlySpan span) + { + return Node.MatchClosest(_root, span).Value; + } + + private static bool IsEqualIgnoreCase(char a, char b) + { + return (Utility.IsPathSeparator(a) && Utility.IsPathSeparator(b)) || Utility.FastToLower(a) == Utility.FastToLower(b); + } + + private static ReadOnlyMemory TrimTrailingPathSeparator(string key) + { + for (int i = key.Length - 1; i >= 0; i--) + { + if (Utility.IsPathSeparator(key[i])) + { + continue; + } + + return key.AsMemory(0, i + 1); + } + + return ReadOnlyMemory.Empty; + } + + private class Node + { + public T Value { get; } + private bool IsLeaf { get; } + private ReadOnlyMemory StringSegment { get; } + private List ChildNodes { get; } + + public Node(T value, ReadOnlyMemory segment) + { + Value = value; + StringSegment = segment; + IsLeaf = true; + ChildNodes = new List(); + } + + public Node(T value, ReadOnlyMemory segment, List nodes) + { + Value = value; + StringSegment = segment; + IsLeaf = true; + ChildNodes = nodes; + } + + private Node(T value, ReadOnlyMemory segment, Node left) + { + Value = value; + StringSegment = segment; + IsLeaf = true; + ChildNodes = new List + { + left, + }; + } + + private Node(Node node, ReadOnlyMemory segment) + { + Value = node.Value; + StringSegment = segment; + IsLeaf = node.IsLeaf; + ChildNodes = node.ChildNodes; + } + + private Node(ReadOnlyMemory segment, Node left, Node right) + { + Value = default; + StringSegment = segment; + IsLeaf = false; + ChildNodes = new List + { + left, + right, + }; + } + + private bool IsMatch(ReadOnlySpan span) + { + if (span.Length < StringSegment.Length) + { + return false; + } + + var segmentSpan = StringSegment.Span; + for (int i = 0; i < StringSegment.Length; i++) + { + if (!IsEqualIgnoreCase(span[i], segmentSpan[i])) + { + return false; + } + } + + return true; + } + + public static Node MatchClosest(Node current, ReadOnlySpan span) + { + Node currentLeaf = current; + Next: + if (span.Length == 0) + { + return currentLeaf; + } + + foreach (var childNode in current.ChildNodes) + { + // We can only have 1 candidate + if (childNode.IsCandidate(span[0])) + { + if (childNode.IsMatch(span)) + { + span = span[childNode.StringSegment.Length..]; + if (childNode.IsLeaf && (span.Length == 0 || Utility.IsPathSeparator(span[0]))) + { + current = currentLeaf = childNode; + goto Next; + } + + current = childNode; + goto Next; + } + + return currentLeaf; + } + } + + return currentLeaf; + } + + public void SplitInsert(ReadOnlyMemory currentStringSegment, T value) + { + Assert.IsTrue(currentStringSegment.Length > 0); + + if (ChildNodes.Count == 0) + { + ChildNodes.Add(new Node(value, currentStringSegment)); + } + + var currentStringSegmentSpan = currentStringSegment.Span; + + for (var childIndex = 0; childIndex < ChildNodes.Count; childIndex++) + { + var childNode = ChildNodes[childIndex]; + if (childNode.IsCandidate(currentStringSegmentSpan[0])) + { + var childSegmentSpan = childNode.StringSegment.Span; + for (int i = 0; childSegmentSpan.Length > i; i++) + { + if (i >= currentStringSegment.Length || + !IsEqualIgnoreCase(currentStringSegmentSpan[i], childSegmentSpan[i])) + { + // Split existing node + var childHead = childNode.StringSegment[..i]; + var childTail = childNode.StringSegment[i..]; + var splitTail = currentStringSegment[i..]; + + if (splitTail.Length > 0) + { + ChildNodes[childIndex] = new Node(childHead, + new Node(childNode, childTail), + new Node(value, splitTail)); + return; + } + + ChildNodes[childIndex] = new Node(value, childHead, + new Node(childNode, childTail)); + + return; + } + } + + var remaining = currentStringSegment[childSegmentSpan.Length..]; + if (remaining.Length == 0) + { + // Replace existing value + ChildNodes[childIndex] = new Node(value, childNode.StringSegment, ChildNodes[childIndex].ChildNodes); + } + else + { + childNode.SplitInsert(remaining, value); + } + + return; + } + } + + // No candidates found add new branch to the tree + ChildNodes.Add(new Node(value, currentStringSegment)); + } + + private bool IsCandidate(char firstCharacter) + { + return IsEqualIgnoreCase(firstCharacter, StringSegment.Span[0]); + } + } + } +} diff --git a/Editor/Mono/Scripting/ScriptCompilation/PrecompiledAssembly.cs b/Editor/Mono/Scripting/ScriptCompilation/PrecompiledAssembly.cs index 801bc0cdb6..cf0354d643 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/PrecompiledAssembly.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/PrecompiledAssembly.cs @@ -14,6 +14,7 @@ namespace UnityEditor.Scripting.ScriptCompilation { [DebuggerDisplay("{Path}")] [NativeHeader("Editor/Src/ScriptCompilation/ScriptCompilationPipeline.h")] + [NativeHeader("Runtime/Scripting/ScriptingTypes.h")] [StructLayout(LayoutKind.Sequential)] struct PrecompiledAssembly { @@ -21,12 +22,14 @@ struct PrecompiledAssembly public string Path; [NativeName("flags")] public AssemblyFlags Flags; + [NativeName("redirected")] + public bool Redirected; } abstract class PrecompiledAssemblyProviderBase { - public abstract PrecompiledAssembly[] GetPrecompiledAssemblies(bool isEditor, BuildTargetGroup buildTargetGroup, BuildTarget buildTarget, string[] extraScriptingDefines = null); - public abstract Dictionary GetPrecompiledAssembliesDictionary(bool isEditor, BuildTargetGroup buildTargetGroup, BuildTarget buildTarget, string[] extraScriptingDefines); + 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[] CachedEditorPrecompiledAssemblies { get; } public abstract PrecompiledAssembly[] CachedUnityAssemblies { get; } @@ -38,13 +41,13 @@ protected virtual PrecompiledAssembly[] GetUnityAssembliesInternal(bool isEditor return GetUnityAssembliesNative(isEditor, buildTarget); } - protected virtual PrecompiledAssembly[] GetPrecompiledAssembliesInternal(bool buildingForEditor, BuildTargetGroup buildTargetGroup, BuildTarget target, string[] extraScriptingDefines) + protected virtual PrecompiledAssembly[] GetPrecompiledAssembliesInternal(EditorScriptCompilationOptions compilationOptions, BuildTargetGroup buildTargetGroup, BuildTarget target, string[] extraScriptingDefines) { - return GetPrecompiledAssembliesNative(buildingForEditor, buildTargetGroup, target, extraScriptingDefines); + return GetPrecompiledAssembliesNative(compilationOptions, buildTargetGroup, target, extraScriptingDefines); } [FreeFunction("GetPrecompiledAssembliesManaged")] - protected static extern PrecompiledAssembly[] GetPrecompiledAssembliesNative(bool buildingForEditor, BuildTargetGroup buildTargetGroup, BuildTarget target, string[] extraScriptingDefines); + protected static extern PrecompiledAssembly[] GetPrecompiledAssembliesNative(EditorScriptCompilationOptions compilationOptions, BuildTargetGroup buildTargetGroup, BuildTarget target, string[] extraScriptingDefines); [FreeFunction("GetUnityAssembliesManaged")] protected static extern PrecompiledAssembly[] GetUnityAssembliesNative(bool buildingForEditor, BuildTarget target); @@ -111,9 +114,9 @@ public override PrecompiledAssembly[] GetUnityAssemblies(bool isEditor, BuildTar return unityAssembliesInternal; } - public override PrecompiledAssembly[] GetPrecompiledAssemblies(bool isEditor, BuildTargetGroup buildTargetGroup, BuildTarget buildTarget, string[] extraScriptingDefines) + public override PrecompiledAssembly[] GetPrecompiledAssemblies(EditorScriptCompilationOptions compilationOptions, BuildTargetGroup buildTargetGroup, BuildTarget buildTarget, string[] extraScriptingDefines) { - return GetPrecompiledAssembliesDictionary(isEditor, buildTargetGroup, buildTarget, extraScriptingDefines).Values.ToArray(); + return GetPrecompiledAssembliesDictionary(compilationOptions, buildTargetGroup, buildTarget, extraScriptingDefines).Values.ToArray(); } public override string[] GetRoslynAnalyzerPaths() @@ -123,15 +126,20 @@ public override string[] GetRoslynAnalyzerPaths() [FreeFunction] internal static extern string[] GetAllRoslynAnalyzerPaths(); - public override Dictionary GetPrecompiledAssembliesDictionary(bool isEditor, BuildTargetGroup buildTargetGroup, BuildTarget buildTarget, string[] extraScriptingDefines) + static bool IsEditor(EditorScriptCompilationOptions options) { + return (options & EditorScriptCompilationOptions.BuildingForEditor) == EditorScriptCompilationOptions.BuildingForEditor; + } + public override Dictionary GetPrecompiledAssembliesDictionary(EditorScriptCompilationOptions compilationOptions, BuildTargetGroup buildTargetGroup, BuildTarget buildTarget, string[] extraScriptingDefines) + { + bool isEditor = IsEditor(compilationOptions); if (isEditor && (extraScriptingDefines == null || extraScriptingDefines.Length == 0) && m_EditorPrecompiledAssemblies != null) { return m_EditorPrecompiledAssemblies; } - var precompiledAssembliesInternal = GetPrecompiledAssembliesInternal(isEditor, buildTargetGroup, buildTarget, extraScriptingDefines); - var fileNameToPrecompiledAssembly = ValidateAndGetNameToPrecompiledAssembly(precompiledAssembliesInternal); + var precompiledAssembliesInternal = GetPrecompiledAssembliesInternal(compilationOptions, buildTargetGroup, buildTarget, extraScriptingDefines); + var fileNameToPrecompiledAssembly = FilenameToPrecompiledAssembly(precompiledAssembliesInternal); if (isEditor && (extraScriptingDefines == null || extraScriptingDefines.Length == 0)) { @@ -147,46 +155,29 @@ public override void Dirty() m_UnityAssemblies = null; } - private static Dictionary ValidateAndGetNameToPrecompiledAssembly(PrecompiledAssembly[] precompiledAssemblies) + private static Dictionary FilenameToPrecompiledAssembly(PrecompiledAssembly[] precompiledAssemblies) { if (precompiledAssemblies == null) { return new Dictionary(0); } - Dictionary fileNameToUserPrecompiledAssemblies = new Dictionary(precompiledAssemblies.Length); - - var sameNamedPrecompiledAssemblies = new Dictionary>(precompiledAssemblies.Length); - for (int i = 0; i < precompiledAssemblies.Length; i++) + var dictionary = new Dictionary(); + foreach (var assembly in precompiledAssemblies) { - var precompiledAssembly = precompiledAssemblies[i]; - - var fileName = AssetPath.GetFileName(precompiledAssembly.Path); - if (!fileNameToUserPrecompiledAssemblies.ContainsKey(fileName)) + var filename = AssetPath.GetFileName(assembly.Path); + if (!dictionary.TryGetValue(filename, out var existingAssembly)) { - fileNameToUserPrecompiledAssemblies.Add(fileName, precompiledAssembly); + dictionary[filename] = assembly; + continue; } - else + + if (existingAssembly.Redirected) { - if (!sameNamedPrecompiledAssemblies.ContainsKey(fileName)) - { - sameNamedPrecompiledAssemblies.Add(fileName, new List - { - fileNameToUserPrecompiledAssemblies[fileName].Path - }); - } - sameNamedPrecompiledAssemblies[fileName].Add(precompiledAssembly.Path); + dictionary[filename] = assembly; } } - - foreach (var precompiledAssemblyNameToIndexes in sameNamedPrecompiledAssemblies) - { - throw new PrecompiledAssemblyException( - $"Multiple precompiled assemblies with the same name {precompiledAssemblyNameToIndexes.Key} included on the current platform. Only one assembly with the same name is allowed per platform.", - precompiledAssemblyNameToIndexes.Value.ToArray()); - } - - return fileNameToUserPrecompiledAssemblies; + return dictionary; } } } diff --git a/Editor/Mono/Scripting/ScriptCompilation/RoslynAnalyzers.cs b/Editor/Mono/Scripting/ScriptCompilation/RoslynAnalyzers.cs new file mode 100644 index 0000000000..12d9ca6595 --- /dev/null +++ b/Editor/Mono/Scripting/ScriptCompilation/RoslynAnalyzers.cs @@ -0,0 +1,91 @@ +// 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 System.Linq; + +namespace UnityEditor.Scripting.ScriptCompilation +{ + internal static class RoslynAnalyzers + { + private static readonly string[] Unset = null; + private static readonly string[] CyclicDependencies = {}; + + private static string[] SetAnalyzers(ScriptAssembly scriptAssembly, IEnumerable<(string scriptAssemblyFileName, string analyzerDll)> allAnalyzers, bool scanPrecompiledReferences) + { + if (scriptAssembly.CompilerOptions.RoslynAnalyzerDllPaths == Unset) + { + // If this is a cyclic chain we want to detect that and do two iterations + // Doing two iterations ensures that all participants in the chain will see all the analyzers of all members involved in the chain. + scriptAssembly.CompilerOptions.RoslynAnalyzerDllPaths = CyclicDependencies; + } + else if (scriptAssembly.CompilerOptions.RoslynAnalyzerDllPaths == CyclicDependencies) + { + // On second iteration return an empty array (this will be replaced be actual content of the cyclic chain) + scriptAssembly.CompilerOptions.RoslynAnalyzerDllPaths = Array.Empty(); + } + else + { + // Analyzers for this ScriptAssembly has already been setup + return scriptAssembly.CompilerOptions.RoslynAnalyzerDllPaths; + } + + scriptAssembly.CompilerOptions.RoslynAnalyzerDllPaths = + scriptAssembly.ScriptAssemblyReferences + .SelectMany(sa => SetAnalyzers(sa, allAnalyzers, scanPrecompiledReferences)) + .Concat(allAnalyzers + .Where(a => a.scriptAssemblyFileName == null || + a.scriptAssemblyFileName == scriptAssembly.Filename || + scanPrecompiledReferences && scriptAssembly.References.Select(Path.GetFileName).Contains(a.scriptAssemblyFileName)) + .Select(a => a.analyzerDll)) + .Distinct() + .ToArray(); + + if (scriptAssembly.CompilerOptions.RoslynAnalyzerDllPaths.Length > 0) + { + if(scriptAssembly.TargetAssemblyType == TargetAssemblyType.Predefined) + { + var originPath = Path.ChangeExtension(scriptAssembly.Filename, null); + scriptAssembly.CompilerOptions.RoslynAnalyzerRulesetPath = RuleSetFileCache.GetRuleSetFilePathInRootFolder(originPath); + scriptAssembly.CompilerOptions.AnalyzerConfigPath = RoslynAnalyzerConfigFiles.GetAnalyzerConfigRootFolder(originPath); + } + else + { + scriptAssembly.CompilerOptions.RoslynAnalyzerRulesetPath = RuleSetFileCache.GetPathForAssembly(scriptAssembly.OriginPath); + scriptAssembly.CompilerOptions.AnalyzerConfigPath = RoslynAnalyzerConfigFiles.GetAnalyzerConfigForAssembly(scriptAssembly.OriginPath); + } + scriptAssembly.CompilerOptions.RoslynAdditionalFilePaths = scriptAssembly.CompilerOptions.RoslynAnalyzerDllPaths + .SelectMany(a=>RoslynAdditionalFiles.GetAnalyzerAdditionalFilesForTargetAssembly(a, scriptAssembly.OriginPath)) + .Distinct() + .ToArray(); + } + + return scriptAssembly.CompilerOptions.RoslynAnalyzerDllPaths; + } + + internal static void SetAnalyzers(ScriptAssembly[] scriptAssemblies, TargetAssembly[] potentialAnalyzerOwners, string[] analyzerDlls, bool scanPrecompiledReferences) + { + // Figure out what assemblies own each analyzer + var analyzerAssemblies = analyzerDlls.Select(analyzerDll => + { + var potentialAnalyzerOwner = potentialAnalyzerOwners + .Where(targetAssembly => targetAssembly.PathFilter(analyzerDll) > 0) + .OrderBy(targetAssembly => targetAssembly.PathFilter(analyzerDll)) + .LastOrDefault(); + + return (potentialOwnerOfAnalyzer: potentialAnalyzerOwner?.Filename, dll: analyzerDll); + + }).ToArray(); + + // Null out all RoslynAnalyzerDllPaths to indicate they need to be set + foreach (var scriptAssembly in scriptAssemblies) + scriptAssembly.CompilerOptions.RoslynAnalyzerDllPaths = Unset; + + foreach (var scriptAssembly in scriptAssemblies) + SetAnalyzers(scriptAssembly, analyzerAssemblies, scanPrecompiledReferences); + } + } +} diff --git a/Editor/Mono/Scripting/ScriptCompilation/UnitySpecificCompilerMessageProcessor.cs b/Editor/Mono/Scripting/ScriptCompilation/UnitySpecificCompilerMessageProcessor.cs index 77ca330d49..df61d10fb1 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/UnitySpecificCompilerMessageProcessor.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/UnitySpecificCompilerMessageProcessor.cs @@ -22,6 +22,7 @@ public static void AugmentMessagesInCompilationErrorsWithUnitySpecificAdvice(Com UnsafeErrorProcessor.PostProcess(ref messages[i], editorCompilation); ModuleReferenceErrorProcessor.PostProcess(ref messages[i]); DeterministicAssemblyVersionErrorProcessor.PostProcess(ref messages[i]); + CyclicAssemblyReferencesErrorProcessor.PostProcess(ref messages[i]); } } @@ -96,5 +97,22 @@ private static CustomScriptAssembly CustomScriptAssemblyFor(CompilerMessage m, E .FirstOrDefault(c => file.IsChildOf(new NPath(c.PathPrefix).MakeAbsolute())); } } + internal static class CyclicAssemblyReferencesErrorProcessor + { + public static void PostProcess(ref CompilerMessage message) + { + int cyclickDependencyMessageStart = message.message.IndexOf("One or more cyclic dependencies detected between assemblies"); + if (cyclickDependencyMessageStart >= 0) + { + int cyclickDependencyMessageEnd = message.message.IndexOf(System.Environment.NewLine, cyclickDependencyMessageStart); + if (cyclickDependencyMessageEnd >= 0) + message.message = message.message.Substring(cyclickDependencyMessageStart, cyclickDependencyMessageEnd - cyclickDependencyMessageStart); + else + message.message = message.message.Substring(cyclickDependencyMessageStart); + } + } + } + + } } diff --git a/Editor/Mono/Scripting/ScriptCompilation/Utility.cs b/Editor/Mono/Scripting/ScriptCompilation/Utility.cs index 683009f64a..fe4522cd85 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/Utility.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/Utility.cs @@ -4,6 +4,7 @@ using System; using System.Globalization; +using System.Runtime.CompilerServices; using UnityEngine; namespace UnityEditor.Scripting.ScriptCompilation @@ -139,5 +140,11 @@ public static string FileNameWithoutExtension(string path) return path.Substring(indexOfSlash, indexOfDot - indexOfSlash); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsPathSeparator(char character) + { + return character == '/' || character == '\\'; + } } } diff --git a/Editor/Mono/Search/ObjectSelectorSearch.cs b/Editor/Mono/Search/ObjectSelectorSearch.cs index 8a5bc33d2c..72dc85a7a4 100644 --- a/Editor/Mono/Search/ObjectSelectorSearch.cs +++ b/Editor/Mono/Search/ObjectSelectorSearch.cs @@ -38,78 +38,93 @@ public interface IObjectSelectorEngine : ISelectorEngine {} [InitializeOnLoad] public static class ObjectSelectorSearch { - static readonly SearchApiBaseImp k_EngineImp; + static SearchApiBaseImp s_EngineImp; + static SearchApiBaseImp engineImp + { + get + { + if (s_EngineImp == null) + StaticInit(); + return s_EngineImp; + } + } public const SearchEngineScope EngineScope = SearchEngineScope.ObjectSelector; static ObjectSelectorSearch() { - k_EngineImp = new SearchApiBaseImp(EngineScope, "Object Selector"); + EditorApplication.tick += StaticInit; + } + + private static void StaticInit() + { + EditorApplication.tick -= StaticInit; + s_EngineImp = s_EngineImp ?? new SearchApiBaseImp(EngineScope, "Object Selector"); } internal static bool SelectObject(ObjectSelectorSearchContext context, Action onObjectSelectorClosed, Action onObjectSelectedUpdated) { - var activeEngine = k_EngineImp.activeSearchEngine; + var activeEngine = engineImp.activeSearchEngine; try { return activeEngine.SelectObject(context, onObjectSelectorClosed, onObjectSelectedUpdated); } catch (Exception ex) { - k_EngineImp.HandleUserException(ex); + engineImp.HandleUserException(ex); return false; } } internal static void SetSearchFilter(string searchFilter, ObjectSelectorSearchContext context) { - var activeEngine = k_EngineImp.activeSearchEngine; + var activeEngine = engineImp.activeSearchEngine; activeEngine.SetSearchFilter(context, searchFilter); } internal static bool HasEngineOverride() { - return k_EngineImp.HasEngineOverride(); + return engineImp.HasEngineOverride(); } internal static void BeginSession(ObjectSelectorSearchContext context) { - k_EngineImp.BeginSession(context); + engineImp.BeginSession(context); } internal static void EndSession(ObjectSelectorSearchContext context) { - k_EngineImp.EndSession(context); + engineImp.EndSession(context); } internal static void BeginSearch(string query, ObjectSelectorSearchContext context) { - k_EngineImp.BeginSearch(query, context); + engineImp.BeginSearch(query, context); } internal static void EndSearch(ObjectSelectorSearchContext context) { - k_EngineImp.EndSearch(context); + engineImp.EndSearch(context); } internal static IObjectSelectorEngine GetActiveSearchEngine() { - return k_EngineImp.GetActiveSearchEngine(); + return engineImp.GetActiveSearchEngine(); } internal static void SetActiveSearchEngine(string searchEngineName) { - k_EngineImp.SetActiveSearchEngine(searchEngineName); + engineImp.SetActiveSearchEngine(searchEngineName); } public static void RegisterEngine(IObjectSelectorEngine engine) { - k_EngineImp.RegisterEngine(engine); + engineImp.RegisterEngine(engine); } public static void UnregisterEngine(IObjectSelectorEngine engine) { - k_EngineImp.UnregisterEngine(engine); + engineImp.UnregisterEngine(engine); } } diff --git a/Editor/Mono/Search/ProjectSearch.cs b/Editor/Mono/Search/ProjectSearch.cs index cb9f27cbf8..f4d54d6c78 100644 --- a/Editor/Mono/Search/ProjectSearch.cs +++ b/Editor/Mono/Search/ProjectSearch.cs @@ -17,6 +17,7 @@ public class ProjectSearchContext : ISearchContext public SearchEngineScope engineScope { get; protected set; } = ProjectSearch.EngineScope; public IEnumerable requiredTypes { get; set; } public IEnumerable requiredTypeNames { get; set; } + internal SearchFilter searchFilter { get; set; } } public interface IProjectSearchEngine : ISearchEngine {} @@ -24,72 +25,87 @@ public interface IProjectSearchEngine : ISearchEngine {} [InitializeOnLoad] public static class ProjectSearch { - static readonly SearchApiBaseImp k_EngineImp; + static SearchApiBaseImp s_EngineImp; + static SearchApiBaseImp engineImp + { + get + { + if (s_EngineImp == null) + StaticInit(); + return s_EngineImp; + } + } public const SearchEngineScope EngineScope = SearchEngineScope.Project; static ProjectSearch() { - k_EngineImp = new SearchApiBaseImp(EngineScope, "Project"); + EditorApplication.tick += StaticInit; + } + + private static void StaticInit() + { + EditorApplication.tick -= StaticInit; + s_EngineImp = s_EngineImp ?? new SearchApiBaseImp(EngineScope, "Project"); } internal static IEnumerable Search(string query, ProjectSearchContext context, Action> asyncItemsReceived) { - var activeEngine = k_EngineImp.activeSearchEngine; + var activeEngine = engineImp.activeSearchEngine; try { return activeEngine.Search(context, query, asyncItemsReceived); } catch (Exception ex) { - k_EngineImp.HandleUserException(ex); + engineImp.HandleUserException(ex); return null; } } internal static bool HasEngineOverride() { - return k_EngineImp.HasEngineOverride(); + return engineImp.HasEngineOverride(); } internal static void BeginSession(ProjectSearchContext context) { - k_EngineImp.BeginSession(context); + engineImp.BeginSession(context); } internal static void EndSession(ProjectSearchContext context) { - k_EngineImp.EndSession(context); + engineImp.EndSession(context); } internal static void BeginSearch(string query, ProjectSearchContext context) { - k_EngineImp.BeginSearch(query, context); + engineImp.BeginSearch(query, context); } internal static void EndSearch(ProjectSearchContext context) { - k_EngineImp.EndSearch(context); + engineImp.EndSearch(context); } internal static IProjectSearchEngine GetActiveSearchEngine() { - return k_EngineImp.GetActiveSearchEngine(); + return engineImp.GetActiveSearchEngine(); } internal static void SetActiveSearchEngine(string searchEngineName) { - k_EngineImp.SetActiveSearchEngine(searchEngineName); + engineImp.SetActiveSearchEngine(searchEngineName); } public static void RegisterEngine(IProjectSearchEngine engine) { - k_EngineImp.RegisterEngine(engine); + engineImp.RegisterEngine(engine); } public static void UnregisterEngine(IProjectSearchEngine engine) { - k_EngineImp.UnregisterEngine(engine); + engineImp.UnregisterEngine(engine); } } diff --git a/Editor/Mono/Search/SceneSearch.cs b/Editor/Mono/Search/SceneSearch.cs index 33792b54d0..bf8d896c80 100644 --- a/Editor/Mono/Search/SceneSearch.cs +++ b/Editor/Mono/Search/SceneSearch.cs @@ -26,72 +26,87 @@ public interface ISceneSearchEngine : IFilterEngine [InitializeOnLoad] public static class SceneSearch { - static readonly SearchApiBaseImp k_EngineImp; + static SearchApiBaseImp s_EngineImp; + static SearchApiBaseImp engineImp + { + get + { + if (s_EngineImp == null) + StaticInit(); + return s_EngineImp; + } + } public const SearchEngineScope EngineScope = SearchEngineScope.Scene; static SceneSearch() { - k_EngineImp = new SearchApiBaseImp(EngineScope, "Scene"); + EditorApplication.tick += StaticInit; + } + + private static void StaticInit() + { + EditorApplication.tick -= StaticInit; + s_EngineImp = s_EngineImp ?? new SearchApiBaseImp(EngineScope, "Scene"); } internal static bool Filter(string query, HierarchyProperty objectToFilter, SceneSearchContext context) { - var activeEngine = k_EngineImp.activeSearchEngine; + var activeEngine = engineImp.activeSearchEngine; try { return activeEngine.Filter(context, query, objectToFilter); } catch (Exception ex) { - k_EngineImp.HandleUserException(ex); + engineImp.HandleUserException(ex); return false; } } internal static bool HasEngineOverride() { - return k_EngineImp.HasEngineOverride(); + return engineImp.HasEngineOverride(); } internal static void BeginSession(SceneSearchContext context) { - k_EngineImp.BeginSession(context); + engineImp.BeginSession(context); } internal static void EndSession(SceneSearchContext context) { - k_EngineImp.EndSession(context); + engineImp.EndSession(context); } internal static void BeginSearch(string query, SceneSearchContext context) { - k_EngineImp.BeginSearch(query, context); + engineImp.BeginSearch(query, context); } internal static void EndSearch(SceneSearchContext context) { - k_EngineImp.EndSearch(context); + engineImp.EndSearch(context); } internal static ISceneSearchEngine GetActiveSearchEngine() { - return k_EngineImp.GetActiveSearchEngine(); + return engineImp.GetActiveSearchEngine(); } internal static void SetActiveSearchEngine(string searchEngineName) { - k_EngineImp.SetActiveSearchEngine(searchEngineName); + engineImp.SetActiveSearchEngine(searchEngineName); } public static void RegisterEngine(ISceneSearchEngine engine) { - k_EngineImp.RegisterEngine(engine); + engineImp.RegisterEngine(engine); } public static void UnregisterEngine(ISceneSearchEngine engine) { - k_EngineImp.UnregisterEngine(engine); + engineImp.UnregisterEngine(engine); } } diff --git a/Editor/Mono/Search/SearchService.cs b/Editor/Mono/Search/SearchService.cs index 61869d7e85..d8fa52f3e3 100644 --- a/Editor/Mono/Search/SearchService.cs +++ b/Editor/Mono/Search/SearchService.cs @@ -107,8 +107,10 @@ public SearchSessionHandler(SearchEngineScope engineScope) Init(); } - void Init() + void Init(bool pullEngines = false) { + if (pullEngines) + PullEngines(); var index = SearchService.searchApis.FindIndex(api => api.engineScope == m_EngineScope); if (index >= 0) { @@ -117,6 +119,20 @@ void Init() } } + void PullEngines() + { + // Calling HasEngineOverride will make sure the engine apis are registered. + switch (m_EngineScope) + { + case SearchEngineScope.Project: ProjectSearch.HasEngineOverride(); + break; + case SearchEngineScope.Scene: SceneSearch.HasEngineOverride(); + break; + case SearchEngineScope.ObjectSelector: ObjectSelectorSearch.HasEngineOverride(); + break; + } + } + void OnActiveEngineChanged(string newSearchEngineName) { EndSession(); @@ -129,7 +145,7 @@ public void BeginSession(Func searchContextCreator, SearchSessio // Do a lazy initialize if the apis were not available during creation if (m_Api == null) { - Init(); + Init(true); if (m_Api == null) throw new NullReferenceException("SearchService Apis were not initialized properly."); } @@ -188,6 +204,7 @@ interface ISearchApi event Action activeEngineChanged; } + [InitializeOnLoad] static class SearchService { public const string keyPrefix = "searchservice"; @@ -215,6 +232,7 @@ private static void AddSearchServiceBuildDefines(BuildTarget target, HashSet + /// 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. + /// For example if an object "Foo" with id 1 is referenced by multiple fields "a", "b" and + /// "c.nested" on a MonoBehaviour (declared in that order), then calling this method with + /// "managedReferences[1].x" would return the SerializedProperty with property path "a.x". + /// + internal SerializedProperty FindFirstPropertyFromManagedReferencePath(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.FindFirstPropertyFromManagedReferencePathInternal(propertyPath)) + return i; + else + return null; + } + extern public bool ApplyModifiedProperties(); // Update /hasMultipleDifferentValues/ cache on the next /Update()/ call. @@ -140,6 +164,9 @@ internal void Cache(int instanceID) public extern bool ApplyModifiedPropertiesWithoutUndo(); + // Enable/Disable live property feature globally. + internal extern static void EnableLivePropertyFeatureGlobally(bool value); + // Copies a value from a SerializedProperty to the same serialized property on this serialized object. public void CopyFromSerializedProperty(SerializedProperty prop) { diff --git a/Editor/Mono/SerializedProperty.bindings.cs b/Editor/Mono/SerializedProperty.bindings.cs index 5edc0d8e2a..e8cadd434b 100644 --- a/Editor/Mono/SerializedProperty.bindings.cs +++ b/Editor/Mono/SerializedProperty.bindings.cs @@ -7,6 +7,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using UnityEngine.Bindings; +using UnityEngine.Serialization; using UnityObject = UnityEngine.Object; using System.Reflection; @@ -475,6 +476,9 @@ public void ClearArray() [NativeName("FindProperty")] extern internal bool FindPropertyInternal(string propertyPath); + [NativeName("FindFirstPropertyFromManagedReferencePath")] + extern internal bool FindFirstPropertyFromManagedReferencePathInternal(string managedReferencePath); + [ThreadAndSerializationSafe()] public void Dispose() { @@ -751,6 +755,19 @@ internal bool isKey [NativeName("IsKey")] private extern bool IsKeyInternal(); + [NativeName("IsLiveModified")] + private extern bool IsLiveModified(); + + // Is this property live modified? + internal bool isLiveModified + { + get + { + Verify(VerifyFlags.IteratorNotAtEnd); + return IsLiveModified(); + } + } + // Is this property expanded in the inspector? public bool isExpanded { @@ -848,6 +865,27 @@ internal bool isReferencingAManagedReferenceField [NativeName("GetPropertyPathInCurrentManagedTypeTree")] internal extern string GetPropertyPathInCurrentManagedTypeTreeInternal(); + + /// + /// If the current field is on a SerializeReference instance this returns the path + /// of the field relative the ManagedReferenceRegistry. managedReferences[refId].field + /// + internal string managedReferencePropertyPath + { + get + { + Verify(VerifyFlags.IteratorNotAtEnd); + return GetManagedReferencePropertyPathInternal(); + } + } + + /// + /// Returns the path of the current field relative to the managed reference registry managedReferences[refId]. + /// + // Useful in the same context as 'isReferencingAManagedReferenceField'. + [NativeName("GetManagedReferencePropertyPath")] + internal extern string GetManagedReferencePropertyPathInternal(); + // Is property's value different from the prefab it belongs to? public bool prefabOverride { @@ -1230,7 +1268,7 @@ public RefId managedReferenceId { Verify(VerifyFlags.IteratorNotAtEnd); var referencedObj = LookupInstanceByIdInternal(value); - if (value != SerializationUtility.RefIdNull && referencedObj == null) + if (value != ManagedReferenceUtility.RefIdNull && referencedObj == null) { throw new System.InvalidOperationException( $"The specified managed reference id cannot be set because it is not currently assigned to an object."); diff --git a/Editor/Mono/SerializedProperty/SerializedPropertyTreeView.cs b/Editor/Mono/SerializedProperty/SerializedPropertyTreeView.cs index d174c9c704..df2e07cc22 100644 --- a/Editor/Mono/SerializedProperty/SerializedPropertyTreeView.cs +++ b/Editor/Mono/SerializedProperty/SerializedPropertyTreeView.cs @@ -141,8 +141,7 @@ public bool Repopulate(out bool needsReload) int elementIndex = 0; for (int i = 0; i < objs.Length; i++) { - // we don't want to list hidden objects - if (objs[i] == null || objs[i].hideFlags == HideFlags.HideAndDontSave || objs[i].hideFlags == HideFlags.HideInHierarchy) + if (objs[i] == null) continue; m_Elements[elementIndex] = new Data(objs[i], m_PropNames); @@ -164,7 +163,7 @@ private void UpdateActiveObjects(Object[] objs) for (int i = 0; i < objs.Length; i++) { - if (objs[i] == null || objs[i].hideFlags == HideFlags.HideAndDontSave || objs[i].hideFlags == HideFlags.HideInHierarchy) + if (objs[i] == null) continue; if (objs[i] is Component) diff --git a/Editor/Mono/Settings/Providers/AssetSettingsProvider.cs b/Editor/Mono/Settings/Providers/AssetSettingsProvider.cs index 5ecc0727a3..e98a065dac 100644 --- a/Editor/Mono/Settings/Providers/AssetSettingsProvider.cs +++ b/Editor/Mono/Settings/Providers/AssetSettingsProvider.cs @@ -99,7 +99,7 @@ public override void OnGUI(string searchContext) var remainingRect = GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none, GUILayout.ExpandHeight(true)); if ((Event.current.type == EventType.DragUpdated || Event.current.type == EventType.DragPerform) && remainingRect.Contains(Event.current.mousePosition)) { - DragAndDrop.visualMode = DragAndDrop.Drop(DragAndDropWindowTarget.inspector, new[] { settingsEditor.target }, Event.current.type == EventType.DragPerform); + DragAndDrop.visualMode = DragAndDrop.DropOnInspectorWindow(new[] { settingsEditor.target }, Event.current.type == EventType.DragPerform); if (Event.current.type == EventType.DragPerform) DragAndDrop.AcceptDrag(); } diff --git a/Editor/Mono/Settings/SettingsService.cs b/Editor/Mono/Settings/SettingsService.cs index cbfeb35f42..128b20c8ae 100644 --- a/Editor/Mono/Settings/SettingsService.cs +++ b/Editor/Mono/Settings/SettingsService.cs @@ -36,15 +36,71 @@ public static void RepaintAllSettingsWindow() repaintAllSettingsWindow?.Invoke(); } + internal static IEnumerable FilterAndWarnAgainstDuplicates(IEnumerable settingsProviders, SettingsScope scope) + { + Dictionary settingPaths = new Dictionary(); + foreach (var provider in settingsProviders) + { + if (provider.scope != scope) + { + yield return provider; + continue; + } + + if (settingPaths.ContainsKey(provider.settingsPath)) + { + settingPaths[provider.settingsPath] += 1; + continue; + } + else + { + settingPaths.Add(provider.settingsPath, 1); + yield return provider; + } + } + + foreach (var settingPath in settingPaths) + { + if (settingPath.Value > 1) + Debug.LogWarning($"There are {settingPath.Value} settings providers with the same name {settingPath.Key} in {scope} scope."); + } + } + internal static event Action settingsProviderChanged; internal static SettingsProvider[] FetchSettingsProviders() { - return + var settingsProviders = FetchSettingProviderFromAttribute() .Concat(FetchSettingProvidersFromAttribute()) .Concat(FetchPreferenceItems()) - .Where(provider => provider != null) - .ToArray(); + .Where(provider => provider != null); + + settingsProviders = FilterAndWarnAgainstDuplicates(settingsProviders, SettingsScope.Project); + return FilterAndWarnAgainstDuplicates(settingsProviders, SettingsScope.User).ToArray(); + } + + internal static SettingsProvider[] FetchSettingsProviders(SettingsScope scope) + { + var settingsProviders = + FetchSettingProviderFromAttribute() + .Concat(FetchSettingProvidersFromAttribute()) + .Concat(FetchPreferenceItems()) + .Where(provider => provider != null && provider.scope == scope); + + return FilterAndWarnAgainstDuplicates(settingsProviders, scope).ToArray(); + } + + public static bool Exists(string settingsPath) + { + var settingsProviders = FetchSettingsProviders(); + + foreach (var provider in settingsProviders) + { + if (settingsPath.Equals(provider.settingsPath, StringComparison.Ordinal)) + return true; + } + + return false; } internal static EditorWindow OpenUserPreferenceWindow() diff --git a/Editor/Mono/Settings/SettingsWindow.cs b/Editor/Mono/Settings/SettingsWindow.cs index 48b5f29145..6c7d6f0a9a 100644 --- a/Editor/Mono/Settings/SettingsWindow.cs +++ b/Editor/Mono/Settings/SettingsWindow.cs @@ -137,7 +137,7 @@ internal void OnDisable() SettingsService.settingsProviderChanged -= OnSettingsProviderChanged; SettingsService.repaintAllSettingsWindow -= OnRepaintAllWindows; - Undo.undoRedoPerformed -= OnUndoRedoPerformed; + Undo.undoRedoEvent -= OnUndoRedoPerformed; EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; } @@ -154,8 +154,8 @@ internal void InitProviders() SettingsService.repaintAllSettingsWindow -= OnRepaintAllWindows; SettingsService.repaintAllSettingsWindow += OnRepaintAllWindows; - Undo.undoRedoPerformed -= OnUndoRedoPerformed; - Undo.undoRedoPerformed += OnUndoRedoPerformed; + Undo.undoRedoEvent -= OnUndoRedoPerformed; + Undo.undoRedoEvent += OnUndoRedoPerformed; EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; EditorApplication.playModeStateChanged += OnPlayModeStateChanged; @@ -166,7 +166,7 @@ internal void OnInspectorUpdate() m_TreeView?.currentProvider?.OnInspectorUpdate(); } - private void OnUndoRedoPerformed() + private void OnUndoRedoPerformed(in UndoRedoInfo info) { Repaint(); } @@ -230,10 +230,7 @@ private void RestoreSelection() private void Init() { - m_Providers = SettingsService.FetchSettingsProviders().Where(p => p.scope == m_Scope).ToArray(); - - WarnAgainstDuplicates(); - + m_Providers = SettingsService.FetchSettingsProviders(m_Scope); foreach (var provider in m_Providers) { provider.settingsWindow = this; @@ -250,16 +247,6 @@ private void Init() m_TreeView.currentProviderChanged += ProviderChanged; } - private void WarnAgainstDuplicates() - { - // Warn for providers with same id (will be supported later) - foreach (var g in m_Providers.GroupBy(x => x.settingsPath)) - { - if (g.Count() > 1) - Debug.LogWarning($"There are {g.Count()} settings providers with the same name {g.Key}."); - } - } - private void ProviderChanged(SettingsProvider lastSelectedProvider, SettingsProvider newlySelectedProvider) { if (m_SettingsPanel == null) @@ -498,7 +485,6 @@ static void SendTopMenuProjectSettingsEvent() }); } - [Serializable] internal struct EditorGameServiceEvent { @@ -509,7 +495,6 @@ internal struct EditorGameServiceEvent public string package_ver; } - internal static SettingsWindow OpenUserPreferences() { return Show(SettingsScope.User); diff --git a/Editor/Mono/SettingsWindow/GraphicsSettingsEditors.cs b/Editor/Mono/SettingsWindow/GraphicsSettingsEditors.cs index 50ff94f9d3..b0239880f8 100644 --- a/Editor/Mono/SettingsWindow/GraphicsSettingsEditors.cs +++ b/Editor/Mono/SettingsWindow/GraphicsSettingsEditors.cs @@ -53,7 +53,6 @@ internal class BuiltinShadersEditor : Editor { BuiltinShaderSettings m_Deferred; BuiltinShaderSettings m_DeferredReflections; - BuiltinShaderSettings m_LegacyDeferred; BuiltinShaderSettings m_ScreenSpaceShadows; BuiltinShaderSettings m_DepthNormals; BuiltinShaderSettings m_MotionVectors; @@ -64,7 +63,6 @@ internal class Styles { public static GUIContent deferredString = EditorGUIUtility.TrTextContent("Deferred", "Shader used for Deferred Shading."); public static GUIContent deferredReflString = EditorGUIUtility.TrTextContent("Deferred Reflections", "Shader used for Deferred reflection probes."); - public static GUIContent legacyDeferredString = EditorGUIUtility.TrTextContent("Legacy Deferred", "Shader used for Legacy (light prepass) Deferred Lighting."); public static GUIContent screenShadowsString = EditorGUIUtility.TrTextContent("Screen Space Shadows", "Shader used for screen-space cascaded shadows."); public static GUIContent depthNormalsString = EditorGUIUtility.TrTextContent("Depth Normals", "Shader used for depth and normals texture when enabled on a Camera."); public static GUIContent motionVectorsString = EditorGUIUtility.TrTextContent("Motion Vectors", "Shader for generation of Motion Vectors when the rendering camera has renderMotionVectors set to true."); @@ -76,7 +74,6 @@ public void OnEnable() { m_Deferred = new BuiltinShaderSettings(Styles.deferredString, "m_Deferred", serializedObject); m_DeferredReflections = new BuiltinShaderSettings(Styles.deferredReflString, "m_DeferredReflections", serializedObject); - m_LegacyDeferred = new BuiltinShaderSettings(Styles.legacyDeferredString, "m_LegacyDeferred", serializedObject); m_ScreenSpaceShadows = new BuiltinShaderSettings(Styles.screenShadowsString, "m_ScreenSpaceShadows", serializedObject); m_DepthNormals = new BuiltinShaderSettings(Styles.depthNormalsString, "m_DepthNormals", serializedObject); m_MotionVectors = new BuiltinShaderSettings(Styles.motionVectorsString, "m_MotionVectors", serializedObject); @@ -97,7 +94,6 @@ public override void OnInspectorGUI() if (EditorGUI.EndChangeCheck()) ShaderUtil.ReloadAllShaders(); - m_LegacyDeferred.DoGUI(); m_ScreenSpaceShadows.DoGUI(); m_DepthNormals.DoGUI(); m_MotionVectors.DoGUI(); @@ -446,9 +442,9 @@ internal partial class Styles { (int)ShaderQuality.Low, (int)ShaderQuality.Medium, (int)ShaderQuality.High }; public static readonly GUIContent[] renderingPathName = - { EditorGUIUtility.TrTextContent("Forward"), EditorGUIUtility.TrTextContent("Deferred"), EditorGUIUtility.TrTextContent("Legacy Vertex Lit"), EditorGUIUtility.TrTextContent("Legacy Deferred (light prepass)") }; + { EditorGUIUtility.TrTextContent("Forward"), EditorGUIUtility.TrTextContent("Deferred"), EditorGUIUtility.TrTextContent("Legacy Vertex Lit") }; public static readonly int[] renderingPathValue = - { (int)RenderingPath.Forward, (int)RenderingPath.DeferredShading, (int)RenderingPath.VertexLit, (int)RenderingPath.DeferredLighting }; + { (int)RenderingPath.Forward, (int)RenderingPath.DeferredShading, (int)RenderingPath.VertexLit }; public static readonly GUIContent[] hdrModeName = { EditorGUIUtility.TrTextContent("FP16"), EditorGUIUtility.TrTextContent("R11G11B10") }; @@ -638,16 +634,16 @@ internal partial class Styles public static readonly GUIContent renderingSettings = EditorGUIUtility.TrTextContent("Rendering"); public static readonly GUIContent standardShaderQuality = EditorGUIUtility.TrTextContent("Standard Shader Quality"); - public static readonly GUIContent reflectionProbeBoxProjection = EditorGUIUtility.TrTextContent("Reflection Probes Box Projection"); - public static readonly GUIContent reflectionProbeBlending = EditorGUIUtility.TrTextContent("Reflection Probes Blending"); - public static readonly GUIContent detailNormalMap = EditorGUIUtility.TrTextContent("Detail Normal Map"); + public static readonly GUIContent reflectionProbeBoxProjection = EditorGUIUtility.TrTextContent("Reflection Probes Box Projection", "Enable projection for reflection UV mappings on Reflection Probes."); + public static readonly GUIContent reflectionProbeBlending = EditorGUIUtility.TrTextContent("Reflection Probes Blending", "Gradually fade out one probe's cubemap while fading in the other's as the reflective object passes from one zone to the other."); + public static readonly GUIContent detailNormalMap = EditorGUIUtility.TrTextContent("Detail Normal Map", "Enable Detail (secondary) Normal Map sampling for up-close viewing, if assigned."); public static readonly GUIContent cascadedShadowMaps = EditorGUIUtility.TrTextContent("Cascaded Shadows"); - public static readonly GUIContent prefer32BitShadowMaps = EditorGUIUtility.TrTextContent("Prefer 32-bit shadow maps"); + public static readonly GUIContent prefer32BitShadowMaps = EditorGUIUtility.TrTextContent("Prefer 32-bit shadow maps", "Enable 32-bit float shadow map when you are targeting PS4 or platforms using DX11 or DX12."); public static readonly GUIContent semitransparentShadows = EditorGUIUtility.TrTextContent("Enable Semitransparent Shadows"); - public static readonly GUIContent enableLPPV = EditorGUIUtility.TrTextContent("Enable Light Probe Proxy Volume"); - public static readonly GUIContent renderingPath = EditorGUIUtility.TrTextContent("Rendering Path"); - public static readonly GUIContent useHDR = EditorGUIUtility.TrTextContent("Use HDR"); - public static readonly GUIContent hdrMode = EditorGUIUtility.TrTextContent("HDR Mode"); + public static readonly GUIContent enableLPPV = EditorGUIUtility.TrTextContent("Enable Light Probe Proxy Volume", "Enable rendering a 3D grid of interpolated Light Probes inside a Bounding Volume."); + public static readonly GUIContent renderingPath = EditorGUIUtility.TrTextContent("Rendering Path", "Choose how Unity should render graphics. Different rendering paths affect the performance of your game, and how lighting and shading are calculated."); + public static readonly GUIContent useHDR = EditorGUIUtility.TrTextContent("Use HDR", "Enable High Dynamic Range rendering for this tier."); + public static readonly GUIContent hdrMode = EditorGUIUtility.TrTextContent("HDR Mode", "Color render texture format for the HDR buffer to use when HDR is enabled."); public static readonly GUIContent realtimeGICPUUsage = EditorGUIUtility.TrTextContent("Realtime Global Illumination 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."); } } diff --git a/Editor/Mono/ShaderUtil.bindings.cs b/Editor/Mono/ShaderUtil.bindings.cs index 84cb831286..c6e51b7a48 100644 --- a/Editor/Mono/ShaderUtil.bindings.cs +++ b/Editor/Mono/ShaderUtil.bindings.cs @@ -245,6 +245,7 @@ internal static void GetShaderVariantEntriesFiltered(Shader shader, int maxEntri [FreeFunction] extern internal static int GetShaderSubshaderCount([NotNull] Shader shader); [FreeFunction] extern internal static int GetShaderTotalPassCount([NotNull] Shader shader, int subShaderIndex); [FreeFunction] extern internal static int GetSubshaderLOD([NotNull] Shader shader, int subShaderIndex); + [FreeFunction] extern internal static bool IsGrabPass([NotNull] Shader shader, int subShaderIndex, int passId); [FreeFunction("ShaderUtil::FindSubShaderTagValue")] extern internal static int FindSubShaderTagValue([NotNull] Shader shader, int subShaderIndex, int tagName); [FreeFunction("ShaderUtil::FindPassTagValue")] extern internal static int FindPassTagValue([NotNull] Shader shader, int subShaderIndex, int passIndex, int tagName); @@ -332,7 +333,7 @@ public static BuiltinShaderDefine[] GetShaderPlatformKeywordsForBuildTarget(Shad } extern internal static ShaderData.VariantCompileInfo CompileShaderVariant([NotNull] Shader shader, int subShaderIndex, int passId, - ShaderType shaderType, BuiltinShaderDefine[] platformKeywords, string[] keywords, ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget, GraphicsTier tier); + ShaderType shaderType, BuiltinShaderDefine[] platformKeywords, string[] keywords, ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget, GraphicsTier tier, bool outputForExternalTool); extern internal static ShaderData.PreprocessedVariant PreprocessShaderVariant([NotNull] Shader shader, int subShaderIndex, int passId, ShaderType shaderType, BuiltinShaderDefine[] platformKeywords, string[] keywords, ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget, GraphicsTier tier, bool stripLineDirectives); diff --git a/Editor/Mono/ShaderUtil.cs b/Editor/Mono/ShaderUtil.cs index 4c16ecbb39..2fd4b81c7f 100644 --- a/Editor/Mono/ShaderUtil.cs +++ b/Editor/Mono/ShaderUtil.cs @@ -64,6 +64,7 @@ internal Pass(Subshader subshader, int passIndex) public string SourceCode { get { return ShaderUtil.GetShaderPassSourceCode(SourceShader, SubshaderIndex, m_PassIndex); } } public string Name { get { return ShaderUtil.GetShaderPassName(SourceShader, SubshaderIndex, m_PassIndex); } } + public bool IsGrabPass { get { return ShaderUtil.IsGrabPass(SourceShader, SubshaderIndex, m_PassIndex); } } public UnityEngine.Rendering.ShaderTagId FindTagValue(UnityEngine.Rendering.ShaderTagId tagName) { @@ -80,28 +81,52 @@ public bool HasShaderStage(ShaderType shaderType) public VariantCompileInfo CompileVariant(ShaderType shaderType, string[] keywords, ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget) + { + return CompileVariant(shaderType, keywords, shaderCompilerPlatform, buildTarget, false); + } + + public VariantCompileInfo CompileVariant(ShaderType shaderType, string[] keywords, + ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget, bool forExternalTool) { var platformKeywords = ShaderUtil.GetShaderPlatformKeywordsForBuildTarget(shaderCompilerPlatform, buildTarget, kNoGraphicsTier); - return ShaderUtil.CompileShaderVariant(SourceShader, SubshaderIndex, m_PassIndex, shaderType, platformKeywords, keywords, shaderCompilerPlatform, buildTarget, kNoGraphicsTier); + return ShaderUtil.CompileShaderVariant(SourceShader, SubshaderIndex, m_PassIndex, shaderType, platformKeywords, keywords, shaderCompilerPlatform, buildTarget, kNoGraphicsTier, forExternalTool); } public VariantCompileInfo CompileVariant(ShaderType shaderType, string[] keywords, ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget, GraphicsTier tier) + { + return CompileVariant(shaderType, keywords, shaderCompilerPlatform, buildTarget, tier, false); + } + + public VariantCompileInfo CompileVariant(ShaderType shaderType, string[] keywords, + ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget, GraphicsTier tier, bool forExternalTool) { var platformKeywords = ShaderUtil.GetShaderPlatformKeywordsForBuildTarget(shaderCompilerPlatform, buildTarget, tier); - return ShaderUtil.CompileShaderVariant(SourceShader, SubshaderIndex, m_PassIndex, shaderType, platformKeywords, keywords, shaderCompilerPlatform, buildTarget, tier); + return ShaderUtil.CompileShaderVariant(SourceShader, SubshaderIndex, m_PassIndex, shaderType, platformKeywords, keywords, shaderCompilerPlatform, buildTarget, tier, forExternalTool); } public VariantCompileInfo CompileVariant(ShaderType shaderType, string[] keywords, ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget, BuiltinShaderDefine[] platformKeywords) { - return ShaderUtil.CompileShaderVariant(SourceShader, SubshaderIndex, m_PassIndex, shaderType, platformKeywords, keywords, shaderCompilerPlatform, buildTarget, kNoGraphicsTier); + return CompileVariant(shaderType, keywords, shaderCompilerPlatform, buildTarget, platformKeywords, false); + } + + public VariantCompileInfo CompileVariant(ShaderType shaderType, string[] keywords, + ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget, BuiltinShaderDefine[] platformKeywords, bool forExternalTool) + { + return ShaderUtil.CompileShaderVariant(SourceShader, SubshaderIndex, m_PassIndex, shaderType, platformKeywords, keywords, shaderCompilerPlatform, buildTarget, kNoGraphicsTier, forExternalTool); } public VariantCompileInfo CompileVariant(ShaderType shaderType, string[] keywords, ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget, BuiltinShaderDefine[] platformKeywords, GraphicsTier tier) { - return ShaderUtil.CompileShaderVariant(SourceShader, SubshaderIndex, m_PassIndex, shaderType, platformKeywords, keywords, shaderCompilerPlatform, buildTarget, tier); + return CompileVariant(shaderType, keywords, shaderCompilerPlatform, buildTarget, platformKeywords, tier, false); + } + + public VariantCompileInfo CompileVariant(ShaderType shaderType, string[] keywords, + ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget, BuiltinShaderDefine[] platformKeywords, GraphicsTier tier, bool forExternalTool) + { + return ShaderUtil.CompileShaderVariant(SourceShader, SubshaderIndex, m_PassIndex, shaderType, platformKeywords, keywords, shaderCompilerPlatform, buildTarget, tier, forExternalTool); } public PreprocessedVariant PreprocessVariant(ShaderType shaderType, string[] keywords, diff --git a/Editor/Mono/Shaders/MaterialHierarchyPopup.cs b/Editor/Mono/Shaders/MaterialHierarchyPopup.cs new file mode 100644 index 0000000000..85e3caab73 --- /dev/null +++ b/Editor/Mono/Shaders/MaterialHierarchyPopup.cs @@ -0,0 +1,595 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.Collections.Generic; +using UnityEngine; +using UnityEditorInternal; +using Object = UnityEngine.Object; +using static UnityEditor.MaterialEditor; + +namespace UnityEditor +{ + class MaterialHierarchyPopup : PopupWindowContent + { + const int k_MaxSearchIterationPerFrame = 500; + const int k_MaxTableRows = 10; + const int k_MinIconSize = 20; + + const float k_MinWindowWidth = 300f, k_MaxWindowWidth = 500f; + + const float k_HeaderHeight = 49f; + const float k_EntryHeight = 20f; + const float k_SliderWidth = 55f; + const float k_SearchHeight = 150f; + const float k_ConvertLabelWidth = 150f; + + const float k_Padding = 3f; + const float k_OffsetX = 6f; + const float k_SplitWidth = 1f; + + readonly float k_MinNameWidth; + readonly float k_TitleWidth = k_OffsetX + 50f; + readonly float k_LocksWidth = EditorStyles.miniLabel.CalcSize(Styles.locksLabel).x + 2 * k_Padding; + readonly float k_OverridesWidth = k_SplitWidth + EditorStyles.miniLabel.CalcSize(Styles.overridesLabel).x + 2 * k_Padding; + + readonly float k_ScrollbarWidth = GUI.skin.verticalScrollbar.fixedWidth + GUI.skin.verticalScrollbar.margin.left; + readonly float k_ScrollbarHeight = GUI.skin.horizontalScrollbar.fixedHeight + GUI.skin.horizontalScrollbar.margin.top; + + readonly MaterialEditor materialEditor; + readonly Material target; + readonly bool enabled; + readonly GUID targetGUID; + float windowWidth, namesWidth, maxNameWidth, noResultsX, locksX, overridesX; + Vector2 scroll = Vector2.zero; + int numRows; + + // Children list + bool displayChildren; + ObjectListArea listArea; + ObjectListAreaState m_ListAreaState; + SavedInt m_SavedGridSize = new SavedInt("MaterialHierarchyPopup.GridSize", 56); + + // Search + readonly Delayer debounce; + readonly SearchFilter searchFilter; + int[] results = null; + string searchFilterString = ""; + IEnumerator enumerator = null; + + public static class Colors + { + static Color header_l = new Color32(0xDF, 0xDF, 0xDF, 0xFF); + static Color header_d = new Color(0.5f, 0.5f, 0.5f, 0.2f); + + static Color[] rows_l = new Color[2] + { + new Color32(0xC8, 0xC8, 0xC8, 0xFF), + new Color32(0xCE, 0xCE, 0xCE, 0xFF) + }; + + static Color[] rows_d = new Color[2] + { + new Color32(0x38, 0x38, 0x38, 0xFF), + new Color32(0x3E, 0x3E, 0x3E, 0xFF) + }; + + public static Color headerBackground { get { return EditorGUIUtility.isProSkin ? Colors.header_d : Colors.header_l; } } + public static Color rowBackground(int i) => EditorGUIUtility.isProSkin ? Colors.rows_d[i % 2] : Colors.rows_l[i % 2]; + } + + public static class Styles + { + public static readonly GUIContent rootLabel = EditorGUIUtility.TrTextContent("Root", "The root of the hierarchy."); + public static readonly GUIContent selectedLabel = EditorGUIUtility.TrTextContent("Current", "The currently selected Material."); + + public static readonly GUIContent instanceLabel = EditorGUIUtility.TrTextContent("Variant Family of"); + public static readonly GUIContent ancestorLabel = EditorGUIUtility.TrTextContent("Ancestors"); + public static readonly GUIContent overridesLabel = EditorGUIUtility.TrTextContent("Overrides"); + public static readonly GUIContent locksLabel = EditorGUIUtility.TrTextContent("Locks"); + public static readonly GUIContent childrenLabel = EditorGUIUtility.TrTextContent("Children"); + public static readonly GUIContent noResultsLabel = EditorGUIUtility.TrTextContent("No results"); + public static readonly GUIContent noChildrenLabel = EditorGUIUtility.TrTextContent("This Material doesn't have any children.\nMaterial Variants created from this Material\nwill be listed here."); + + public static readonly string[] headerPopupOptions = new string[] { "Material", "Material Variant" }; + public static readonly GUIContent convertingLabel = EditorGUIUtility.TrTextContent("Converting to Material Variant"); + public static readonly GUIContent conversionHelpLabel = EditorGUIUtility.TrTextContent("To convert, select a Parent Material"); + + public static readonly GUIStyle searchBackground = new GUIStyle("ProjectBrowserIconAreaBg"); + public static readonly GUIStyle centered = new GUIStyle(EditorStyles.label) { alignment = TextAnchor.MiddleCenter }; + public static readonly GUIStyle boldRightAligned = new GUIStyle(EditorStyles.boldLabel) + { + alignment = TextAnchor.MiddleRight, + fontSize = (int)(1.1f * EditorStyles.boldLabel.fontSize) + }; + public static readonly GUIStyle boldNumber = new GUIStyle(EditorStyles.boldLabel) + { + fontSize = (int)(0.9f * EditorStyles.boldLabel.fontSize) + }; + + public static readonly GUIStyle searchFieldStyle = new GUIStyle(EditorStyles.toolbarSearchField) + { + margin = new RectOffset(5, 4, 4, 5) + }; + } + + internal MaterialHierarchyPopup(Material target, bool enabled, MaterialEditor materialEditor, Rect activatorRect) + { + this.enabled = enabled; + this.materialEditor = materialEditor; + this.target = target; + targetGUID = AssetDatabase.GUIDFromAssetPath(AssetDatabase.GetAssetPath(target)); + + k_MinNameWidth = k_MinWindowWidth - (k_TitleWidth + k_SplitWidth + k_OverridesWidth + k_LocksWidth); + + searchFilter = new SearchFilter() + { + classNames = new string[] { "Material" }, + searchArea = SearchFilter.SearchArea.AllAssets + }; + debounce = Delayer.Debounce(_ => + { + SearchFilterChanged(); + editorWindow.Repaint(); + }); + + Init(); + } + + void Init() + { + displayChildren = !target.isVariant; + + numRows = 0; + namesWidth = k_MinNameWidth; + if (target.isVariant) + { + Material current = target; + while (current != null) + { + numRows++; + namesWidth = Mathf.Max(GUI.skin.label.CalcSize(EditorGUIUtility.TempContent(current.name)).x + 23, namesWidth); + current = current.parent; + } + numRows = Mathf.Max(numRows, 2); // at least this and his parent + } + + float scrollBarWidthOffset = numRows >= k_MaxTableRows ? k_ScrollbarWidth : 0; + maxNameWidth = k_MaxWindowWidth - (k_TitleWidth + k_SplitWidth + k_OverridesWidth + k_LocksWidth) - scrollBarWidthOffset; + + float prevWidth = windowWidth; + if (namesWidth <= k_MinNameWidth) + windowWidth = k_MinWindowWidth; + else if (namesWidth >= maxNameWidth) + windowWidth = k_MaxWindowWidth; + else + windowWidth = k_TitleWidth + namesWidth + k_SplitWidth + k_OverridesWidth + k_LocksWidth; + + // Prevent window size from getting smaller when changing options + windowWidth = Mathf.Max(windowWidth, prevWidth); + + locksX = windowWidth - k_LocksWidth - scrollBarWidthOffset; + overridesX = locksX - k_OverridesWidth; + noResultsX = (windowWidth - EditorStyles.label.CalcSize(Styles.noResultsLabel).x) * 0.5f; + } + + public override void OnClose() + { + if (listArea != null) + listArea.OnDestroy(); + } + + public override Vector2 GetWindowSize() + { + var height = k_HeaderHeight; + + if (target.isVariant) + { + // Horizontal scrollbar + if (namesWidth > maxNameWidth) + height += k_ScrollbarHeight; + + // Ancestors table + height += Mathf.Min(numRows, k_MaxTableRows) * k_EntryHeight + k_EntryHeight; + } + + if (materialEditor.convertState != ConvertAction.Convert) + { + // Children list + height += k_EntryHeight; + if (displayChildren) + height += k_SearchHeight; + } + else + { + // Conversion panel + height += 2 * k_EntryHeight + k_Padding; + } + + return new Vector2(windowWidth, height); + } + + public override void OnGUI(Rect rect) + { + // Escape closes the window + if (Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Escape) + { + editorWindow.Close(); + GUIUtility.ExitGUI(); + } + + // Handle undo + if (target.parent != null && materialEditor.convertState == ConvertAction.Convert) + { + materialEditor.convertState = ConvertAction.None; + Init(); + } + + if (!DrawHeader()) + return; + + float height = k_HeaderHeight; + if (target.isVariant) + height += DrawVariantHierarchy(); + + height += DrawChildrenLabel(height); + + if (displayChildren) + DrawChildren(height); + + if (materialEditor.convertState == ConvertAction.Flatten) + { + materialEditor.convertState = ConvertAction.None; + Undo.RecordObject(target, "Flatten Material Variant"); + target.parent = null; + Init(); + } + } + + bool DrawHeader() + { + Rect headerRect = GUILayoutUtility.GetRect(20, windowWidth, k_HeaderHeight, k_HeaderHeight); + EditorGUI.DrawRect(headerRect, Colors.headerBackground); + + float labelSize = Styles.boldRightAligned.CalcSize(Styles.instanceLabel).x; + + Rect labelRect = new Rect(k_OffsetX, headerRect.y + k_Padding, labelSize, EditorGUIUtility.singleLineHeight); + Rect contentRect = new Rect(labelRect.x + labelRect.width + k_Padding, labelRect.y, windowWidth, labelRect.height); + + GUI.Label(labelRect, Styles.instanceLabel, Styles.boldRightAligned); + DoObjectLabel(contentRect, target, EditorStyles.boldLabel); + + labelRect.y = labelRect.height + 2 * k_Padding; + if (materialEditor.convertState == ConvertAction.None) + { + int result; + labelRect.width = k_ConvertLabelWidth; + using (new EditorGUI.DisabledScope(!enabled)) + result = EditorGUI.Popup(labelRect, target.isVariant ? 1 : 0, Styles.headerPopupOptions); + if (result == 0 && target.isVariant) + materialEditor.convertState = ConvertAction.Flatten; + if (result == 1 && !target.isVariant) + materialEditor.convertState = ConvertAction.Convert; + } + else if (materialEditor.convertState == ConvertAction.Convert) + { + GUI.enabled = false; + labelRect.width = 200f; + EditorGUI.Button(labelRect, Styles.convertingLabel); + GUI.enabled = true; + + // Conversion helper + labelRect.y = k_HeaderHeight; + labelRect.width = windowWidth; + EditorGUI.LabelField(labelRect, Styles.conversionHelpLabel); + + labelRect.x = windowWidth - 14; + if (GUI.Button(labelRect, GUIContent.none, EditorStyles.toolbarSearchFieldCancelButton)) + { + materialEditor.convertState = ConvertAction.None; + materialEditor.Repaint(); + } + + labelRect.x = k_OffsetX; + var oldLabelWidth = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth = 70; + EditorGUI.BeginChangeCheck(); + var parent = target.parent; + materialEditor.ParentField(new Rect(k_OffsetX, labelRect.yMax + k_Padding, windowWidth - 2 * k_OffsetX, k_EntryHeight)); + if (EditorGUI.EndChangeCheck() && parent != target.parent) + { + materialEditor.convertState = ConvertAction.None; + Init(); + } + EditorGUIUtility.labelWidth = oldLabelWidth; + return false; + } + + return true; + } + + float DrawVariantHierarchy() + { + // Draw table header + Rect entryRect = new Rect(0, k_HeaderHeight, windowWidth, k_EntryHeight); + EditorGUI.DrawRect(entryRect, Colors.rowBackground(0)); + + var labelRect = entryRect; + labelRect.x = k_TitleWidth; + GUI.Label(labelRect, Styles.ancestorLabel, EditorStyles.miniLabel); + + labelRect.x = overridesX + k_Padding; + GUI.Label(labelRect, Styles.overridesLabel, EditorStyles.miniLabel); + + labelRect.x = locksX + k_Padding; + GUI.Label(labelRect, Styles.locksLabel, EditorStyles.miniLabel); + + float tableHeight = Mathf.Min(numRows, k_MaxTableRows) * k_EntryHeight + (namesWidth > maxNameWidth ? k_ScrollbarHeight : 0); + float realHeight = numRows * k_EntryHeight + (namesWidth > maxNameWidth ? k_ScrollbarHeight : 0); + var tableRect = new Rect(0, k_HeaderHeight + k_EntryHeight, windowWidth - 1, tableHeight); + scroll.y = GUI.BeginScrollView(tableRect, scroll, new Rect(tableRect) { width = tableRect.width - k_ScrollbarWidth, height = realHeight }).y; + + // Draw overrides and locks table + int i = numRows; + Material current = target; + while (current != null && i > 0) + { + entryRect.y = k_HeaderHeight + i * k_EntryHeight; + EditorGUI.DrawRect(entryRect, Colors.rowBackground(i--)); + + DisplayOverridesAndLocks(entryRect, current); + current = current.parent; + } + + var scrollRect = new Rect(k_TitleWidth, k_HeaderHeight + k_EntryHeight, Mathf.Min(namesWidth, maxNameWidth), numRows * k_EntryHeight); + scroll.x = GUI.BeginScrollView(new Rect(scrollRect) { height = scrollRect.height + k_ScrollbarHeight }, scroll, new Rect(scrollRect) { width = namesWidth }).x; + + // Draw scrollable table + i = numRows; + current = target; + entryRect.x = k_TitleWidth; + while (i != 0) + { + entryRect.y = k_HeaderHeight + i-- * k_EntryHeight; + + if (current == null) + { + GUI.Label(entryRect, EditorGUIUtility.TempContent("Missing (Material)")); + break; + } + DoObjectLabel(entryRect, current); + current = current.parent; + } + + GUI.EndScrollView(); + + float height = tableHeight + k_EntryHeight; + + // Draw selected label + labelRect.x = k_OffsetX; + labelRect.y = k_HeaderHeight + numRows * k_EntryHeight; + labelRect.width = k_TitleWidth - labelRect.x; + GUI.Label(labelRect, Styles.selectedLabel); + + // Draw root label + if (labelRect.y != k_HeaderHeight + k_EntryHeight) + { + labelRect.y = k_HeaderHeight + k_EntryHeight; + GUI.Label(labelRect, Styles.rootLabel); + } + + // Draw vertical splits + Rect splitBar = new Rect(overridesX - k_SplitWidth, k_HeaderHeight, k_SplitWidth, (numRows + 1) * k_EntryHeight); + EditorGUI.DrawRect(splitBar, Colors.headerBackground); + splitBar.x = locksX - k_SplitWidth; + EditorGUI.DrawRect(splitBar, Colors.headerBackground); + + GUI.EndScrollView(); + + return height; + } + + float DrawChildrenLabel(float yMin) + { + var labelRect = new Rect(k_OffsetX, yMin, 100, k_EntryHeight); + if (target.isVariant) + displayChildren = EditorGUI.Foldout(labelRect, displayChildren, Styles.childrenLabel, true); + else + EditorGUI.LabelField(labelRect, Styles.childrenLabel); + + if (displayChildren) + { + if (listArea == null) + InitListArea(); + + labelRect = new Rect(labelRect.x + 58 + (target.isVariant ? 12 : 0), labelRect.y + 2, k_SliderWidth, EditorGUI.kSingleLineHeight); + if (results.Length != 0) + EditorGUI.LabelField(labelRect, results.Length.ToString(), Styles.boldNumber); + + EditorGUI.BeginChangeCheck(); + labelRect.x = windowWidth - k_OffsetX - k_SliderWidth; + var newGridSize = (int)GUI.HorizontalSlider(labelRect, listArea.gridSize, listArea.minGridSize, listArea.maxGridSize); + if (EditorGUI.EndChangeCheck()) + listArea.gridSize = m_SavedGridSize.value = newGridSize; + } + + return k_EntryHeight; + } + + void DrawChildren(float yMin) + { + var backgroundRect = new Rect(0, yMin, windowWidth, k_SearchHeight); + GUI.Label(backgroundRect, GUIContent.none, Styles.searchBackground); + + EditorGUI.BeginChangeCheck(); + var searchRect = new Rect(k_OffsetX + k_Padding, backgroundRect.y + k_Padding, windowWidth - 2 * k_OffsetX - k_Padding, Styles.searchFieldStyle.fixedHeight); + searchFilterString = EditorGUI.ToolbarSearchField(searchRect, searchFilterString, false); + if (EditorGUI.EndChangeCheck()) + debounce.Execute(); + + if (enumerator != null) + Search(); + + yMin = searchRect.height + (listArea.gridSize < k_MinIconSize ? 11f : 0f); + var listRect = new Rect(k_Padding, searchRect.y + yMin, windowWidth - 2 * k_Padding, k_SearchHeight - yMin - k_Padding); + + int listKeyboardControlID = GUIUtility.GetControlID(FocusType.Keyboard); + listArea.OnGUI(listRect, listKeyboardControlID); + + if (enumerator == null && results.Length == 0) + { + var labelRect = new Rect(noResultsX, backgroundRect.y + 69f, windowWidth, EditorGUI.kSingleLineHeight); + EditorGUI.LabelField(backgroundRect, searchFilter.nameFilter.Length == 0 ? Styles.noChildrenLabel : Styles.noResultsLabel, Styles.centered); + } + } + + void InitListArea() + { + m_ListAreaState = new ObjectListAreaState() { m_GridSize = m_SavedGridSize }; + listArea = new ObjectListArea(m_ListAreaState, editorWindow, false) + { + allowDeselection = true, + allowMultiSelect = false, + allowRenaming = false, + allowBuiltinResources = true, + }; + + listArea.itemSelectedCallback += (bool doubleClicked) => + { + if (listArea.GetSelection().Length == 0) + return; + var selection = listArea.GetSelection()[0]; + GUIUtility.keyboardControl = GUIUtility.GetControlID(FocusType.Keyboard); + if (doubleClicked) + { + Selection.SetActiveObjectWithContext(EditorUtility.InstanceIDToObject(selection), null); + Event.current.Use(); + editorWindow.Close(); + GUIUtility.ExitGUI(); + } + else + { + EditorGUIUtility.PingObject(selection); + Event.current.Use(); + } + }; + + SearchFilterChanged(); + } + + static IEnumerator FindInAllAssets(SearchFilter searchFilter) + { + var rootPaths = new List(); + rootPaths.Add("Assets"); + foreach (var package in PackageManagerUtilityInternal.GetAllVisiblePackages(false)) + { + if (package.source == PackageManager.PackageSource.Local) + rootPaths.Add(package.assetPath); + } + + foreach (var rootPath in rootPaths) + { + var property = new HierarchyProperty(rootPath, false); + property.SetSearchFilter(searchFilter); + while (property.Next(null)) + yield return property; + } + } + + void SearchFilterChanged() + { + searchFilter.nameFilter = searchFilterString; + + var size = GetWindowSize(); + var rect = new Rect(0, size.y - k_SearchHeight, size.x, k_SearchHeight); + + listArea.Init(rect, HierarchyType.Assets, new SearchFilter(), true, SearchService.SearchSessionOptions.Default); + enumerator = FindInAllAssets(searchFilter); + results = new int[0]; + } + + void Search() + { + var newResults = new List(); + + var maxAddCount = k_MaxSearchIterationPerFrame; + while (--maxAddCount >= 0) + { + if (!enumerator.MoveNext()) + { + enumerator = null; + break; + } + var child = InternalEditorUtility.GetLoadedObjectFromInstanceID(enumerator.Current.GetInstanceIDIfImported()) as Material; + if (!child) + { + // First check guid from file to avoid loading material in memory + string path = AssetDatabase.GUIDToAssetPath(enumerator.Current.guid); + if (EditorMaterialUtility.GetMaterialParentFromFile(path) != targetGUID) + continue; + child = AssetDatabase.LoadAssetAtPath(path); + } + if (child != null && child.parent == target) + newResults.Add(child.GetInstanceID()); + } + + int newElements = newResults.Count; + int i = results.Length; + System.Array.Resize(ref results, results.Length + newElements); + for (var j = 0; j < newElements && i < results.Length; ++j, ++i) + results[i] = newResults[j]; + + listArea.ShowObjectsInList(results); + } + + void DisplayOverridesAndLocks(Rect rect, Material entry) + { + rect.x = overridesX; + rect.width = k_OverridesWidth; + int overrideCount = entry.overrideCount; + GUI.Label(rect, overrideCount == 0 ? "-" : overrideCount.ToString(), Styles.centered); + + rect.x = locksX; + rect.width = k_LocksWidth; + int lockCount = entry.lockCount; + GUI.Label(rect, lockCount == 0 ? "-" : lockCount.ToString(), Styles.centered); + } + + void DoObjectLabel(Rect rect, Object entry) + { + DoObjectLabel(rect, entry, GUI.skin.label); + } + + void DoObjectLabel(Rect rect, Object entry, GUIStyle style) + { + GUI.Label(rect, AssetPreview.GetMiniThumbnail(entry)); + + if (Event.current.type == EventType.MouseDown && Event.current.button == 0 && rect.Contains(Event.current.mousePosition)) + { + // One click shows where the referenced object is + if (Event.current.clickCount == 1) + { + GUIUtility.keyboardControl = GUIUtility.GetControlID(FocusType.Keyboard); + + EditorGUIUtility.PingObject(entry); + Event.current.Use(); + } + // Double click changes selection to referenced object + else if (Event.current.clickCount == 2) + { + if (entry) + { + Selection.SetActiveObjectWithContext(entry, null); + Event.current.Use(); + editorWindow.Close(); + GUIUtility.ExitGUI(); + } + } + } + + rect.x += rect.height; + rect.width -= rect.height; + GUI.Label(rect, EditorGUIUtility.TempContent(entry.name, entry.name), style); + } + } +} diff --git a/Editor/Mono/SplashScreenLogo.cs b/Editor/Mono/SplashScreenLogo.cs index ec120bff75..c8fd472ad1 100644 --- a/Editor/Mono/SplashScreenLogo.cs +++ b/Editor/Mono/SplashScreenLogo.cs @@ -21,7 +21,7 @@ public partial struct SplashScreenLogo static SplashScreenLogo() { - s_UnityLogo = Resources.GetBuiltinResource("UnitySplash-cube.png"); + s_UnityLogo = AssetDatabase.GetBuiltinExtraResource("SplashScreen/UnitySplash-Light.png"); } public Sprite logo diff --git a/Editor/Mono/SpritePacker.bindings.cs b/Editor/Mono/SpritePacker.bindings.cs index 54d0163853..2f8e5a2b23 100644 --- a/Editor/Mono/SpritePacker.bindings.cs +++ b/Editor/Mono/SpritePacker.bindings.cs @@ -73,25 +73,6 @@ public extern static string[] atlasNames [FreeFunction("SpritePacker::GetAlphaTexturesForAtlas")] public static extern Texture2D[] GetAlphaTexturesForAtlas(string atlasName); - [FreeFunction("SpritePacker::RebuildAtlasCacheIfNeededFromScript")] - internal static extern void RebuildAtlasCacheIfNeededInternal(BuildTarget target, bool displayProgressBar, Execution execution); - - [Obsolete("Sprite Packing Tags are deprecated. Please use Sprite Atlas asset.")] - [FreeFunction("SpritePacker::RebuildAtlasCacheIfNeededFromScript")] - public static extern void RebuildAtlasCacheIfNeeded(BuildTarget target, bool displayProgressBar, Execution execution); - - [Obsolete("Sprite Packing Tags are deprecated. Please use Sprite Atlas asset.")] - public static void RebuildAtlasCacheIfNeeded(BuildTarget target, bool displayProgressBar) - { - RebuildAtlasCacheIfNeeded(target, displayProgressBar, Execution.Normal); - } - - [Obsolete("Sprite Packing Tags are deprecated. Please use Sprite Atlas asset.")] - public static void RebuildAtlasCacheIfNeeded(BuildTarget target) - { - RebuildAtlasCacheIfNeeded(target, false, Execution.Normal); - } - public static void GetAtlasDataForSprite(Sprite sprite, out string atlasName, out Texture2D atlasTexture) { atlasName = Internal_GetAtlasNameForSprite(sprite); diff --git a/Editor/Mono/Sprites/DefaultSpritePackerPolicy.cs b/Editor/Mono/Sprites/DefaultSpritePackerPolicy.cs deleted file mode 100644 index 49e0708dad..0000000000 --- a/Editor/Mono/Sprites/DefaultSpritePackerPolicy.cs +++ /dev/null @@ -1,152 +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.Linq; -using UnityEngine; -using System.Collections.Generic; - -namespace UnityEditor.Sprites -{ - // DefaultPackerPolicy will pack rectangles no matter what Sprite mesh type is unless their packing tag contains "[TIGHT]". - internal class DefaultPackerPolicy : IPackerPolicy - { - protected class Entry - { - public Sprite sprite; - public AtlasSettings settings; - public string atlasName; - public SpritePackingMode packingMode; - public int anisoLevel; - } - - private const uint kDefaultPaddingPower = 3; // Good for base and two mip levels. - - public virtual int GetVersion() { return 1; } - public virtual bool AllowSequentialPacking { get { return false; } } - - protected virtual string TagPrefix { get { return "[TIGHT]"; } } - protected virtual bool AllowTightWhenTagged { get { return true; } } - protected virtual bool AllowRotationFlipping { get { return false; } } - - public void OnGroupAtlases(BuildTarget target, PackerJob job, int[] textureImporterInstanceIDs) - { - List entries = new List(); - - string targetName = ""; - if (target != BuildTarget.NoTarget) - { - targetName = BuildPipeline.GetBuildTargetName(target); - } - - foreach (int instanceID in textureImporterInstanceIDs) - { - TextureImporter ti = EditorUtility.InstanceIDToObject(instanceID) as TextureImporter; - - TextureFormat desiredFormat; - ColorSpace colorSpace; - int compressionQuality; - ti.ReadTextureImportInstructions(target, out desiredFormat, out colorSpace, out compressionQuality); - - TextureImporterSettings tis = new TextureImporterSettings(); - ti.ReadTextureSettings(tis); - - bool hasAlphaSplittingForCompression = (targetName != "" && HasPlatformEnabledAlphaSplittingForCompression(targetName, ti)); - - Sprite[] sprites = AssetDatabase.LoadAllAssetRepresentationsAtPath(ti.assetPath).Select(x => x as Sprite).Where(x => x != null).ToArray(); - foreach (Sprite sprite in sprites) - { - Entry entry = new Entry(); - entry.sprite = sprite; - entry.settings.format = desiredFormat; - entry.settings.colorSpace = colorSpace; - // Use Compression Quality for Grouping later only for Compressed Formats. Otherwise leave it Empty. - entry.settings.compressionQuality = UnityEditor.TextureUtil.IsCompressedTextureFormat(desiredFormat) ? compressionQuality : 0; - entry.settings.filterMode = Enum.IsDefined(typeof(FilterMode), ti.filterMode) ? ti.filterMode : FilterMode.Bilinear; - entry.settings.maxWidth = 2048; - entry.settings.maxHeight = 2048; - entry.settings.generateMipMaps = ti.mipmapEnabled; - entry.settings.enableRotation = AllowRotationFlipping; - entry.settings.allowsAlphaSplitting = TextureImporter.IsTextureFormatETC1Compression(desiredFormat) && hasAlphaSplittingForCompression; - if (ti.mipmapEnabled) - entry.settings.paddingPower = kDefaultPaddingPower; - else - entry.settings.paddingPower = (uint)EditorSettings.spritePackerPaddingPower; - entry.atlasName = ParseAtlasName(ti.spritePackingTag); - entry.packingMode = GetPackingMode(ti.spritePackingTag, tis.spriteMeshType); - entry.anisoLevel = ti.anisoLevel; - - entries.Add(entry); - } - - Resources.UnloadAsset(ti); - } - - // First split sprites into groups based on atlas name - var atlasGroups = - from e in entries - group e by e.atlasName; - foreach (var atlasGroup in atlasGroups) - { - int page = 0; - // Then split those groups into smaller groups based on texture settings - var settingsGroups = - from t in atlasGroup - group t by t.settings; - foreach (var settingsGroup in settingsGroups) - { - string atlasName = atlasGroup.Key; - if (settingsGroups.Count() > 1) - atlasName += string.Format(" (Group {0})", page); - - AtlasSettings settings = settingsGroup.Key; - settings.anisoLevel = 1; - // Use the highest aniso level from all entries in this atlas - if (settings.generateMipMaps) - foreach (Entry entry in settingsGroup) - if (entry.anisoLevel > settings.anisoLevel) - settings.anisoLevel = entry.anisoLevel; - - job.AddAtlas(atlasName, settings); - foreach (Entry entry in settingsGroup) - { - job.AssignToAtlas(atlasName, entry.sprite, entry.packingMode, SpritePackingRotation.None); - } - - ++page; - } - } - } - - protected bool HasPlatformEnabledAlphaSplittingForCompression(string targetName, TextureImporter ti) - { - TextureImporterPlatformSettings platformSettings = ti.GetPlatformTextureSettings(targetName); - return (platformSettings.overridden && platformSettings.allowsAlphaSplitting); - } - - protected bool IsTagPrefixed(string packingTag) - { - packingTag = packingTag.Trim(); - if (packingTag.Length < TagPrefix.Length) - return false; - return (packingTag.Substring(0, TagPrefix.Length) == TagPrefix); - } - - private string ParseAtlasName(string packingTag) - { - string name = packingTag.Trim(); - if (IsTagPrefixed(name)) - name = name.Substring(TagPrefix.Length).Trim(); - return (name.Length == 0) ? "(unnamed)" : name; - } - - private SpritePackingMode GetPackingMode(string packingTag, SpriteMeshType meshType) - { - if (meshType == SpriteMeshType.Tight) - if (IsTagPrefixed(packingTag) == AllowTightWhenTagged) - return SpritePackingMode.Tight; - return SpritePackingMode.Rectangle; - } - } -} diff --git a/Editor/Mono/Sprites/SpritePacker.cs b/Editor/Mono/Sprites/SpritePacker.cs deleted file mode 100644 index c61581c27e..0000000000 --- a/Editor/Mono/Sprites/SpritePacker.cs +++ /dev/null @@ -1,144 +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.Linq; -using UnityEngine; -using System.Collections.Generic; - -namespace UnityEditor.Sprites -{ - public sealed partial class Packer - { - public enum Execution - { - Normal = 0, - ForceRegroup - } - - public static string kDefaultPolicy = typeof(DefaultPackerPolicy).Name; - - private static string[] m_policies = null; - public static string[] Policies - { - get - { - RegenerateList(); - return m_policies; - } - } - - private static string m_selectedPolicy = null; - private static void SetSelectedPolicy(string value) - { - m_selectedPolicy = value; - PlayerSettings.spritePackerPolicy = m_selectedPolicy; - } - - public static string SelectedPolicy - { - get - { - RegenerateList(); - return m_selectedPolicy; - } - set - { - RegenerateList(); - if (value == null) - throw new ArgumentNullException(); - if (!m_policies.Contains(value)) - throw new ArgumentException("Specified policy {0} is not in the policy list.", value); - SetSelectedPolicy(value); - } - } - - private static Dictionary m_policyTypeCache = null; - private static void RegenerateList() - { - if (m_policies != null) - return; - - var types = TypeCache.GetTypesDerivedFrom(); - - m_policies = types.Select(t => t.Name).ToArray(); - - m_policyTypeCache = new Dictionary(); - foreach (var t in types) - { - if (m_policyTypeCache.ContainsKey(t.Name)) - { - Type otherT = m_policyTypeCache[t.Name]; - Debug.LogError(string.Format("Duplicate Sprite Packer policies found: {0} and {1}. Please rename one.", t.FullName, otherT.FullName)); - } - else - m_policyTypeCache[t.Name] = t; - } - - m_selectedPolicy = String.IsNullOrEmpty(PlayerSettings.spritePackerPolicy) ? kDefaultPolicy : PlayerSettings.spritePackerPolicy; - - // Did policies change? - if (!m_policies.Contains(m_selectedPolicy)) - SetSelectedPolicy(kDefaultPolicy); - } - - // Called from SpritePacker::GetSelectedPolicyId() - internal static string GetSelectedPolicyId() - { - RegenerateList(); - - Type t = m_policyTypeCache[m_selectedPolicy]; - IPackerPolicy policy = Activator.CreateInstance(t) as IPackerPolicy; - string versionString = string.Format("{0}::{1}", t.AssemblyQualifiedName, policy.GetVersion()); - - return versionString; - } - - internal static bool AllowSequentialPacking() - { - RegenerateList(); - - Type t = m_policyTypeCache[m_selectedPolicy]; - IPackerPolicy policy = Activator.CreateInstance(t) as IPackerPolicy; - return policy.AllowSequentialPacking; - } - - internal static void ExecuteSelectedPolicy(BuildTarget target, int[] textureImporterInstanceIDs) - { - RegenerateList(); - - Type t = m_policyTypeCache[m_selectedPolicy]; - IPackerPolicy policy = Activator.CreateInstance(t) as IPackerPolicy; - policy.OnGroupAtlases(target, new PackerJob(), textureImporterInstanceIDs); - } - - internal static void SaveUnappliedTextureImporterSettings() - { - foreach (InspectorWindow i in InspectorWindow.GetAllInspectorWindows()) - { - ActiveEditorTracker activeEditor = i.tracker; - foreach (Editor e in activeEditor.activeEditors) - { - TextureImporterInspector inspector = e as TextureImporterInspector; - if (inspector == null) - continue; - if (!inspector.HasModified()) - continue; - TextureImporter importer = inspector.target as TextureImporter; - if (EditorUtility.DisplayDialog("Unapplied import settings", "Unapplied import settings for \'" + importer.assetPath + "\'", "Apply", "Revert")) - { - inspector.ApplyAndImport(); // No way to apply/revert only some assets. Bug: 564192. - } - } - } - } - } - - public interface IPackerPolicy - { - bool AllowSequentialPacking { get; } - void OnGroupAtlases(BuildTarget target, PackerJob job, int[] textureImporterInstanceIDs); - int GetVersion(); - } -} diff --git a/Editor/Mono/Sprites/SpritePackerWindow.cs b/Editor/Mono/Sprites/SpritePackerWindow.cs deleted file mode 100644 index b06110695d..0000000000 --- a/Editor/Mono/Sprites/SpritePackerWindow.cs +++ /dev/null @@ -1,383 +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.Collections; -using System.Linq; -using UnityEditorInternal; -using UnityEngine; - -namespace UnityEditor.Sprites -{ - internal class PackerWindow : SpriteUtilityWindow - { - private class PackerWindowStyle - { - public static readonly GUIContent packLabel = EditorGUIUtility.TrTextContent("Pack"); - public static readonly GUIContent repackLabel = EditorGUIUtility.TrTextContent("Repack"); - public static readonly GUIContent viewAtlasLabel = EditorGUIUtility.TrTextContent("View Atlas:"); - public static readonly GUIContent windowTitle = EditorGUIUtility.TrTextContent("Sprite Packer"); - public static readonly GUIContent pageContentLabel = EditorGUIUtility.TrTextContent("Page {0}"); - public static readonly GUIContent packingDisabledLabel = EditorGUIUtility.TrTextContent("Legacy sprite packing is disabled. Enable it in Edit > Project Settings > Editor."); - public static readonly GUIContent openProjectSettingButton = EditorGUIUtility.TrTextContent("Open Project Editor Settings"); - } - - struct Edge - { - public UInt16 v0; - public UInt16 v1; - public Edge(UInt16 a, UInt16 b) - { - v0 = a; - v1 = b; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(obj, null)) - return false; - if (obj.GetType() != GetType()) - return false; - Edge item = (Edge)obj; - return (v0 == item.v0 && v1 == item.v1) || (v0 == item.v1 && v1 == item.v0); - } - - public override int GetHashCode() - { - return (v0 << 16 | v1) ^ (v1 << 16 | v0).GetHashCode(); - } - } - - private static string[] s_AtlasNamesEmpty = new string[1] { "Sprite atlas cache is empty" }; - private string[] m_AtlasNames = s_AtlasNamesEmpty; - private int m_SelectedAtlas = 0; - - private static string[] s_PageNamesEmpty = new string[0]; - private string[] m_PageNames = s_PageNamesEmpty; - private int m_SelectedPage = 0; - - private Sprite m_SelectedSprite = null; - - - void OnEnable() - { - minSize = new Vector2(400f, 256f); - titleContent = PackerWindowStyle.windowTitle; - - Reset(); - } - - private void Reset() - { - RefreshAtlasNameList(); - RefreshAtlasPageList(); - - m_SelectedAtlas = 0; - m_SelectedPage = 0; - m_SelectedSprite = null; - } - - private void RefreshAtlasNameList() - { - m_AtlasNames = Packer.atlasNames; - - // Validate - if (m_SelectedAtlas >= m_AtlasNames.Length) - m_SelectedAtlas = 0; - } - - private void RefreshAtlasPageList() - { - if (m_AtlasNames.Length > 0) - { - string atlas = m_AtlasNames[m_SelectedAtlas]; - Texture2D[] textures = Packer.GetTexturesForAtlas(atlas); - m_PageNames = new string[textures.Length]; - for (int i = 0; i < textures.Length; ++i) - m_PageNames[i] = string.Format(PackerWindowStyle.pageContentLabel.text, i + 1); - } - else - { - m_PageNames = s_PageNamesEmpty; - } - - // Validate - if (m_SelectedPage >= m_PageNames.Length) - m_SelectedPage = 0; - } - - private void OnAtlasNameListChanged() - { - if (m_AtlasNames.Length > 0) - { - string[] atlasNames = Packer.atlasNames; - string curAtlasName = m_AtlasNames[m_SelectedAtlas]; - string newAtlasName = (atlasNames.Length <= m_SelectedAtlas) ? null : atlasNames[m_SelectedAtlas]; - if (curAtlasName.Equals(newAtlasName)) - { - RefreshAtlasNameList(); - RefreshAtlasPageList(); - m_SelectedSprite = null; - return; - } - } - - Reset(); - } - - private bool ValidateIsPackingEnabled() - { - return true; - } - - private Rect DoToolbarGUI() - { - Rect toolbarRect = new Rect(0 , 0, position.width, k_ToolbarHeight); - - if (Event.current.type == EventType.Repaint) - { - EditorStyles.toolbar.Draw(toolbarRect, false, false, false, false); - } - - bool wasEnabled = GUI.enabled; - GUI.enabled = m_AtlasNames.Length > 0; - toolbarRect = DoAlphaZoomToolbarGUI(toolbarRect); - GUI.enabled = wasEnabled; - - Rect drawRect = new Rect(EditorGUI.kSpacing, 0, 0, k_ToolbarHeight); - toolbarRect.width -= drawRect.x; - - using (new EditorGUI.DisabledScope(Application.isPlaying)) - { - drawRect.width = EditorStyles.toolbarButton.CalcSize(PackerWindowStyle.packLabel).x; - DrawToolBarWidget(ref drawRect, ref toolbarRect, (adjustedDrawRect) => - { - if (GUI.Button(adjustedDrawRect, PackerWindowStyle.packLabel, EditorStyles.toolbarButton)) - { - Packer.RebuildAtlasCacheIfNeededInternal(EditorUserBuildSettings.activeBuildTarget, true, Packer.Execution.Normal); - m_SelectedSprite = null; - RefreshAtlasPageList(); - RefreshState(); - } - }); - - using (new EditorGUI.DisabledScope(Packer.SelectedPolicy == Packer.kDefaultPolicy)) - { - drawRect.x += drawRect.width; - drawRect.width = EditorStyles.toolbarButton.CalcSize(PackerWindowStyle.repackLabel).x; - DrawToolBarWidget(ref drawRect, ref toolbarRect, (adjustedDrawRect) => - { - if (GUI.Button(adjustedDrawRect, PackerWindowStyle.repackLabel, EditorStyles.toolbarButton)) - { - Packer.RebuildAtlasCacheIfNeededInternal(EditorUserBuildSettings.activeBuildTarget, true, Packer.Execution.ForceRegroup); - m_SelectedSprite = null; - RefreshAtlasPageList(); - RefreshState(); - } - }); - } - } - - const float kAtlasNameWidth = 100; - const float kPagesWidth = 70; - const float kPolicyWidth = 100; - - float viewAtlasWidth = GUI.skin.label.CalcSize(PackerWindowStyle.viewAtlasLabel).x; - float totalWidth = viewAtlasWidth + kAtlasNameWidth + kPagesWidth + kPolicyWidth; - - drawRect.x += EditorGUI.kSpacing; // leave some space from previous control for cosmetic - toolbarRect.width -= EditorGUI.kSpacing; - float availableWidth = toolbarRect.width; - - using (new EditorGUI.DisabledScope(m_AtlasNames.Length == 0)) - { - drawRect.x += drawRect.width; - drawRect.width = viewAtlasWidth / totalWidth * availableWidth; - DrawToolBarWidget(ref drawRect, ref toolbarRect, (adjustedDrawArea) => - { - GUI.Label(adjustedDrawArea, PackerWindowStyle.viewAtlasLabel); - }); - - EditorGUI.BeginChangeCheck(); - drawRect.x += drawRect.width; - drawRect.width = kAtlasNameWidth / totalWidth * availableWidth; - DrawToolBarWidget(ref drawRect, ref toolbarRect, (adjustedDrawArea) => - { - m_SelectedAtlas = EditorGUI.Popup(adjustedDrawArea, m_SelectedAtlas, m_AtlasNames, EditorStyles.toolbarPopup); - }); - if (EditorGUI.EndChangeCheck()) - { - RefreshAtlasPageList(); - m_SelectedSprite = null; - } - - EditorGUI.BeginChangeCheck(); - drawRect.x += drawRect.width; - drawRect.width = kPagesWidth / totalWidth * availableWidth; - DrawToolBarWidget(ref drawRect, ref toolbarRect, (adjustedDrawArea) => - { - m_SelectedPage = EditorGUI.Popup(adjustedDrawArea, m_SelectedPage, m_PageNames, EditorStyles.toolbarPopup); - }); - - if (EditorGUI.EndChangeCheck()) - { - m_SelectedSprite = null; - } - } - - EditorGUI.BeginChangeCheck(); - string[] policies = Packer.Policies; - int selectedPolicy = Array.IndexOf(policies, Packer.SelectedPolicy); - drawRect.x += drawRect.width; - drawRect.width = kPolicyWidth / totalWidth * availableWidth; - DrawToolBarWidget(ref drawRect, ref toolbarRect, (adjustedDrawArea) => - { - selectedPolicy = EditorGUI.Popup(adjustedDrawArea, selectedPolicy, policies, EditorStyles.toolbarPopup); - }); - - if (EditorGUI.EndChangeCheck()) - { - Packer.SelectedPolicy = policies[selectedPolicy]; - } - - return toolbarRect; - } - - void OnSelectionChange() - { - if (Selection.activeObject == null) - return; - - Sprite selectedSprite = Selection.activeObject as Sprite; - if (selectedSprite != m_SelectedSprite) - { - if (selectedSprite != null) - { - string selAtlasName; - Texture2D selAtlasTexture; - Packer.GetAtlasDataForSprite(selectedSprite, out selAtlasName, out selAtlasTexture); - - int selAtlasIndex = m_AtlasNames.ToList().FindIndex(delegate(string s) { return selAtlasName == s; }); - if (selAtlasIndex == -1) - return; - int selAtlasPage = Packer.GetTexturesForAtlas(selAtlasName).ToList().FindIndex(delegate(Texture2D t) { return selAtlasTexture == t; }); - if (selAtlasPage == -1) - return; - - m_SelectedAtlas = selAtlasIndex; - m_SelectedPage = selAtlasPage; - RefreshAtlasPageList(); - } - - m_SelectedSprite = selectedSprite; - - Repaint(); - } - } - - private void RefreshState() - { - // Check if atlas name list changed - string[] atlasNames = Packer.atlasNames; - if (!atlasNames.SequenceEqual(m_AtlasNames)) - { - if (atlasNames.Length == 0) - { - Reset(); - return; - } - else - { - OnAtlasNameListChanged(); - } - } - - if (m_AtlasNames.Length == 0) - { - SetNewTexture(null); - return; - } - - // Validate selections - if (m_SelectedAtlas >= m_AtlasNames.Length) - m_SelectedAtlas = 0; - string curAtlasName = m_AtlasNames[m_SelectedAtlas]; - - Texture2D[] textures = Packer.GetTexturesForAtlas(curAtlasName); - if (m_SelectedPage >= textures.Length) - m_SelectedPage = 0; - - SetNewTexture(textures[m_SelectedPage]); - - // check if the atlas has alpha as an external texture (as in ETC1 atlases with alpha) - Texture2D[] alphaTextures = Packer.GetAlphaTexturesForAtlas(curAtlasName); - Texture2D selectedAlphaTexture = (m_SelectedPage < alphaTextures.Length) ? alphaTextures[m_SelectedPage] : null; - SetAlphaTextureOverride(selectedAlphaTexture); - } - - public void OnGUI() - { - if (!ValidateIsPackingEnabled()) - return; - - Matrix4x4 oldHandlesMatrix = Handles.matrix; - InitStyles(); - - RefreshState(); - - // Top menu bar - Rect toolbarRect = DoToolbarGUI(); - - if (m_Texture == null) - return; - - // Texture view - EditorGUILayout.BeginHorizontal(); - m_TextureViewRect = new Rect(0f, toolbarRect.yMax, position.width - k_ScrollbarMargin, position.height - k_ScrollbarMargin - toolbarRect.height); - GUILayout.FlexibleSpace(); - DoTextureGUI(); - string info = string.Format("{1}x{2}, {0}", TextureUtil.GetTextureFormatString(m_Texture.format), m_Texture.width, m_Texture.height); - EditorGUI.DropShadowLabel(new Rect(m_TextureViewRect.x, m_TextureViewRect.y + 10, m_TextureViewRect.width, 20), info); - EditorGUILayout.EndHorizontal(); - - Handles.matrix = oldHandlesMatrix; - } - - private void DrawLineUtility(Vector2 from, Vector2 to) - { - SpriteEditorUtility.DrawLine(new Vector3(from.x * m_Texture.width + 1f / m_Zoom, from.y * m_Texture.height + 1f / m_Zoom, 0.0f), new Vector3(to.x * m_Texture.width + 1f / m_Zoom, to.y * m_Texture.height + 1f / m_Zoom, 0.0f)); - } - - private Edge[] FindUniqueEdges(UInt16[] indices) - { - Edge[] allEdges = new Edge[indices.Length]; - int tris = indices.Length / 3; - for (int i = 0; i < tris; ++i) - { - allEdges[i * 3] = new Edge(indices[i * 3], indices[i * 3 + 1]); - allEdges[i * 3 + 1] = new Edge(indices[i * 3 + 1], indices[i * 3 + 2]); - allEdges[i * 3 + 2] = new Edge(indices[i * 3 + 2], indices[i * 3]); - } - - Edge[] uniqueEdges = allEdges.GroupBy(x => x).Where(x => x.Count() == 1).Select(x => x.First()).ToArray(); - return uniqueEdges; - } - - protected override void DrawGizmos() - { - if (m_SelectedSprite != null && m_Texture != null) - { - Vector2[] uvs = SpriteUtility.GetSpriteUVs(m_SelectedSprite, true); - UInt16[] indices = m_SelectedSprite.triangles; - Edge[] uniqueEdges = FindUniqueEdges(indices); // Assumes that our mesh has no duplicate vertices - - SpriteEditorUtility.BeginLines(new Color(0.3921f, 0.5843f, 0.9294f, 0.75f)); // Cornflower blue :) - foreach (Edge e in uniqueEdges) - DrawLineUtility(uvs[e.v0], uvs[e.v1]); - SpriteEditorUtility.EndLines(); - } - } - } // class -} diff --git a/Editor/Mono/Sprites/SpriteUtility.cs b/Editor/Mono/Sprites/SpriteUtility.cs index 616cf7d9ed..1f174c35aa 100644 --- a/Editor/Mono/Sprites/SpriteUtility.cs +++ b/Editor/Mono/Sprites/SpriteUtility.cs @@ -570,61 +570,7 @@ public static UnityTexture2D RenderStaticPreview(Sprite sprite, Color color, int public static UnityTexture2D RenderStaticPreview(Sprite sprite, Color color, int width, int height, Matrix4x4 transform) { - if (sprite == null) - return null; - - PreviewHelpers.AdjustWidthAndHeightForStaticPreview((int)sprite.rect.width, (int)sprite.rect.height, ref width, ref height); - - SavedRenderTargetState savedRTState = new SavedRenderTargetState(); - - RenderTexture tmp = RenderTexture.GetTemporary(width, height, 0, SystemInfo.GetGraphicsFormat(DefaultFormat.LDR)); - RenderTexture.active = tmp; - GL.Clear(true, true, new Color(0f, 0f, 0f, 0.1f)); - - previewSpriteDefaultMaterial.mainTexture = sprite.texture; - previewSpriteDefaultMaterial.SetPass(0); - - RenderSpriteImmediate(sprite, color, transform); - - UnityTexture2D copy = new UnityTexture2D(width, height, TextureFormat.ARGB32, false); - copy.hideFlags = HideFlags.HideAndDontSave; - copy.ReadPixels(new Rect(0, 0, width, height), 0, 0); - copy.Apply(); - RenderTexture.ReleaseTemporary(tmp); - - savedRTState.Restore(); - return copy; - } - - internal static void RenderSpriteImmediate(Sprite sprite, Color color, Matrix4x4 transform) - { - float spriteWidth = sprite.rect.width; - float spriteHeight = sprite.rect.height; - - float pixelsToUnits = sprite.rect.width / sprite.bounds.size.x; - Vector2[] vertices = sprite.vertices; - Vector2[] uvs = sprite.uv; - ushort[] triangles = sprite.triangles; - Vector2 pivot = sprite.pivot; - - GL.PushMatrix(); - GL.LoadOrtho(); - GL.Begin(GL.TRIANGLES); - for (int i = 0; i < sprite.triangles.Length; ++i) - { - ushort index = triangles[i]; - Vector2 spriteVertex = vertices[index]; - Vector2 uv = uvs[index]; - Vector3 vertex = new Vector3(spriteVertex.x, spriteVertex.y, 0); - vertex = transform.MultiplyPoint(vertex); - vertex.x = (vertex.x * pixelsToUnits + pivot.x) / spriteWidth; - vertex.y = (vertex.y * pixelsToUnits + pivot.y) / spriteHeight; - GL.Color(color); - GL.TexCoord(new Vector3(uv.x, uv.y, 0)); - GL.Vertex3(vertex.x, vertex.y, vertex.z); - } - GL.End(); - GL.PopMatrix(); + return SpriteInspector.BuildPreviewTexture(sprite, previewSpriteDefaultMaterial, false, width, height, color, transform); } public static UnityTexture2D CreateTemporaryDuplicate(UnityTexture2D original, int width, int height) diff --git a/Editor/Mono/Sprites/TightRotateEnabledSpritePackerPolicy.cs b/Editor/Mono/Sprites/TightRotateEnabledSpritePackerPolicy.cs deleted file mode 100644 index f0323ebd25..0000000000 --- a/Editor/Mono/Sprites/TightRotateEnabledSpritePackerPolicy.cs +++ /dev/null @@ -1,19 +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.Linq; -using UnityEngine; -using System.Collections.Generic; - -namespace UnityEditor.Sprites -{ - // TightRotateEnabledSpritePackerPolicy will tightly pack non-rectangle Sprites unless their packing tag contains "[RECT]" with rotation and flipping for optimal packing. - internal class TightRotateEnabledSpritePackerPolicy : DefaultPackerPolicy - { - protected override string TagPrefix { get { return "[RECT]"; } } - protected override bool AllowTightWhenTagged { get { return false; } } - protected override bool AllowRotationFlipping { get { return true; } } - } -} diff --git a/Editor/Mono/Sprites/TightSpritePackerPolicy.cs b/Editor/Mono/Sprites/TightSpritePackerPolicy.cs deleted file mode 100644 index ef78eec698..0000000000 --- a/Editor/Mono/Sprites/TightSpritePackerPolicy.cs +++ /dev/null @@ -1,19 +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.Linq; -using UnityEngine; -using System.Collections.Generic; - -namespace UnityEditor.Sprites -{ - // TightPackerPolicy will tightly pack non-rectangle Sprites unless their packing tag contains "[RECT]". - internal class TightPackerPolicy : DefaultPackerPolicy - { - protected override string TagPrefix { get { return "[RECT]"; } } - protected override bool AllowTightWhenTagged { get { return false; } } - protected override bool AllowRotationFlipping { get { return false; } } - } -} diff --git a/Editor/Mono/TooltipView/TooltipView.cs b/Editor/Mono/TooltipView/TooltipView.cs index 7de21a33b9..685175b210 100644 --- a/Editor/Mono/TooltipView/TooltipView.cs +++ b/Editor/Mono/TooltipView/TooltipView.cs @@ -53,7 +53,7 @@ protected override void OnEnable() m_VisualRoot = new VisualElement(); m_VisualRoot.pseudoStates |= PseudoStates.Root; m_VisualRoot.style.overflow = Overflow.Hidden; - EditorUIService.instance.AddDefaultEditorStyleSheets(m_VisualRoot); + UIElementsEditorUtility.AddDefaultEditorStyleSheets(m_VisualRoot); m_VisualRoot.style.flexGrow = 1; visualTree.Add(m_VisualRoot); EditorApplication.update += Update; @@ -86,18 +86,13 @@ protected override void OldOnGUI() m_DynamicHintContent.Extended = s_EnableExtendedDynamicHints && (m_HoldingShift || (m_DynamicHintIsBeingDisplayed && DynamicHintAutoExtendTimeReached)); m_DynamicHintContent.Update(); Size = m_DynamicHintContent.GetContentSize(); + /* Repainting is an expensive operation (causes CPU/GPU spikes), but + * Dynamic Hints need to be repainted in order to be expanded + * and to play media properly. + */ + Repaint(); } - - Repaint(); - - if (s_CloseState == CloseState.CloseRequested && !m_HoldingShift) - { - if (m_DynamicHintContent != null && m_DynamicHintContent.GetRect().Contains(evt.mousePosition)) - { - return; - } - s_CloseState = CloseState.CloseApproved; - } + ValidateCloseRequest(evt, false); } void Setup(string tooltip, Rect rect, GUIView hostView) @@ -143,7 +138,7 @@ void Setup(string tooltip, Rect rect, GUIView hostView) m_VisualRoot.Query