From a048de916b23331bf6dfe92c4a6c205989b83b4f Mon Sep 17 00:00:00 2001 From: Unity Technologies Date: Wed, 8 Dec 2021 19:49:54 +0000 Subject: [PATCH 1/7] Unity 2022.1.0b1 C# reference source code --- Editor/Mono/AssetPostprocessor.cs | 83 ++- .../DesktopStandalonePostProcessor.cs | 16 +- Editor/Mono/BuildPlayerWindow.cs | 94 +-- Editor/Mono/EditorApplication.bindings.cs | 37 +- Editor/Mono/EditorApplication.cs | 15 +- Editor/Mono/EditorGUI.cs | 5 +- Editor/Mono/EditorGUIUtility.cs | 39 +- .../Mono/EditorUserBuildSettings.bindings.cs | 4 +- Editor/Mono/GI/Lightmapping.bindings.cs | 5 + .../EditorMaterialUtility.bindings.cs | 3 + Editor/Mono/HandleUtility.cs | 22 +- Editor/Mono/Handles.cs | 2 +- .../Inspector/AutodeskInteractiveShaderGUI.cs | 4 +- Editor/Mono/Inspector/LightEditor.cs | 54 +- Editor/Mono/Inspector/MaterialEditor.cs | 392 ++++++++++-- .../Inspector/MaterialEditorGUIHelpers.cs | 83 ++- .../Mono/Inspector/MaterialPropertyDrawer.cs | 33 +- .../PlayerSettingsEditor.cs | 87 ++- Editor/Mono/Inspector/StandardShaderGUI.cs | 24 +- Editor/Mono/MaterialProperty.cs | 594 ++++++++++++++++++ Editor/Mono/OrderedCallbackCollection.cs | 378 +++++++++++ Editor/Mono/PlayerSettings.bindings.cs | 14 + Editor/Mono/Prefabs/PrefabUtility.bindings.cs | 3 + .../PreferencesSettingsProviders.cs | 10 +- Editor/Mono/ProjectBrowser.cs | 19 +- .../Mono/ProjectWindow/ProjectWindowUtil.cs | 128 +++- Editor/Mono/SceneView/SceneView.cs | 8 +- Editor/Mono/SceneView/SceneViewMotion.cs | 2 +- Editor/Mono/Shaders/MaterialHierarchyPopup.cs | 580 +++++++++++++++++ .../Bee}/BeeAsALibrary/Bee.BeeDriver.dll | Bin 87552 -> 87552 bytes .../com.unity.ui/Core/Controls/BaseSlider.cs | 24 +- .../Controls/BaseVerticalCollectionView.cs | 7 +- .../com.unity.ui/Core/Controls/ScrollView.cs | 99 +-- .../com.unity.ui/Core/Controls/Slider.cs | 11 + .../com.unity.ui/Core/Controls/SliderInt.cs | 11 + .../Core/Renderer/UIRRenderChain.cs | 2 +- .../StyleSheets/Validation/StyleMatcher.cs | 2 +- .../StyleSheets/StyleSheetImporterImpl.cs | 4 +- .../Editor/Services/Common/UIError.cs | 2 +- .../Editor/UI/PackageDetailsHeader.cs | 26 +- .../ParticleSystemModules/ModuleUIHelpers.cs | 4 +- .../ContactModification.bindings.cs | 9 +- .../IProfilerWindowController.cs | 1 - .../LegacyProfilerAreaUtility.cs | 35 ++ .../ProfilerAreaReferenceCounter.cs | 120 ---- .../ProfilerCategoryActivator.cs | 51 ++ .../ProfilerModules/ProfilerModule.cs | 28 +- .../ProfilerModules/ProfilerModuleBase.cs | 16 +- .../ProfilerWindow/ProfilerWindow.cs | 25 +- .../Public/ProfilerAPI.bindings.cs | 4 + Modules/QuickSearch/Editor/UI/ListView.cs | 83 +-- .../SceneTemplateEditor/DependencyListView.cs | 11 + .../SceneTemplateAssetInspectorWindow.cs | 18 + Modules/Tilemap/Managed/ITilemap.cs | 2 +- .../ScriptBindings/Tilemap.bindings.cs | 11 + .../Controls/EditorToolbarButton.cs | 29 +- .../Controls/EditorToolbarDropdown.cs | 2 - .../Controls/EditorToolbarDropdownToggle.cs | 33 +- .../Controls/EditorToolbarToggle.cs | 22 +- .../SceneView/SceneViewToolbarElements.cs | 1 + .../ScriptBindings/VFXEnums.bindings.cs | 3 +- .../ScriptBindings/VFXManager.bindings.cs | 1 + Projects/CSharp/UnityEditor.csproj | 15 +- Projects/CSharp/UnityEngine.csproj | 6 + README.md | 2 +- .../SpriteDataAccess.bindings.cs | 7 + .../BurstAuthorizedExternalMethodAttribute.cs | 14 + .../Export/BurstLike/BurstLike.bindings.cs | 26 + Runtime/Export/Graphics/Graphics.cs | 12 + Runtime/Export/Graphics/GraphicsEnums.cs | 12 +- .../RenderPipeline/RendererList.bindings.cs | 10 +- .../Export/RenderPipeline/ShadowSplitData.cs | 8 + .../Rendering/BatchRendererGroup.bindings.cs | 74 ++- Runtime/Export/Shaders/Material.bindings.cs | 52 ++ Runtime/Export/Shaders/ShaderProperties.cs | 8 + .../Export/Unsafe/UnsafeUtility.bindings.cs | 21 + .../ScriptBindings/ProfilerCategory.cs | 5 + .../ProfilerUnsafeUtility.bindings.cs | 1 + 78 files changed, 3147 insertions(+), 561 deletions(-) create mode 100644 Editor/Mono/OrderedCallbackCollection.cs create mode 100644 Editor/Mono/Shaders/MaterialHierarchyPopup.cs rename {artifacts/Stevedore/bee_bcfb => External/Bee}/BeeAsALibrary/Bee.BeeDriver.dll (74%) create mode 100644 Modules/ProfilerEditor/ProfilerWindow/LegacyProfilerAreaUtility.cs delete mode 100644 Modules/ProfilerEditor/ProfilerWindow/ProfilerAreaReferenceCounter.cs create mode 100644 Modules/ProfilerEditor/ProfilerWindow/ProfilerCategoryActivator.cs create mode 100644 Runtime/Export/Burst/BurstAuthorizedExternalMethodAttribute.cs create mode 100644 Runtime/Export/BurstLike/BurstLike.bindings.cs 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/BuildPipeline/DesktopStandalonePostProcessor.cs b/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs index 4e9887ba76..891389b394 100644 --- a/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs +++ b/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs @@ -56,25 +56,31 @@ public override void LaunchPlayer(BuildLaunchPlayerArgs args) readonly bool m_HasMonoPlayers; readonly bool m_HasIl2CppPlayers; + readonly bool m_HasServerMonoPlayers; + readonly bool m_HasServerIl2CppPlayers; - protected DesktopStandalonePostProcessor(bool hasMonoPlayers, bool hasIl2CppPlayers) + protected DesktopStandalonePostProcessor(bool hasMonoPlayers, bool hasIl2CppPlayers, bool hasServerMonoPlayers, bool hasServerIl2CppPlayers) { m_HasMonoPlayers = hasMonoPlayers; m_HasIl2CppPlayers = hasIl2CppPlayers; + m_HasServerMonoPlayers = hasServerMonoPlayers; + m_HasServerIl2CppPlayers = hasServerIl2CppPlayers; } public override string PrepareForBuild(BuildOptions options, BuildTarget target) { - if (!m_HasMonoPlayers) + var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(target); + var isServer = buildTargetGroup == BuildTargetGroup.Standalone && + (StandaloneBuildSubtarget)EditorUserBuildSettings.GetActiveSubtargetFor(target) == StandaloneBuildSubtarget.Server; + + if ((!isServer && !m_HasMonoPlayers) || (isServer && !m_HasServerMonoPlayers)) { - var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(target); if (PlayerSettings.GetScriptingBackend(buildTargetGroup) != ScriptingImplementation.IL2CPP) return "Currently selected scripting backend (Mono) is not installed."; } - if (!m_HasIl2CppPlayers) + if ((!isServer && !m_HasIl2CppPlayers) || (isServer && !m_HasServerIl2CppPlayers)) { - var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(target); if (PlayerSettings.GetScriptingBackend(buildTargetGroup) == ScriptingImplementation.IL2CPP) return "Currently selected scripting backend (IL2CPP) is not installed."; } diff --git a/Editor/Mono/BuildPlayerWindow.cs b/Editor/Mono/BuildPlayerWindow.cs index fda2e95fef..edf31d98c9 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; @@ -587,34 +588,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) @@ -1068,23 +1077,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) - { - enableBuildAndRunButton = false; - enableBuildButton = false; - EditorGUILayout.HelpBox(styles.invalidLightmapEncodingMessage); - } - else if (!IsVirtualTexturingSettingsValid(platform) && enableBuildButton && enableBuildAndRunButton) + if (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) 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..feb96a9f5a 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); @@ -247,6 +247,7 @@ internal static void ClearStacks() s_EnabledStack.Clear(); s_ChangedStack.Clear(); s_PropertyStack.Clear(); + MaterialProperty.ClearStack(); ScriptAttributeUtility.s_DrawerStack.Clear(); s_FoldoutHeaderGroupActive = 0; } @@ -6596,7 +6597,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) 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/EditorUserBuildSettings.bindings.cs b/Editor/Mono/EditorUserBuildSettings.bindings.cs index b52dd43bd7..ae35e320e3 100644 --- a/Editor/Mono/EditorUserBuildSettings.bindings.cs +++ b/Editor/Mono/EditorUserBuildSettings.bindings.cs @@ -684,10 +684,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)] diff --git a/Editor/Mono/GI/Lightmapping.bindings.cs b/Editor/Mono/GI/Lightmapping.bindings.cs index 1335da13b8..d816cf215d 100644 --- a/Editor/Mono/GI/Lightmapping.bindings.cs +++ b/Editor/Mono/GI/Lightmapping.bindings.cs @@ -457,6 +457,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); diff --git a/Editor/Mono/Graphics/EditorMaterialUtility.bindings.cs b/Editor/Mono/Graphics/EditorMaterialUtility.bindings.cs index f7f6be0ca0..51ada20d89 100644 --- a/Editor/Mono/Graphics/EditorMaterialUtility.bindings.cs +++ b/Editor/Mono/Graphics/EditorMaterialUtility.bindings.cs @@ -28,5 +28,8 @@ public sealed partial class EditorMaterialUtility [FreeFunction("EditorMaterialUtilityBindings::SetShaderNonModifiableDefaults")] extern public static void SetShaderNonModifiableDefaults([NotNull] Shader shader, string[] name, Texture[] textures); + + [FreeFunction("EditorMaterialUtilityBindings::GetMaterialParentFromFile")] + extern internal static GUID GetMaterialParentFromFile(string assetPath); } } diff --git a/Editor/Mono/HandleUtility.cs b/Editor/Mono/HandleUtility.cs index 0f8ad8420f..1a5a36ce6a 100644 --- a/Editor/Mono/HandleUtility.cs +++ b/Editor/Mono/HandleUtility.cs @@ -1567,14 +1567,17 @@ internal static void FilterRendererIDs(Renderer[] renderers, out int[] parentRen return; } - var childCount = 0; + var childCount = 1024; var parentIndex = 0; var childIndex = 0; parentRendererIDs = new int[renderers.Length]; foreach (var renderer in renderers) { - childCount += renderer.transform.hierarchyCount; + // Transform.hierarchyCapacity is used as a starting point as there is no reliable way to pre-calculate + // the real count without traversing the hierarchy and cross-checking parents/children which is too slow (case 1372127) + if (renderer.transform.hierarchyCapacity > childCount) + childCount = renderer.transform.hierarchyCapacity; parentRendererIDs[parentIndex++] = renderer.GetInstanceID(); } @@ -1585,7 +1588,7 @@ internal static void FilterRendererIDs(Renderer[] renderers, out int[] parentRen for (int i = 1; i < children.Length; i++) { var id = children[i].GetInstanceID(); - if (!HasMatchingInstanceID(parentRendererIDs, id)) + if (!HasMatchingInstanceID(parentRendererIDs, id, childIndex) && !HasMatchingInstanceID(childRendererIDs, id, childIndex)) childRendererIDs[childIndex++] = id; } } @@ -1601,14 +1604,17 @@ internal static void FilterRendererIDs(GameObject[] gameObjects, out int[] paren return; } - var childCount = 0; + var childCount = 1024; var parentIndex = 0; var childIndex = 0; parentRendererIDs = new int[gameObjects.Length]; foreach (var go in gameObjects) { - childCount += go.transform.hierarchyCount; + // Transform.hierarchyCapacity is used as a starting point as there is no reliable way to pre-calculate + // the real count without traversing the hierarchy and cross-checking parents/children which is too slow (case 1372127) + if (go.transform.hierarchyCapacity > childCount) + childCount = go.transform.hierarchyCapacity; if (go.TryGetComponent(out Renderer renderer)) parentRendererIDs[parentIndex++] = renderer.GetInstanceID(); } @@ -1620,18 +1626,20 @@ internal static void FilterRendererIDs(GameObject[] gameObjects, out int[] paren for (int i = 1; i < children.Length; i++) { var id = children[i].GetInstanceID(); - if (!HasMatchingInstanceID(parentRendererIDs, id)) + if (!HasMatchingInstanceID(parentRendererIDs, id, parentRendererIDs.Length) && !HasMatchingInstanceID(childRendererIDs, id, childIndex)) childRendererIDs[childIndex++] = id; } } } - 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.cs b/Editor/Mono/Handles.cs index 51dbf11201..916385db1e 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; } 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/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/MaterialEditor.cs b/Editor/Mono/Inspector/MaterialEditor.cs index 4d53cd8eee..8bed83e201 100644 --- a/Editor/Mono/Inspector/MaterialEditor.cs +++ b/Editor/Mono/Inspector/MaterialEditor.cs @@ -71,9 +71,17 @@ 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"); + + 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(); @@ -82,6 +90,15 @@ private static class Styles 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 +128,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); @@ -340,6 +355,162 @@ 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 static void ParentField(Rect rect, Object[] targets) + { + rect = EditorGUI.PrefixLabel(rect, Styles.parentContent); + + EditorGUI.BeginChangeCheck(); + var parent = DoParentObjectField(rect, targets); + if (EditorGUI.EndChangeCheck()) + { + Undo.RecordObjects(targets, "Assign parent"); + foreach (Material target in targets) + target.parent = parent; + } + } + + private void ParentFieldAndPopup() + { + Rect rect = EditorGUILayout.GetControlRect(); + + bool hasMixedParent = HasMixedParent(); + EditorGUI.showMixedValue = hasMixedParent; + Rect fieldRect = new Rect(rect) { width = rect.width - (Styles.kHierarchyIconWidth + Styles.kPadding - 1) }; + ParentField(fieldRect, targets); + EditorGUI.showMixedValue = false; + + bool enabled = GUI.enabled; + GUI.enabled = !hasMixedParent; + { + rect.x = fieldRect.xMax + Styles.kPadding; + rect.width = Styles.kHierarchyIconWidth; + if (EditorGUI.DropdownButton(rect, GUIContent.none, FocusType.Passive)) + { + PopupWindow.Show(rect, new MaterialHierarchyPopup(targets)); + GUIUtility.ExitGUI(); + } + rect.x += 6; + EditorGUI.LabelField(rect, Styles.hierarchyIcon); + } + GUI.enabled = enabled; + } + private class ShaderSelectionDropdown : AdvancedDropdown { Action m_OnSelectedShaderPopup; @@ -782,8 +953,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,6 +971,10 @@ 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; @@ -813,7 +987,8 @@ internal override void OnHeaderControlsGUI() EditorGUIUtility.labelWidth = 50; // Shader selection dropdown - ShaderPopup("MiniPulldown"); + using (new EditorGUI.DisabledScope(GetVariantCount() != 0)) + ShaderPopup("MiniPulldown"); // Edit button for custom shaders if (m_Shader != null && !HasMultipleMixedShaderValues() && (m_Shader.hideFlags & HideFlags.DontSave) == 0) @@ -821,9 +996,33 @@ internal override void OnHeaderControlsGUI() if (GUILayout.Button("Edit...", EditorStyles.miniButton, GUILayout.ExpandWidth(false))) AssetDatabase.OpenAsset(m_Shader); } - } - EditorGUIUtility.labelWidth = oldLabelWidth; + if (AllTargetsAreVariants()) + { + // Start a new line for parent + EditorGUILayout.EndHorizontal(); + EditorGUILayout.BeginHorizontal(); + if (!firstInspectedEditor) + GUILayout.Space(Styles.kSpaceForFoldoutArrow); + ParentFieldAndPopup(); + } + else if (targets.Length == 1) + { + bool enabled = GUI.enabled; + GUI.enabled = true; + if (EditorGUILayout.DropdownButton(GUIContent.none, FocusType.Passive, GUILayout.MaxWidth(Styles.kHierarchyIconWidth))) + { + PopupWindow.Show(GUILayoutUtility.topLevel.GetLast(), new MaterialHierarchyPopup(targets)); + GUIUtility.ExitGUI(); + } + GUI.enabled = enabled; + var rect = new Rect(GUILayoutUtility.topLevel.GetLast()); + rect.x += 6; + EditorGUI.LabelField(rect, Styles.hierarchyIcon); + } + + EditorGUIUtility.labelWidth = oldLabelWidth; + } } // -------- obsolete helper functions to get/set material values @@ -958,6 +1157,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 +1249,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 +1264,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 +1287,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 +1316,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 +1346,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 +1376,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 +1403,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 +1416,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 +1442,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 +1452,8 @@ public float TextureScaleOffsetProperty(Rect position, MaterialProperty property if (EditorGUI.EndChangeCheck()) property.textureScaleAndOffset = scaleAndOffset; + EndProperty(); + EndAnimatedCheck(); return 2 * kLineHeight; } @@ -1257,6 +1535,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 +1551,8 @@ public Texture TexturePropertyMiniThumbnail(Rect position, MaterialProperty prop TextureCompatibilityWarning(prop); + EndProperty(); + return retValue; } @@ -1299,6 +1581,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 +1593,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 +1855,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 +1879,7 @@ public void LightmapEmissionProperty(Rect position, int labelIndent) } } + EndProperty(); EditorGUI.indentLevel -= labelIndent; } @@ -1615,6 +1904,8 @@ public bool EmissionEnabledProperty() } } + BeginProperty(MaterialSerializedProperty.LightmapFlags, targets); + // initial checkbox for enabling/disabling emission EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = isMixed; @@ -1626,8 +1917,11 @@ public bool EmissionEnabledProperty() { mat.globalIlluminationFlags = enabled ? defaultEnabled : MaterialGlobalIlluminationFlags.EmissiveIsBlack; } + EndProperty(); return enabled; } + + EndProperty(); return !isMixed && enabled; } @@ -1666,6 +1960,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); @@ -1687,6 +1982,8 @@ public void LightmapEmissionFlagsProperty(int indent, bool enabled, bool ignoreE FixupEmissiveFlag(mat); } } + + EndProperty(); } void ShaderPropertyInternal(Rect position, MaterialProperty prop, GUIContent label) @@ -1938,6 +2235,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 +2282,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 +2289,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; @@ -2497,9 +2804,6 @@ 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; PropertiesChanged(); @@ -2688,5 +2992,21 @@ 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; + } } } // 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/PlayerSettingsEditor/PlayerSettingsEditor.cs b/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsEditor.cs index db289c1e49..60494d51bd 100644 --- a/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsEditor.cs +++ b/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsEditor.cs @@ -49,8 +49,9 @@ 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."); @@ -217,9 +218,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."); @@ -403,6 +407,7 @@ PlayerSettingsIconsEditor iconsEditor SerializedProperty m_RequireES32; SerializedProperty m_LightmapEncodingQuality; + SerializedProperty m_HDRCubemapEncodingQuality; SerializedProperty m_LightmapStreamingEnabled; SerializedProperty m_LightmapStreamingPriority; @@ -1821,12 +1826,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,46 +1972,72 @@ 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); } } } diff --git a/Editor/Mono/Inspector/StandardShaderGUI.cs b/Editor/Mono/Inspector/StandardShaderGUI.cs index 0a44867c6f..30f0f5f019 100644 --- a/Editor/Mono/Inspector/StandardShaderGUI.cs +++ b/Editor/Mono/Inspector/StandardShaderGUI.cs @@ -90,6 +90,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 +196,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 +252,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 +264,7 @@ bool BlendModePopup() blendMode.floatValue = (float)mode; } - EditorGUI.showMixedValue = false; + MaterialEditor.EndProperty(); return result; } diff --git a/Editor/Mono/MaterialProperty.cs b/Editor/Mono/MaterialProperty.cs index baa88178d4..4c6b853da9 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,598 @@ 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(List properties, Shader shader) + { + foreach (var property in capturedProperties) + { + // fetch default value from 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: + var importer = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(shader)) as ShaderImporter; + if (importer != null) + property.textureValue = importer.GetDefaultTexture(property.name); + else + property.textureValue = null; + 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(""); + + var shader = (targets[0] as Material).shader; + GenericMenu.MenuFunction func = () => ResetMaterialProperties(capturedProperties, shader); + if ((AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(shader)) as ShaderImporter) == null) + { + foreach (var property in capturedProperties) + { + if (property.type == PropType.Texture) + { + func = null; + break; + } + } + } + menu.AddItem(Styles.resetContent, false, func); + } + 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/OrderedCallbackCollection.cs b/Editor/Mono/OrderedCallbackCollection.cs new file mode 100644 index 0000000000..85ae8a7bd8 --- /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/PlayerSettings.bindings.cs b/Editor/Mono/PlayerSettings.bindings.cs index 4b068ba447..eb123cafd1 100644 --- a/Editor/Mono/PlayerSettings.bindings.cs +++ b/Editor/Mono/PlayerSettings.bindings.cs @@ -242,6 +242,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 { @@ -717,6 +725,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); diff --git a/Editor/Mono/Prefabs/PrefabUtility.bindings.cs b/Editor/Mono/Prefabs/PrefabUtility.bindings.cs index a112cc2919..904965241f 100644 --- a/Editor/Mono/Prefabs/PrefabUtility.bindings.cs +++ b/Editor/Mono/Prefabs/PrefabUtility.bindings.cs @@ -362,6 +362,9 @@ 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(); } diff --git a/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs b/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs index dc1eb9741b..51813bb7ac 100644 --- a/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs +++ b/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs @@ -121,7 +121,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 +137,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."); } @@ -775,7 +776,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 +984,11 @@ private void ShowDeveloperMode(string searchContext) { Help.docRedirectionServer = docServer; } + + if (GUILayout.Button(DeveloperModeProperties.generateOnPostprocessAllAssets)) + { + AssetPostprocessingInternal.s_OnPostprocessAllAssetsCallbacks.GenerateDependencyDiagram("OnPostprocessAllAssets.dot"); + } } if (m_DeveloperModeDirty) diff --git a/Editor/Mono/ProjectBrowser.cs b/Editor/Mono/ProjectBrowser.cs index 70e0ae1008..2738f19159 100644 --- a/Editor/Mono/ProjectBrowser.cs +++ b/Editor/Mono/ProjectBrowser.cs @@ -2814,6 +2814,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 +2835,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 +3052,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/ProjectWindow/ProjectWindowUtil.cs b/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs index 72c6ff09eb..fabad4a6a5 100644 --- a/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs +++ b/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs @@ -868,6 +868,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 +961,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 +979,62 @@ 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(); + infotext.AppendLine("One or more files are Materials. Would you like to reparent all their children in project to the closest 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/SceneView/SceneView.cs b/Editor/Mono/SceneView/SceneView.cs index 1c3ebaad00..c24195f5c6 100644 --- a/Editor/Mono/SceneView/SceneView.cs +++ b/Editor/Mono/SceneView/SceneView.cs @@ -3819,12 +3819,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 +3954,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/Shaders/MaterialHierarchyPopup.cs b/Editor/Mono/Shaders/MaterialHierarchyPopup.cs new file mode 100644 index 0000000000..d79a64df51 --- /dev/null +++ b/Editor/Mono/Shaders/MaterialHierarchyPopup.cs @@ -0,0 +1,580 @@ +// 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; + +namespace UnityEditor +{ + class MaterialHierarchyPopup : PopupWindowContent + { + const int k_MaxSearchIterationPerFrame = 500; + + 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, k_MaxNameWidth; + 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_ScrollbarHeight = GUI.skin.horizontalScrollbar.fixedHeight + GUI.skin.horizontalScrollbar.margin.top; + + Object[] targets; + Material target; + GUID targetGUID; + int numRows; + float windowWidth, namesWidth, noResultsX, locksX, overridesX; + + enum ConvertAction { None, Flatten, Convert } + ConvertAction convertState; + + // Children list + bool displayChildren; + ObjectListArea listArea; + int[] results = null; + Delayer debounce; + SearchFilter searchFilter; + string searchFilterString = ""; + Vector2 scroll = Vector2.zero; + IEnumerator enumerator = null; + + + const int k_MinIconSize = 20; + static ObjectListAreaState s_ListAreaState = new ObjectListAreaState() { m_GridSize = 56 }; + + 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 const string materialVariantHierarchyText = "Material Variant Hierarchy"; + + public static readonly GUIContent parentLabel = EditorGUIUtility.TrTextContent("Parent", "The direct parent of the Material."); + 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("Hierarchy of"); + public static readonly GUIContent ancestorLabel = EditorGUIUtility.TrTextContent("Ancestor"); + 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(Object[] targets) + { + this.targets = targets; + target = targets[0] as Material; + targetGUID = AssetDatabase.GUIDFromAssetPath(AssetDatabase.GetAssetPath(target)); + + k_MinNameWidth = k_MinWindowWidth - (k_TitleWidth + k_SplitWidth + k_OverridesWidth + k_LocksWidth); + k_MaxNameWidth = k_MaxWindowWidth - (k_TitleWidth + k_SplitWidth + k_OverridesWidth + k_LocksWidth); + + convertState = ConvertAction.None; + 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 prevWidth = windowWidth; + if (namesWidth <= k_MinNameWidth) + windowWidth = k_MinWindowWidth; + else if (namesWidth >= k_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; + 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 > k_MaxNameWidth) + height += k_ScrollbarHeight; + + // Ancestors table + height += numRows * k_EntryHeight + k_EntryHeight; + } + + if (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(); + } + + if (!DrawHeader()) + return; + + float height = k_HeaderHeight; + if (target.isVariant) + height += DrawVariantHierarchy(); + + height += DrawChildrenLabel(height); + + if (displayChildren) + DrawChildren(height); + + if (convertState == ConvertAction.Flatten) + { + 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 (convertState == ConvertAction.None) + { + labelRect.width = k_ConvertLabelWidth; + int result = EditorGUI.Popup(labelRect, target.isVariant ? 1 : 0, Styles.headerPopupOptions); + if (result == 0 && target.isVariant) + convertState = ConvertAction.Flatten; + if (result == 1 && !target.isVariant) + convertState = ConvertAction.Convert; + } + else if (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)) + convertState = ConvertAction.None; + + 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), targets); + if (EditorGUI.EndChangeCheck() && parent != target.parent) + { + 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); + + // Draw overrides and locks table + int i = numRows; + Material current = target; + while (current != null) + { + 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, k_MaxNameWidth), numRows * k_EntryHeight); + scroll = GUI.BeginScrollView(new Rect(scrollRect) { height = scrollRect.height + k_ScrollbarHeight }, scroll, new Rect(scrollRect) { width = namesWidth }); + + // 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 = (numRows + 1) * k_EntryHeight; + if (namesWidth > k_MaxNameWidth) + height += k_ScrollbarHeight; + + // 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 parent label + labelRect.y = k_HeaderHeight + (numRows - 1) * k_EntryHeight; + GUI.Label(labelRect, Styles.parentLabel); + + // 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); + + 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 = 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() + { + listArea = new ObjectListArea(s_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/artifacts/Stevedore/bee_bcfb/BeeAsALibrary/Bee.BeeDriver.dll b/External/Bee/BeeAsALibrary/Bee.BeeDriver.dll similarity index 74% rename from artifacts/Stevedore/bee_bcfb/BeeAsALibrary/Bee.BeeDriver.dll rename to External/Bee/BeeAsALibrary/Bee.BeeDriver.dll index bfeb6e2168b73c755f0f2dc45b6aa5ca75a42ebf..1766d4c7c84eaf79969bd1393d3c6e545c64bfbb 100644 GIT binary patch delta 15726 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 delta 15610 zcmZXb349b~^7pHHuFhmK)7^9AP9_N<2?UZr?gJ7C0s;{f5Ck;=R3adv92#V2#$Xmf zkT}5L!L@+GiU*5|c!EIwo~(GRpo=653L*&W#Ublk^>j1qzMBv8oBCGObJg>7&*2!{ zW(jVyEMH^}*G>Me{h<^rZQpvmvuhf{H9&tsgnCnZhTE|o!9jffc7)O43@JZ69&>PF zID`S!I~_R{hR{O-3v}C|O5;*I#Q>N)1;Czr3rT*8WZGODUXXUDw}b-uW2E#6GjF9p z_jtF>G^ZpirH{kP@PzciSh{0l`aTO5ho@#|lP=5t9&^I;yU)Xt@Q>X`Q__%}&6IQ| z=cOcU*s*!Quaa~Q!fOW&Q=g<6SbIsH9@6|#0OpDn;fsS(!+BQ)Tr{M@a|Cd?pmOAn z7q7Y|DfUakVF0SRVs+Tnl#NBBlowy z4six;V+zl|uL)ldAGt4?^um2Jao3LK1sPK6>#!Qk5|6pX3h+8SX3*YRqVQ9Jr*K#J znqV2_?S=l5iSjZ{oYz6*rh(5hg*OKC0=wVE{l!GXs8h#E>p_YEBjQ^lEfuK!%fZE;y6PKRz~{G?3|-h*_|=Hq&VZ zNjYt?LZwWzgYLeGXodQIY;P~hp=heDkQ&^aSwVVRRSkJQt7>#~hc(4@&hDVuhLC32 zM$uqv^Cvh*1#S5rs==wA6P#I)V4GsQF8H^!Mn@4G$mgl7w%+VSXbU)Y(2QPnPbK&` zcDnP*>Xr0)&auI`Zmg$O*46%bG_aMXn*oQM^U1!+$rIWc%l_f6sWi-TFGmNI1e_*V zmbj4gox}%8*C#F|ot5|qjpFGRA?-|jf;=yF4{V^P&s^N>mx?E;qX#@wNv{*r`Wtnc z0pYj^X=&`!)NDQZ5#A8dxrNV>=g+yjNY@HoSoAvCAII^M?uhFkdq~{7q_>Jbdr&_J zeO$cy)3Fl~L9y=$Py@b#c0WL#nZ^Gg53dPfGZj1wa!NbNvsSF* zq$6s0y3<@gInD5L$$9d;>ijq9Uz|MYDyP|%0jDzUq^jz1S>bB`L=1Pd zrUZD8k5JcE2-sP(6L{}aO6JoZ-&n)7K_?GzDKpX47r!*8Q5O-ajT;)%vdG>Mm*qlu zI-B(%sTJNTp6@J$0!yyT2g9Xa^UZF5;-rD?ZsC}f0Oo+zJv#a|B%ZU03RG6 z*Y%_s5E6%RL+laPm22U}^dm01L-al(8hk~WAHcV+?r^H`fU`S{q0_)M`;N4j4w@BC zbl*UF#Cjmk3eQ!Xqb2E{!u(&jA4@u2O^;aU<`Zn>TTn0%FP=WNT%zz*)=REY!SBhw zGW=gX|0e&|TGl(0{w@5Rd9=o)j13l-v)1@o>x7+8Hn-)RdRZp_-XzxPsjQ(a*4cen z4+`BMu*%T}2m8fQU{iNiv(T-zY^S8LzOJwi%3>YWpY@2Fby*ebw=t}}5?GJhS(Am% z@w2^4M31Jky`+S-N%U_|3NQ>PV7*r8V$t10Vwin2dETXF1bQ*9~ILIJ2Kf*5X-ht=tm;+)3kKnZ=nsvY%h&xT_<#v&{`XNCJJpJ9qFg{ z1<;n@DnJN-{^z9BGzB3job5o@DbZUx$v@X-hSf!b=>5s{iw2G9r8uB_wrC8OKr}h=vhg0Pp(c=a(ac&Eow?_P&|;+Q}qG$;vhPrjZvqR(h05ta+;fK%xzCc$bWb}D_ek_7vV z*s9_ilw|lw&^NfMxIsyQp9QUwzo@-A9J4qt^=8Enn|kRz&(4{GnNZvN3f&gIW^q|y zhLR2S^p=KK_(9PeB?m@F={_YFUNESjY=+VUK8(^FB_F;t=xE^#r5F4dr8!CgV4>dU z3HMN?5Hh1wNi@))HT{RCC`B+cij_pm4Vs(zKsaMba-dQvh93;ynSM)^67Uu2T|b;! zsgyynpb&hNvs5XE_l?+VNslX)FrD7y@WTGDY=cq-iwwF@I#j8K1bWK@unJtYTZn>! z+F(UmrBVYby$hz8Tv(eKQ2K#iM_5<;EK$7?Z0?__^oKhPI@FV?MNkMXRw49<<$~Ja z>#|CUy=TycvY|>Xd|}Y@)l8QSdP9((-l_4lFQzpp*VV$e2A&|S1G!WL8y$3lJukGBe5OX?X=$H6@Yj_6aZj)zSKo$zl_ zCcrj>e(&3$PK1vQTA6*5dL#U9(A)ND>P?Vesdu`k$2|3B7%r$Cwx!;uHbSsJ$J*hX zw^f}42L-KymwPN$1C!xI6x-D)u%cE^O;lE^w?cKDPS;ngQ=6f908a@Rnt9l3Acs0{1SYEzQy^~G)+v}>!d|$mQ0Lw+N z4PMASsNMy;qx7XZ8-9xty--CNDILNK_Pj(zbriT;pB?Ie_RkL3{1ImJrMq)G8Mg4-c<-I5{wuGQJePHaL?sQM(^tguaAr%0z7;)EYFZ zY@*f%!DwuLO-$+^3kl?OCWFLxk-RsQO8J0ZtjT zs=Lem8o*dRvuFQA_ZzU=Ab(k|dk>Uf&+Wp{&06Z-3tO0Igl^V;PWOJealAmjro$)O zn_Pq42jHh0*tZJu`V9|{e}wxvp~`gryTW(gr8w8^X^#GAOml{V6;+k$oXK zSDQh!LJ$|GCF}|(R|MUMz<;CO>QH$b(YPorb03C%27OV!%KZhW(O-w9q40_DE8oZl*|)kDKw;p8zx zm2Y6T;oC|tl;6M+gFf~%{bZ2W!=yG*V`}}b$jmZmNw>edzk!|xJ!|PPdOg2Fp+P?+ zrg+Z7eFnW+k>$AnUmFzZ(c=CM&I#&(`PF>_p387vp#E0xGWSS-9To~w3~pu8??snE z78H`Rs7RMVG3fcK7Pt%v2EAToA<7WMeU^Lv4Ml?X+NbqU5wKhkZz4hf@Kb>uV)cNX zM)1~5*#qd9%EO1`5{(;r1aYTz6ze8vpZtDSE`7O@Bd9|jr?wFFH0UjliW2tG30A}~ zRT^|-1%gzM)dKgy37Ui!?#4T17kd9>~%ZG^~zbWL=aE?Mvn!r7o^ui#ybV=gIlVRzUF$^Ag+##?Sk~; z$hg9Y-5A5M6Gn^=ij39Mc)Wc;g$sCOJUgAS4KjK+6TLQrDI{mm$&bN92Jv+egU1Ew zTSfie!I}EhVo?^PZ&fS?XL3vo;fck4vvfLMfkcM{=@Y%)fkW=%Umz`24&H^Gy2Fc{`R`~Y&dYP9-}(7P-eTJHae&Xakwsgbai$>_Qc`e4F9c}P9nXB z4hvsCPTVJA`;cqo#It&Tu&P=iGTqI&`;e#XLQT*@I9?I-C|GRJk0nby8jdsQ?UI!q z556y`gD*+?ERNN)xtlh*q;##vhxLM58Ga~-Y-Z$J>77jL40L*!-oXv=cOAi6U1k*g<=swdN0|y)@a8U zL^cL?8p!*bjr#=Y-DKn2MvRBZ#=?7ei6Q7k-=t;ZwSrc`W6CcSyV;Q|P-VCv8o$G`vbM#oBhyEPe(#8A>OJ(4b7Yj3W;Q6JqBJ+N*n2IW6UEoh z`WU>RZv}Ai$DsROUVaB~@yB6?L0tUtSilsZAQ%4z>}LeI_!DuoL0tTsagsq?{6?H- z5Ep+Ewi(35pMon4;^NH(egX4`L7k@6^Z4ej# zUYsjPFK`P6jTjH#g45^g!*faJ<6eWfYWLw;K_PgaXaNqnPtWA~{t1s66yTDEaLWQc z$otfa_CNh+O$)J5PzYAi3Ln6;MvPb3hTrSg5AX^Xp(CWX<5RL2A2Ntf$r5zjugAEC z%W$GW=L1yWhjEL6d}!KnORJvBwOE1M7V5;i9lRpd159-K!{}=qkAl{gbn7dey@vd#cMnUaR>5F(b z;fDtO#T)T%#*s@nvmNT)YrIe5CkDl4Jm-BH`#i)kz9Az#gVlmMfa(-bp239%a-p8Z z{RZ*T+m7G;q2Uhvm!O4mefCS<9e7bE>hv|Dn58^Khq&-wz(j+1Lw911L3~PH#u9_L zYJpu?Zy+CxS8=pKe1zV>Ne1yLc@t+D#9Ofkg9dTA_u?{xcw_eA8iTm_9r(0CT>SsT zT?X-W_jUl^Hjs<|F8;$HUj2J`+#o(QAK;G$@yS1cxJ+L!UwD5EY=d~4KF5WEct>CJ9>Gn5^p~nH z@ttT4;Y&Oys1+WO8k8^bm>~V*`cXU^_(SSZJa43qrPQN%DH^jtCwd>|^>$$by#*vS zXhuyg(OR8i{zhN>cHxso?B{aMe8!0VSXn{zvJqR?d#|?(FFh=--!A;B_catWE$1Qh zQ`&{Gf?8oFO{NPI{}B5MGmO}?Ql;`0*8U;(H4ZUiYbo|M-u8#sF`Q|{KA_k!T(n%g z{^Ba-Z}3e)dJ(_H_x_OiEq-idZpL29xA=uY4(js+ zer-@+>g6PUPn30Kz2D(aBB;M*{0{rHb1nF$`$1UUl{(Bs!XNl|ldu;qKni{1pYWKOI5WHyp&U*&G5u|_kI*Tcfh(5)Kud|qE z5P$eOi$iqc4_^Vo;|B7lue10sLHdWUv-rC~{Nd{?s$uRl1pV=6?^(gsUMN#(sf{g~f?5B^@I7yJ+&9Biq zal>Enc0Ci_oAGJ|&GU>`~Y1nJ#KQiCA98%Y{#Wcp#SPm-DpY9ca8)1#RUKC^Vc zPV)UVDq5w*1~n*SeOBp_fI$7gTBYX%=>uA&SM^kJ5UtW)L9Or^Or(bI{UK(PPW~62 zGcwzxk3BYt>k@=j{<)cxegC0(p6Nb2#jeqVk9y|#9MM!r`jgKUr6>Bd`jjX=+h>ta zjS_ldBzKga^ehXndn(zr(w7qTt?9ou{LWK(lQ;QtqP`REr+s-*y2-cOR~V&tyzlr* zqVz0Ld6b^cJm{;6QmFhhUybBh$qS_a$YSl>F=opW{`#@&#pIZGpqzdqQx1E=mY3$$ zG$X%gpA#RwL&*1A4`@}z5JC@Kb0O5KDj)^0U=Xw;7F+|5iu{v8$M4wn(gzr~0Z*md zO&1`wdoFAZhhNUB*^a}@Q%u`2DLd2j4n7x?L($mAjJ`pi?{P5<91C#4|8yxp`&%QkPt6LL@U_VCJGx76&y;na2)Wfq!O!0FV( z<~`U(o;~D|Eaz~MhVTR|(uyHQ+85KqvI+i@S`5unS4p|0Ib8Nioeib4H8)sd!hu)v z%0|)RAF&ik6XnNAf3dYdk;F4UXG*KwU_t56(zL7p7d?J0<82sm9(v3nl&R_@oE<4g>QT{FFj8>lf=<8Nfx08tQpch-9IM% zJhvNVZhmzE^^^5lUZ6?h&1;h8r(Uu(NryZN*(*vFn1!k7AU8?pYuG=yH)|hZYeG-< z=gin5n_Ohh%8rwpML*5bqMmMQ^&YLINop%qu}d0Lo+MY8zm3nNocYzcP-iZsugqpi z^`y-XsikKQ1)is6G>1QX?Yf>ZqN5l}MaYmA7i^F-q(2q3ko~yOvcBvYTS6Kh^ZF0A zp;XF^_VM9iZxrCU@ccLEO<1_<&4MiUABp8ce;d2O>?$}D%jNhfcB^zQ-Xb-d&m@_{ zOW%wSR5?_<+c(X+)qJme7}yS2A9eIjPPtHmEmOo%Gm{* z0`&B{2z&rfQs5KO(IGL|KWTUkjtEaDSSWf@*r!RIbPoW~=*w81L^iS(k;*U>dFsui zPI!l2zBKqiL=Ou+F7z}h^+3^p1{VZe7O8O(cjOhCDl}VYFH$EAqC1ubLxnv;*rS9! zUf4GZdx5Y+WXo_u==ah|>hm<&c1SVVxwjVrnBjWLiS0hfSNv{-y zX%!439SGw{hrunR*9rdw(t$9Uj_N>qfja4!EbPg`4#9l#w8AvVOl*a_$=*nVQ6{oZ z7TU@haBRTG$+bslmr#`0W0GiX4ztusI#Ae65)T#<+A48J8-%@4`Vk5e_XxX7XoiV1 z>x2$8Z6v!%*dd`)%-rWj3-=J%YvI%kD{GU`y+XehYO;x_&@7>KLI(eSsH~l2~SYiAz`l<_6A|^ z7IvplaB?fZ&|0DO&Qs)BFYMhyn_S%gLYMHnIDe08a)6$?T)f}ML;w{|S}%0BP*B<9 z7rI_(r_gR1cQi%lKB3?inL-1Z+9)(AbiL5s zLOX?a`FI$J=T;ccb})f;z0hEyu!ZhUV!Kl)B(v=ox?bpR)_|i^Krlr#6S`Yyr%*^0 z&4ktpZ4|m&Xs1v}w2NPg?0*sUZQfnIH$9mwYKJK(ng`{h3*#GDHQr~)Grk3INv`~JarCWfPt*L zuVU>K>XEj@`J`=eiRS0xa_G^M9)0OiL616m45r60dNk2vo_TxRz4Qjs_V#lbt7Esu zZjap+`(fCK|Ln5cy606m@bWpF@*EhRZ*mt^LX>J9o9my(^{t2XMHQ8tL|I{9kSYs7@~ z9!85!bFv%ApOZbBbbP;wq+Llox%YeUr1P~#^8Bc={YvTdc>1y>w`dl*wy5(-z13Wa z_Y%0l_Wo=?<7WNL&zk6AJs>;}cMDSf-z;sUf3NpGxNkb#zNd58BvT$_WkGviyyS$o}IIbu~;JilciXma!}HOpUT<- zEqIu$Jt^9~l9Z|ikI0zgs8bXm_;Z||figPa0v%UZ_-M~9oZ6OJ66}dmlG`LgJ zLO(N>h7xJ+vLefVq|vEEE3jd=(oc6&sXTVx$jj*T*=({Ja;PD(m63B5nBwKhj;3rg z{XZX=qDZ3nk<3cW3Q$k{&t@~Fnk02dADhC@!bf9Jm-}f}S~Yo6O{w|_+?ob*l1~dh zBb)qS*XI()tKxj#JIWw0B{;c=sU}&bXPKYVY&L4@RYR$$20}Dh{Lh4GSDi6_5J5M{ zdQwO>(>~i{K)NzBn@OTj1|`!|Ha*3PHW{?5ZnPO8-IpEpah};v39&XQ)o9mOj-kTo zeb6@+vWe_G~hti>Zn(Vl7n8J3xja&e3W*uG*AXn@J0<)#L*+CSn-NnD-r=S#50!JX2lQqf zSXhHO4w^}Di_gZ#VSi+24W?#LYH*8%mPngxGj2VK%|eUULiHl;=Njzo-A}WlMT;)o z5NG2{dVi$6AEwf6a%?~BJ(LzeEmTEZ|AXkNqzt}7`C1OuyKELUG+YaARzoA{3aS^? z%;VD1$&2%w(W&0wOTE8H4Q`Ko(2xGFzYCF{`{Bd6p*pjTPKm`vW#vOBJ0)vEk}cMm zs?qZKAMK$UTpD?5ZRpx!QZ8fhMYj+8ZY%b^z%y5^0<6iQU=0wV1La6lhAAfa+{ z-YYVQ6oQHLXuML67|!N2MYkfBff&?Ec$)q=id{L*k;p*2AD`KoRFBILk4Lr+!ep)j z-Gn558KR3O#px8sMStm_d!~(UDD-M6(M>_CcKGS`XS_N&oqX%nf)_;foK8<6n&{=n zIAVsAbN-WJ-pBES5~*Uv~)FS)pU*1m9Ev(D-*5NtJU+I z%2|f%u0*X|Pbqi#O=>M2<12IJiPv5MBY5RymTnVqh-xkOAsR)xu0n5s`q#?o&4NA@ ztsShoX{Xyu68-v2Ou^1~c>SNTWN`xw(XC5$>W!}q8wX^JXrUUqjqsjCvaZH$@&;-u+I@C4PRc?{ zF1=h^NNGW4nYSl5x;?^*i%cAbd73^`b1tMhMMm)C&d4y_j|udJ-0f#t9swLNlD-5J zzqK1q|CjOb$gB|ScREMnMUzxsy;B{H KKVW;I?f(J9h1+KU diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/BaseSlider.cs b/ModuleOverrides/com.unity.ui/Core/Controls/BaseSlider.cs index 147cd7438c..51c1d1bbad 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/BaseSlider.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/BaseSlider.cs @@ -26,7 +26,7 @@ public enum SliderDirection /// /// This is a base class for the Slider fields. /// - public abstract class BaseSlider : BaseField + public abstract class BaseSlider : BaseField, IValueField where TValueType : System.IComparable { internal VisualElement dragContainer { get; private set; } @@ -172,6 +172,24 @@ public override TValueType value } } + /// + /// Called when the user is dragging the label to update the value contained in the field. + /// + /// Delta on the move. + /// Speed of the move. + /// Starting value. + public virtual void ApplyInputDeviceDelta(Vector3 delta, DeltaSpeed speed, TValueType startValue) {} + + /// + /// Method called by the application when the label of the field is started to be dragged to change the value of it. + /// + void IValueField.StartDragging() {} + + /// + /// Method called by the application when the label of the field is stopped to be dragged to change the value of it. + /// + void IValueField.StopDragging() {} + public override void SetValueWithoutNotify(TValueType newValue) { // Clamp the value around the real lowest and highest range values. @@ -309,6 +327,10 @@ internal BaseSlider(string label, TValueType start, TValueType end, SliderDirect RegisterCallback(OnKeyDown); UpdateTextFieldVisibility(); + + var mouseDragger = new FieldMouseDragger(this); + mouseDragger.SetDragZone(labelElement); + labelElement.AddToClassList(labelDraggerVariantUssClassName); } /// diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/BaseVerticalCollectionView.cs b/ModuleOverrides/com.unity.ui/Core/Controls/BaseVerticalCollectionView.cs index 496ac10011..300172f20b 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/BaseVerticalCollectionView.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/BaseVerticalCollectionView.cs @@ -1004,8 +1004,11 @@ void HandleSelectionAndScroll(int index) ClearSelection(); return true; case KeyboardNavigationOperation.Submit: - onItemsChosen?.Invoke(m_SelectedItems); - ScrollToItem(selectedIndex); + if (m_SelectionType != SelectionType.None) + { + onItemsChosen?.Invoke(m_SelectedItems); + ScrollToItem(selectedIndex); + } return true; case KeyboardNavigationOperation.Previous: if (selectedIndex > 0) diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/ScrollView.cs b/ModuleOverrides/com.unity.ui/Core/Controls/ScrollView.cs index a8ee8bd1e3..f7ee2407aa 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/ScrollView.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/ScrollView.cs @@ -1147,18 +1147,8 @@ void OnPointerDown(PointerDownEvent evt) var touchStopsVelocityOnly = Mathf.Abs(m_Velocity.x) > 10 || Mathf.Abs(m_Velocity.y) > 10; m_ScrollingPointerId = evt.pointerId; - m_PointerStartPosition = evt.position; - m_StartPosition = scrollOffset; m_StartedMoving = false; - m_Velocity = Vector2.zero; - m_SpringBackVelocity = Vector2.zero; - - m_LowBounds = new Vector2( - Mathf.Min(horizontalScroller.lowValue, horizontalScroller.highValue), - Mathf.Min(verticalScroller.lowValue, verticalScroller.highValue)); - m_HighBounds = new Vector2( - Mathf.Max(horizontalScroller.lowValue, horizontalScroller.highValue), - Mathf.Max(verticalScroller.lowValue, verticalScroller.highValue)); + InitTouchScrolling(evt.position); if (touchStopsVelocityOnly) { @@ -1186,16 +1176,64 @@ void OnPointerMove(PointerMoveEvent evt) m_StartedMoving = true; + var scrollOffsetChanged = ComputeTouchScrolling(evt.position); + + if (scrollOffsetChanged) + { + evt.isHandledByDraggable = true; + contentContainer.CapturePointer(evt.pointerId); + evt.StopPropagation(); + } + else + { + m_Velocity = Vector2.zero; + } + } + + void OnPointerCancel(PointerCancelEvent evt) + { + ReleaseScrolling(evt.pointerId, evt.target); + } + + void OnPointerUp(PointerUpEvent evt) + { + if (ReleaseScrolling(evt.pointerId, evt.target)) + { + contentContainer.panel.PreventCompatibilityMouseEvents(evt.pointerId); + evt.StopPropagation(); + } + } + + // Internal for tests. + internal void InitTouchScrolling(Vector2 position) + { + m_PointerStartPosition = position; + m_StartPosition = scrollOffset; + m_Velocity = Vector2.zero; + m_SpringBackVelocity = Vector2.zero; + + m_LowBounds = new Vector2( + Mathf.Min(horizontalScroller.lowValue, horizontalScroller.highValue), + Mathf.Min(verticalScroller.lowValue, verticalScroller.highValue)); + m_HighBounds = new Vector2( + Mathf.Max(horizontalScroller.lowValue, horizontalScroller.highValue), + Mathf.Max(verticalScroller.lowValue, verticalScroller.highValue)); + } + + // Internal for tests. + internal bool ComputeTouchScrolling(Vector2 position) + { + // Calculate offset based on touch scroll behavior. Vector2 newScrollOffset; if (touchScrollBehavior == TouchScrollBehavior.Clamped) { - newScrollOffset = m_StartPosition - (new Vector2(evt.position.x, evt.position.y) - m_PointerStartPosition); + newScrollOffset = m_StartPosition - (new Vector2(position.x, position.y) - m_PointerStartPosition); newScrollOffset = Vector2.Max(newScrollOffset, m_LowBounds); newScrollOffset = Vector2.Min(newScrollOffset, m_HighBounds); } else if (touchScrollBehavior == TouchScrollBehavior.Elastic) { - Vector2 deltaPointer = new Vector2(evt.position.x, evt.position.y) - m_PointerStartPosition; + Vector2 deltaPointer = new Vector2(position.x, position.y) - m_PointerStartPosition; newScrollOffset.x = ComputeElasticOffset(deltaPointer.x, m_StartPosition.x, m_LowBounds.x, m_LowBounds.x - contentViewport.resolvedStyle.width, m_HighBounds.x, m_HighBounds.x + contentViewport.resolvedStyle.width); @@ -1205,9 +1243,15 @@ void OnPointerMove(PointerMoveEvent evt) } else { - newScrollOffset = m_StartPosition - (new Vector2(evt.position.x, evt.position.y) - m_PointerStartPosition); + newScrollOffset = m_StartPosition - (new Vector2(position.x, position.y) - m_PointerStartPosition); } + // Cancel opposite axis if mode is set to only a single direction. + if (mode == ScrollViewMode.Vertical) + newScrollOffset.x = m_LowBounds.x; + else if (mode == ScrollViewMode.Horizontal) + newScrollOffset.y = m_LowBounds.y; + if (hasInertia) { // Reset velocity if we reached bounds. @@ -1215,7 +1259,7 @@ void OnPointerMove(PointerMoveEvent evt) { m_Velocity = Vector2.zero; scrollOffset = newScrollOffset; - return; // We don't want to stop propagation, to allow nested draggables to respond. + return false; // We don't want to stop propagation, to allow nested draggables to respond. } // Account for idle pointer time. @@ -1235,30 +1279,7 @@ void OnPointerMove(PointerMoveEvent evt) var scrollOffsetChanged = scrollOffset != newScrollOffset; scrollOffset = newScrollOffset; - if (scrollOffsetChanged) - { - evt.isHandledByDraggable = true; - contentContainer.CapturePointer(evt.pointerId); - evt.StopPropagation(); - } - else - { - m_Velocity = Vector2.zero; - } - } - - void OnPointerCancel(PointerCancelEvent evt) - { - ReleaseScrolling(evt.pointerId, evt.target); - } - - void OnPointerUp(PointerUpEvent evt) - { - if (ReleaseScrolling(evt.pointerId, evt.target)) - { - contentContainer.panel.PreventCompatibilityMouseEvents(evt.pointerId); - evt.StopPropagation(); - } + return scrollOffsetChanged; } bool ReleaseScrolling(int pointerId, IEventHandler target) diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/Slider.cs b/ModuleOverrides/com.unity.ui/Core/Controls/Slider.cs index fe671b5668..fd1c258bed 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/Slider.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/Slider.cs @@ -88,6 +88,17 @@ public Slider(string label, float start = 0, float end = kDefaultHighValue, Slid visualInput.AddToClassList(inputUssClassName); } + /// + public override void ApplyInputDeviceDelta(Vector3 delta, DeltaSpeed speed, float startValue) + { + double sensitivity = NumericFieldDraggerUtility.CalculateFloatDragSensitivity(startValue); + float acceleration = NumericFieldDraggerUtility.Acceleration(speed == DeltaSpeed.Fast, speed == DeltaSpeed.Slow); + double v = value; + + v += NumericFieldDraggerUtility.NiceDelta(delta, acceleration) * sensitivity; + value = (float)v; + } + internal override float SliderLerpUnclamped(float a, float b, float interpolant) { var newValue = Mathf.LerpUnclamped(a, b, interpolant); diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/SliderInt.cs b/ModuleOverrides/com.unity.ui/Core/Controls/SliderInt.cs index b48a1335bf..df4a7ec3fa 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/SliderInt.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/SliderInt.cs @@ -108,6 +108,17 @@ public override float pageSize set { base.pageSize = Mathf.RoundToInt(value); } } + /// + public override void ApplyInputDeviceDelta(Vector3 delta, DeltaSpeed speed, int startValue) + { + double sensitivity = NumericFieldDraggerUtility.CalculateIntDragSensitivity(startValue); + float acceleration = NumericFieldDraggerUtility.Acceleration(speed == DeltaSpeed.Fast, speed == DeltaSpeed.Slow); + long v = value; + + v += (long)Math.Round(NumericFieldDraggerUtility.NiceDelta(delta, acceleration) * sensitivity); + value = (int)v; + } + internal override int SliderLerpUnclamped(int a, int b, float interpolant) { return Mathf.RoundToInt(Mathf.LerpUnclamped((float)a, (float)b, interpolant)); diff --git a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRRenderChain.cs b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRRenderChain.cs index cde3841359..45603edc25 100644 --- a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRRenderChain.cs +++ b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRRenderChain.cs @@ -492,7 +492,7 @@ public void Render() public void UIEOnChildAdded(VisualElement ve) { VisualElement parent = ve.hierarchy.parent; - int index = parent != null ? parent.IndexOf(ve) : 0; + int index = parent != null ? parent.hierarchy.IndexOf(ve) : 0; if (m_BlockDirtyRegistration) throw new InvalidOperationException("VisualElements cannot be added to an active visual tree during generateVisualContent callback execution nor during visual tree rendering"); diff --git a/ModuleOverrides/com.unity.ui/Core/StyleSheets/Validation/StyleMatcher.cs b/ModuleOverrides/com.unity.ui/Core/StyleSheets/Validation/StyleMatcher.cs index 3069a79f4a..3257ec1eba 100644 --- a/ModuleOverrides/com.unity.ui/Core/StyleSheets/Validation/StyleMatcher.cs +++ b/ModuleOverrides/com.unity.ui/Core/StyleSheets/Validation/StyleMatcher.cs @@ -743,7 +743,7 @@ protected override bool MatchResource() protected override bool MatchUrl() { - return current.handle.valueType == StyleValueType.AssetReference; + return current.handle.valueType is StyleValueType.AssetReference or StyleValueType.ScalableImage; } protected override bool MatchTime() diff --git a/ModuleOverrides/com.unity.ui/Editor/StyleSheets/StyleSheetImporterImpl.cs b/ModuleOverrides/com.unity.ui/Editor/StyleSheets/StyleSheetImporterImpl.cs index 7166c317e4..de46dbb7f4 100644 --- a/ModuleOverrides/com.unity.ui/Editor/StyleSheets/StyleSheetImporterImpl.cs +++ b/ModuleOverrides/com.unity.ui/Editor/StyleSheets/StyleSheetImporterImpl.cs @@ -36,7 +36,6 @@ abstract class StyleValueImporter protected readonly StyleValidator m_Validator; protected string m_AssetPath; protected int m_CurrentLine; - protected string m_CurrentPropertyName; public StyleValueImporter(UnityEditor.AssetImporters.AssetImportContext context) { @@ -481,7 +480,7 @@ protected void VisitUrlFunction(PrimitiveTerm term) if (!disableValidation) { - var propertyName = new StylePropertyName(m_CurrentPropertyName); + var propertyName = new StylePropertyName(m_Builder.currentProperty.name); // Unknown properties (not custom) should beforehand if (propertyName.id == StylePropertyId.Unknown) @@ -1022,7 +1021,6 @@ void VisitSheet(ParserStyleSheet styleSheet) foreach (Property property in rule.Declarations) { m_CurrentLine = property.Line; - m_CurrentPropertyName = property.Name; ValidateProperty(property); diff --git a/Modules/PackageManagerUI/Editor/Services/Common/UIError.cs b/Modules/PackageManagerUI/Editor/Services/Common/UIError.cs index 31e642d29d..e21475abc4 100644 --- a/Modules/PackageManagerUI/Editor/Services/Common/UIError.cs +++ b/Modules/PackageManagerUI/Editor/Services/Common/UIError.cs @@ -10,7 +10,7 @@ namespace UnityEditor.PackageManager.UI.Internal [Serializable] internal class UIError : Error { - private static readonly string k_EntitlementErrorMessage = L10n.Tr("This package is not available to use because there is no license registered for your user. Please sign in with a licensed account. If the problem persists, please contact your administrator."); + private static readonly string k_EntitlementErrorMessage = L10n.Tr("This package is not available to use because there is no license registered for your user. If you believe you have permission to use this package, refresh your license in the license management window of Unity Hub. Otherwise, contact your administrator."); internal static readonly string k_InvalidSignatureWarningMessage = L10n.Tr("This package version doesn't have a valid signature. For your security, install a different version or report a bug to Unity."); internal static readonly string k_UnsignedUnityPackageWarningMessage = L10n.Tr("This package version has no signature. For your security, install a different version or review your scoped registry and load the package from the Unity registry."); internal static readonly string k_readMoreDocsUrl = "https://docs.unity3d.com/Manual/upm-errors.html"; diff --git a/Modules/PackageManagerUI/Editor/UI/PackageDetailsHeader.cs b/Modules/PackageManagerUI/Editor/UI/PackageDetailsHeader.cs index 8d90d558cf..153f5f4bbc 100644 --- a/Modules/PackageManagerUI/Editor/UI/PackageDetailsHeader.cs +++ b/Modules/PackageManagerUI/Editor/UI/PackageDetailsHeader.cs @@ -76,6 +76,7 @@ public PackageDetailsHeader() Add(root); cache = new VisualElementCache(root); + m_PageManager.onVisualStateChange += OnVisualStateChange; detailAuthorLink.clickable.clicked += AuthorClick; scopedRegistryInfoBox.Q