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..453242a73c 100644 --- a/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs +++ b/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using UnityEditor; +using UnityEditor.Build; using UnityEditor.Modules; using UnityEditorInternal; using UnityEngine; @@ -25,11 +26,7 @@ protected virtual string GetVariationName(BuildPostProcessArgs args) } protected bool GetServer(BuildPostProcessArgs args) => - (args.target == BuildTarget.StandaloneWindows || - args.target == BuildTarget.StandaloneWindows64 || - args.target == BuildTarget.StandaloneOSX || - args.target == BuildTarget.StandaloneLinux64) && - (StandaloneBuildSubtarget)args.subtarget == StandaloneBuildSubtarget.Server; + GetNamedBuildTarget(args) == NamedBuildTarget.Server; protected string GetVariationFolder(BuildPostProcessArgs args) => $"{args.playerPackage}/Variations/{GetVariationName(args)}"; @@ -41,7 +38,7 @@ public override void UpdateBootConfig(BuildTarget target, BootConfigData config, config.AddKey("single-instance"); if (!PlayerSettings.useFlipModelSwapchain) config.AddKey("force-d3d11-bitblt-mode"); - if (IL2CPPUtils.UseIl2CppCodegenWithMonoBackend(BuildPipeline.GetBuildTargetGroup(target))) + if (IL2CPPUtils.UseIl2CppCodegenWithMonoBackend(NamedBuildTarget.FromActiveSettings(target))) config.Set("mono-codegen", "il2cpp"); if ((options & BuildOptions.EnableCodeCoverage) != 0) config.Set("enableCodeCoverage", "1"); @@ -56,26 +53,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 namedBuildTarget = NamedBuildTarget.FromActiveSettings(target); + var isServer = namedBuildTarget == NamedBuildTarget.Server; + + if ((!isServer && !m_HasMonoPlayers) || (isServer && !m_HasServerMonoPlayers)) { - var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(target); - if (PlayerSettings.GetScriptingBackend(buildTargetGroup) != ScriptingImplementation.IL2CPP) + if (PlayerSettings.GetScriptingBackend(namedBuildTarget) != 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) + if (PlayerSettings.GetScriptingBackend(namedBuildTarget) == ScriptingImplementation.IL2CPP) return "Currently selected scripting backend (IL2CPP) is not installed."; } diff --git a/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs b/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs index e834c5bc91..c6ade887e6 100644 --- a/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs +++ b/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs @@ -275,21 +275,21 @@ internal static IIl2CppPlatformProvider PlatformProviderForNotModularPlatform(Bu internal static IL2CPPBuilder RunIl2Cpp(string tempFolder, string stagingAreaData, IIl2CppPlatformProvider platformProvider, Action modifyOutputBeforeCompile, RuntimeClassRegistry runtimeClassRegistry) { - var builder = new IL2CPPBuilder(tempFolder, stagingAreaData, platformProvider, modifyOutputBeforeCompile, runtimeClassRegistry, IL2CPPUtils.UseIl2CppCodegenWithMonoBackend(BuildPipeline.GetBuildTargetGroup(platformProvider.target))); + var builder = new IL2CPPBuilder(tempFolder, stagingAreaData, platformProvider, modifyOutputBeforeCompile, runtimeClassRegistry, IL2CPPUtils.UseIl2CppCodegenWithMonoBackend(platformProvider.namedBuildTarget)); builder.Run(); return builder; } internal static IL2CPPBuilder RunIl2Cpp(string stagingAreaData, IIl2CppPlatformProvider platformProvider, Action modifyOutputBeforeCompile, RuntimeClassRegistry runtimeClassRegistry) { - var builder = new IL2CPPBuilder(stagingAreaData, stagingAreaData, platformProvider, modifyOutputBeforeCompile, runtimeClassRegistry, IL2CPPUtils.UseIl2CppCodegenWithMonoBackend(BuildPipeline.GetBuildTargetGroup(platformProvider.target))); + var builder = new IL2CPPBuilder(stagingAreaData, stagingAreaData, platformProvider, modifyOutputBeforeCompile, runtimeClassRegistry, IL2CPPUtils.UseIl2CppCodegenWithMonoBackend(platformProvider.namedBuildTarget)); builder.Run(); return builder; } internal static IL2CPPBuilder RunCompileAndLink(string tempFolder, string stagingAreaData, IIl2CppPlatformProvider platformProvider, Action modifyOutputBeforeCompile, RuntimeClassRegistry runtimeClassRegistry, string il2cppBuildCacheSource) { - var builder = new IL2CPPBuilder(tempFolder, stagingAreaData, platformProvider, modifyOutputBeforeCompile, runtimeClassRegistry, IL2CPPUtils.UseIl2CppCodegenWithMonoBackend(BuildPipeline.GetBuildTargetGroup(platformProvider.target))); + var builder = new IL2CPPBuilder(tempFolder, stagingAreaData, platformProvider, modifyOutputBeforeCompile, runtimeClassRegistry, IL2CPPUtils.UseIl2CppCodegenWithMonoBackend(platformProvider.namedBuildTarget)); builder.RunCompileAndLink(il2cppBuildCacheSource); return builder; } @@ -338,18 +338,18 @@ internal static string ApiCompatibilityLevelToDotNetProfileArgument(ApiCompatibi } } - internal static bool UseIl2CppCodegenWithMonoBackend(BuildTargetGroup targetGroup) + internal static bool UseIl2CppCodegenWithMonoBackend(NamedBuildTarget namedBuildTarget) { return EditorApplication.useLibmonoBackendForIl2cpp && - PlayerSettings.GetScriptingBackend(targetGroup) == ScriptingImplementation.IL2CPP; + PlayerSettings.GetScriptingBackend(namedBuildTarget) == ScriptingImplementation.IL2CPP; } - internal static bool EnableIL2CPPDebugger(IIl2CppPlatformProvider provider, BuildTargetGroup targetGroup) + internal static bool EnableIL2CPPDebugger(IIl2CppPlatformProvider provider) { if (!provider.allowDebugging || !provider.development) return false; - switch (PlayerSettings.GetApiCompatibilityLevel(targetGroup)) + switch (PlayerSettings.GetApiCompatibilityLevel(provider.namedBuildTarget)) { case ApiCompatibilityLevel.NET_Unity_4_8: case ApiCompatibilityLevel.NET_Standard: @@ -360,10 +360,10 @@ internal static bool EnableIL2CPPDebugger(IIl2CppPlatformProvider provider, Buil } } - internal static string[] GetBuilderDefinedDefines(IIl2CppPlatformProvider il2cppPlatformProvider, BuildTargetGroup buildTargetGroup) + internal static string[] GetBuilderDefinedDefines(IIl2CppPlatformProvider il2cppPlatformProvider) { List defines = new List(); - var apiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(buildTargetGroup); + var apiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(il2cppPlatformProvider.namedBuildTarget); switch (apiCompatibilityLevel) { @@ -398,7 +398,7 @@ internal static string[] GetBuilderDefinedDefines(IIl2CppPlatformProvider il2cpp } } - if (EnableIL2CPPDebugger(il2cppPlatformProvider, buildTargetGroup)) + if (EnableIL2CPPDebugger(il2cppPlatformProvider)) defines.Add("IL2CPP_MONO_DEBUGGER=1"); if (BuildPipeline.IsFeatureSupported("ENABLE_SCRIPTING_GC_WBARRIERS", target)) @@ -424,21 +424,21 @@ internal static string[] GetBuilderDefinedDefines(IIl2CppPlatformProvider il2cpp return defines.ToArray(); } - internal static string[] GetDebuggerIL2CPPArguments(IIl2CppPlatformProvider il2cppPlatformProvider, BuildTargetGroup buildTargetGroup) + internal static string[] GetDebuggerIL2CPPArguments(IIl2CppPlatformProvider il2cppPlatformProvider) { var arguments = new List(); - if (EnableIL2CPPDebugger(il2cppPlatformProvider, buildTargetGroup)) + if (EnableIL2CPPDebugger(il2cppPlatformProvider)) arguments.Add("--enable-debugger"); return arguments.ToArray(); } - internal static string[] GetBuildingIL2CPPArguments(IIl2CppPlatformProvider il2cppPlatformProvider, BuildTargetGroup buildTargetGroup) + internal static string[] GetBuildingIL2CPPArguments(IIl2CppPlatformProvider il2cppPlatformProvider) { // When changing this function, don't forget to change GetBuilderDefinedDefines! var arguments = new List(); - var apiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(buildTargetGroup); + var apiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(il2cppPlatformProvider.namedBuildTarget); if (BuildPipeline.IsFeatureSupported("ENABLE_SCRIPTING_GC_WBARRIERS", il2cppPlatformProvider.target)) { @@ -611,9 +611,7 @@ public void Run() // Make all assemblies in Staging/Managed writable for stripping. ClearReadOnlyFlagOnAllFilesNonRecursively(managedDir); - var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(m_PlatformProvider.target); - - var managedStrippingLevel = PlayerSettings.GetManagedStrippingLevel(buildTargetGroup); + var managedStrippingLevel = PlayerSettings.GetManagedStrippingLevel(m_PlatformProvider.namedBuildTarget); // IL2CPP does not support a managed stripping level of disabled. If the player settings // do try this (which should not be possible from the editor), use Low instead. @@ -667,23 +665,22 @@ public void RunCompileAndLink(string il2cppBuildCacheSource) Directory.CreateDirectory(buildCacheDirectory); var buildCacheNativeOutputFile = Path.Combine(GetNativeOutputRelativeDirectory(buildCacheDirectory), m_PlatformProvider.nativeLibraryFileName); - var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(m_PlatformProvider.target); - var compilerConfiguration = PlayerSettings.GetIl2CppCompilerConfiguration(buildTargetGroup); + var compilerConfiguration = PlayerSettings.GetIl2CppCompilerConfiguration(m_PlatformProvider.namedBuildTarget); var arguments = Il2CppNativeCodeBuilderUtils.AddBuilderArguments(il2CppNativeCodeBuilder, buildCacheNativeOutputFile, m_PlatformProvider.includePaths, m_PlatformProvider.libraryPaths, compilerConfiguration).ToList(); var additionalArgs = IL2CPPUtils.GetAdditionalArguments(); if (!string.IsNullOrEmpty(additionalArgs)) arguments.Add(additionalArgs); - foreach (var buildingArgument in IL2CPPUtils.GetBuildingIL2CPPArguments(m_PlatformProvider, buildTargetGroup)) + foreach (var buildingArgument in IL2CPPUtils.GetBuildingIL2CPPArguments(m_PlatformProvider)) { if (!arguments.Contains(buildingArgument)) arguments.Add(buildingArgument); } arguments.Add($"--generatedcppdir={CommandLineFormatter.PrepareFileName(GetCppOutputDirectory(il2cppBuildCacheSource))}"); - arguments.Add($"--dotnetprofile=\"{IL2CPPUtils.ApiCompatibilityLevelToDotNetProfileArgument(PlayerSettings.GetApiCompatibilityLevel(buildTargetGroup), m_PlatformProvider.target)}\""); - arguments.AddRange(IL2CPPUtils.GetDebuggerIL2CPPArguments(m_PlatformProvider, buildTargetGroup)); + arguments.Add($"--dotnetprofile=\"{IL2CPPUtils.ApiCompatibilityLevelToDotNetProfileArgument(PlayerSettings.GetApiCompatibilityLevel(m_PlatformProvider.namedBuildTarget), m_PlatformProvider.target)}\""); + arguments.AddRange(IL2CPPUtils.GetDebuggerIL2CPPArguments(m_PlatformProvider)); Action setupStartInfo = il2CppNativeCodeBuilder.SetupStartInfo; RunIl2CppWithArguments(arguments, setupStartInfo); @@ -729,12 +726,10 @@ private void ConvertPlayerDlltoCpp(Il2CppBuildPipelineData data) if (m_BuildForMonoRuntime) arguments.Add("--mono-runtime"); - var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(m_PlatformProvider.target); - var apiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(buildTargetGroup); + var apiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(m_PlatformProvider.namedBuildTarget); arguments.Add(string.Format("--dotnetprofile=\"{0}\"", IL2CPPUtils.ApiCompatibilityLevelToDotNetProfileArgument(apiCompatibilityLevel, m_PlatformProvider.target))); - var namedBuildTarget = NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup); - var il2cppCodeGeneration = PlayerSettings.GetIl2CppCodeGeneration(namedBuildTarget); + var il2cppCodeGeneration = PlayerSettings.GetIl2CppCodeGeneration(m_PlatformProvider.namedBuildTarget); if (il2cppCodeGeneration == Il2CppCodeGeneration.OptimizeSize) arguments.Add("--generics-option=EnableFullSharing"); @@ -742,7 +737,7 @@ private void ConvertPlayerDlltoCpp(Il2CppBuildPipelineData data) if (il2CppNativeCodeBuilder != null) { var buildCacheNativeOutputFile = Path.Combine(GetNativeOutputRelativeDirectory(m_PlatformProvider.il2cppBuildCacheDirectory), m_PlatformProvider.nativeLibraryFileName); - var compilerConfiguration = PlayerSettings.GetIl2CppCompilerConfiguration(buildTargetGroup); + var compilerConfiguration = PlayerSettings.GetIl2CppCompilerConfiguration(m_PlatformProvider.namedBuildTarget); Il2CppNativeCodeBuilderUtils.ClearAndPrepareCacheDirectory(il2CppNativeCodeBuilder); arguments.AddRange(Il2CppNativeCodeBuilderUtils.AddBuilderArguments(il2CppNativeCodeBuilder, buildCacheNativeOutputFile, m_PlatformProvider.includePaths, m_PlatformProvider.libraryPaths, compilerConfiguration)); } @@ -755,8 +750,8 @@ private void ConvertPlayerDlltoCpp(Il2CppBuildPipelineData data) foreach (var additionalCppFile in Directory.GetFiles(GetAdditionalCppFilesDirectory(m_PlatformProvider.il2cppBuildCacheDirectory))) arguments.Add($"--additional-cpp={CommandLineFormatter.PrepareFileName(GetShortPathName(Path.GetFullPath(additionalCppFile)))}"); - arguments.AddRange(IL2CPPUtils.GetDebuggerIL2CPPArguments(m_PlatformProvider, buildTargetGroup)); - foreach (var buildingArgument in IL2CPPUtils.GetBuildingIL2CPPArguments(m_PlatformProvider, buildTargetGroup)) + arguments.AddRange(IL2CPPUtils.GetDebuggerIL2CPPArguments(m_PlatformProvider)); + foreach (var buildingArgument in IL2CPPUtils.GetBuildingIL2CPPArguments(m_PlatformProvider)) { if (!arguments.Contains(buildingArgument)) arguments.Add(buildingArgument); @@ -876,6 +871,7 @@ private string GetMonoBleedingEdgeExe() internal interface IIl2CppPlatformProvider { BuildTarget target { get; } + NamedBuildTarget namedBuildTarget { get; } bool emitNullChecks { get; } bool enableStackTraces { get; } bool enableArrayBoundsCheck { get; } @@ -907,6 +903,7 @@ public BaseIl2CppPlatformProvider(BuildTarget target, string libraryFolder, Buil string baselibLibraryDirectory) { this.target = target; + this.namedBuildTarget = NamedBuildTarget.FromActiveSettings(target); this.libraryFolder = libraryFolder; this.buildReport = buildReport; _baselibLibraryDirectory = baselibLibraryDirectory; @@ -914,6 +911,8 @@ public BaseIl2CppPlatformProvider(BuildTarget target, string libraryFolder, Buil public virtual BuildTarget target { get; private set; } + public virtual NamedBuildTarget namedBuildTarget { get; private set; } + public virtual string libraryFolder { get; private set; } public virtual bool emitNullChecks diff --git a/Editor/Mono/BuildPipeline/NamedBuildTarget.cs b/Editor/Mono/BuildPipeline/NamedBuildTarget.cs index 82dfeec30a..6130437d89 100644 --- a/Editor/Mono/BuildPipeline/NamedBuildTarget.cs +++ b/Editor/Mono/BuildPipeline/NamedBuildTarget.cs @@ -118,6 +118,22 @@ public static NamedBuildTarget FromBuildTargetGroup(BuildTargetGroup buildTarget throw new ArgumentException($"There is no a valid NamedBuildTarget for BuildTargetGroup '{buildTargetGroup}'"); } + // TODO: We shouldn't be assuming that the namedBuildTarget can be extracted from the + // active settings. This should be passed through the callstack instead when building. + // We will need to use BuildTargetSelection (BuildTarget + Subtarget) that is in the cpp side. + // For now this fixes an issue where Dedicated Server compiles with the Standalone settings. + internal static NamedBuildTarget FromActiveSettings(BuildTarget target) + { + var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(target); + + if (buildTargetGroup == BuildTargetGroup.Standalone && (StandaloneBuildSubtarget)EditorUserBuildSettings.GetActiveSubtargetFor(target) == StandaloneBuildSubtarget.Server) + { + return NamedBuildTarget.Server; + } + + return NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup); + } + public static bool operator==(NamedBuildTarget lhs, NamedBuildTarget rhs) { return lhs.Equals(rhs); diff --git a/Editor/Mono/BuildPipeline/RuntimeClassMetadata.cs b/Editor/Mono/BuildPipeline/RuntimeClassMetadata.cs index 965b55325b..1fc912aecf 100644 --- a/Editor/Mono/BuildPipeline/RuntimeClassMetadata.cs +++ b/Editor/Mono/BuildPipeline/RuntimeClassMetadata.cs @@ -7,6 +7,7 @@ using System.Linq; using UnityEditor.Compilation; using UnityEditor.Scripting.ScriptCompilation; +using UnityEngine.Scripting; namespace UnityEditor { @@ -52,6 +53,16 @@ public void SetUsedTypesInUserAssembly(string[] typeNames, string assemblyName) m_UsedTypesPerUserAssembly[assemblyName] = typeNames; } + [RequiredByNativeCode] + public void SetSerializedTypesInUserAssembly(string[] typeNames, string assemblyName) + { + if (!serializedClassesPerAssembly.TryGetValue(assemblyName, out HashSet types)) + serializedClassesPerAssembly[assemblyName] = types = new HashSet(); + + foreach (var typeName in typeNames) + types.Add(typeName); + } + public bool IsDLLUsed(string dll) { if (m_UsedTypesPerUserAssembly == null) @@ -133,6 +144,18 @@ public IEnumerable> GetAllSerializedClassesAsStri } } + [RequiredByNativeCode] + public string[] GetAllSerializedClassesAssemblies() + { + return serializedClassesPerAssembly.Keys.ToArray(); + } + + [RequiredByNativeCode] + public string[] GetAllSerializedClassesForAssembly(string assembly) + { + return serializedClassesPerAssembly[assembly].ToArray(); + } + public static RuntimeClassRegistry Create() { return new RuntimeClassRegistry(); diff --git a/Editor/Mono/BuildPipeline/UnityLinker/UnityLinkerArgumentValueProvider.cs b/Editor/Mono/BuildPipeline/UnityLinker/UnityLinkerArgumentValueProvider.cs index b86479ee5b..80a98e4c92 100644 --- a/Editor/Mono/BuildPipeline/UnityLinker/UnityLinkerArgumentValueProvider.cs +++ b/Editor/Mono/BuildPipeline/UnityLinker/UnityLinkerArgumentValueProvider.cs @@ -20,7 +20,7 @@ public string Runtime { get { - var backend = PlayerSettings.GetScriptingBackend(m_RunInformation.buildTargetGroup); + var backend = PlayerSettings.GetScriptingBackend(m_RunInformation.namedBuildTarget); switch (backend) { case ScriptingImplementation.IL2CPP: @@ -33,7 +33,7 @@ public string Runtime } } - public string Profile => IL2CPPUtils.ApiCompatibilityLevelToDotNetProfileArgument(PlayerSettings.GetApiCompatibilityLevel(m_RunInformation.buildTargetGroup), m_RunInformation.target); + public string Profile => IL2CPPUtils.ApiCompatibilityLevelToDotNetProfileArgument(PlayerSettings.GetApiCompatibilityLevel(m_RunInformation.namedBuildTarget), m_RunInformation.target); public string RuleSet { diff --git a/Editor/Mono/BuildPipeline/UnityLinker/UnityLinkerRunInformation.cs b/Editor/Mono/BuildPipeline/UnityLinker/UnityLinkerRunInformation.cs index 0a5fdf6aab..998255a8d1 100644 --- a/Editor/Mono/BuildPipeline/UnityLinker/UnityLinkerRunInformation.cs +++ b/Editor/Mono/BuildPipeline/UnityLinker/UnityLinkerRunInformation.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using UnityEditor; +using UnityEditor.Build; using UnityEditor.Build.Reporting; using UnityEditor.UnityLinker; @@ -17,6 +18,7 @@ class UnityLinkerRunInformation public readonly string managedAssemblyFolderPath; public readonly BuildTarget target; public readonly BuildTargetGroup buildTargetGroup; + public readonly NamedBuildTarget namedBuildTarget; public readonly BaseUnityLinkerPlatformProvider platformProvider; public readonly RuntimeClassRegistry rcr; public readonly ManagedStrippingLevel managedStrippingLevel; @@ -43,8 +45,9 @@ public UnityLinkerRunInformation(string managedAssemblyFolderPath, pipelineData = new UnityLinkerBuildPipelineData(target, managedAssemblyFolderPath); buildTargetGroup = BuildPipeline.GetBuildTargetGroup(buildTarget); + namedBuildTarget = NamedBuildTarget.FromActiveSettings(buildTarget); argumentProvider = new UnityLinkerArgumentValueProvider(this); - isMonoBackend = PlayerSettings.GetScriptingBackend(buildTargetGroup) == ScriptingImplementation.Mono2x; + isMonoBackend = PlayerSettings.GetScriptingBackend(namedBuildTarget) == ScriptingImplementation.Mono2x; engineStrippingSupported = (platformProvider?.supportsEngineStripping ?? false) && !isMonoBackend; performEngineStripping = rcr != null && PlayerSettings.stripEngineCode && engineStrippingSupported; } diff --git a/Editor/Mono/BuildPlayerWindow.cs b/Editor/Mono/BuildPlayerWindow.cs index fda2e95fef..e3962a9ce9 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) @@ -933,7 +942,7 @@ void ShowBuildTargetSettings() if (EditorUserBuildSettings.allowDebugging && PlayerSettings.GetScriptingBackend(namedBuildTarget) == ScriptingImplementation.IL2CPP) { - var apiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(namedBuildTarget.ToBuildTargetGroup()); + var apiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(namedBuildTarget); bool isDebuggerUsable = apiCompatibilityLevel == ApiCompatibilityLevel.NET_4_6 || apiCompatibilityLevel == ApiCompatibilityLevel.NET_Standard_2_0 || apiCompatibilityLevel == ApiCompatibilityLevel.NET_Unity_4_8 || apiCompatibilityLevel == ApiCompatibilityLevel.NET_Standard; @@ -1068,23 +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/BuildTargetConverter.cs b/Editor/Mono/BuildTargetConverter.cs index 0c9674a106..1bc2a4e4eb 100644 --- a/Editor/Mono/BuildTargetConverter.cs +++ b/Editor/Mono/BuildTargetConverter.cs @@ -22,6 +22,8 @@ internal static class BuildTargetConverter return RuntimePlatform.PS5; case BuildTarget.StandaloneLinux64: return RuntimePlatform.LinuxPlayer; + case BuildTarget.CloudRendering: + return RuntimePlatform.LinuxPlayer; case BuildTarget.StandaloneOSX: return RuntimePlatform.OSXPlayer; case BuildTarget.StandaloneWindows: diff --git a/Editor/Mono/Clipboard/ClipboardContextMenu.cs b/Editor/Mono/Clipboard/ClipboardContextMenu.cs index 9d2a16e164..b064c38ce8 100644 --- a/Editor/Mono/Clipboard/ClipboardContextMenu.cs +++ b/Editor/Mono/Clipboard/ClipboardContextMenu.cs @@ -220,6 +220,8 @@ static void SetupAction(SerializedProperty property, GenericMenu menu, Event evt var prop = (SerializedProperty)o; pasteFunc(prop); prop.serializedObject.ApplyModifiedProperties(); + // Constrain proportions scale widget might need extra recalculation, notify if a paste + ConstrainProportionsTransformScale.NotifyPropertyPasted(prop.propertyPath); }, property); } else diff --git a/Editor/Mono/CompilationPipeline.bindings.cs b/Editor/Mono/CompilationPipeline.bindings.cs index 4eeefa9cc9..f2682ad15d 100644 --- a/Editor/Mono/CompilationPipeline.bindings.cs +++ b/Editor/Mono/CompilationPipeline.bindings.cs @@ -11,10 +11,9 @@ namespace UnityEditor.Compilation enum CompilationSetupErrors // Keep in sync with enum CompilationSetupErrors::Flags in ScriptCompilationPipeline.h { None = 0, - CyclicReferences = 1 << 0, // set when CyclicAssemblyReferenceException is thrown - LoadError = 1 << 1, // set when AssemblyDefinitionException is thrown - PrecompiledAssemblyError = 1 << 2, // set when PrecompiledAssemblyException is thrown - All = CyclicReferences | LoadError | PrecompiledAssemblyError, + LoadError = 1 << 0, // set when AssemblyDefinitionException is thrown + PrecompiledAssemblyError = 1 << 1, // set when PrecompiledAssemblyException is thrown + All = LoadError | PrecompiledAssemblyError, }; [NativeHeader("Editor/Src/ScriptCompilation/ScriptCompilationPipeline.h")] 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..7b8c2c3993 100644 --- a/Editor/Mono/EditorGUI.cs +++ b/Editor/Mono/EditorGUI.cs @@ -113,7 +113,7 @@ private enum DragCandidateState internal static string kDoubleFieldFormatString = UINumericFieldsUtils.k_DoubleFieldFormatString; internal static string kIntFieldFormatString = UINumericFieldsUtils.k_IntFieldFormatString; internal static int ms_IndentLevel = 0; - private const float kIndentPerLevel = 15; + internal const float kIndentPerLevel = 15; internal const int kControlVerticalSpacingLegacy = 2; internal const int kDefaultSpacing = 6; internal static readonly SVC kControlVerticalSpacing = new SVC("--theme-control-vertical-spacing", 2.0f); @@ -141,6 +141,8 @@ private enum DragCandidateState private static readonly float[] s_Vector4Floats = {0, 0, 0, 0}; private static readonly GUIContent[] s_XYZWLabels = {EditorGUIUtility.TextContent("X"), EditorGUIUtility.TextContent("Y"), EditorGUIUtility.TextContent("Z"), EditorGUIUtility.TextContent("W")}; + private const float kQuaternionFloatPrecision = 1e-6f; + private static readonly GUIContent[] s_WHLabels = {EditorGUIUtility.TextContent("W"), EditorGUIUtility.TextContent("H")}; private static readonly GUIContent s_CenterLabel = EditorGUIUtility.TrTextContent("Center"); @@ -245,8 +247,11 @@ internal static void ClearStacks() { s_PropertyCount = 0; s_EnabledStack.Clear(); + s_IsInsideListStack.Clear(); + GUI.isInsideList = false; s_ChangedStack.Clear(); s_PropertyStack.Clear(); + MaterialProperty.ClearStack(); ScriptAttributeUtility.s_DrawerStack.Clear(); s_FoldoutHeaderGroupActive = 0; } @@ -258,6 +263,7 @@ internal static void ClearStacks() private static readonly Stack s_PropertyStack = new Stack(); private static readonly Stack s_EnabledStack = new Stack(); + private static readonly Stack<(bool insideList, int depth)> s_IsInsideListStack = new Stack<(bool insideList, int depth)>(); // @TODO: API soon to be deprecated but still in a grace period; documentation states that users // are encouraged to use EditorGUI.DisabledScope instead. Uncomment next line when appropriate. @@ -365,6 +371,28 @@ internal static void EndDisabled() GUI.enabled = s_EnabledStack.Pop(); } + internal static void BeginIsInsideList(int depth) + { + s_IsInsideListStack.Push((GUI.isInsideList, depth)); + GUI.isInsideList = true; + } + + internal static int GetInsideListDepth() + { + if (s_IsInsideListStack.Count > 0) + return s_IsInsideListStack.Peek().depth; + return -1; + } + + internal static void EndIsInsideList() + { + // Stack might have been cleared with ClearStack(), check before pop. + if (s_IsInsideListStack.Count > 0) + GUI.isInsideList = s_IsInsideListStack.Pop().insideList; + else + GUI.isInsideList = false; + } + private static readonly Stack s_ChangedStack = new Stack(); public class ChangeCheckScope : GUI.Scope @@ -4373,12 +4401,9 @@ internal static Vector3 LinkedVector3Field(Rect position, GUIContent label, GUI { GUIContent copy = label; Rect fullLabelRect = position; - // If SerializedProperty is passed, make sure to call begin and end for property - // Since we have two separate properties to handle, make sure fields are not overlaping each other - // If only localScale property has override, make sure constrainProportionsScale property is in the layer behind - if (proportionalScaleProperty != null && property.prefabOverride && !proportionalScale) - label = BeginPropertyInternal(fullLabelRect, label, proportionalScaleProperty); + if(proportionalScaleProperty != null) + BeginPropertyInternal(fullLabelRect, label, proportionalScaleProperty); if (property != null) label = BeginPropertyInternal(position, label, property); @@ -4395,31 +4420,16 @@ internal static Vector3 LinkedVector3Field(Rect position, GUIContent label, GUI BeginChangeCheck(); Styles.linkButton.alignment = TextAnchor.MiddleCenter; - if (proportionalScaleProperty != null) - { - // If proportional scaling is enabled, make sure to use full scale rect, to be able revert all axis and state at the same time, - // otherwise rect size should match label size - if (!property.prefabOverride && !proportionalScale) - fullLabelRect.xMax = toggleRect.xMin; - - // Make sure proportional scale property is used only if enabled, otherwise use localScale's one. - if (!property.prefabOverride || proportionalScale) - label = BeginPropertyInternal(fullLabelRect, label, proportionalScale? proportionalScaleProperty : property); + // In case we have a background overlay, make sure Constrain proportions toggle won't be affected + Color currentColor = GUI.backgroundColor; + GUI.backgroundColor = Color.white; - BeginChangeCheck(); - bool previousProportionalScale = proportionalScale; - proportionalScale = GUI.Toggle(toggleRect, proportionalScale, toggleContent, Styles.linkButton); - if (previousProportionalScale != proportionalScale) - proportionalScaleProperty.boolValue = proportionalScale; - EndChangeCheck(); + bool previousProportionalScale = proportionalScale; + proportionalScale = GUI.Toggle(toggleRect, proportionalScale, toggleContent, Styles.linkButton); + if (proportionalScaleProperty != null && previousProportionalScale != proportionalScale) + proportionalScaleProperty.boolValue = proportionalScale; - if (proportionalScale) - EndProperty(); - } - else - { - proportionalScale = GUI.Toggle(toggleRect, proportionalScale, toggleContent, Styles.linkButton); - } + GUI.backgroundColor = currentColor; position.x += toggle.x + kDefaultSpacing; position.width -= toggle.x + kDefaultSpacing; @@ -4430,7 +4440,7 @@ internal static Vector3 LinkedVector3Field(Rect position, GUIContent label, GUI if (property != null) EndProperty(); - if (proportionalScaleProperty != null && property.prefabOverride && !proportionalScale) + if(proportionalScaleProperty != null) EndProperty(); return newValue; @@ -4459,8 +4469,7 @@ static Vector3 LinkedVector3Field(Rect position, Vector3 value, bool proportiona valueAfterChangeCheck.z = s_Vector3Floats[2]; } - return proportionalScale && valueAfterChangeCheck != value ? - ConstrainProportionsTransformScale.DoScaleProportions(valueAfterChangeCheck, value, initialScale, ref axisModified) : valueAfterChangeCheck; + return proportionalScale? ConstrainProportionsTransformScale.DoScaleProportions(valueAfterChangeCheck, value, initialScale, ref axisModified) : valueAfterChangeCheck; } // Make an X, Y field - not public (use PropertyField instead) @@ -4485,6 +4494,24 @@ private static void Vector3Field(Rect position, SerializedProperty property, GUI MultiPropertyFieldInternal(position, s_XYZLabels, cur, PropertyVisibility.All); } + // Make an X, Y and Z field for Quaternions - not public (use PropertyField instead) + private static void QuaternionEulerField(Rect position, SerializedProperty property, GUIContent label) + { + int id = GUIUtility.GetControlID(s_FoldoutHash, FocusType.Keyboard, position); + position = MultiFieldPrefixLabel(position, id, label, 3); + position.height = kSingleLineHeight; + Vector3 eulerValue = property.quaternionValue.eulerAngles; + s_Vector3Floats[0] = Mathf.Floor(eulerValue.x / kQuaternionFloatPrecision) * kQuaternionFloatPrecision; + s_Vector3Floats[1] = Mathf.Floor(eulerValue.y / kQuaternionFloatPrecision) * kQuaternionFloatPrecision; + s_Vector3Floats[2] = Mathf.Floor(eulerValue.z / kQuaternionFloatPrecision) * kQuaternionFloatPrecision; + BeginChangeCheck(); + MultiFloatFieldInternal(position, s_XYZLabels, s_Vector3Floats); + if (EndChangeCheck()) + { + property.quaternionValue = Quaternion.Euler(s_Vector3Floats[0], s_Vector3Floats[1], s_Vector3Floats[2]); + } + } + // Make an X, Y, Z and W field - not public (use PropertyField instead) static void Vector4Field(Rect position, SerializedProperty property, GUIContent label) { @@ -6596,7 +6623,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) @@ -6955,7 +6982,7 @@ internal static void StreamTexture(Texture texture, Material mat, float mipLevel } // This will return appriopriate material to use with the texture according to its usage mode - internal static Material GetMaterialForSpecialTexture(Texture t, Material defaultMat = null, bool normals2Linear = false, bool useVTMaterialWhenPossible = true) + internal static Material GetMaterialForSpecialTexture(Texture t, Material defaultMat = null, bool manualTex2Linear = false, bool useVTMaterialWhenPossible = true) { bool useVT = useVTMaterialWhenPossible && UseVTMaterial(t); @@ -6970,14 +6997,20 @@ internal static Material GetMaterialForSpecialTexture(Texture t, Material defaul return lightmapDoubleLDRMaterial; else if (usage == TextureUsageMode.BakedLightmapFullHDR) return lightmapFullHDRMaterial; - else if (usage == TextureUsageMode.NormalmapDXT5nm || (usage == TextureUsageMode.NormalmapPlain && format == TextureFormat.BC5)) + else if (TextureUtil.IsNormalMapUsageMode(usage)) { var normalMat = useVT ? normalmapVTMaterial : normalmapMaterial; - normalMat.SetFloat("_ManualTex2Linear", normals2Linear ? 1.0f : 0.0f); + normalMat.SetFloat("_IsPlainNormalmap", usage == TextureUsageMode.NormalmapPlain && format != TextureFormat.BC5 ? 1.0f : 0.0f); + normalMat.SetFloat("_ManualTex2Linear", manualTex2Linear ? 1.0f : 0.0f); return normalMat; } else if (TextureUtil.IsAlphaOnlyTextureFormat(format)) - return useVT ? alphaVTMaterial : alphaMaterial; + { + var alphaOnlyMat = useVT ? alphaVTMaterial : alphaMaterial; + alphaOnlyMat.SetFloat("_ManualTex2Linear", manualTex2Linear ? 1.0f : 0.0f); + return alphaOnlyMat; + } + return defaultMat; } @@ -7400,6 +7433,10 @@ internal static bool DefaultPropertyField(Rect position, SerializedProperty prop } } } + else if (type == SerializedPropertyType.Quaternion) + { + QuaternionEulerField(position, property, label); + } // Handle Foldout else { 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/EditorWindow.cs b/Editor/Mono/EditorWindow.cs index 9ba3e9b413..5ba6eb1878 100644 --- a/Editor/Mono/EditorWindow.cs +++ b/Editor/Mono/EditorWindow.cs @@ -1327,7 +1327,6 @@ public bool active } } - [Shortcut("Overlays/Toggle All Overlays", typeof(OverlayShortcutContext), KeyCode.BackQuote)] static void ToggleAllOverlays(ShortcutArguments args) { @@ -1365,6 +1364,15 @@ public bool TryGetOverlay(string id, out Overlay match) match = null; return false; } + + internal void OnBackingScaleFactorChangedInternal() + { + if(overlayCanvas != null) + overlayCanvas.Rebuild(); + OnBackingScaleFactorChanged(); + } + + protected virtual void OnBackingScaleFactorChanged() { } } [AttributeUsage(AttributeTargets.Class)] diff --git a/Editor/Mono/FileUtil.bindings.cs b/Editor/Mono/FileUtil.bindings.cs index 8136718e36..7ae78577ec 100644 --- a/Editor/Mono/FileUtil.bindings.cs +++ b/Editor/Mono/FileUtil.bindings.cs @@ -17,8 +17,16 @@ namespace UnityEditor public partial class FileUtil { // Deletes a file or a directory given a path. - [FreeFunction] - public static extern bool DeleteFileOrDirectory(string path); + public static bool DeleteFileOrDirectory(string path) + { + if (path is null) throw new ArgumentNullException("path"); + if (path == string.Empty) throw new ArgumentException("path", "The path cannot be empty."); + + return DeleteFileOrDirectoryInternal(path); + } + + [FreeFunction("DeleteFileOrDirectory")] + private static extern bool DeleteFileOrDirectoryInternal(string path); [FreeFunction("IsPathCreated")] private static extern bool PathExists(string path); diff --git a/Editor/Mono/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/GUI/EditorApplicationLayout.cs b/Editor/Mono/GUI/EditorApplicationLayout.cs index e284d0ee04..8bb16aeb37 100644 --- a/Editor/Mono/GUI/EditorApplicationLayout.cs +++ b/Editor/Mono/GUI/EditorApplicationLayout.cs @@ -155,11 +155,13 @@ static internal void FinalizePlaymodeLayout() { foreach (var playModeView in m_PlayModeViewList) { - if (playModeView != null && playModeView.enterPlayModeBehavior == PlayModeView.EnterPlayModeBehavior.PlayMaximized) + if (playModeView != null) { if (m_MaximizePending) WindowLayout.MaximizePresent(playModeView); + // All StartView references on all play mode views must be cleared before play mode starts. Otherwise it may cause issues + // with input being routed to the correct game window. See case 1381985 playModeView.m_Parent.ClearStartView(); } } diff --git a/Editor/Mono/GUI/GradientPicker.cs b/Editor/Mono/GUI/GradientPicker.cs index cf13ddf013..788d347828 100644 --- a/Editor/Mono/GUI/GradientPicker.cs +++ b/Editor/Mono/GUI/GradientPicker.cs @@ -43,7 +43,7 @@ internal class GradientPicker : EditorWindow // Static methods public static void Show(Gradient newGradient, bool hdr, ColorSpace colorSpace = ColorSpace.Gamma) { - Show(newGradient, hdr, ColorSpace.Gamma, null, GUIView.current); + Show(newGradient, hdr, colorSpace, null, GUIView.current); } public static void Show(Gradient newGradient, bool hdr, System.Action onGradientChanged) @@ -53,7 +53,7 @@ public static void Show(Gradient newGradient, bool hdr, System.Action public static void Show(Gradient newGradient, bool hdr, ColorSpace colorSpace, System.Action onGradientChanged) { - Show(newGradient, hdr, ColorSpace.Gamma, onGradientChanged, null); + Show(newGradient, hdr, colorSpace, onGradientChanged, null); } private static void Show(Gradient newGradient, bool hdr, ColorSpace colorSpace, System.Action onGradientChanged, GUIView currentView) diff --git a/Editor/Mono/GUI/ObjectField.cs b/Editor/Mono/GUI/ObjectField.cs index c0c5faed64..f4d8f72979 100644 --- a/Editor/Mono/GUI/ObjectField.cs +++ b/Editor/Mono/GUI/ObjectField.cs @@ -364,8 +364,8 @@ static Object DoObjectField(Rect position, Rect dropRect, int id, Object obj, Ob var parentArrayProperty = property.serializedObject.FindProperty(parentArrayPropertyPath); bool isReorderableList = PropertyHandler.s_reorderableLists.ContainsKey(ReorderableListWrapper.GetPropertyIdentifier(parentArrayProperty)); - // If it's an element of an non-orderable array, remove that element from the array - if (!isReorderableList) + // If it's an element of an non-orderable array and it is displayed inside a list, remove that element from the array (cases 1379541 & 1335322) + if (!isReorderableList && GUI.isInsideList && GetInsideListDepth() == parentArrayProperty.depth) TargetChoiceHandler.DeleteArrayElement(property); else property.objectReferenceValue = null; diff --git a/Editor/Mono/GUI/Toolbar.cs b/Editor/Mono/GUI/Toolbar.cs index e5882f33b4..add3f085a4 100644 --- a/Editor/Mono/GUI/Toolbar.cs +++ b/Editor/Mono/GUI/Toolbar.cs @@ -77,21 +77,24 @@ internal static string lastLoadedLayoutName protected override void OnEnable() { base.OnEnable(); - EditorApplication.modifierKeysChanged += Repaint; - get = this; + m_EventInterests.wantsLessLayoutEvents = true; + CreateContents(); + } + void CreateContents() + { m_MainToolbarVisual = (MainToolbarVisual)Activator.CreateInstance(EditorUIService.instance.GetDefaultToolbarType()); - + m_Root?.RemoveFromHierarchy(); m_Root = CreateRoot(); - if (windowBackend.visualTree is VisualElement visualTree) + + if (windowBackend?.visualTree is VisualElement visualTree) { visualTree.Add(m_Root); m_Root.Add(m_MainToolbarVisual.root); } - m_EventInterests.wantsLessLayoutEvents = true; RepaintToolbar(); } @@ -131,6 +134,11 @@ static VisualElement CreateRoot() return root; } + protected override void OnBackingScaleFactorChanged() + { + CreateContents(); + } + internal static void RepaintToolbar() { if (get != null) diff --git a/Editor/Mono/GUI/WindowLayout.cs b/Editor/Mono/GUI/WindowLayout.cs index a39b2fe521..195c50ddc0 100644 --- a/Editor/Mono/GUI/WindowLayout.cs +++ b/Editor/Mono/GUI/WindowLayout.cs @@ -239,8 +239,11 @@ private static ContainerWindow GenerateLayout(bool keepMainWindow, Type[] availa mainContainerWindow.SetMinMaxSizes(mainWindowMinSize, mainWindowMaxSize); } - mainContainerWindow.windowID = $"MainView_{ModeService.currentId}"; - mainContainerWindow.LoadGeometry(true); + var mainViewID = $"MainView_{ModeService.currentId}"; + var hasMainViewGeometrySettings = EditorPrefs.HasKey($"{mainViewID}h"); + mainContainerWindow.windowID = mainViewID; + if (hasMainViewGeometrySettings) + mainContainerWindow.LoadGeometry(true); var width = mainContainerWindow.position.width; var height = mainContainerWindow.position.height; diff --git a/Editor/Mono/GUIView.cs b/Editor/Mono/GUIView.cs index 0bbf125639..a1c000a816 100644 --- a/Editor/Mono/GUIView.cs +++ b/Editor/Mono/GUIView.cs @@ -155,9 +155,7 @@ internal IWindowBackend windowBackend set { if (m_WindowBackend != null) - { m_WindowBackend.OnDestroy(this); - } m_WindowBackend = value; m_WindowBackend?.OnCreate(this); @@ -195,6 +193,8 @@ protected virtual void OldOnGUI() {} // In that case, commands are not delegated (e.g., keyboard-based delete in Hierarchy/Project) protected virtual void OnGUI() {} + protected virtual void OnBackingScaleFactorChanged() { } + protected override void SetPosition(Rect newPos) { Rect oldWinPos = windowPosition; diff --git a/Editor/Mono/GameView/GameView.cs b/Editor/Mono/GameView/GameView.cs index 2b9f97c1ab..1c13698b1d 100644 --- a/Editor/Mono/GameView/GameView.cs +++ b/Editor/Mono/GameView/GameView.cs @@ -510,9 +510,16 @@ internal override void OnResized() internal override void OnBackgroundViewResized(Rect pos) { + // Should only update the game view size if it's in Aspect Ratio mode, otherwise + // we keep the static size + if (currentGameViewSize.sizeType != GameViewSizeType.AspectRatio) + return; + Rect viewInWindow = GetViewInWindow(pos); Rect viewPixelRect = GetViewPixelRect(viewInWindow); - SetDisplayViewSize(targetDisplay, new Vector2(viewPixelRect.width, viewPixelRect.height)); + var newTargetSize = + GameViewSizes.GetRenderTargetSize(viewPixelRect, currentSizeGroupType, selectedSizeIndex, out m_TargetClamped); + SetDisplayViewSize(targetDisplay, new Vector2(newTargetSize.x, newTargetSize.y)); UpdateZoomAreaAndParent(); } 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..9db21ec9be 100644 --- a/Editor/Mono/HandleUtility.cs +++ b/Editor/Mono/HandleUtility.cs @@ -1567,71 +1567,78 @@ internal static void FilterRendererIDs(Renderer[] renderers, out int[] parentRen return; } - var childCount = 0; var parentIndex = 0; - var childIndex = 0; parentRendererIDs = new int[renderers.Length]; foreach (var renderer in renderers) - { - childCount += renderer.transform.hierarchyCount; parentRendererIDs[parentIndex++] = renderer.GetInstanceID(); - } - childRendererIDs = new int[childCount]; + var tempChildRendererIDs = new HashSet(); foreach (var renderer in renderers) { var children = renderer.GetComponentsInChildren(); for (int i = 1; i < children.Length; i++) { var id = children[i].GetInstanceID(); - if (!HasMatchingInstanceID(parentRendererIDs, id)) - childRendererIDs[childIndex++] = id; + if (!HasMatchingInstanceID(parentRendererIDs, id, parentIndex)) + tempChildRendererIDs.Add(id); } } + + childRendererIDs = tempChildRendererIDs.ToArray(); } - internal static void FilterRendererIDs(GameObject[] gameObjects, out int[] parentRendererIDs, out int[] childRendererIDs) + internal static void FilterInstanceIDs(GameObject[] gameObjects, out int[] parentInstanceIDs, out int[] childInstanceIDs) { if (gameObjects == null) { Debug.LogWarning("The GameObject array is null. Handles.DrawOutline will not be rendered."); - parentRendererIDs = new int[0]; - childRendererIDs = new int[0]; + parentInstanceIDs = new int[0]; + childInstanceIDs = new int[0]; return; } - var childCount = 0; - var parentIndex = 0; - var childIndex = 0; - parentRendererIDs = new int[gameObjects.Length]; - + var tempParentInstanceIDs = new HashSet(); foreach (var go in gameObjects) { - childCount += go.transform.hierarchyCount; if (go.TryGetComponent(out Renderer renderer)) - parentRendererIDs[parentIndex++] = renderer.GetInstanceID(); + tempParentInstanceIDs.Add(renderer.GetInstanceID()); + else if (go.TryGetComponent(out Terrain terrain)) + tempParentInstanceIDs.Add(terrain.GetInstanceID()); } - childRendererIDs = new int[childCount]; + var tempChildInstanceIDs = new HashSet(); foreach (var go in gameObjects) { - var children = go.GetComponentsInChildren(); - for (int i = 1; i < children.Length; i++) + var childRenderers = go.GetComponentsInChildren(); + for (int i = 0; i < childRenderers.Length; i++) { - var id = children[i].GetInstanceID(); - if (!HasMatchingInstanceID(parentRendererIDs, id)) - childRendererIDs[childIndex++] = id; + var id = childRenderers[i].GetInstanceID(); + if (!tempParentInstanceIDs.Contains(id)) + tempChildInstanceIDs.Add(id); + } + + var childTerrains = go.GetComponentsInChildren(); + for (int i = 0; i < childTerrains.Length; i++) + { + var id = childTerrains[i].GetInstanceID(); + if (!tempParentInstanceIDs.Contains(id)) + tempChildInstanceIDs.Add(id); } } + + parentInstanceIDs = tempParentInstanceIDs.ToArray(); + childInstanceIDs = tempChildInstanceIDs.ToArray(); } - static bool HasMatchingInstanceID(int[] ids, int id) + static bool HasMatchingInstanceID(int[] ids, int id, int cutoff) { for (int i = 0; i < ids.Length; i++) { if (ids[i] == id) return true; + if (i > cutoff) + return false; } return false; } diff --git a/Editor/Mono/Handles.cs b/Editor/Mono/Handles.cs index 51dbf11201..78b630ac57 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; } @@ -1436,7 +1436,7 @@ public static void DrawOutline(Renderer[] renderers, Color color, float fillOpac public static void DrawOutline(GameObject[] objects, Color parentNodeColor, Color childNodeColor, float fillOpacity = 0) { int[] parentRenderers, childRenderers; - HandleUtility.FilterRendererIDs(objects, out parentRenderers, out childRenderers); + HandleUtility.FilterInstanceIDs(objects, out parentRenderers, out childRenderers); Internal_DrawOutline(parentNodeColor, childNodeColor, 0, parentRenderers, childRenderers, fillOpacity, fillOpacity); Internal_FinishDrawingCamera(Camera.current, true); @@ -1460,7 +1460,7 @@ public static void DrawOutline(GameObject[] objects, Color color, float fillOpac public static void DrawOutline(List objects, Color parentNodeColor, Color childNodeColor, float fillOpacity = 0) { int[] parentRenderers, childRenderers; - HandleUtility.FilterRendererIDs((GameObject[])NoAllocHelpers.ExtractArrayFromList(objects), out parentRenderers, out childRenderers); + HandleUtility.FilterInstanceIDs((GameObject[])NoAllocHelpers.ExtractArrayFromList(objects), out parentRenderers, out childRenderers); Internal_DrawOutline(parentNodeColor, childNodeColor, 0, parentRenderers, childRenderers, fillOpacity, fillOpacity); Internal_FinishDrawingCamera(Camera.current, true); @@ -1494,7 +1494,7 @@ internal static void DrawOutlineInternal(Color parentNodeColor, Color childNodeC internal static void DrawSubmeshOutline(Color parentNodeColor, Color childNodeColor, float outlineAlpha, int submeshOutlineMaterialId) { int[] parentRenderers, childRenderers; - HandleUtility.FilterRendererIDs(Selection.gameObjects, out parentRenderers, out childRenderers); + HandleUtility.FilterInstanceIDs(Selection.gameObjects, out parentRenderers, out childRenderers); // RenderOutline will swap color.a and outlineAlpha so we reverse it here to preserve correct behavior wrt Color settings in Preferences var parentOutlineAlpha = parentNodeColor.a; diff --git a/Editor/Mono/HostView.cs b/Editor/Mono/HostView.cs index 48fc052d84..9b8bb1f8ca 100644 --- a/Editor/Mono/HostView.cs +++ b/Editor/Mono/HostView.cs @@ -307,6 +307,12 @@ internal void OnLostFocus() Repaint(); } + protected override void OnBackingScaleFactorChanged() + { + if (m_ActualView != null) + m_ActualView.OnBackingScaleFactorChangedInternal(); + } + protected override void OnDestroy() { if (m_ActualView) 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/CameraOverlay.cs b/Editor/Mono/Inspector/CameraOverlay.cs index 9ff80d77ec..e175db7b46 100644 --- a/Editor/Mono/Inspector/CameraOverlay.cs +++ b/Editor/Mono/Inspector/CameraOverlay.cs @@ -159,9 +159,9 @@ public override void OnGUI() } // Get and reserve rect - drawingContainer.style.minWidth = previewSize.x; - drawingContainer.style.minHeight = previewSize.y; - var cameraRect = drawingContainer.rect; + imguiContainer.style.minWidth = previewSize.x; + imguiContainer.style.minHeight = previewSize.y; + var cameraRect = imguiContainer.rect; cameraRect.width = Mathf.Floor(cameraRect.width); if (Event.current.type == EventType.Repaint) diff --git a/Editor/Mono/Inspector/ConstrainProportionsTransformScale.cs b/Editor/Mono/Inspector/ConstrainProportionsTransformScale.cs index b5af711663..2f2f33c3dc 100644 --- a/Editor/Mono/Inspector/ConstrainProportionsTransformScale.cs +++ b/Editor/Mono/Inspector/ConstrainProportionsTransformScale.cs @@ -15,11 +15,14 @@ internal class ConstrainProportionsTransformScale bool m_ConstrainProportionsScale; Vector3 m_InitialScale; + static bool s_IsPropertyPaste; + internal bool constrainProportionsScale { get => m_ConstrainProportionsScale; set => m_ConstrainProportionsScale = value; } internal ConstrainProportionsTransformScale(Vector3 previousScale) { m_InitialScale = previousScale != Vector3.zero ? previousScale : Vector3.one; + s_IsPropertyPaste = false; } internal Vector3 DoGUI(Rect rect, GUIContent scaleContent, Vector3 value, UnityEngine.Object[] targetObjects, ref int axisModified, SerializedProperty property = null, SerializedProperty constrainProportionsProperty = null) @@ -86,6 +89,10 @@ internal static Vector3 GetVector3WithRatio(Vector3 vector, float ratio) internal static Vector3 DoScaleProportions(Vector3 value, Vector3 previousValue, Vector3 initialScale, ref int axisModified) { float ratio = 1; + bool ratioChanged = false; + + if (!Selection.DoAllGOsHaveConstrainProportionsEnabled(Selection.gameObjects)) + return value; if (previousValue != value) { @@ -94,7 +101,6 @@ internal static Vector3 DoScaleProportions(Vector3 value, Vector3 previousValue, // X axis ratio = SetRatio(value.x, previousValue.x, initialScale.x); axisModified = ratio != 1 || !Mathf.Approximately(value.x, previousValue.x) ? 0 : -1; - // Y axis if (axisModified == -1) { @@ -102,16 +108,45 @@ internal static Vector3 DoScaleProportions(Vector3 value, Vector3 previousValue, axisModified = ratio != 1 || !Mathf.Approximately(value.y, previousValue.y) ? 1 : -1; } // Z axis - if (axisModified == -1) { ratio = SetRatio(value.z, previousValue.z, initialScale.z); axisModified = ratio != 1 || !Mathf.Approximately(value.z, previousValue.z) ? 2 : -1; } - value = GetVector3WithRatio(initialScale, ratio); + ratioChanged = true; } - return value; + // If customer has pasted a scale property via a context menu, we might need to enforce proportions + else if (s_IsPropertyPaste) + { + s_IsPropertyPaste = false; + // Catch if any value has changed by checking scale based on X axis + if (initialScale * (previousValue.x / initialScale.x) != value) + { + Vector3 axisRatios = new Vector3(previousValue.x / initialScale.x, previousValue.y / initialScale.y, + previousValue.z / initialScale.z); + + if (axisRatios.x != axisRatios.y && axisRatios.x != axisRatios.z && IsValidRatio(axisRatios.x)) + { + axisModified = 0; + ratio = axisRatios.x; + } + else if (axisRatios.y != axisRatios.x && axisRatios.y != axisRatios.z && IsValidRatio(axisRatios.y)) + { + axisModified = 1; + ratio = axisRatios.y; + } + else if (axisRatios.z != axisRatios.x && axisRatios.z != axisRatios.y && IsValidRatio(axisRatios.z)) + { + axisModified = 2; + ratio = axisRatios.z; + } + + ratioChanged = axisModified != -1; + } + } + + return ratioChanged ? GetVector3WithRatio(initialScale, ratio) : value; } static float SetRatio(float value, float previousValue, float initialValue) @@ -243,5 +278,16 @@ internal static uint SetBit(uint mask, int index, bool value) else return mask & (~bitmask); } + + static bool IsValidRatio(float value) + { + return !float.IsNaN(value) && !float.IsInfinity(value); + } + + internal static void NotifyPropertyPasted(string propertyPath) + { + // If user has pasted a scale property via a context menu, we might need to enforce proportions. + s_IsPropertyPaste = propertyPath.StartsWith("m_LocalScale"); + } } } diff --git a/Editor/Mono/Inspector/GenericInspector.cs b/Editor/Mono/Inspector/GenericInspector.cs index 263dd5117e..debe3050f8 100644 --- a/Editor/Mono/Inspector/GenericInspector.cs +++ b/Editor/Mono/Inspector/GenericInspector.cs @@ -125,6 +125,12 @@ internal override bool OnOptimizedInspectorGUI(Rect contentRect) { while (property.NextVisible(childrenAreExpanded)) { + if (GUI.isInsideList && property.depth <= EditorGUI.GetInsideListDepth()) + EditorGUI.EndIsInsideList(); + + if (property.isArray) + EditorGUI.BeginIsInsideList(property.depth); + var handler = ScriptAttributeUtility.GetHandler(property); var hasPropertyDrawer = handler.propertyDrawer != null; childrenAreExpanded = !hasPropertyDrawer && property.isExpanded && EditorGUI.HasVisibleChildFields(property); diff --git a/Editor/Mono/Inspector/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/ShaderInspector.cs b/Editor/Mono/Inspector/ShaderInspector.cs index 32d252e711..3a96f2c07f 100644 --- a/Editor/Mono/Inspector/ShaderInspector.cs +++ b/Editor/Mono/Inspector/ShaderInspector.cs @@ -503,6 +503,9 @@ private class Styles private readonly Shader m_Shader; + private ulong totalVariants; + private ulong variantsWithUsage; + public static int currentMode { @@ -560,6 +563,8 @@ public ShaderInspectorPlatformsPopup(Shader shader) { m_Shader = shader; InitializeShaderPlatforms(); + totalVariants = 0; + variantsWithUsage = 0; } static void InitializeShaderPlatforms() @@ -679,7 +684,7 @@ static string FormatCount(ulong count) if (count > 1000 * 1000) return ((double)count / 1000000.0).ToString("f2", CultureInfo.InvariantCulture.NumberFormat) + "M"; if (count > 1000) - return ((double)count / 1000.0).ToString("f2", CultureInfo.InvariantCulture.NumberFormat) + "k"; + return ((double)count / 1000.0).ToString("f2", CultureInfo.InvariantCulture.NumberFormat) + "K"; return count.ToString(); } @@ -694,7 +699,19 @@ void DoShaderVariants(EditorWindow caller, ref Rect drawPos) // display included variant count, and a button to show list of them drawPos.y += kSeparatorHeight; - ulong variantCount = ShaderUtil.GetVariantCount(m_Shader, strip); + ulong variantCount = 0; + if (strip) + { + if (variantsWithUsage == 0) + variantsWithUsage = ShaderUtil.GetVariantCount(m_Shader, true); + variantCount = variantsWithUsage; + } + else + { + if (totalVariants == 0) + totalVariants = ShaderUtil.GetVariantCount(m_Shader, false); + variantCount = totalVariants; + } var variantText = FormatCount(variantCount) + (strip ? " variants included" : diff --git a/Editor/Mono/Inspector/StandardShaderGUI.cs b/Editor/Mono/Inspector/StandardShaderGUI.cs index 0a44867c6f..25336b53f8 100644 --- a/Editor/Mono/Inspector/StandardShaderGUI.cs +++ b/Editor/Mono/Inspector/StandardShaderGUI.cs @@ -4,7 +4,6 @@ using System; using UnityEngine; -using TargetAttributes = UnityEditor.BuildTargetDiscovery.TargetAttributes; namespace UnityEditor { @@ -90,6 +89,11 @@ private static class Styles MaterialEditor m_MaterialEditor; WorkflowMode m_WorkflowMode = WorkflowMode.Specular; + static int _SpecGlossMap = Shader.PropertyToID("_SpecGlossMap"); + static int _SpecColor = Shader.PropertyToID("_SpecColor"); + static int _MetallicGlossMap = Shader.PropertyToID("_MetallicGlossMap"); + static int _Metallic = Shader.PropertyToID("_Metallic"); + public void FindProperties(MaterialProperty[] props) { blendMode = FindProperty("_Mode", props); @@ -191,11 +195,22 @@ public void ShaderPropertiesGUI(Material material) m_MaterialEditor.DoubleSidedGIField(); } + bool ShaderHasProperty(Shader shader, int nameId) + { + for (int i = 0, count = shader.GetPropertyCount(); i < count; i++) + { + if (shader.GetPropertyNameId(i) == nameId) + return true; + } + return false; + } + internal void DetermineWorkflow(Material material) { - if (material.HasProperty("_SpecGlossMap") && material.HasProperty("_SpecColor")) + var shader = material.shader; + if (ShaderHasProperty(shader, _SpecGlossMap) && ShaderHasProperty(shader, _SpecColor)) m_WorkflowMode = WorkflowMode.Specular; - if (material.HasProperty("_MetallicGlossMap") && material.HasProperty("_Metallic")) + else if (ShaderHasProperty(shader, _MetallicGlossMap) && ShaderHasProperty(shader, _Metallic)) m_WorkflowMode = WorkflowMode.Metallic; else m_WorkflowMode = WorkflowMode.Dielectric; @@ -236,7 +251,7 @@ public override void AssignNewShaderToMaterial(Material material, Shader oldShad bool BlendModePopup() { - EditorGUI.showMixedValue = blendMode.hasMixedValue; + MaterialEditor.BeginProperty(blendMode); var mode = (BlendMode)blendMode.floatValue; EditorGUI.BeginChangeCheck(); @@ -248,7 +263,7 @@ bool BlendModePopup() blendMode.floatValue = (float)mode; } - EditorGUI.showMixedValue = false; + MaterialEditor.EndProperty(); return result; } @@ -257,7 +272,7 @@ void DoNormalArea() { m_MaterialEditor.TexturePropertySingleLine(Styles.normalMapText, bumpMap, bumpMap.textureValue != null ? bumpScale : null); if (bumpScale.floatValue != 1 - && BuildTargetDiscovery.PlatformHasFlag(EditorUserBuildSettings.activeBuildTarget, TargetAttributes.HasIntegratedGPU)) + && UnityEditorInternal.InternalEditorUtility.IsMobilePlatform(EditorUserBuildSettings.activeBuildTarget)) if (m_MaterialEditor.HelpBoxWithButton( EditorGUIUtility.TrTextContent("Bump scale is not supported on mobile platforms"), EditorGUIUtility.TrTextContent("Fix Now"))) diff --git a/Editor/Mono/Inspector/TagManagerInspector.cs b/Editor/Mono/Inspector/TagManagerInspector.cs index ddfa324787..1ac0eb1f36 100644 --- a/Editor/Mono/Inspector/TagManagerInspector.cs +++ b/Editor/Mono/Inspector/TagManagerInspector.cs @@ -244,6 +244,7 @@ void AddToSortLayerList(ReorderableList list) public void ReorderSortLayerList(ReorderableList list) { + serializedObject.ApplyModifiedProperties(); tagManager.UpdateSortingLayersOrder(); } diff --git a/Editor/Mono/Inspector/Texture3DPreview.cs b/Editor/Mono/Inspector/Texture3DPreview.cs index 187b5ed714..bf7057e6a6 100644 --- a/Editor/Mono/Inspector/Texture3DPreview.cs +++ b/Editor/Mono/Inspector/Texture3DPreview.cs @@ -10,7 +10,7 @@ namespace UnityEditor { - internal class Texture3DPreview : Editor + internal class Texture3DPreview : ScriptableObject { enum Preview3DMode { @@ -181,7 +181,7 @@ static Texture2D TurboColorRamp float m_StepScale = 1; float m_SurfaceOffset = 0; - public override string GetInfoString() + public string GetInfoString() { if (Texture == null) return ""; @@ -438,7 +438,7 @@ void DrawPreview() m_PreviewUtility.Render(); } - public override void OnPreviewGUI(Rect r, GUIStyle background) + public void OnPreviewGUI(Rect r, GUIStyle background) { if (!ShaderUtil.hardwareSupportsRectRenderTexture || !SystemInfo.supports3DTextures) { diff --git a/Editor/Mono/Inspector/TextureInspector.cs b/Editor/Mono/Inspector/TextureInspector.cs index 27a604368d..94b1160ed3 100644 --- a/Editor/Mono/Inspector/TextureInspector.cs +++ b/Editor/Mono/Inspector/TextureInspector.cs @@ -172,7 +172,7 @@ protected virtual void OnEnable() SetMipLevelDefaultForVT(); - if (m_Texture3DPreview == null) m_Texture3DPreview = CreateInstance(); + m_Texture3DPreview = CreateInstance(); m_Texture3DPreview.Texture = target as Texture; m_Texture3DPreview.OnEnable(); } @@ -231,7 +231,7 @@ protected virtual void OnDisable() RestoreLastTextureMipLevels(); m_CubemapPreview.OnDisable(); - m_Texture3DPreview.OnDisable(); + DestroyImmediate(m_Texture3DPreview); } public override bool RequiresConstantRepaint() diff --git a/Editor/Mono/InternalEditorUtility.cs b/Editor/Mono/InternalEditorUtility.cs index 5cedce2850..a9717e0b53 100644 --- a/Editor/Mono/InternalEditorUtility.cs +++ b/Editor/Mono/InternalEditorUtility.cs @@ -13,6 +13,7 @@ using UnityEditor.Scripting.ScriptCompilation; using UnityEngine.UIElements; using UnityEngine.Video; +using UnityEditor.Build; namespace UnityEditorInternal { @@ -647,7 +648,7 @@ internal static string GetMonolithicEngineAssemblyPath() internal static string[] GetCompilationDefines(EditorScriptCompilationOptions options, BuildTargetGroup targetGroup, BuildTarget target) { - return GetCompilationDefines(options, targetGroup, target, PlayerSettings.GetApiCompatibilityLevel(targetGroup)); + return GetCompilationDefines(options, targetGroup, target, PlayerSettings.GetApiCompatibilityLevel(NamedBuildTarget.FromActiveSettings(target))); } public static void SetShowGizmos(bool value) 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/Modules/BeeBuildPostprocessor.cs b/Editor/Mono/Modules/BeeBuildPostprocessor.cs index a0db0aae5b..05a77c7956 100644 --- a/Editor/Mono/Modules/BeeBuildPostprocessor.cs +++ b/Editor/Mono/Modules/BeeBuildPostprocessor.cs @@ -119,8 +119,8 @@ private IEnumerable GetPluginsFor(BuildTarget target) LinkerConfig LinkerConfigFor(BuildPostProcessArgs args) { - var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(args.target); - var strippingLevel = PlayerSettings.GetManagedStrippingLevel(buildTargetGroup); + var namedBuildTarget = GetNamedBuildTarget(args); + var strippingLevel = PlayerSettings.GetManagedStrippingLevel(namedBuildTarget); // IL2CPP does not support a managed stripping level of disabled. If the player settings // do try this (which should not be possible from the editor), use Low instead. @@ -162,7 +162,7 @@ LinkerConfig LinkerConfigFor(BuildPostProcessArgs args) .ToArray(), Runtime = GetUseIl2Cpp(args) ? "il2cpp" : "mono", Profile = IL2CPPUtils.ApiCompatibilityLevelToDotNetProfileArgument( - PlayerSettings.GetApiCompatibilityLevel(buildTargetGroup), args.target), + PlayerSettings.GetApiCompatibilityLevel(namedBuildTarget), args.target), Ruleset = strippingLevel switch { ManagedStrippingLevel.Minimal => "Minimal", @@ -185,8 +185,7 @@ LinkerConfig LinkerConfigFor(BuildPostProcessArgs args) protected virtual string Il2CppBuildConfigurationNameFor(BuildPostProcessArgs args) { - var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(args.target); - return Il2CppNativeCodeBuilderUtils.GetConfigurationName(PlayerSettings.GetIl2CppCompilerConfiguration(buildTargetGroup)); + return Il2CppNativeCodeBuilderUtils.GetConfigurationName(PlayerSettings.GetIl2CppCompilerConfiguration(GetNamedBuildTarget(args))); } protected virtual IEnumerable AdditionalIl2CppArgsFor(BuildPostProcessArgs args) @@ -194,6 +193,26 @@ protected virtual IEnumerable AdditionalIl2CppArgsFor(BuildPostProcessAr yield break; } + IEnumerable SplitArgs(string args) + { + int startIndex = 0; + bool inQuotes = false; + int i = 0; + for (; i < args.Length; i++) + { + if (args[i] == '"') + inQuotes = !inQuotes; + if (args[i] == ' ' && !inQuotes) + { + if (i - startIndex > 0) + yield return args.Substring(startIndex, i - startIndex); + startIndex = i + 1; + } + } + if (i - startIndex > 0) + yield return args.Substring(startIndex, i - startIndex); + } + Il2CppConfig Il2CppConfigFor(BuildPostProcessArgs args) { if (!GetUseIl2Cpp(args)) @@ -203,18 +222,17 @@ Il2CppConfig Il2CppConfigFor(BuildPostProcessArgs args) var diagArgs = Debug.GetDiagnosticSwitch("VMIl2CppAdditionalArgs").value as string; if (!string.IsNullOrEmpty(diagArgs)) - additionalArgs.Add(diagArgs.Trim('\'')); + additionalArgs.AddRange(SplitArgs(diagArgs.Trim('\''))); var playerSettingsArgs = PlayerSettings.GetAdditionalIl2CppArgs(); if (!string.IsNullOrEmpty(playerSettingsArgs)) - additionalArgs.Add(playerSettingsArgs); + additionalArgs.AddRange(SplitArgs(playerSettingsArgs)); if (CrashReportingSettings.enabled) additionalArgs.Add("--emit-source-mapping"); - var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(args.target); - var apiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(buildTargetGroup); - var namedBuildTarget = NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup); + var namedBuildTarget = GetNamedBuildTarget(args); + var apiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(namedBuildTarget); var il2cppCodeGeneration = PlayerSettings.GetIl2CppCodeGeneration(namedBuildTarget); var platformHasIncrementalGC = BuildPipeline.IsFeatureSupported("ENABLE_SCRIPTING_GC_WBARRIERS", args.target); return new Il2CppConfig @@ -223,7 +241,7 @@ Il2CppConfig Il2CppConfigFor(BuildPostProcessArgs args) IsBuildOptionSet(args.report.summary.options, BuildOptions.EnableDeepProfilingSupport), EnableFullGenericSharing = il2cppCodeGeneration == Il2CppCodeGeneration.OptimizeSize, - Profile = IL2CPPUtils.ApiCompatibilityLevelToDotNetProfileArgument(PlayerSettings.GetApiCompatibilityLevel(buildTargetGroup), args.target), + Profile = IL2CPPUtils.ApiCompatibilityLevelToDotNetProfileArgument(PlayerSettings.GetApiCompatibilityLevel(namedBuildTarget), args.target), ConfigurationName = Il2CppBuildConfigurationNameFor(args), GcWBarrierValidation = platformHasIncrementalGC && PlayerSettings.gcWBarrierValidation, GcIncremental = platformHasIncrementalGC && PlayerSettings.gcIncremental && @@ -258,7 +276,7 @@ static bool IsNewInputSystemEnabled() CompanyName = args.companyName, ProductName = Paths.MakeValidFileName(args.productName), PlayerPackage = args.playerPackage, - ApplicationIdentifier = PlayerSettings.GetApplicationIdentifier(BuildPipeline.GetBuildTargetGroup(args.target)), + ApplicationIdentifier = PlayerSettings.GetApplicationIdentifier(GetNamedBuildTarget(args)), InstallIntoBuildsFolder = GetInstallingIntoBuildsFolder(args), GenerateIdeProject = GetCreateSolution(args), Development = (args.report.summary.options & BuildOptions.Development) == BuildOptions.Development, @@ -552,7 +570,7 @@ public override void PostProcessCompletedBuild(BuildPostProcessArgs args) { base.PostProcessCompletedBuild(args); - if (PlayerSettings.GetManagedStrippingLevel(BuildPipeline.GetBuildTargetGroup(args.target)) == ManagedStrippingLevel.Disabled) + if (PlayerSettings.GetManagedStrippingLevel(GetNamedBuildTarget(args)) == ManagedStrippingLevel.Disabled) return; var strippingInfo = GetStrippingInfoFromBuild(args); @@ -576,6 +594,19 @@ protected virtual bool IsPluginCompatibleWithCurrentBuild(BuildTarget buildTarge return !string.Equals(cpu, "None", StringComparison.OrdinalIgnoreCase); } + protected NamedBuildTarget GetNamedBuildTarget(BuildPostProcessArgs args) + { + var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(args.target); + + if (buildTargetGroup == BuildTargetGroup.Standalone) + { + return (StandaloneBuildSubtarget)args.subtarget == StandaloneBuildSubtarget.Server + ? NamedBuildTarget.Server : NamedBuildTarget.Standalone; + } + + return NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup); + } + protected bool GetDevelopment(BuildPostProcessArgs args) => IsBuildOptionSet(args.options, BuildOptions.Development); @@ -586,6 +617,6 @@ protected bool ShouldAppendBuild(BuildPostProcessArgs args) => IsBuildOptionSet(args.options, BuildOptions.AcceptExternalModificationsToPlayer); protected virtual bool GetUseIl2Cpp(BuildPostProcessArgs args) => - PlayerSettings.GetScriptingBackend(BuildPipeline.GetBuildTargetGroup(args.target)) == ScriptingImplementation.IL2CPP; + PlayerSettings.GetScriptingBackend(GetNamedBuildTarget(args)) == ScriptingImplementation.IL2CPP; } } diff --git a/Editor/Mono/ObjectListArea.cs b/Editor/Mono/ObjectListArea.cs index 960f17ed59..bfaad79f38 100644 --- a/Editor/Mono/ObjectListArea.cs +++ b/Editor/Mono/ObjectListArea.cs @@ -341,7 +341,7 @@ internal float GetVisibleWidth() public float m_RightMargin = 10f; public float m_LeftMargin = 10f; - public virtual void OnGUI(Rect position, int keyboardControlID) + public void OnGUI(Rect position, int keyboardControlID) { s_VCEnabled = VersionControlUtils.isVersionControlConnected; @@ -643,7 +643,7 @@ public void SelectAll() SetSelection(instanceIDs.ToArray(), false); } - protected void SetSelection(int[] selectedInstanceIDs, bool doubleClicked) + void SetSelection(int[] selectedInstanceIDs, bool doubleClicked) { InitSelection(selectedInstanceIDs); diff --git a/Editor/Mono/ObjectListLocalGroup.cs b/Editor/Mono/ObjectListLocalGroup.cs index c28d3d2cfb..7ddf86d435 100644 --- a/Editor/Mono/ObjectListLocalGroup.cs +++ b/Editor/Mono/ObjectListLocalGroup.cs @@ -662,12 +662,6 @@ void DrawSubAssetBackground(int beginIndex, int endIndex, float yOffset) } } - internal void DrawItem(Rect itemRect, ExtraItem extraItem) - { - GUI.Label(itemRect, GUIContent.none, Styles.iconAreaBg); - DrawItem(itemRect, null, extraItem, false); - } - void DrawItem(Rect position, FilteredHierarchy.FilterResult filterItem, BuiltinResource builtinResource, bool isFolderBrowsing) { System.Diagnostics.Debug.Assert((filterItem != null && builtinResource == null) || 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/Overlays/IMGUIOverlay.cs b/Editor/Mono/Overlays/IMGUIOverlay.cs index 6fc79aa42d..9daf39149d 100644 --- a/Editor/Mono/Overlays/IMGUIOverlay.cs +++ b/Editor/Mono/Overlays/IMGUIOverlay.cs @@ -28,24 +28,25 @@ abstract class TransientSceneViewOverlay : IMGUIOverlay, ITransientOverlay public abstract class IMGUIOverlay : Overlay { - internal IMGUIContainer drawingContainer { get; private set; } + internal IMGUIContainer imguiContainer { get; private set; } public sealed override VisualElement CreatePanelContent() { rootVisualElement.pickingMode = PickingMode.Position; - var imgui = new IMGUIContainer(); - imgui.onGUIHandler = () => OnPanelGUIHandler(imgui); - return imgui; + imguiContainer = new IMGUIContainer(); + imguiContainer.onGUIHandler = OnPanelGUIHandler; + OnContentRebuild(); + return imguiContainer; } - void OnPanelGUIHandler(IMGUIContainer container) + internal virtual void OnContentRebuild() { } + + void OnPanelGUIHandler() { if (!displayed) return; - drawingContainer = container; OnGUI(); - drawingContainer = null; if (Event.current.isMouse) Event.current.Use(); diff --git a/Editor/Mono/Overlays/OverlayCanvas.cs b/Editor/Mono/Overlays/OverlayCanvas.cs index 35a588c83e..98c7f142d0 100644 --- a/Editor/Mono/Overlays/OverlayCanvas.cs +++ b/Editor/Mono/Overlays/OverlayCanvas.cs @@ -594,6 +594,12 @@ internal void Move(Overlay overlay, DockZone zone, DockPosition position = DockP overlay.RebuildContent(); } + internal void Rebuild() + { + OnBeforeSerialize(); + RestoreOverlays(); + } + public void Add(Overlay overlay, bool show = true) { if(m_Overlays.Contains(overlay)) diff --git a/Editor/Mono/Overlays/OverlayDragger.cs b/Editor/Mono/Overlays/OverlayDragger.cs index e5c7c17d90..2fd68ec20b 100644 --- a/Editor/Mono/Overlays/OverlayDragger.cs +++ b/Editor/Mono/Overlays/OverlayDragger.cs @@ -11,8 +11,6 @@ namespace UnityEditor.Overlays { sealed class OverlayDragger : MouseManipulator { - internal const string k_DragAreaHovered = "unity-overlay-drag-area-hovered"; - public static event Action dragStarted; public static event Action dragEnded; @@ -43,10 +41,8 @@ public OverlayDragger(Overlay overlay) protected override void RegisterCallbacksOnTarget() { target.RegisterCallback(OnMouseDown); - target.RegisterCallback(OnMouseMove, TrickleDown.TrickleDown); target.RegisterCallback(OnMouseUp); target.RegisterCallback(OnKeyDown); - target.RegisterCallback(OnMouseLeave); } protected override void UnregisterCallbacksFromTarget() @@ -55,7 +51,6 @@ protected override void UnregisterCallbacksFromTarget() target.UnregisterCallback(OnMouseMove); target.UnregisterCallback(OnMouseUp); target.UnregisterCallback(OnKeyDown); - target.UnregisterCallback(OnMouseLeave); } OverlayDropZoneBase GetOverlayDropZone(Vector2 mousePosition, Overlay ignoreTarget) @@ -126,17 +121,15 @@ void OnMouseDown(MouseDownEvent e) } m_Active = true; + target.RegisterCallback(OnMouseMove, TrickleDown.TrickleDown); target.CaptureMouse(); e.StopPropagation(); - UpdateHovered(e.mousePosition); dragStarted?.Invoke(m_Overlay); } void OnMouseMove(MouseMoveEvent e) { - UpdateHovered(e.mousePosition); - if (!m_Active) return; @@ -182,22 +175,6 @@ void OnMouseUp(MouseUpEvent e) OnDragEnd(e.mousePosition); } - void OnMouseLeave(MouseLeaveEvent evt) - { - //No need to consider the event position in case of a mouse leave event - UpdateHovered(false); - } - - void UpdateHovered(Vector2 mousePosition) - { - UpdateHovered(m_Active || IsInDraggableArea(mousePosition)); - } - - void UpdateHovered(bool hoverStatus) - { - m_Overlay.rootVisualElement.EnableInClassList(k_DragAreaHovered, hoverStatus); - } - void OnKeyDown(KeyDownEvent evt) { if (m_Active && evt.keyCode == KeyCode.Escape) @@ -237,7 +214,7 @@ void OnDragEnd(Vector2 mousePosition) canvas.HideOriginGhost(); canvas.destinationMarker.SetTarget(null); m_StartContainer.stateLocked = false; - UpdateHovered(mousePosition); + target.UnregisterCallback(OnMouseMove); dragEnded?.Invoke(m_Overlay); } diff --git a/Editor/Mono/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..c6556f1511 100644 --- a/Editor/Mono/ProjectBrowser.cs +++ b/Editor/Mono/ProjectBrowser.cs @@ -836,27 +836,28 @@ public void EndRenaming() m_ListArea.EndRename(true); } - string[] GetTypesDisplayNames() - { - return new[] - { - "AnimationClip", - "AudioClip", - "AudioMixer", - "ComputeShader", - "Font", - "GUISkin", - "Material", - "Mesh", - "Model", - "PhysicMaterial", - "Prefab", - "Scene", - "Script", - "Shader", - "Sprite", - "Texture", - "VideoClip", + Dictionary GetTypesDisplayNames() + { + return new Dictionary + { + { "Animation Clip", new [] { "AnimationClip" } }, + { "Audio Clip", new [] { "AudioClip"} }, + { "Audio Mixer", new [] { "AudioMixer" } }, + { "Compute Shader", new [] { "ComputeShader" } }, + { "Font", new [] { "Font" } }, + { "GUI Skin", new [] { "GUISkin" } }, + { "Material", new [] { "Material" } }, + { "Mesh", new [] { "Mesh" } }, + { "Model", new [] { "Model" } }, + { "Physic Material", new [] { "PhysicMaterial" } }, + { "Prefab", new [] { "Prefab" } }, + { "Scene", new [] { "Scene"} }, + { "Script", new [] { "Script" } }, + { "Shader", new [] { "Shader" } }, + { "Sprite", new [] { "Sprite" } }, + { "Texture", new [] { "Texture" } }, + { "Video Clip", new [] { "VideoClip" } }, + { "Visual Effect Asset", new [] { "VisualEffectAsset", "VisualEffectSubgraph" } }, // "Texture2D", // "RenderTexture", @@ -877,7 +878,7 @@ public void TypeListCallback(PopupList.ListElement element) // Toggle clicked element element.selected = !element.selected; - string[] selectedDisplayNames = (from item in m_ObjectTypes.m_ListElements where item.selected select item.text).ToArray(); + string[] selectedDisplayNames = m_ObjectTypes.m_ListElements.Where(x => x.selected).SelectMany(x => x.types).ToArray(); m_SearchFilter.classNames = selectedDisplayNames; m_SearchFieldText = m_SearchFilter.FilterToSearchFieldString(); @@ -917,13 +918,12 @@ void SetupDroplists() m_ObjectTypes.m_OnSelectCallback = TypeListCallback; m_ObjectTypes.m_SortAlphabetically = false; m_ObjectTypes.m_MaxCount = 0; - string[] types = GetTypesDisplayNames(); - for (int i = 0; i < types.Length; ++i) + var types = GetTypesDisplayNames(); + foreach (var keyPair in types) { - PopupList.ListElement element = m_ObjectTypes.NewOrMatchingElement(types[i]); - if (i == 0) - element.selected = true; + m_ObjectTypes.AddElement(keyPair.Key, keyPair.Value); } + m_ObjectTypes.m_ListElements[0].selected = true; } void SetupAssetLabelList() @@ -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/ProjectBrowserPopups.cs b/Editor/Mono/ProjectBrowserPopups.cs index b02bfbc175..b999d94fac 100644 --- a/Editor/Mono/ProjectBrowserPopups.cs +++ b/Editor/Mono/ProjectBrowserPopups.cs @@ -30,8 +30,11 @@ public class ListElement private bool m_WasSelected; private bool m_PartiallySelected; private bool m_Enabled; + private string[] m_Types; - public ListElement(string text, bool selected, float score) + public ListElement(string text, bool selected, float score) : this(text, new [] { text }, selected, score) { } + + public ListElement(string text, string[] types, bool selected, float score) { m_Content = new GUIContent(text); if (!string.IsNullOrEmpty(m_Content.text)) @@ -40,6 +43,7 @@ public ListElement(string text, bool selected, float score) a[0] = char.ToUpper(a[0]); m_Content.text = new string(a); } + m_Types = types; m_Selected = selected; filterScore = score; m_PartiallySelected = false; @@ -124,6 +128,14 @@ public string text } } + public IEnumerable types + { + get + { + return m_Types; + } + } + public void ResetScore() { m_WasSelected = m_Selected || m_PartiallySelected; @@ -189,6 +201,12 @@ public int GetFilteredCount(string prefix) return res.Count(); } + public void AddElement(string label, string[] types) + { + var res = new ListElement(label, types, false, -1); + m_ListElements.Add(res); + } + public ListElement NewOrMatchingElement(string label) { foreach (var element in m_ListElements) diff --git a/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs b/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs index 72c6ff09eb..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/SceneModeWindows/LightingWindow.cs b/Editor/Mono/SceneModeWindows/LightingWindow.cs index 14dffe7f1b..87968b05fb 100644 --- a/Editor/Mono/SceneModeWindows/LightingWindow.cs +++ b/Editor/Mono/SceneModeWindows/LightingWindow.cs @@ -44,6 +44,14 @@ static class Styles public static readonly float ButtonWidth = 90; } + public interface WindowTab + { + void OnEnable(); + void OnDisable(); + void OnGUI(); + void OnSelectionChange(); + } + enum BakeMode { BakeReflectionProbes = 0, @@ -64,10 +72,7 @@ enum Mode List m_Modes = null; GUIContent[] m_ModeStrings; - LightingWindowLightingTab m_LightingSettingsTab; - LightingWindowEnvironmentTab m_EnvironmentSettingsTab; - LightingWindowLightmapPreviewTab m_RealtimeLightmapsTab; - LightingWindowLightmapPreviewTab m_BakedLightmapsTab; + Dictionary m_Tabs = new Dictionary(); SerializedObject m_LightingSettings; SerializedProperty m_WorkflowMode; @@ -106,17 +111,20 @@ internal void SetSelectedTabIndex(int index) m_SelectedModeIndex = index; } + LightingWindow() + { + m_Tabs.Add(Mode.LightingSettings, new LightingWindowLightingTab()); + m_Tabs.Add(Mode.EnvironmentSettings, new LightingWindowEnvironmentTab()); + m_Tabs.Add(Mode.RealtimeLightmaps, new LightingWindowLightmapPreviewTab(LightmapType.DynamicLightmap)); + m_Tabs.Add(Mode.BakedLightmaps, new LightingWindowLightmapPreviewTab(LightmapType.StaticLightmap)); + } + void OnEnable() { titleContent = GetLocalizedTitleContent(); - m_LightingSettingsTab = new LightingWindowLightingTab(); - m_LightingSettingsTab.OnEnable(); - m_EnvironmentSettingsTab = new LightingWindowEnvironmentTab(); - m_EnvironmentSettingsTab.OnEnable(); - - m_RealtimeLightmapsTab = new LightingWindowLightmapPreviewTab(LightmapType.DynamicLightmap); - m_BakedLightmapsTab = new LightingWindowLightmapPreviewTab(LightmapType.StaticLightmap); + foreach (var pair in m_Tabs) + pair.Value.OnEnable(); Undo.undoRedoPerformed += Repaint; Lightmapping.lightingDataUpdated += Repaint; @@ -126,7 +134,9 @@ void OnEnable() void OnDisable() { - m_LightingSettingsTab.OnDisable(); + foreach (var pair in m_Tabs) + pair.Value.OnDisable(); + Undo.undoRedoPerformed -= Repaint; Lightmapping.lightingDataUpdated -= Repaint; } @@ -143,14 +153,14 @@ void OnBecameInvisible() void OnSelectionChange() { - if (m_RealtimeLightmapsTab == null || m_BakedLightmapsTab == null || m_Modes == null) + if (m_Modes == null) return; - if (m_Modes.Contains(Mode.RealtimeLightmaps)) - m_RealtimeLightmapsTab.UpdateActiveGameObjectSelection(); - - if (m_Modes.Contains(Mode.BakedLightmaps)) - m_BakedLightmapsTab.UpdateActiveGameObjectSelection(); + foreach (var pair in m_Tabs) + { + if (m_Modes.Contains(pair.Key)) + pair.Value.OnSelectionChange(); + } Repaint(); } @@ -178,24 +188,8 @@ void OnGUI() EditorGUILayout.Space(); - switch (selectedMode) - { - case Mode.LightingSettings: - m_LightingSettingsTab.OnGUI(); - break; - - case Mode.EnvironmentSettings: - m_EnvironmentSettingsTab.OnGUI(); - break; - - case Mode.RealtimeLightmaps: - m_RealtimeLightmapsTab.OnGUI(position); - break; - - case Mode.BakedLightmaps: - m_BakedLightmapsTab.OnGUI(position); - break; - } + if (m_Tabs.ContainsKey(selectedMode)) + m_Tabs[selectedMode].OnGUI(); Buttons(); Summary(); diff --git a/Editor/Mono/SceneModeWindows/LightingWindowEnvironmentTab.cs b/Editor/Mono/SceneModeWindows/LightingWindowEnvironmentTab.cs index d3ef1cc1df..d6dfc8b917 100644 --- a/Editor/Mono/SceneModeWindows/LightingWindowEnvironmentTab.cs +++ b/Editor/Mono/SceneModeWindows/LightingWindowEnvironmentTab.cs @@ -17,7 +17,7 @@ public virtual void OnDisable() {} public virtual void OnInspectorGUI() {} } - internal class LightingWindowEnvironmentTab + internal class LightingWindowEnvironmentTab : LightingWindow.WindowTab { class Styles { @@ -62,8 +62,7 @@ public override void OnDisable() SavedBool m_ShowOtherSettings; Object m_RenderSettings = null; Vector2 m_ScrollPosition = Vector2.zero; - - Type m_SRP = GraphicsSettings.currentRenderPipeline?.GetType(); + Type m_SRP; Object renderSettings { @@ -130,6 +129,7 @@ Editor otherRenderingEditor public void OnEnable() { + m_SRP = GraphicsSettings.currentRenderPipeline?.GetType(); m_ShowOtherSettings = new SavedBool($"LightingWindow.ShowOtherSettings", true); } @@ -172,6 +172,10 @@ public void OnGUI() EditorGUILayout.Space(); } + public void OnSelectionChange() + { + } + void OtherSettingsGUI() { if (SupportedRenderingFeatures.active.overridesFog && SupportedRenderingFeatures.active.overridesOtherLightingSettings) diff --git a/Editor/Mono/SceneModeWindows/LightingWindowLightingTab.cs b/Editor/Mono/SceneModeWindows/LightingWindowLightingTab.cs index ed4b24d130..26052fa62e 100644 --- a/Editor/Mono/SceneModeWindows/LightingWindowLightingTab.cs +++ b/Editor/Mono/SceneModeWindows/LightingWindowLightingTab.cs @@ -16,7 +16,7 @@ namespace UnityEditor { - internal class LightingWindowLightingTab + internal class LightingWindowLightingTab : LightingWindow.WindowTab { class Styles { @@ -122,6 +122,10 @@ public void OnGUI() lightmapSettings.ApplyModifiedProperties(); } + public void OnSelectionChange() + { + } + void WorkflowSettingsGUI() { m_ShowWorkflowSettings.value = EditorGUILayout.FoldoutTitlebar(m_ShowWorkflowSettings.value, Styles.workflowSettings, true); diff --git a/Editor/Mono/SceneModeWindows/LightingWindowLightmapPreviewTab.cs b/Editor/Mono/SceneModeWindows/LightingWindowLightmapPreviewTab.cs index 6c53d5d201..757d5542aa 100644 --- a/Editor/Mono/SceneModeWindows/LightingWindowLightmapPreviewTab.cs +++ b/Editor/Mono/SceneModeWindows/LightingWindowLightmapPreviewTab.cs @@ -15,7 +15,7 @@ namespace UnityEditor { - internal class LightingWindowLightmapPreviewTab + internal class LightingWindowLightmapPreviewTab : LightingWindow.WindowTab { LightmapType m_LightmapType = LightmapType.NoLightmap; Vector2 m_ScrollPosition = Vector2.zero; @@ -41,8 +41,6 @@ static class Styles public LightingWindowLightmapPreviewTab(LightmapType type) { m_LightmapType = type; - - InitSettings(); } private bool isRealtimeLightmap @@ -61,7 +59,7 @@ private bool showDebugInfo } } - public void UpdateActiveGameObjectSelection() + public void OnSelectionChange() { MeshRenderer renderer; Terrain terrain = null; @@ -92,7 +90,16 @@ public void UpdateActiveGameObjectSelection() m_ShouldScrollToLightmapIndex = true; } - public void OnGUI(Rect position) + public void OnEnable() + { + InitSettings(); + } + + public void OnDisable() + { + } + + public void OnGUI() { InitSettings(); diff --git a/Editor/Mono/SceneView/SceneOrientationGizmo.cs b/Editor/Mono/SceneView/SceneOrientationGizmo.cs index 432e6bbc32..79f2111192 100644 --- a/Editor/Mono/SceneView/SceneOrientationGizmo.cs +++ b/Editor/Mono/SceneView/SceneOrientationGizmo.cs @@ -45,7 +45,6 @@ sealed class SceneOrientationGizmo : IMGUIOverlay new GUIContent("x"), new GUIContent("y"), new GUIContent("z") }; - bool showBackGround { get @@ -142,7 +141,7 @@ public SceneOrientationGizmo() collapsedChanged += OnCollapsedChanged; } - void OnCollapsedChanged(bool collapsed) + void OnCollapsedChanged(bool _) { UpdateHeaderAndBackground(); } @@ -216,16 +215,12 @@ public override void OnCreated() { m_ViewDirectionControlIDs = new int[kDirectionRotations.Length]; for (int i = 0; i < m_ViewDirectionControlIDs.Length; ++i) - { m_ViewDirectionControlIDs[i] = GUIUtility.GetPermanentControlID(); - } m_CenterButtonControlID = GUIUtility.GetPermanentControlID(); m_RotationLockControlID = GUIUtility.GetPermanentControlID(); m_PerspectiveIsoControlID = GUIUtility.GetPermanentControlID(); } - - UpdateHeaderAndBackground(); } public override void OnWillBeDestroyed() @@ -237,6 +232,11 @@ public override void OnWillBeDestroyed() Object.DestroyImmediate(m_Camera.gameObject); } + internal override void OnContentRebuild() + { + UpdateHeaderAndBackground(); + } + void AxisSelectors(SceneView view, Camera cam, float size, float sgn, GUIStyle viewAxisLabelStyle) { for (int h = kDirectionRotations.Length - 1; h >= 0; h--) diff --git a/Editor/Mono/SceneView/SceneView.cs b/Editor/Mono/SceneView/SceneView.cs index 1c3ebaad00..8e6d7a4d83 100644 --- a/Editor/Mono/SceneView/SceneView.cs +++ b/Editor/Mono/SceneView/SceneView.cs @@ -1235,7 +1235,7 @@ public override void OnEnable() baseRootVisualElement.styleSheets.Add(EditorGUIUtility.Load(k_StyleCommon) as StyleSheet); baseRootVisualElement.styleSheets.Add(EditorGUIUtility.Load(EditorGUIUtility.isProSkin ? k_StyleDark : k_StyleLight) as StyleSheet); - HandleUtility.FilterRendererIDs(Selection.gameObjects, out m_CachedParentRenderersFromSelection, out m_CachedChildRenderersFromSelection); + HandleUtility.FilterInstanceIDs(Selection.gameObjects, out m_CachedParentRenderersFromSelection, out m_CachedChildRenderersFromSelection); } IMGUIContainer m_PrefabToolbar; @@ -1567,7 +1567,7 @@ void OnSelectionChange() m_WasFocused = false; - HandleUtility.FilterRendererIDs(Selection.gameObjects, out m_CachedParentRenderersFromSelection, out m_CachedChildRenderersFromSelection); + HandleUtility.FilterInstanceIDs(Selection.gameObjects, out m_CachedParentRenderersFromSelection, out m_CachedChildRenderersFromSelection); Repaint(); } @@ -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/ScriptAttributeGUI/PropertyHandler.cs b/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs index 8c8964fb4e..9c2f1b5b86 100644 --- a/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs +++ b/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs @@ -203,8 +203,15 @@ internal bool OnGUI(Rect position, SerializedProperty property, GUIContent label // Calculate visibility rect specifically for reorderable list as when applied for the whole serialized object, // it causes collapsed out of sight array elements appear thus messing up scroll-bar experience var screenPos = GUIUtility.GUIToScreenPoint(position.position); - screenPos.y = Mathf.Clamp(screenPos.y, 0, Screen.height); - Rect listVisibility = new Rect(screenPos.x, screenPos.y, Screen.width, Screen.height); + + screenPos.y = Mathf.Clamp(screenPos.y, + GUIView.current?.screenPosition.yMin ?? 0, + GUIView.current?.screenPosition.yMax ?? Screen.height); + + Rect listVisibility = new Rect(screenPos.x, screenPos.y, + GUIView.current?.screenPosition.width ?? Screen.width, + GUIView.current?.screenPosition.height ?? Screen.height); + listVisibility = GUIUtility.ScreenToGUIRect(listVisibility); reorderableList.Property = property; @@ -228,12 +235,21 @@ internal bool OnGUI(Rect position, SerializedProperty property, GUIContent label bool childrenAreExpanded = EditorGUI.DefaultPropertyField(position, prop, label) && EditorGUI.HasVisibleChildFields(prop); position.y += position.height + EditorGUI.kControlVerticalSpacing; + if (property.isArray) + EditorGUI.BeginIsInsideList(prop.depth); + // Loop through all child properties if (childrenAreExpanded) { SerializedProperty endProperty = prop.GetEndProperty(); while (prop.NextVisible(childrenAreExpanded) && !SerializedProperty.EqualContents(prop, endProperty)) { + if (GUI.isInsideList && prop.depth <= EditorGUI.GetInsideListDepth()) + EditorGUI.EndIsInsideList(); + + if (prop.isArray) + EditorGUI.BeginIsInsideList(prop.depth); + var handler = ScriptAttributeUtility.GetHandler(prop); EditorGUI.indentLevel = prop.depth + relIndent; position.height = handler.GetHeight(prop, null, UseReorderabelListControl(prop) && includeChildren); @@ -253,6 +269,8 @@ internal bool OnGUI(Rect position, SerializedProperty property, GUIContent label } // Restore state + if (GUI.isInsideList && property.depth <= EditorGUI.GetInsideListDepth()) + EditorGUI.EndIsInsideList(); GUI.enabled = wasEnabled; EditorGUIUtility.SetIconSize(oldIconSize); EditorGUI.indentLevel = origIndent; diff --git a/Editor/Mono/Scripting/ScriptCompilation/AssemblyBuilder.cs b/Editor/Mono/Scripting/ScriptCompilation/AssemblyBuilder.cs index d7bd8b0c26..05ca5ce6e9 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/AssemblyBuilder.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/AssemblyBuilder.cs @@ -175,7 +175,7 @@ private void InvokeBuildFinished(BeeDriverResult result) try { buildFinished?.Invoke(assemblyPath, EditorCompilation.ConvertCompilerMessages(BeeScriptCompilation - .ParseAllNodeResultsIntoCompilerMessages(result.NodeResults, EditorCompilationInterface.Instance) + .ParseAllResultsIntoCompilerMessages(result.BeeDriverMessages, result.NodeResults, EditorCompilationInterface.Instance) .SelectMany(a => a).ToArray())); } catch (Exception e) diff --git a/Editor/Mono/Scripting/ScriptCompilation/AssemblyDefinitionException.cs b/Editor/Mono/Scripting/ScriptCompilation/AssemblyDefinitionException.cs index b44a0ea0a1..f983be7913 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/AssemblyDefinitionException.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/AssemblyDefinitionException.cs @@ -6,26 +6,12 @@ namespace UnityEditor.Compilation { - enum AssemblyDefinitionErrorType - { - LoadError, - CyclicReferences - } - public class AssemblyDefinitionException : Exception { - internal AssemblyDefinitionErrorType errorType { get; } public string[] filePaths { get; } - internal AssemblyDefinitionException(string message, AssemblyDefinitionErrorType errorType, params string[] filePaths) : base(message) - { - this.errorType = errorType; - this.filePaths = filePaths; - } - public AssemblyDefinitionException(string message, params string[] filePaths) : base(message) { - this.errorType = AssemblyDefinitionErrorType.LoadError; this.filePaths = filePaths; } } diff --git a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/BeeScriptCompilation.cs b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/BeeScriptCompilation.cs index f3b46f0366..2889db4684 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/BeeScriptCompilation.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/BeeScriptCompilation.cs @@ -148,29 +148,49 @@ private static AssemblyData AssemblyDataFrom(ScriptAssembly a, ScriptAssembly[] }; } + private static CompilerMessage AsCompilerMessage(BeeDriverResult.Message message) + { + return new CompilerMessage + { + message = message.Text, + type = message.Kind == BeeDriverResult.MessageKind.Error + ? CompilerMessageType.Error + : CompilerMessageType.Warning, + }; + } + /// /// the returned array of compiler messages corresponds to the input array of noderesult. Each node result can result in 0,1 or more compilermessages. /// We return them as an array of arrays, so on the caller side you're still able to map a compilermessage to the noderesult where it originated from, /// which we need when invoking per assembly compilation callbacks. /// - public static CompilerMessage[][] ParseAllNodeResultsIntoCompilerMessages(NodeResult[] nodeResults, EditorCompilation editorCompilation) + public static CompilerMessage[][] ParseAllResultsIntoCompilerMessages(BeeDriverResult.Message[] beeDriverMessages, NodeResult[] nodeResults, EditorCompilation editorCompilation) { - var result = new CompilerMessage[nodeResults.Length][]; + // If there's any messages from the bee driver, we add one additional array to the result which contains all of the driver messages converted and augmented like the nodes messages arrays. + bool hasBeeDriverMessages = beeDriverMessages.Length > 0; + var result = new CompilerMessage[nodeResults.Length + (hasBeeDriverMessages ? 1 : 0)][]; - int totalErrors = 0; + int resultIndex = 0; + if (hasBeeDriverMessages) + { + result[resultIndex] = beeDriverMessages.Select(AsCompilerMessage).ToArray(); + ++resultIndex; + } for (int i = 0; i != nodeResults.Length; i++) { - var compilerMessages = ParseCompilerOutput(nodeResults[i]); - - //To be more kind to performance issues in situations where there are thousands of compiler messages, we're going to assume - //that after the first 10 compiler error messages, we get very little benefit from augmenting the rest with higher quality unity specific messaging. - if (totalErrors < 10) - { - UnitySpecificCompilerMessages.AugmentMessagesInCompilationErrorsWithUnitySpecificAdvice(compilerMessages, editorCompilation); - totalErrors += compilerMessages.Count(m => m.type == CompilerMessageType.Error); - } + result[resultIndex] = ParseCompilerOutput(nodeResults[i]); + ++resultIndex; + } - result[i] = compilerMessages; + //To be more kind to performance issues in situations where there are thousands of compiler messages, we're going to assume + //that after the first 10 compiler error messages, we get very little benefit from augmenting the rest with higher quality unity specific messaging. + int totalErrors = 0; + int nextResultToAugment = 0; + while (totalErrors < 10 && nextResultToAugment < result.Length) + { + UnitySpecificCompilerMessages.AugmentMessagesInCompilationErrorsWithUnitySpecificAdvice(result[nextResultToAugment], editorCompilation); + totalErrors += result[nextResultToAugment].Count(m => m.type == CompilerMessageType.Error); + ++nextResultToAugment; } return result; diff --git a/Editor/Mono/Scripting/ScriptCompilation/CompilationSetupErrorsTracker.cs b/Editor/Mono/Scripting/ScriptCompilation/CompilationSetupErrorsTracker.cs index 04ba48f90d..62311a7306 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/CompilationSetupErrorsTracker.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/CompilationSetupErrorsTracker.cs @@ -40,10 +40,7 @@ public static bool ProcessException(this ICompilationSetupErrorsTracker tracker, if (assemblyDefinitionException != null && assemblyDefinitionException.filePaths.Length > 0) { - tracker.LogCompilationSetupErrors( - assemblyDefinitionException.errorType == AssemblyDefinitionErrorType.LoadError ? - CompilationSetupErrors.LoadError : CompilationSetupErrors.CyclicReferences, - assemblyDefinitionException.filePaths, assemblyDefinitionException.Message); + tracker.LogCompilationSetupErrors(CompilationSetupErrors.LoadError, assemblyDefinitionException.filePaths, assemblyDefinitionException.Message); return true; } else if (precompiledAssemblyException != null) diff --git a/Editor/Mono/Scripting/ScriptCompilation/EditorCompilation.cs b/Editor/Mono/Scripting/ScriptCompilation/EditorCompilation.cs index cff92f9917..e4f1ccda18 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/EditorCompilation.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/EditorCompilation.cs @@ -387,105 +387,6 @@ bool VerifyReferencesIsCompiled( return removed; } - string[] CustomTargetAssembliesToFilePaths(IEnumerable targetAssemblies) - { - var customAssemblies = targetAssemblies.Select(FindCustomTargetAssemblyFromTargetAssembly); - var filePaths = customAssemblies.Select(a => a.FilePath).ToArray(); - return filePaths; - } - - string CustomTargetAssemblyToFilePath(TargetAssembly targetAssembly) - { - return FindCustomTargetAssemblyFromTargetAssembly(targetAssembly).FilePath; - } - - public struct CheckCyclicAssemblyReferencesFunctions - { - public Func ToFilePathFunc; - public Func, string[]> ToFilePathsFunc; - } - - static void CheckCyclicAssemblyReferencesDFS(TargetAssembly visitAssembly, - HashSet visited, - HashSet recursion, - CheckCyclicAssemblyReferencesFunctions functions) - { - visited.Add(visitAssembly); - recursion.Add(visitAssembly); - - foreach (var reference in visitAssembly.References) - { - if (reference.Filename == visitAssembly.Filename) - { - throw new AssemblyDefinitionException("Assembly contains a references to itself", - AssemblyDefinitionErrorType.CyclicReferences, functions.ToFilePathFunc(visitAssembly)); - } - - if (recursion.Contains(reference)) - { - throw new AssemblyDefinitionException("Assembly with cyclic references detected", - AssemblyDefinitionErrorType.CyclicReferences, functions.ToFilePathsFunc(recursion)); - } - - if (!visited.Contains(reference)) - { - CheckCyclicAssemblyReferencesDFS(reference, - visited, - recursion, - functions); - } - } - - recursion.Remove(visitAssembly); - } - - public static void CheckCyclicAssemblyReferences(IDictionary customTargetAssemblies, - CheckCyclicAssemblyReferencesFunctions functions) - { - if (customTargetAssemblies == null || customTargetAssemblies.Count < 1) - { - return; - } - - var visited = new HashSet(); - - foreach (var entry in customTargetAssemblies) - { - var assembly = entry.Value; - if (visited.Contains(assembly)) - { - continue; - } - - var recursion = new HashSet(); - CheckCyclicAssemblyReferencesDFS(assembly, - visited, - recursion, - functions); - } - } - - void CheckCyclicAssemblyReferences() - { - try - { - CheckCyclicAssemblyReferencesFunctions functions; - - functions.ToFilePathFunc = CustomTargetAssemblyToFilePath; - functions.ToFilePathsFunc = CustomTargetAssembliesToFilePaths; - - CheckCyclicAssemblyReferences(customTargetAssemblies, functions); - } - catch (AssemblyDefinitionException e) - { - if (e.errorType == AssemblyDefinitionErrorType.CyclicReferences) - { - CompilationSetupErrorsTracker.SetCompilationSetupErrors(CompilationSetupErrors.CyclicReferences); - } - throw; - } - } - public static Exception[] UpdateCustomScriptAssemblies(CustomScriptAssembly[] customScriptAssemblies, List customScriptAssemblyReferences, AssetPathMetaData[] assetPathsMetaData, @@ -592,14 +493,15 @@ Exception[] UpdateCustomTargetAssemblies() } customTargetAssemblies = EditorBuildRules.CreateTargetAssemblies(loadingAssemblyDefinition.CustomScriptAssemblies); - - CompilationSetupErrorsTracker.ClearCompilationSetupErrors(CompilationSetupErrors.CyclicReferences); - return exceptions; } public void SkipCustomScriptAssemblyGraphValidation(bool skipChecks) { + // If we have successfully compiled and reloaded all assemblies, then we can skip asmdef compilation graph checks + // for setup errors like cyclic references, self-references, duplicate assembly names, etc. + // If there is compilation errors in a Safe Mode domain or a partially loaded domain (when SafeMode is forcefully exited), + // then we need to keep the graph validation checks to rediscover potential setup errors in subsequent compilations. skipCustomScriptAssemblyGraphValidation = skipChecks; } @@ -612,7 +514,8 @@ public Exception[] SetAllCustomScriptAssemblyReferenceJsonsContents(string[] pat { RefreshLoadingAssemblyDefinition(); loadingAssemblyDefinition.SetAllCustomScriptAssemblyReferenceJsonsContents(paths, contents); - return GetLoadingExceptions(); + var updateExceptions = UpdateCustomTargetAssemblies(); + return loadingAssemblyDefinition.Exceptions.Concat(updateExceptions).ToArray(); } public Exception[] SetAllCustomScriptAssemblyJsons(string[] paths, string[] guids) @@ -624,12 +527,8 @@ public Exception[] SetAllCustomScriptAssemblyJsonContents(string[] paths, string { RefreshLoadingAssemblyDefinition(); loadingAssemblyDefinition.SetAllCustomScriptAssemblyJsonContents(paths, contents, guids); - return GetLoadingExceptions(); - } - - Exception[] GetLoadingExceptions() - { - return loadingAssemblyDefinition.Exceptions.Concat(UpdateCustomTargetAssemblies()).ToArray(); + var updateExceptions = UpdateCustomTargetAssemblies(); + return loadingAssemblyDefinition.Exceptions.Concat(updateExceptions).ToArray(); } void RefreshLoadingAssemblyDefinition() @@ -910,16 +809,6 @@ public CompileStatus CompileScriptsWithSettings(ScriptAssemblySettings scriptAss PlayerSettings.EnableRoslynAnalyzers && (scriptAssemblySettings.CompilationOptions & EditorScriptCompilationOptions.BuildingWithRoslynAnalysis) != 0; - // If we have successfully compiled and reloaded all assemblies, then we can - // skip checks on the asmdef compilation graph to ensure there no - // setup errors like cyclic references, duplicate assembly names, etc. - // If there is compilation errors n Safe Mode domain or a partial domain (if SafeMode is forcefully exited), - // Then we need to keep the validation checks to rediscover potential setup errors in subsequent compilations. - if (!skipCustomScriptAssemblyGraphValidation) - { - CheckCyclicAssemblyReferences(); - } - ScriptAssembly[] scriptAssemblies; try { @@ -1014,6 +903,7 @@ public ScriptAssemblySettings CreateScriptAssemblySettings(BuildTargetGroup buil public ScriptAssemblySettings CreateScriptAssemblySettings(BuildTargetGroup buildTargetGroup, BuildTarget buildTarget, EditorScriptCompilationOptions options, string[] extraScriptingDefines) { var predefinedAssembliesCompilerOptions = new ScriptCompilerOptions(); + var namedBuildTarget = NamedBuildTarget.FromActiveSettings(buildTarget); if ((options & EditorScriptCompilationOptions.BuildingPredefinedAssembliesAllowUnsafeCode) == EditorScriptCompilationOptions.BuildingPredefinedAssembliesAllowUnsafeCode) { @@ -1025,7 +915,7 @@ public ScriptAssemblySettings CreateScriptAssemblySettings(BuildTargetGroup buil predefinedAssembliesCompilerOptions.UseDeterministicCompilation = true; } - predefinedAssembliesCompilerOptions.ApiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup)); + predefinedAssembliesCompilerOptions.ApiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(namedBuildTarget); ICompilationExtension compilationExtension = null; if ((options & EditorScriptCompilationOptions.BuildingForEditor) == 0) @@ -1034,7 +924,7 @@ public ScriptAssemblySettings CreateScriptAssemblySettings(BuildTargetGroup buil } - List additionalCompilationArguments = new List(PlayerSettings.GetAdditionalCompilerArguments(NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup))); + List additionalCompilationArguments = new List(PlayerSettings.GetAdditionalCompilerArguments(namedBuildTarget)); if (PlayerSettings.suppressCommonWarnings) { @@ -1070,18 +960,6 @@ ScriptAssemblySettings CreateEditorScriptAssemblySettings(EditorScriptCompilatio { return CreateScriptAssemblySettings(EditorUserBuildSettings.activeBuildTargetGroup, EditorUserBuildSettings.activeBuildTarget, options); } - - static CompilerMessage AsCompilerMessage(BeeDriverResult.Message message) - { - return new CompilerMessage - { - message = message.Text, - type = message.Kind == BeeDriverResult.MessageKind.Error - ? CompilerMessageType.Error - : CompilerMessageType.Warning, - }; - } - //only used in for tests to peek in. public CompilerMessage[] GetCompileMessages() => _currentEditorCompilationCompilerMessages; @@ -1181,14 +1059,13 @@ public CompileStatus TickCompilationPipeline(EditorScriptCompilationOptions opti return CompileStatus.Compiling; } - var messagesForNodeResults = ProcessCompilationResult(activeBeeBuild.assemblies, result, activeBeeBuild.settings.BuildingForEditor, activeBeeBuild); + var compilerMessages = ProcessCompilationResult(activeBeeBuild.assemblies, result, activeBeeBuild.settings.BuildingForEditor, activeBeeBuild).SelectMany(m => m).ToArray(); int logIdentifier = activeBeeBuild.settings.BuildingForEditor //these numbers are "randomly picked". they are used to so that when you log a message with a certain identifier, later all messages with that identifier can be cleared. //one means "compilation error for compiling-assemblies-for-editor" the other means "compilation error for building a player". ? kLogIdentifierFor_EditorMessages : kLogIdentifierFor_PlayerMessages; - var compilerMessages = result.BeeDriverMessages.Select(AsCompilerMessage).Concat(messagesForNodeResults.SelectMany(m => m)).ToArray(); if (activeBeeBuild.settings.BuildingForEditor) { @@ -1219,10 +1096,10 @@ public void DisableLoggingEditorCompilerMessages() public CompilerMessage[][] ProcessCompilationResult(ScriptAssembly[] assemblies, BeeDriverResult result, bool buildingForEditor, object context) { - var compilerMessagesForNodeResults = BeeScriptCompilation.ParseAllNodeResultsIntoCompilerMessages(result.NodeResults, this); - InvokeAssemblyCompilationFinished(assemblies, result, buildingForEditor, compilerMessagesForNodeResults); + var compilerMessages = BeeScriptCompilation.ParseAllResultsIntoCompilerMessages(result.BeeDriverMessages, result.NodeResults, this); + InvokeAssemblyCompilationFinished(assemblies, result, buildingForEditor, compilerMessages); InvokeCompilationFinished(context); - return compilerMessagesForNodeResults; + return compilerMessages; } void InvokeAssemblyCompilationFinished(ScriptAssembly[] assemblies, BeeDriverResult beeDriverResult, bool buildingForEditor, CompilerMessage[][] compilerMessagesForNodeResults) @@ -1707,7 +1584,7 @@ ScriptAssembly InitializeScriptAssemblyWithoutReferencesAndDefines(AssemblyBuild ScriptAssemblyReferences = new ScriptAssembly[0], RootNamespace = string.Empty }; - scriptAssembly.CompilerOptions.ApiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(assemblyBuilder.buildTargetGroup); + scriptAssembly.CompilerOptions.ApiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(NamedBuildTarget.FromActiveSettings(assemblyBuilder.buildTarget)); return scriptAssembly; } diff --git a/Editor/Mono/Scripting/ScriptCompilation/MonoLibraryHelpers.cs b/Editor/Mono/Scripting/ScriptCompilation/MonoLibraryHelpers.cs index 6dbca5b68c..c0dccddfdc 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/MonoLibraryHelpers.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/MonoLibraryHelpers.cs @@ -7,6 +7,7 @@ using System.Linq; using UnityEditor.Scripting.Compilers; using UnityEditor.Utils; +using UnityEngine.Scripting; namespace UnityEditor.Scripting.ScriptCompilation { @@ -20,11 +21,26 @@ class CachedReferences static CachedReferences cachedReferences; + [RequiredByNativeCode] public static string[] GetSystemLibraryReferences(ApiCompatibilityLevel apiCompatibilityLevel) { return GetCachedSystemLibraryReferences(apiCompatibilityLevel); } + static IEnumerable FindAllFilesInDirectories(string[] directories, string pattern) + { + foreach (string dir in directories) + { + if (!Directory.Exists(dir)) { continue; } + + var files = Directory.GetFiles(dir, pattern); + foreach (string file in files) + { + yield return file; + } + } + } + static string[] FindReferencesInDirectories(this string[] references, string[] directories) { return ( @@ -53,12 +69,18 @@ static string[] GetCachedSystemLibraryReferences(ApiCompatibilityLevel apiCompat } else if (apiCompatibilityLevel == ApiCompatibilityLevel.NET_Unity_4_8) { - references.AddRange(GetSystemReferences().FindReferencesInDirectories(monoAssemblyDirectories)); - references.AddRange(GetNet46SystemReferences().FindReferencesInDirectories(monoAssemblyDirectories)); + var systemReferences = GetSystemReferences().FindReferencesInDirectories(monoAssemblyDirectories); + var net46References = GetNet46SystemReferences().FindReferencesInDirectories(monoAssemblyDirectories); + var additionalSystemReferences = FindAllFilesInDirectories(monoAssemblyDirectories, "System.*.dll"); + var facades = Directory.GetFiles(Path.Combine(GetUnityReferenceProfileDirectory(), "Facades"), "*.dll"); // Look in the mono assembly directory for a facade folder and get a list of all the DLL's to be // used later by the language compilers. - references.AddRange(Directory.GetFiles(Path.Combine(GetUnityReferenceProfileDirectory(), "Facades"), "*.dll")); + references.AddRange(systemReferences + .Concat(net46References) + .Concat(additionalSystemReferences) + .Concat(facades) + .Distinct()); } else { diff --git a/Editor/Mono/Scripting/ScriptCompilation/UnitySpecificCompilerMessageProcessor.cs b/Editor/Mono/Scripting/ScriptCompilation/UnitySpecificCompilerMessageProcessor.cs index 77ca330d49..df61d10fb1 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/UnitySpecificCompilerMessageProcessor.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/UnitySpecificCompilerMessageProcessor.cs @@ -22,6 +22,7 @@ public static void AugmentMessagesInCompilationErrorsWithUnitySpecificAdvice(Com UnsafeErrorProcessor.PostProcess(ref messages[i], editorCompilation); ModuleReferenceErrorProcessor.PostProcess(ref messages[i]); DeterministicAssemblyVersionErrorProcessor.PostProcess(ref messages[i]); + CyclicAssemblyReferencesErrorProcessor.PostProcess(ref messages[i]); } } @@ -96,5 +97,22 @@ private static CustomScriptAssembly CustomScriptAssemblyFor(CompilerMessage m, E .FirstOrDefault(c => file.IsChildOf(new NPath(c.PathPrefix).MakeAbsolute())); } } + internal static class CyclicAssemblyReferencesErrorProcessor + { + public static void PostProcess(ref CompilerMessage message) + { + int cyclickDependencyMessageStart = message.message.IndexOf("One or more cyclic dependencies detected between assemblies"); + if (cyclickDependencyMessageStart >= 0) + { + int cyclickDependencyMessageEnd = message.message.IndexOf(System.Environment.NewLine, cyclickDependencyMessageStart); + if (cyclickDependencyMessageEnd >= 0) + message.message = message.message.Substring(cyclickDependencyMessageStart, cyclickDependencyMessageEnd - cyclickDependencyMessageStart); + else + message.message = message.message.Substring(cyclickDependencyMessageStart); + } + } + } + + } } diff --git a/Editor/Mono/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 bfeb6e2168..1766d4c7c8 100644 Binary files a/artifacts/Stevedore/bee_bcfb/BeeAsALibrary/Bee.BeeDriver.dll and b/External/Bee/BeeAsALibrary/Bee.BeeDriver.dll differ diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/BuilderExternalPackages.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/BuilderExternalPackages.cs index e5a9441453..0e0a810b33 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/BuilderExternalPackages.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/BuilderExternalPackages.cs @@ -7,14 +7,6 @@ namespace Unity.UI.Builder { static class BuilderExternalPackages { - public static bool isVectorGraphicsInstalled - { - get - { - return PackageInfo.GetAllRegisteredPackages().Any(x => x.name == "com.unity.vectorgraphics" && x.version == "1.0.0"); - } - } - public static bool is2DSpriteEditorInstalled { get diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Explorer/BuilderStyleSheets.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Explorer/BuilderStyleSheets.cs index a1308f8579..c2318245e1 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Explorer/BuilderStyleSheets.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Explorer/BuilderStyleSheets.cs @@ -116,8 +116,6 @@ public BuilderStyleSheets( m_NewSelectorTextField.SelectRange(m_NewSelectorTextField.value.Length, m_NewSelectorTextField.value.Length); }); - evt.PreventDefault(); - evt.StopImmediatePropagation(); }, TrickleDown.TrickleDown); m_NewSelectorTextInputField.RegisterCallback((evt) => diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Inspector/BuilderInspectorStyleFields.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Inspector/BuilderInspectorStyleFields.cs index e4e35b4a94..78a5926e02 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Inspector/BuilderInspectorStyleFields.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Inspector/BuilderInspectorStyleFields.cs @@ -228,8 +228,6 @@ public void BindStyleField(BuilderStyleRow styleRow, string styleName, VisualEle else if (IsComputedStyleBackground(val) && fieldElement is ImageStyleField imageStyleField) { imageStyleField.RegisterValueChangedCallback(e => OnFieldValueChange(e, styleName)); - if (BuilderExternalPackages.isVectorGraphicsInstalled) - imageStyleField.TryEnableVectorGraphicTypeSupport(); } else if (IsComputedStyleCursor(val) && fieldElement is ObjectField) { diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Utilities/BuilderConstants.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Utilities/BuilderConstants.cs index 507643398a..e672af6349 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Utilities/BuilderConstants.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Utilities/BuilderConstants.cs @@ -376,7 +376,7 @@ internal static class BuilderConstants // UXML public static readonly string UxmlOpenTagSymbol = "<"; public static readonly string UxmlCloseTagSymbol = ">"; - public static readonly string UxmlEndTagSymbol = "/" + UxmlCloseTagSymbol; + public static readonly string UxmlEndTagSymbol = " /" + UxmlCloseTagSymbol; public static readonly string UxmlTemplateClassTag = "Template"; public static readonly string UxmlNameAttr = "name"; public static readonly string UxmlHeader = " GetAttributeDescriptions(this Visua foreach (IUxmlFactory f in factoryList) { - foreach (var a in f.uxmlAttributesDescription) + // For user created types, they may return null for uxmlAttributeDescription, so we need to check in order not to crash. + if (f.uxmlAttributesDescription != null) { - if (s_SkippedAttributeNames.Contains(a.name)) - continue; + foreach (var a in f.uxmlAttributesDescription) + { + // For user created types, they may `yield return null` which would create an array with a null, so we need + // to check in order not to crash. + if (a == null || s_SkippedAttributeNames.Contains(a.name)) + continue; - attributeList.Add(a); + attributeList.Add(a); + } } } diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Utilities/VisualTreeAssetExtensions/VisualTreeAssetToUXML.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Utilities/VisualTreeAssetExtensions/VisualTreeAssetToUXML.cs index 17fe5d5cbd..47164b04e1 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Utilities/VisualTreeAssetExtensions/VisualTreeAssetToUXML.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Utilities/VisualTreeAssetExtensions/VisualTreeAssetToUXML.cs @@ -176,7 +176,7 @@ static void AppendTemplateRegistrations( { Debug.LogError("UI Builder: VisualTreeAsset.m_Usings field has not been found! Update the reflection code!"); } - stringBuilder.Append(" " + BuilderConstants.UxmlEndTagSymbol); + stringBuilder.Append(BuilderConstants.UxmlEndTagSymbol); stringBuilder.Append(BuilderConstants.newlineCharFromEditorSettings); } @@ -246,7 +246,7 @@ static void ProcessStyleSheetPath( styleSheetPath = GetProcessedPathForSrcAttribute(styleSheet, vtaPath, styleSheetPath); AppendElementAttribute("src", styleSheetPath, stringBuilder); } - stringBuilder.Append(" " + BuilderConstants.UxmlEndTagSymbol); + stringBuilder.Append(BuilderConstants.UxmlEndTagSymbol); stringBuilder.Append(BuilderConstants.newlineCharFromEditorSettings); hasChildTags = true; @@ -361,7 +361,7 @@ static void GenerateUXMLRecursive( foreach (var attributeOverride in overrides) AppendElementAttribute(attributeOverride.m_AttributeName, attributeOverride.m_Value, stringBuilder); - stringBuilder.Append(" " + BuilderConstants.UxmlCloseTagSymbol); + stringBuilder.Append(BuilderConstants.UxmlEndTagSymbol); stringBuilder.Append(BuilderConstants.newlineCharFromEditorSettings); } @@ -416,7 +416,7 @@ static void GenerateUXMLRecursive( } else { - stringBuilder.Append(" " + BuilderConstants.UxmlEndTagSymbol); + stringBuilder.Append(BuilderConstants.UxmlEndTagSymbol); stringBuilder.Append(BuilderConstants.newlineCharFromEditorSettings); } } diff --git a/ModuleOverrides/com.unity.ui/Core/Collections/Virtualization/CollectionVirtualizationController.cs b/ModuleOverrides/com.unity.ui/Core/Collections/Virtualization/CollectionVirtualizationController.cs index a883bc3239..2ac17c4f0f 100644 --- a/ModuleOverrides/com.unity.ui/Core/Collections/Virtualization/CollectionVirtualizationController.cs +++ b/ModuleOverrides/com.unity.ui/Core/Collections/Virtualization/CollectionVirtualizationController.cs @@ -25,6 +25,7 @@ protected CollectionVirtualizationController(ScrollView scrollView) public abstract void OnScroll(Vector2 offset); public abstract int GetIndexFromPosition(Vector2 position); public abstract float GetItemHeight(int index); + public abstract void OnFocus(VisualElement leafTarget); public abstract void UpdateBackground(); public abstract IEnumerable activeItems { get; } diff --git a/ModuleOverrides/com.unity.ui/Core/Collections/Virtualization/VerticalVirtualizationController.cs b/ModuleOverrides/com.unity.ui/Core/Collections/Virtualization/VerticalVirtualizationController.cs index a2d4c6f64b..f9d93f4d7f 100644 --- a/ModuleOverrides/com.unity.ui/Core/Collections/Virtualization/VerticalVirtualizationController.cs +++ b/ModuleOverrides/com.unity.ui/Core/Collections/Virtualization/VerticalVirtualizationController.cs @@ -19,6 +19,9 @@ namespace UnityEngine.UIElements public override IEnumerable activeItems => m_ActiveItems as IEnumerable; + int m_LastFocusedElementIndex = -1; + List m_LastFocusedElementTreeChildIndexes = new List(); + protected int m_FirstVisibleIndex; Func m_VisibleItemPredicateDelegate; @@ -121,13 +124,15 @@ protected void Setup(T recycledItem, int newIndex, bool forceHide = false) return; } - var newId = m_CollectionView.viewController.GetIdForIndex(newIndex); recycledItem.rootElement.style.display = DisplayStyle.Flex; if (recycledItem.index == newIndex) return; var useAlternateUss = m_CollectionView.showAlternatingRowBackgrounds != AlternatingRowBackground.None && newIndex % 2 == 1; recycledItem.rootElement.EnableInClassList(BaseVerticalCollectionView.itemAlternativeBackgroundUssClassName, useAlternateUss); + var previousIndex = recycledItem.index; + var newId = m_CollectionView.viewController.GetIdForIndex(newIndex); + if (recycledItem.index != ReusableCollectionItem.UndefinedIndex) m_CollectionView.viewController.InvokeUnbindItem(recycledItem, recycledItem.index); @@ -151,7 +156,47 @@ protected void Setup(T recycledItem, int newIndex, bool forceHide = false) m_CollectionView.viewController.InvokeBindItem(recycledItem, newIndex); // Handle focus cycling - m_CollectionView.HandleFocus(recycledItem); + HandleFocus(recycledItem, previousIndex); + } + + public override void OnFocus(VisualElement leafTarget) + { + if (leafTarget == m_ScrollView.contentContainer) + return; + + m_LastFocusedElementTreeChildIndexes.Clear(); + + if (m_ScrollView.contentContainer.FindElementInTree(leafTarget, m_LastFocusedElementTreeChildIndexes)) + { + var recycledElement = m_ScrollView.contentContainer[m_LastFocusedElementTreeChildIndexes[0]]; + foreach (var recycledItem in activeItems) + { + if (recycledItem.rootElement == recycledElement) + { + m_LastFocusedElementIndex = recycledItem.index; + break; + } + } + + m_LastFocusedElementTreeChildIndexes.RemoveAt(0); + } + else + { + m_LastFocusedElementIndex = -1; + } + } + + void HandleFocus(ReusableCollectionItem recycledItem, int previousIndex) + { + if (m_LastFocusedElementIndex == -1) + return; + + if (m_LastFocusedElementIndex == recycledItem.index) + recycledItem.rootElement.ElementAtTreePath(m_LastFocusedElementTreeChildIndexes)?.Focus(); + else if (m_LastFocusedElementIndex != previousIndex) + recycledItem.rootElement.ElementAtTreePath(m_LastFocusedElementTreeChildIndexes)?.Blur(); + else + m_ScrollView.contentContainer.Focus(); } public override void UpdateBackground() 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..6e17052930 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) @@ -1452,27 +1455,7 @@ protected override void ExecuteDefaultAction(EventBase evt) // and set it back in Setup(). else if (evt.eventTypeId == FocusEvent.TypeId()) { - m_LastFocusedElementTreeChildIndexes.Clear(); - var target = evt.leafTarget as VisualElement; - - if (m_ScrollView.contentContainer.FindElementInTree(target, m_LastFocusedElementTreeChildIndexes)) - { - var recycledElement = m_ScrollView.contentContainer[m_LastFocusedElementTreeChildIndexes[0]]; - foreach (var recycledItem in activeItems) - { - if (recycledItem.rootElement == recycledElement) - { - m_LastFocusedElementIndex = recycledItem.index; - break; - } - } - - m_LastFocusedElementTreeChildIndexes.RemoveAt(0); - } - else - { - m_LastFocusedElementIndex = -1; - } + m_VirtualizationController.OnFocus(evt.leafTarget as VisualElement); } else if (evt.eventTypeId == NavigationSubmitEvent.TypeId()) { @@ -1483,21 +1466,6 @@ protected override void ExecuteDefaultAction(EventBase evt) } } - // Used to store the focused element to enable scrolling without losing it. - int m_LastFocusedElementIndex = -1; - List m_LastFocusedElementTreeChildIndexes = new List(); - - internal void HandleFocus(ReusableCollectionItem recycledItem) - { - if (m_LastFocusedElementIndex == -1) - return; - - if (m_LastFocusedElementIndex == recycledItem.index) - recycledItem.rootElement.ElementAtTreePath(m_LastFocusedElementTreeChildIndexes)?.Focus(); - else - recycledItem.rootElement.ElementAtTreePath(m_LastFocusedElementTreeChildIndexes)?.Blur(); - } - private void OnSizeChanged(GeometryChangedEvent evt) { if (!HasValidDataAndBindings()) 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/EventDispatcher.cs b/ModuleOverrides/com.unity.ui/Core/EventDispatcher.cs index 02dee2fc35..fb42c23764 100644 --- a/ModuleOverrides/com.unity.ui/Core/EventDispatcher.cs +++ b/ModuleOverrides/com.unity.ui/Core/EventDispatcher.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using JetBrains.Annotations; namespace UnityEngine.UIElements { @@ -204,7 +205,7 @@ bool dispatchImmediately internal bool processingEvents { get; private set; } - internal void Dispatch(EventBase evt, IPanel panel, DispatchMode dispatchMode) + internal void Dispatch(EventBase evt, [NotNull] IPanel panel, DispatchMode dispatchMode) { evt.MarkReceivedByDispatcher(); @@ -329,7 +330,7 @@ void ProcessEventQueue() } } - void ProcessEvent(EventBase evt, IPanel panel) + void ProcessEvent(EventBase evt, [NotNull] IPanel panel) { Event e = evt.imguiEvent; // Sometimes (in tests only?) we receive Used events. Protect our verification from this case. @@ -344,9 +345,19 @@ void ProcessEvent(EventBase evt, IPanel panel) ApplyDispatchingStrategies(evt, panel, imguiEventIsInitiallyUsed); } - if (evt.path != null) + // Last chance to build a path. Some dispatching strategies (e.g. PointerCaptureDispatchingStrategy) + // don't call PropagateEvents but still need to call ExecuteDefaultActions on composite roots. + var path = evt.path; + if (path == null && evt.bubblesOrTricklesDown && evt.leafTarget is VisualElement leafTarget) { - foreach (var element in evt.path.targetElements) + path = PropagationPaths.Build(leafTarget, evt); + evt.path = path; + EventDebugger.LogPropagationPaths(evt, path); + } + + if (path != null) + { + foreach (var element in path.targetElements) { if (element.panel == panel) { @@ -361,11 +372,15 @@ void ProcessEvent(EventBase evt, IPanel panel) else { // If no propagation path, make sure EventDispatchUtilities.ExecuteDefaultAction has a target - if (evt.target == null && panel != null) + if (!(evt.target is VisualElement target)) + { + evt.target = target = panel.visualTree; + } + + if (target.panel == panel) { - evt.target = panel.visualTree; + EventDispatchUtilities.ExecuteDefaultAction(evt); } - EventDispatchUtilities.ExecuteDefaultAction(evt); } m_DebuggerEventDispatchingStrategy.PostDispatch(evt, panel); diff --git a/ModuleOverrides/com.unity.ui/Core/Events/EventBase.cs b/ModuleOverrides/com.unity.ui/Core/Events/EventBase.cs index 907208c2c8..cf6651f99c 100644 --- a/ModuleOverrides/com.unity.ui/Core/Events/EventBase.cs +++ b/ModuleOverrides/com.unity.ui/Core/Events/EventBase.cs @@ -148,6 +148,9 @@ protected set } } + internal bool bubblesOrTricklesDown => + (propagation & (EventPropagation.Bubbles | EventPropagation.TricklesDown)) != 0; + internal bool skipDisabledElements { get { return (propagation & EventPropagation.SkipDisabledElements) != 0; } diff --git a/ModuleOverrides/com.unity.ui/Core/Events/IEventDispatchingStrategy.cs b/ModuleOverrides/com.unity.ui/Core/Events/IEventDispatchingStrategy.cs index 965644001e..75cad61ea6 100644 --- a/ModuleOverrides/com.unity.ui/Core/Events/IEventDispatchingStrategy.cs +++ b/ModuleOverrides/com.unity.ui/Core/Events/IEventDispatchingStrategy.cs @@ -76,7 +76,7 @@ public static void PropagateEvent(EventBase evt) Debug.Assert(!evt.dispatch, "Event is being dispatched recursively."); evt.dispatch = true; - if ((evt.propagation & (EventBase.EventPropagation.Bubbles | EventBase.EventPropagation.TricklesDown)) == 0) + if (!evt.bubblesOrTricklesDown) { // Early out if no callback on target. if (ve.HasEventCallbacksOrDefaultActionAtTarget(evt.eventCategory)) diff --git a/ModuleOverrides/com.unity.ui/Core/GameObjects/PanelSettings.cs b/ModuleOverrides/com.unity.ui/Core/GameObjects/PanelSettings.cs index 6467523400..d0e41ff823 100644 --- a/ModuleOverrides/com.unity.ui/Core/GameObjects/PanelSettings.cs +++ b/ModuleOverrides/com.unity.ui/Core/GameObjects/PanelSettings.cs @@ -441,7 +441,7 @@ internal static void SetupLiveReloadPanelTrackers(bool isLiveReloadOn) /// internal VisualElement visualTree => m_PanelAccess.panel.visualTree; - private UIDocumentList m_AttachedUIDocumentsList; + internal UIDocumentList m_AttachedUIDocumentsList; [HideInInspector] [SerializeField] @@ -498,7 +498,15 @@ private void OnEnable() { if (themeUss == null) { - Debug.LogWarning("No Theme Style Sheet set to PanelSettings " + name + ", UI will not render properly", this); + // In the Editor, we only want this to run when in play mode, because otherwise users may get a false + // alarm when the project is loading and the theme asset is not yet loaded. By keeping it here, we can + // still inform them of a potential problem (it's also in the PanelSettings inspector). + // On a built player, this will always show, so if they're UI is missing they can have a clue of why. + if (UIDocument.IsEditorPlayingOrWillChangePlaymode()) + { + Debug.LogWarning( + "No Theme Style Sheet set to PanelSettings " + name + ", UI will not render properly", this); + } } InitializeShaders(); diff --git a/ModuleOverrides/com.unity.ui/Core/GameObjects/UIDocument.cs b/ModuleOverrides/com.unity.ui/Core/GameObjects/UIDocument.cs index 8c007202d3..c7ba9d5c18 100644 --- a/ModuleOverrides/com.unity.ui/Core/GameObjects/UIDocument.cs +++ b/ModuleOverrides/com.unity.ui/Core/GameObjects/UIDocument.cs @@ -400,7 +400,7 @@ private void RecreateUI() { if (m_RootVisualElement != null) { - m_RootVisualElement.RemoveFromHierarchy(); + RemoveFromHierarchy(); m_PanelSettings?.panel.liveReloadSystem.UnregisterVisualTreeAssetTracker(m_RootVisualElement); m_RootVisualElement = null; } @@ -496,19 +496,7 @@ private void AddRootVisualElementToTree() } } - private void OnDisable() - { - if (m_RootVisualElement != null) - { - m_RootVisualElement.RemoveFromHierarchy(); - // Unhook tracking, we're going down (but only after we detach from the panel). - if (m_PanelSettings != null) - m_PanelSettings.panel.liveReloadSystem.UnregisterVisualTreeAssetTracker(m_RootVisualElement); - m_RootVisualElement = null; - } - } - - private void OnDestroy() + private void RemoveFromHierarchy() { if (parentUI != null) { @@ -520,6 +508,18 @@ private void OnDestroy() } } + private void OnDisable() + { + if (m_RootVisualElement != null) + { + RemoveFromHierarchy(); + // Unhook tracking, we're going down (but only after we detach from the panel). + if (m_PanelSettings != null) + m_PanelSettings.panel.liveReloadSystem.UnregisterVisualTreeAssetTracker(m_RootVisualElement); + m_RootVisualElement = null; + } + } + private void OnTransformChildrenChanged() { // In Editor, when not playing, we let a watcher listen for hierarchy changed events, except if @@ -677,7 +677,7 @@ private void OnValidate() m_OldUxml = sourceAsset; } - if (m_PreviousPanelSettings != m_PanelSettings) + if (m_PreviousPanelSettings != m_PanelSettings && m_RootVisualElement != null && m_RootVisualElement.panel != null) { // We'll use the setter as it guarantees the right behavior. // It's necessary for the setter that the old value is still in place. @@ -688,8 +688,12 @@ private void OnValidate() if (m_OldSortingOrder != m_SortingOrder) { + if (m_RootVisualElement != null && m_RootVisualElement.panel != null) + { + ApplySortingOrder(); + } + m_OldSortingOrder = m_SortingOrder; - ApplySortingOrder(); } } diff --git a/ModuleOverrides/com.unity.ui/Core/IMGUIContainer.cs b/ModuleOverrides/com.unity.ui/Core/IMGUIContainer.cs index 34ccfc7e43..caf147fca4 100644 --- a/ModuleOverrides/com.unity.ui/Core/IMGUIContainer.cs +++ b/ModuleOverrides/com.unity.ui/Core/IMGUIContainer.cs @@ -502,11 +502,22 @@ public void MarkDirtyLayout() IncrementVersion(VersionChangeType.Layout); } + [EventInterest(EventInterestOptionsInternal.TriggeredByOS)] + internal override void ExecuteDefaultActionDisabledAtTarget(EventBase evt) + { + base.ExecuteDefaultActionDisabledAtTarget(evt); + ProcessEvent(evt); + } + [EventInterest(EventInterestOptionsInternal.TriggeredByOS)] protected override void ExecuteDefaultActionAtTarget(EventBase evt) { base.ExecuteDefaultActionAtTarget(evt); + ProcessEvent(evt); + } + private void ProcessEvent(EventBase evt) + { if (evt.imguiEvent == null) return; 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/Style/CustomStyle.cs b/ModuleOverrides/com.unity.ui/Core/Style/CustomStyle.cs index 0ec5b04116..5f2087f577 100644 --- a/ModuleOverrides/com.unity.ui/Core/Style/CustomStyle.cs +++ b/ModuleOverrides/com.unity.ui/Core/Style/CustomStyle.cs @@ -107,11 +107,6 @@ public interface ICustomStyle /// Gets the value associated with the specified . /// /// True if the property is found, false if not. - bool TryGetValue(CustomStyleProperty property, out Object value); - /// - /// Gets the value associated with the specified . - /// - /// True if the property is found, false if not. bool TryGetValue(CustomStyleProperty property, out T value) where T : Object; /// /// Gets the value associated with the specified . 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/Core/UXML/UxmlFactory.cs b/ModuleOverrides/com.unity.ui/Core/UXML/UxmlFactory.cs index 3d5e610348..48b5c61bed 100644 --- a/ModuleOverrides/com.unity.ui/Core/UXML/UxmlFactory.cs +++ b/ModuleOverrides/com.unity.ui/Core/UXML/UxmlFactory.cs @@ -259,10 +259,7 @@ public virtual IEnumerable uxmlAttributesDescription { get { - foreach (var attr in m_Traits.uxmlAttributesDescription) - { - yield return attr; - } + return m_Traits.uxmlAttributesDescription; } } @@ -273,10 +270,7 @@ public virtual IEnumerable uxmlChildElementsDescrip { get { - foreach (var child in m_Traits.uxmlChildElementsDescription) - { - yield return child; - } + return m_Traits.uxmlChildElementsDescription; } } diff --git a/ModuleOverrides/com.unity.ui/Core/VisualElementStyleAccess.cs b/ModuleOverrides/com.unity.ui/Core/VisualElementStyleAccess.cs index 44d0f88eec..6299e5c2e8 100644 --- a/ModuleOverrides/com.unity.ui/Core/VisualElementStyleAccess.cs +++ b/ModuleOverrides/com.unity.ui/Core/VisualElementStyleAccess.cs @@ -321,17 +321,6 @@ public bool TryGetValue(CustomStyleProperty property, out VectorIma return false; } - public bool TryGetValue(CustomStyleProperty property, out Object value) - { - if (m_CustomProperties != null && m_CustomProperties.TryGetValue(property.name, out var customProp)) - { - return customProp.sheet.TryReadAssetReference(customProp.handle, out value); - } - - value = null; - return false; - } - public bool TryGetValue(CustomStyleProperty property, out T value) where T : Object { if (m_CustomProperties != null && m_CustomProperties.TryGetValue(property.name, out var customProp)) 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/ModuleOverrides/com.unity.ui/Editor/UXMLSchemaGenerator.cs b/ModuleOverrides/com.unity.ui/Editor/UXMLSchemaGenerator.cs index 86d87e6f6d..971f0ec670 100644 --- a/ModuleOverrides/com.unity.ui/Editor/UXMLSchemaGenerator.cs +++ b/ModuleOverrides/com.unity.ui/Editor/UXMLSchemaGenerator.cs @@ -426,26 +426,46 @@ static XmlSchemaType AddElementTypeToXmlSchema(IUxmlFactory factory, SchemaInfo restriction.AnyAttribute = anyAttribute; } - foreach (UxmlAttributeDescription attrDesc in factory.uxmlAttributesDescription) + // For user created types, they may return null for uxmlAttributeDescription, so we need to check in order not to crash. + if (factory.uxmlAttributesDescription != null) { - XmlQualifiedName typeName = AddAttributeTypeToXmlSchema(schemaInfo, attrDesc, factory, processingData); - if (typeName != null) + foreach (UxmlAttributeDescription attrDesc in factory.uxmlAttributesDescription) { - AddAttributeToXmlSchema(restriction, attrDesc, typeName); - schemaInfo.importNamespaces.Add(attrDesc.typeNamespace); + // For user created types, they may `yield return null` which would create an array with a null, so we need + // to check in order not to crash. + if (attrDesc != null) + { + XmlQualifiedName typeName = + AddAttributeTypeToXmlSchema(schemaInfo, attrDesc, factory, processingData); + if (typeName != null) + { + AddAttributeToXmlSchema(restriction, attrDesc, typeName); + schemaInfo.importNamespaces.Add(attrDesc.typeNamespace); + } + } } } - bool hasChildElements = false; - foreach (UxmlChildElementDescription childDesc in factory.uxmlChildElementsDescription) + // For user created types, they may return null for uxmlChildElementsDescription, so we need to check in order not to crash. + if (factory.uxmlChildElementsDescription != null) { - hasChildElements = true; - schemaInfo.importNamespaces.Add(childDesc.elementNamespace); - } + bool hasChildElements = false; + foreach (UxmlChildElementDescription childDesc in factory.uxmlChildElementsDescription) + { + // For user created types, they may `yield return null` which would create an array with a null, so we need + // to check in order not to crash. + if (childDesc != null) + { + hasChildElements = true; + schemaInfo.importNamespaces.Add(childDesc.elementNamespace); + } + } - if (hasChildElements) - { - restriction.Particle = MakeChoiceSequence(factory.uxmlChildElementsDescription); + if (hasChildElements) + { + restriction.Particle = + MakeChoiceSequence(factory.uxmlChildElementsDescription.Where(x => x != null)); + } } schemaInfo.schema.Items.Add(elementType); diff --git a/Modules/AndroidJNI/AndroidJava.cs b/Modules/AndroidJNI/AndroidJava.cs index a2d0496cb3..1fc72fda3e 100644 --- a/Modules/AndroidJNI/AndroidJava.cs +++ b/Modules/AndroidJNI/AndroidJava.cs @@ -486,7 +486,7 @@ protected ReturnType _Call(string methodName, params object[] args) else if (AndroidReflection.IsAssignableFrom(typeof(System.Array), typeof(ReturnType))) { IntPtr jobject = AndroidJNISafe.CallObjectMethod(m_jobject, methodID, jniArgs); - return (jobject == IntPtr.Zero) ? default(ReturnType) : (ReturnType)(object)AndroidJNIHelper.ConvertFromJNIArray(jobject); + return FromJavaArrayDeleteLocalRef(jobject); } else { @@ -544,7 +544,7 @@ protected FieldType _Get(string fieldName) else if (AndroidReflection.IsAssignableFrom(typeof(System.Array), typeof(FieldType))) { IntPtr jobject = AndroidJNISafe.GetObjectField(m_jobject, fieldID); - return (jobject == IntPtr.Zero) ? default(FieldType) : (FieldType)(object)AndroidJNIHelper.ConvertFromJNIArray(jobject); + return FromJavaArrayDeleteLocalRef(jobject); } else { @@ -664,7 +664,7 @@ protected ReturnType _CallStatic(string methodName, params object[] else if (AndroidReflection.IsAssignableFrom(typeof(System.Array), typeof(ReturnType))) { IntPtr jobject = AndroidJNISafe.CallStaticObjectMethod(m_jclass, methodID, jniArgs); - return (jobject == IntPtr.Zero) ? default(ReturnType) : (ReturnType)(object)AndroidJNIHelper.ConvertFromJNIArray(jobject); + return FromJavaArrayDeleteLocalRef(jobject); } else { @@ -723,7 +723,7 @@ protected FieldType _GetStatic(string fieldName) else if (AndroidReflection.IsAssignableFrom(typeof(System.Array), typeof(FieldType))) { IntPtr jobject = AndroidJNISafe.GetStaticObjectField(m_jclass, fieldID); - return (jobject == IntPtr.Zero) ? default(FieldType) : (FieldType)(object)AndroidJNIHelper.ConvertFromJNIArray(jobject); + return FromJavaArrayDeleteLocalRef(jobject); } else { @@ -790,6 +790,20 @@ internal static AndroidJavaClass AndroidJavaClassDeleteLocalRef(IntPtr jclass) try { return new AndroidJavaClass(jclass); } finally { AndroidJNISafe.DeleteLocalRef(jclass); } } + internal static ReturnType FromJavaArrayDeleteLocalRef(IntPtr jobject) + { + if (jobject == IntPtr.Zero) + return default(ReturnType); + try + { + return (ReturnType)(object)AndroidJNIHelper.ConvertFromJNIArray(jobject); + } + finally + { + AndroidJNISafe.DeleteLocalRef(jobject); + } + } + //=================================================================== protected IntPtr _GetRawObject() { return m_jobject == null ? IntPtr.Zero : m_jobject; } protected IntPtr _GetRawClass() { return m_jclass; } diff --git a/Modules/Audio/Public/ScriptBindings/Audio.bindings.cs b/Modules/Audio/Public/ScriptBindings/Audio.bindings.cs index 437abcfec1..296b10e01a 100644 --- a/Modules/Audio/Public/ScriptBindings/Audio.bindings.cs +++ b/Modules/Audio/Public/ScriptBindings/Audio.bindings.cs @@ -127,6 +127,7 @@ public enum GamepadSpeakerOutputType { Speaker = 0, Vibration = 1, + SecondaryVibration = 2 } diff --git a/Modules/BuildPipeline/Editor/Managed/BuildPlayerDataGenerator.cs b/Modules/BuildPipeline/Editor/Managed/BuildPlayerDataGenerator.cs index 3313ce9c98..f6449f1581 100644 --- a/Modules/BuildPipeline/Editor/Managed/BuildPlayerDataGenerator.cs +++ b/Modules/BuildPipeline/Editor/Managed/BuildPlayerDataGenerator.cs @@ -35,10 +35,10 @@ public static List GetStaticSearchPaths(BuildTarget buildTarget) { var unityAssembliesInternal = EditorCompilationInterface.Instance.PrecompiledAssemblyProvider.GetUnityAssemblies(true, buildTarget); - var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(buildTarget); + var namedBuildTarget = NamedBuildTarget.FromActiveSettings(buildTarget); var systemReferenceDirectories = MonoLibraryHelpers.GetSystemReferenceDirectories( - PlayerSettings.GetApiCompatibilityLevel(buildTargetGroup)); + PlayerSettings.GetApiCompatibilityLevel(namedBuildTarget)); var searchPaths = unityAssembliesInternal.Select(x => Path.GetDirectoryName(x.Path)) .Distinct().ToList(); diff --git a/Modules/BuildPipeline/Editor/Managed/ContentBuildInterface.bindings.cs b/Modules/BuildPipeline/Editor/Managed/ContentBuildInterface.bindings.cs index feec5b941f..9f41d12b51 100644 --- a/Modules/BuildPipeline/Editor/Managed/ContentBuildInterface.bindings.cs +++ b/Modules/BuildPipeline/Editor/Managed/ContentBuildInterface.bindings.cs @@ -178,6 +178,7 @@ public static WriteResult WriteSerializedFile(string outputFolder, WriteParamete return WriteSerializedFile_Internal(outputFolder, parameters.writeCommand, parameters.settings, parameters.globalUsage, parameters.usageSet, parameters.referenceMap, parameters.preloadInfo, parameters.bundleInfo); } + [NativeThrows] static extern WriteResult WriteSerializedFile_Internal(string outputFolder, WriteCommand writeCommand, BuildSettings settings, BuildUsageTagGlobal globalUsage, BuildUsageTagSet usageSet, BuildReferenceMap referenceMap, PreloadInfo preloadInfo, AssetBundleInfo bundleInfo); public static WriteResult WriteSceneSerializedFile(string outputFolder, WriteSceneParameters parameters) @@ -194,6 +195,7 @@ public static WriteResult WriteSceneSerializedFile(string outputFolder, WriteSce return WriteSceneSerializedFile_Internal(outputFolder, parameters.scenePath, parameters.writeCommand, parameters.settings, parameters.globalUsage, parameters.usageSet, parameters.referenceMap, parameters.preloadInfo, parameters.sceneBundleInfo); } + [NativeThrows] static extern WriteResult WriteSceneSerializedFile_Internal(string outputFolder, string scenePath, WriteCommand writeCommand, BuildSettings settings, BuildUsageTagGlobal globalUsage, BuildUsageTagSet usageSet, BuildReferenceMap referenceMap, PreloadInfo preloadInfo, SceneBundleInfo sceneBundleInfo); public static WriteResult WriteGameManagersSerializedFile(string outputFolder, WriteManagerParameters parameters) diff --git a/Modules/DeviceSimulatorEditor/DeviceListPopup.cs b/Modules/DeviceSimulatorEditor/DeviceListPopup.cs index 3dab69363a..52f1d0d237 100644 --- a/Modules/DeviceSimulatorEditor/DeviceListPopup.cs +++ b/Modules/DeviceSimulatorEditor/DeviceListPopup.cs @@ -10,6 +10,51 @@ namespace UnityEditor.DeviceSimulation { + internal enum DeviceButtonVisibility { Enabled, Disabled, Hidden} + + internal struct DevicePackageInstallButtonState + { + public DevicePackageStatus PackageStatus; + public Action OnPressed; + + public DeviceButtonVisibility Visibility + { + get + { + switch (PackageStatus) + { + case DevicePackageStatus.Unavailable: + case DevicePackageStatus.Outdated: + return DeviceButtonVisibility.Enabled; + case DevicePackageStatus.Adding: + case DevicePackageStatus.Updating: + return DeviceButtonVisibility.Disabled; + default: + return DeviceButtonVisibility.Hidden; + } + } + } + + public string Text + { + get + { + switch (PackageStatus) + { + case DevicePackageStatus.Unavailable: + return "Install Additional Devices"; + case DevicePackageStatus.Outdated: + return "Update Devices"; + case DevicePackageStatus.Adding: + case DevicePackageStatus.Updating: + return "In Progress..."; + default: + return "Invisible Button"; + } + } + } + } + internal class DeviceListPopup : PopupWindowContent { private struct IndexedDevice @@ -48,8 +93,11 @@ public IndexedDevice(int index, DeviceInfo device) public Action OnSearchInput { get; set; } - public DeviceListPopup(DeviceInfoAsset[] deviceList, int selectedDeviceIndex, int maximumVisibleDeviceCount, string lastSearchContent) + public DevicePackageInstallButtonState DeviceButtonState; + + public DeviceListPopup(DeviceInfoAsset[] deviceList, int selectedDeviceIndex, int maximumVisibleDeviceCount, string lastSearchContent, DevicePackageInstallButtonState deviceButtonState) { + DeviceButtonState = deviceButtonState; m_DeviceList = deviceList; m_SelectedDeviceIndex = selectedDeviceIndex; m_MaximumVisibleDeviceCount = maximumVisibleDeviceCount; @@ -102,8 +150,9 @@ private void CalculateScrollPosition() public override Vector2 GetWindowSize() { + var buttonVisible = DeviceButtonState.Visibility != DeviceButtonVisibility.Hidden; // Add 1 for search filter. - return new Vector2(220, (GetVisibleDeviceCount() + 1) * (k_ItemHeight + k_SpaceHeight) + 5); + return new Vector2(220, (GetVisibleDeviceCount() + 1 + (buttonVisible ? 1 : 0)) * (k_ItemHeight + k_SpaceHeight) + (buttonVisible ? 10 : 5)); } private int GetVisibleDeviceCount() @@ -149,6 +198,18 @@ public override void OnGUI(Rect rect) EditorGUILayout.EndHorizontal(); } GUI.EndScrollView(); + + if (m_FilteredDevices.Count > 0) + { + EditorGUILayout.Space(visibleRect.height); + } + + if (DeviceButtonState.Visibility == DeviceButtonVisibility.Disabled) + GUI.enabled = false; + if(DeviceButtonState.Visibility != DeviceButtonVisibility.Hidden && GUILayout.Button(new GUIContent(DeviceButtonState.Text, "Install the latest available version of Device Simulator Devices package (com.unity.device-simulator.devices)"))) + DeviceButtonState.OnPressed?.Invoke(); + + GUI.enabled = true; } private void OnDeviceListGUI(int startIndex, int drawDeviceCount, Rect totalRect) diff --git a/Modules/DeviceSimulatorEditor/DevicePackage.cs b/Modules/DeviceSimulatorEditor/DevicePackage.cs new file mode 100644 index 0000000000..1021fe7cc1 --- /dev/null +++ b/Modules/DeviceSimulatorEditor/DevicePackage.cs @@ -0,0 +1,119 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Linq; +using UnityEditor.PackageManager; +using UnityEditor.PackageManager.Requests; +using UnityEngine; + +namespace UnityEditor.DeviceSimulation +{ + internal enum DevicePackageStatus { Available, Unavailable, Outdated, Adding, Updating, Unknown, Error } + + internal static class DevicePackage + { + private static bool s_Initialized; + private static DevicePackageStatus s_CurrentStatus = DevicePackageStatus.Unknown; + + private static ListRequest s_ListRequest; + private static AddRequest s_AddRequest; + + private static Action m_OnPackageStatus; + public static event Action OnPackageStatus + { + add + { + m_OnPackageStatus += value; + + if (!s_Initialized) + { + s_Initialized = true; + PackageManager.Events.registeredPackages += OnPackageRegistration; + } + + if (s_CurrentStatus != DevicePackageStatus.Unknown) + { + m_OnPackageStatus?.Invoke(s_CurrentStatus); + } + else if (s_ListRequest == null && s_AddRequest == null) + { + s_ListRequest = Client.List(); + EditorApplication.update += HandleList; + } + } + remove => m_OnPackageStatus -= value; + } + + public static void Add() + { + s_AddRequest = Client.Add("com.unity.device-simulator.devices"); + EditorApplication.update += HandleAdd; + + SetStatus(DevicePackageStatus.Adding); + } + + private static void OnPackageRegistration(PackageRegistrationEventArgs args) + { + if (args.removed.Any(package => package.name == "com.unity.device-simulator.devices")) + SetStatus(DevicePackageStatus.Unavailable); + + var package = args.added.Concat(args.changedTo).FirstOrDefault(package => package.name == "com.unity.device-simulator.devices"); + if (package != null) + SetStatus(GetDevicePackageStatus(package)); + } + + private static void HandleList() + { + if (!s_ListRequest.IsCompleted) + return; + + EditorApplication.update -= HandleList; + + if (s_ListRequest.Status == StatusCode.Success) + { + var package = s_ListRequest.Result.FirstOrDefault(package => package.name == "com.unity.device-simulator.devices"); + SetStatus(GetDevicePackageStatus(package)); + } + else if (s_ListRequest.Status == StatusCode.Failure) + { + SetStatus(DevicePackageStatus.Error); + } + + s_ListRequest = null; + } + + private static void HandleAdd() + { + if (!s_AddRequest.IsCompleted) + return; + + EditorApplication.update -= HandleAdd; + + if (s_AddRequest.Status == StatusCode.Success) + SetStatus(DevicePackageStatus.Available); + else if (s_AddRequest.Status == StatusCode.Failure) + { + Debug.LogError("Failed installing Device Simulator Devices (com.unity.device-simulator.devices) package. Try installing it from the Package Manager window."); + SetStatus(DevicePackageStatus.Error); + } + + s_AddRequest = null; + } + + private static void SetStatus(DevicePackageStatus newStatus) + { + s_CurrentStatus = newStatus; + m_OnPackageStatus?.Invoke(s_CurrentStatus); + } + + private static DevicePackageStatus GetDevicePackageStatus(PackageManager.PackageInfo packageInfo) + { + if (packageInfo == null) + return DevicePackageStatus.Unavailable; + return packageInfo.version == packageInfo.versions.latestCompatible ? DevicePackageStatus.Available : DevicePackageStatus.Outdated; + } + + } +} diff --git a/Modules/DeviceSimulatorEditor/DeviceSimulatorMain.cs b/Modules/DeviceSimulatorEditor/DeviceSimulatorMain.cs index 6c3f5b1178..5035e6683b 100644 --- a/Modules/DeviceSimulatorEditor/DeviceSimulatorMain.cs +++ b/Modules/DeviceSimulatorEditor/DeviceSimulatorMain.cs @@ -22,6 +22,7 @@ internal class DeviceSimulatorMain : IDisposable public PlayModeView playModeView { get; } public Vector2 targetSize => new Vector2(m_ScreenSimulation.currentResolution.width, m_ScreenSimulation.currentResolution.height); + public ScreenSimulation ScreenSimulation => m_ScreenSimulation; public RenderTexture displayTexture { @@ -115,6 +116,7 @@ public void InitSimulation() m_TouchInput.SetDevice(m_ScreenSimulation, currentDevice.deviceInfo.IsAndroidDevice()); m_UserInterface.OnSimulationStart(m_ScreenSimulation); + m_ScreenSimulation.ApplyChanges(); InitScreenUI(); m_ApplicationSimulation.OnSimulationStart(currentDevice.deviceInfo); diff --git a/Modules/DeviceSimulatorEditor/Shims/ScreenSimulation.cs b/Modules/DeviceSimulatorEditor/Shims/ScreenSimulation.cs index da07c7a776..8ad953b68b 100644 --- a/Modules/DeviceSimulatorEditor/Shims/ScreenSimulation.cs +++ b/Modules/DeviceSimulatorEditor/Shims/ScreenSimulation.cs @@ -14,6 +14,13 @@ internal class ScreenSimulation : ScreenShimBase // Reasonable maximum resolution, tested on an Android device and game crashes when set beyond private const int k_MaxResolution = 8192; + private int m_RequestedWidth; + private int m_RequestedHeight; + private ScreenOrientation m_RequestedOrientation; + private bool m_RequestedFullScreen; + private bool m_RequestDefaultResolution; + private bool m_RequestInsetUpdate; + private SimulationPlayerSettings m_PlayerSettings; private DeviceInfo m_DeviceInfo; private ScreenData m_Screen; @@ -70,7 +77,7 @@ public int DeviceRotation public bool IsRenderingLandscape => SimulatorUtilities.IsLandscape(m_RenderedOrientation); - public event Action OnOrientationChanged; + public event Action OnOrientationChanged; public event Action OnAllowedOrientationChanged; public event Action OnResolutionChanged; public event Action OnFullScreenChanged; @@ -93,6 +100,7 @@ public ScreenSimulation(DeviceInfo device, SimulationPlayerSettings playerSettin // Set the full screen mode. m_IsFullScreen = !m_DeviceInfo.IsAndroidDevice() || m_PlayerSettings.androidStartInFullscreen; + m_RequestedFullScreen = m_IsFullScreen; m_IsRenderingOutsideSafeArea = !m_DeviceInfo.IsAndroidDevice() || m_PlayerSettings.androidRenderOutsideSafeArea; // Calculate the right orientation. @@ -105,37 +113,29 @@ public ScreenSimulation(DeviceInfo device, SimulationPlayerSettings playerSettin else if (m_SupportedOrientations.ContainsKey(settingOrientation)) { m_AutoRotation = false; - ForceNewOrientation(settingOrientation); + RequestOrientation(settingOrientation); } else { // The real iPhone X responds to this absolute corner case by crashing, we will not do that. m_AutoRotation = false; - ForceNewOrientation(m_SupportedOrientations.Keys.ToArray()[0]); + RequestOrientation(m_SupportedOrientations.Keys.ToArray()[0]); } - InitResolution(); - ShimManager.UseShim(this); - } + m_RequestInsetUpdate = true; + m_RequestDefaultResolution = true; - private void InitResolution() - { - m_WasResolutionSet = false; - CalculateInsets(); - CalculateResolutionWithInsets(out m_CurrentWidth, out m_CurrentHeight); - CalculateSafeAreaAndCutouts(); + ShimManager.UseShim(this); } public void ChangeScreen(int screenIndex) { m_Screen = m_DeviceInfo.screens[screenIndex]; - FindSupportedOrientations(); if (!m_WasResolutionSet) { - CalculateResolutionWithInsets(out int tempWidth, out int tempHeight); - SetResolution(tempWidth, tempHeight); + m_RequestDefaultResolution = true; } } @@ -152,40 +152,136 @@ private void ApplyAutoRotation() { if (!m_AutoRotation) return; - if (m_DeviceOrientation != m_RenderedOrientation && m_SupportedOrientations.ContainsKey(m_DeviceOrientation) && m_AllowedAutoRotation[m_DeviceOrientation]) + if (m_DeviceOrientation != m_RequestedOrientation && m_SupportedOrientations.ContainsKey(m_DeviceOrientation) && m_AllowedAutoRotation[m_DeviceOrientation]) { - ForceNewOrientation(m_DeviceOrientation); + RequestOrientation(m_DeviceOrientation); } - else + } + + private void RequestOrientation(ScreenOrientation orientation) + { + m_RequestedOrientation = orientation; + } + + private void SetAutoRotationOrientation(ScreenOrientation orientation, bool value) + { + m_AllowedAutoRotation[orientation] = value; + + if (!m_AutoRotation) { - OnOrientationChanged?.Invoke(m_AutoRotation); + OnAllowedOrientationChanged?.Invoke(); + return; } + + // If the current auto rotation is disabled we need to rotate to another allowed orientation + if (!value && orientation == m_RequestedOrientation) + { + SetFirstAvailableAutoOrientation(); + } + else if (value) + { + ApplyAutoRotation(); + } + + OnAllowedOrientationChanged?.Invoke(); } - private void ForceNewOrientation(ScreenOrientation orientation) + private void SetFirstAvailableAutoOrientation() { - // Swap resolution Width and Height if changing from Portrait to Landscape or vice versa - if ((orientation == ScreenOrientation.Portrait || orientation == ScreenOrientation.PortraitUpsideDown) && IsRenderingLandscape || - (orientation == ScreenOrientation.LandscapeLeft || orientation == ScreenOrientation.LandscapeRight) && !IsRenderingLandscape) + foreach (var newOrientation in m_SupportedOrientations.Keys) { - var temp = m_CurrentHeight; - m_CurrentHeight = m_CurrentWidth; - m_CurrentWidth = temp; - OnResolutionChanged?.Invoke(m_CurrentWidth, m_CurrentHeight); + if (m_AllowedAutoRotation[newOrientation]) + { + RequestOrientation(newOrientation); + return; + } } - m_RenderedOrientation = orientation; - OnOrientationChanged?.Invoke(m_AutoRotation); + } - CalculateInsets(); + public void ApplyChanges() + { + var updateSafeArea = false; - // We only change the resolution if we never set the resolution by calling Screen.SetResolution(). - if (!m_WasResolutionSet) + var orientationEvent = false; + var resolutionEvent = false; + var fullScreenEvent = false; + var screenSpaceSafeAreaEvent = false; + var insetsEvent = false; + + if (m_RequestedOrientation != m_RenderedOrientation) { - CalculateResolutionWithInsets(out int tempWidth, out int tempHeight); - SetResolution(tempWidth, tempHeight); + if (m_RequestedOrientation.IsLandscape() != m_RenderedOrientation.IsLandscape()) + { + // Swap resolution Width and Height if changing from Portrait to Landscape or vice versa + if(m_WasResolutionSet) + (m_RequestedHeight, m_RequestedWidth) = (m_RequestedWidth, m_RequestedHeight); + else + m_RequestDefaultResolution = true; + } + + m_RenderedOrientation = m_RequestedOrientation; + orientationEvent = true; + m_RequestInsetUpdate = true; + updateSafeArea = true; } - CalculateSafeAreaAndCutouts(); + if(m_RequestedFullScreen != m_IsFullScreen) + { + m_IsFullScreen = m_RequestedFullScreen; + m_RequestInsetUpdate = true; + + // We only change the resolution if we never set the resolution by calling Screen.SetResolution(). + if (!m_WasResolutionSet) + { + m_RequestDefaultResolution = true; + } + + updateSafeArea = true; + fullScreenEvent = true; + } + + if (m_RequestInsetUpdate) + { + CalculateInsets(); + insetsEvent = true; + } + + if((m_RequestedWidth != m_CurrentWidth || m_RequestedHeight != m_CurrentHeight) && m_WasResolutionSet) + { + m_CurrentWidth = m_RequestedWidth; + m_CurrentHeight = m_RequestedHeight; + updateSafeArea = true; + resolutionEvent = true; + } + else if (m_RequestDefaultResolution) + { + CalculateResolutionWithInsets(); + updateSafeArea = true; + resolutionEvent = true; + } + + if (updateSafeArea) + { + CalculateSafeAreaAndCutouts(); + screenSpaceSafeAreaEvent = true; + } + + if(orientationEvent) + OnOrientationChanged?.Invoke(); + if(resolutionEvent) + OnResolutionChanged?.Invoke(m_CurrentWidth, m_CurrentHeight); + if(fullScreenEvent) + OnFullScreenChanged?.Invoke(m_IsFullScreen); + if(screenSpaceSafeAreaEvent) + OnScreenSpaceSafeAreaChanged?.Invoke(ScreenSpaceSafeArea); + if(insetsEvent) + OnInsetsChanged?.Invoke(Insets); + + m_RequestDefaultResolution = false; + m_RequestedOrientation = m_RenderedOrientation; + m_RequestedHeight = m_CurrentHeight; + m_RequestedWidth = m_CurrentWidth; + m_RequestInsetUpdate = false; } private void CalculateSafeAreaAndCutouts() @@ -233,7 +329,6 @@ private void CalculateSafeAreaAndCutouts() } ScreenSpaceSafeArea = onScreenSafeArea; - OnScreenSpaceSafeAreaChanged?.Invoke(ScreenSpaceSafeArea); var screenWidthInOrientation = IsRenderingLandscape ? m_Screen.height : m_Screen.width; var screenHeightInOrientation = IsRenderingLandscape ? m_Screen.width : m_Screen.height; @@ -339,70 +434,9 @@ private void CalculateInsets() } } Insets = inset; - OnInsetsChanged?.Invoke(inset); } - private void SetAutoRotationOrientation(ScreenOrientation orientation, bool value) - { - m_AllowedAutoRotation[orientation] = value; - - if (!m_AutoRotation) - { - OnAllowedOrientationChanged?.Invoke(); - return; - } - - // If the current auto rotation is disabled we need to rotate to another allowed orientation - if (!value && orientation == m_RenderedOrientation) - { - SetFirstAvailableAutoOrientation(); - } - else if (value) - { - ApplyAutoRotation(); - } - - OnAllowedOrientationChanged?.Invoke(); - } - - private void SetFirstAvailableAutoOrientation() - { - foreach (var newOrientation in m_SupportedOrientations.Keys) - { - if (m_AllowedAutoRotation[newOrientation]) - { - ForceNewOrientation(newOrientation); - } - } - } - - private void SetResolution(int width, int height) - { - if (width > k_MaxResolution || height > k_MaxResolution || width < 0 || height < 0) - { - Debug.LogError($"Failed to change resolution. Make sure that both width and height are at least 0 and less than {k_MaxResolution}."); - return; - } - - if (width == 0 && height == 0) - { - InitResolution(); - return; - } - - if (width == 0) - width = 1; - else if (height == 0) - height = 1; - - m_CurrentWidth = width; - m_CurrentHeight = height; - CalculateSafeAreaAndCutouts(); - - OnResolutionChanged?.Invoke(m_CurrentWidth, m_CurrentHeight); - } - - private void CalculateResolutionWithInsets(out int width, out int height) + private void CalculateResolutionWithInsets() { var screenWidthInOrientation = IsRenderingLandscape ? m_Screen.height : m_Screen.width; var screenHeightInOrientation = IsRenderingLandscape ? m_Screen.width : m_Screen.height; @@ -416,8 +450,8 @@ private void CalculateResolutionWithInsets(out int width, out int height) if (m_PlayerSettings.resolutionScalingMode == ResolutionScalingMode.FixedDpi && m_PlayerSettings.targetDpi < m_Screen.dpi) dpiRatio = m_PlayerSettings.targetDpi / m_Screen.dpi; - width = Mathf.RoundToInt(widthInOrientation * dpiRatio); - height = Mathf.RoundToInt(heightInOrientation * dpiRatio); + m_CurrentWidth = Mathf.RoundToInt(widthInOrientation * dpiRatio); + m_CurrentHeight = Mathf.RoundToInt(heightInOrientation * dpiRatio); } public void Enable() @@ -464,7 +498,7 @@ public override ScreenOrientation orientation else if (m_SupportedOrientations.ContainsKey(value)) { m_AutoRotation = false; - ForceNewOrientation(value); + RequestOrientation(value); } } } @@ -496,7 +530,28 @@ public override bool autorotateToLandscapeRight public override void SetResolution(int width, int height, FullScreenMode fullScreenMode, int refreshRate) { m_WasResolutionSet = true; - SetResolution(width, height); + + if (width > k_MaxResolution || height > k_MaxResolution || width < 0 || height < 0) + { + Debug.LogError($"Failed to change resolution. Make sure that both width and height are at least 0 and less than {k_MaxResolution}."); + return; + } + + if (width == 0 && height == 0) + { + m_WasResolutionSet = false; + m_RequestDefaultResolution = true; + return; + } + + if (width == 0) + width = 1; + else if (height == 0) + height = 1; + + m_RequestedWidth = width; + m_RequestedHeight = height; + fullScreen = (fullScreenMode != FullScreenMode.Windowed); // Tested on Pixel 2 that all other three types go into full screen mode. } @@ -508,21 +563,7 @@ public override bool fullScreen if (!m_DeviceInfo.IsAndroidDevice() || m_IsFullScreen == value) return; - m_IsFullScreen = value; - CalculateInsets(); - - // We only change the resolution if we never set the resolution by calling Screen.SetResolution(). - if (!m_WasResolutionSet) - { - CalculateResolutionWithInsets(out int tempWidth, out int tempHeight); - SetResolution(tempWidth, tempHeight); - } - else - { - CalculateSafeAreaAndCutouts(); - } - - OnFullScreenChanged?.Invoke(m_IsFullScreen); + m_RequestedFullScreen = value; } } diff --git a/Modules/DeviceSimulatorEditor/SimulatorUtilities.cs b/Modules/DeviceSimulatorEditor/SimulatorUtilities.cs index 78025deeae..af5d530631 100644 --- a/Modules/DeviceSimulatorEditor/SimulatorUtilities.cs +++ b/Modules/DeviceSimulatorEditor/SimulatorUtilities.cs @@ -57,7 +57,7 @@ public static ScreenOrientation RotationToScreenOrientation(int angle) return orientation; } - public static bool IsLandscape(ScreenOrientation orientation) + public static bool IsLandscape(this ScreenOrientation orientation) { if (orientation == ScreenOrientation.LandscapeLeft || orientation == ScreenOrientation.LandscapeRight) return true; diff --git a/Modules/DeviceSimulatorEditor/SimulatorWindow.cs b/Modules/DeviceSimulatorEditor/SimulatorWindow.cs index 4e539f57f6..15bebe4cc0 100644 --- a/Modules/DeviceSimulatorEditor/SimulatorWindow.cs +++ b/Modules/DeviceSimulatorEditor/SimulatorWindow.cs @@ -51,6 +51,8 @@ void OnEnable() m_Main = new DeviceSimulatorMain(m_SimulatorState, rootVisualElement, this); s_SimulatorInstances.Add(this); InitPlayModeViewSwapMenu(); + + DevicePackage.OnPackageStatus += OnDevicePackageStatus; } private void InitPlayModeViewSwapMenu() @@ -71,6 +73,8 @@ private void OnDisable() s_SimulatorInstances.Remove(this); m_Main.Dispose(); + DevicePackage.OnPackageStatus -= OnDevicePackageStatus; + PlayModeAnalytics.SimulatorDisableEvent(); } @@ -102,6 +106,7 @@ private void OnGUI() var type = Event.current.type; if (type == EventType.Repaint) { + m_Main.ScreenSimulation.ApplyChanges(); targetSize = m_Main.targetSize; m_Main.displayTexture = RenderView(m_Main.mousePositionInUICoordinates, false); } @@ -152,6 +157,15 @@ protected override void OnEnterPlayModeBehaviorChange() m_Main.userInterface.UpdateEnterPlayModeBehaviorMsg(); } + private void OnDevicePackageStatus(DevicePackageStatus status) + { + m_Main.userInterface.DeviceButtonState = new DevicePackageInstallButtonState + { + PackageStatus = status, + OnPressed = DevicePackage.Add + }; + } + public void OnPlayPopupSelection(int indexClicked, object objectSelected) { playModeBehaviorIdx = indexClicked; diff --git a/Modules/DeviceSimulatorEditor/UserInterfaceController.cs b/Modules/DeviceSimulatorEditor/UserInterfaceController.cs index 63eab31e56..df08ceaf47 100644 --- a/Modules/DeviceSimulatorEditor/UserInterfaceController.cs +++ b/Modules/DeviceSimulatorEditor/UserInterfaceController.cs @@ -67,6 +67,17 @@ private bool HighlightSafeArea private const int kScaleMax = 100; private bool m_FitToScreenEnabled = true; + private DevicePackageInstallButtonState m_DeviceButtonState = new DevicePackageInstallButtonState { PackageStatus = DevicePackageStatus.Unknown}; + public DevicePackageInstallButtonState DeviceButtonState + { + set + { + m_DeviceButtonState = value; + if(m_DeviceListPopup != null) + m_DeviceListPopup.DeviceButtonState = m_DeviceButtonState; + } + } + // Controls for the toolbar private string m_DeviceSearchContent; private VisualElement m_DeviceListMenu; @@ -96,6 +107,7 @@ private bool HighlightSafeArea private float m_ControlPanelWidth; private readonly Dictionary m_PluginFoldouts = new Dictionary(); private VisualElement m_ControlPanel; + private DeviceListPopup m_DeviceListPopup; public UserInterfaceController(DeviceSimulatorMain deviceSimulatorMain, VisualElement rootVisualElement, SimulatorState serializedState, PluginController pluginController, TouchEventManipulator touchEventManipulator) { @@ -225,7 +237,7 @@ public void OnSimulationStart(ScreenSimulation screenSimulation) m_SelectedDeviceName.text = m_Main.currentDevice.deviceInfo.friendlyName; - m_ScreenSimulation.OnOrientationChanged += autoRotate => m_DeviceView.ScreenOrientation = m_ScreenSimulation.orientation; + m_ScreenSimulation.OnOrientationChanged += () => m_DeviceView.ScreenOrientation = m_ScreenSimulation.orientation; m_ScreenSimulation.OnInsetsChanged += insets => m_DeviceView.ScreenInsets = insets; m_ScreenSimulation.OnScreenSpaceSafeAreaChanged += safeArea => m_DeviceView.SafeArea = safeArea; @@ -330,13 +342,13 @@ private int ClampScale(int scale) private void ShowDeviceInfoList() { var rect = new Rect(m_DeviceListMenu.worldBound.position + new Vector2(1, m_DeviceListMenu.worldBound.height), new Vector2()); - var maximumVisibleDeviceCount = 10; + var maximumVisibleDeviceCount = 15; - var deviceListPopup = new DeviceListPopup(m_Main.devices, m_Main.deviceIndex, maximumVisibleDeviceCount, m_DeviceSearchContent); - deviceListPopup.OnDeviceSelected += OnDeviceSelected; - deviceListPopup.OnSearchInput += OnSearchInput; + m_DeviceListPopup = new DeviceListPopup(m_Main.devices, m_Main.deviceIndex, maximumVisibleDeviceCount, m_DeviceSearchContent, m_DeviceButtonState); + m_DeviceListPopup.OnDeviceSelected += OnDeviceSelected; + m_DeviceListPopup.OnSearchInput += OnSearchInput; - PopupWindow.Show(rect, deviceListPopup); + PopupWindow.Show(rect, m_DeviceListPopup); } private void ShowOnPlayBehaviorSelector() diff --git a/Modules/IMGUI/GUI.bindings.cs b/Modules/IMGUI/GUI.bindings.cs index 3ec65b46fd..d1e24a096d 100644 --- a/Modules/IMGUI/GUI.bindings.cs +++ b/Modules/IMGUI/GUI.bindings.cs @@ -20,6 +20,7 @@ partial class GUI public static extern int depth { get; set; } internal static extern bool usePageScrollbars { get; } + internal static extern bool isInsideList { get; set; } internal static extern Material blendMaterial {[FreeFunction("GetGUIBlendMaterial")] get; } internal static extern Material blitMaterial {[FreeFunction("GetGUIBlitMaterial")] get; } internal static extern Material roundedRectMaterial {[FreeFunction("GetGUIRoundedRectMaterial")] get; } diff --git a/Modules/PackageManagerUI/Editor/External/EditorGameServiceExtension.cs b/Modules/PackageManagerUI/Editor/External/EditorGameServiceExtension.cs index 465032515f..b6430ed076 100644 --- a/Modules/PackageManagerUI/Editor/External/EditorGameServiceExtension.cs +++ b/Modules/PackageManagerUI/Editor/External/EditorGameServiceExtension.cs @@ -34,24 +34,12 @@ internal class CloudProjectSettings : ICloudProjectSettings const string k_ProjectGuid = "ProjectGuid"; const string k_PageContentType = "packages and features"; internal const string k_ServicesConfigPath = "Resources/services.json"; - - internal const int k_ToolsPriority = 100; internal const int k_ServicesPriority = 200; internal static Dictionary groupIndexes = new Dictionary(); static Dictionary s_GroupMap = new Dictionary(); internal static ICloudProjectSettings cloudProjectSettings = new CloudProjectSettings(); - internal static bool FilterToolsPackage(IPackage package) - { - return package?.Is(PackageType.Unity) == true && !FilterServicesPackage(package); - } - - internal static string GetToolsPackageGroupName(IPackage package) - { - return package?.Is(PackageType.Feature) == true ? L10n.Tr("Features") : L10n.Tr("Packages"); - } - internal static bool FilterServicesPackage(IPackage package) { return !string.IsNullOrWhiteSpace(GetServicesPackageGroupName(package)); @@ -91,10 +79,7 @@ public void OnWindowCreated(WindowCreatedArgs args) var pageManager = ServicesContainer.instance.Resolve(); - pageManager.AddSubPage(PackageFilterTab.UnityRegistry, "tools", L10n.Tr("Tools"), L10n.Tr(k_PageContentType), k_ToolsPriority, FilterToolsPackage, GetToolsPackageGroupName); pageManager.AddSubPage(PackageFilterTab.UnityRegistry, "services", L10n.Tr("Services"), L10n.Tr(k_PageContentType), k_ServicesPriority, FilterServicesPackage, GetServicesPackageGroupName, CompareGroup); - - pageManager.AddSubPage(PackageFilterTab.InProject, "tools", L10n.Tr("Tools"), L10n.Tr(k_PageContentType), k_ToolsPriority, FilterToolsPackage, GetToolsPackageGroupName); pageManager.AddSubPage(PackageFilterTab.InProject, "services", L10n.Tr("Services"), L10n.Tr(k_PageContentType), k_ServicesPriority, FilterServicesPackage, GetServicesPackageGroupName, CompareGroup); m_ConfigureButton = args.window.AddPackageActionButton(); diff --git a/Modules/PackageManagerUI/Editor/Services/Analytics/PackageCacheManagementAnalytics.cs b/Modules/PackageManagerUI/Editor/Services/Analytics/PackageCacheManagementAnalytics.cs new file mode 100644 index 0000000000..70e1dc487f --- /dev/null +++ b/Modules/PackageManagerUI/Editor/Services/Analytics/PackageCacheManagementAnalytics.cs @@ -0,0 +1,88 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using UnityEngine.Analytics; + +namespace UnityEditor.PackageManager.UI.Internal +{ + [Serializable] + internal struct PackageCacheManagementAnalytics + { + private const int k_MaxEventsPerHour = 1000; + private const int k_MaxNumberOfElementInStruct = 100; + private const string k_VendorKey = "unity.package-manager-ui"; + + public string action; + public string type; + public string[] old_path_statuses; + public string[] new_path_statuses; + public long t_since_start; // in microseconds + public long ts; // in milliseconds + + private static bool s_Registered; + + private static bool RegisterEvent() + { + if (UnityEditorInternal.InternalEditorUtility.inBatchMode) + return false; + + if (!EditorAnalytics.enabled) + { + Console.WriteLine("[PackageManager] Editor analytics are disabled"); + return false; + } + + if (s_Registered) + return true; + + var result = EditorAnalytics.RegisterEventWithLimit("packageCacheManagementUserAction", k_MaxEventsPerHour, k_MaxNumberOfElementInStruct, k_VendorKey); + switch (result) + { + case AnalyticsResult.Ok: + case AnalyticsResult.TooManyRequests: + { + s_Registered = true; + break; + } + default: + { + Console.WriteLine($"[PackageManager] Failed to register analytics event 'packageCacheManagementUserAction'. Result: '{result}'"); + s_Registered = false; + break; + } + } + + return s_Registered; + } + + public static void SendAssetStoreEvent(string action, string[] oldPathStatuses, string[] newPathStatuses = null) + { + SendEvent(action, "AssetStore", oldPathStatuses, newPathStatuses); + } + + public static void SendUpmEvent(string action, string[] oldPathStatuses, string[] newPathStatuses = null) + { + SendEvent(action, "UPM", oldPathStatuses, newPathStatuses); + } + + private static void SendEvent(string action, string type, string[] oldPathStatuses, string[] newPathStatuses) + { + if (!RegisterEvent()) + return; + + var parameters = new PackageCacheManagementAnalytics + { + action = action, + type = type, + old_path_statuses = oldPathStatuses, + new_path_statuses = newPathStatuses, + t_since_start = (long)(EditorApplication.timeSinceStartup * 1E6), + ts = DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond + }; + + EditorAnalytics.SendEventWithLimit("packageCacheManagementUserAction", parameters); + } + } +} diff --git a/Modules/PackageManagerUI/Editor/Services/Analytics/PackageManagerWindowAnalytics.cs b/Modules/PackageManagerUI/Editor/Services/Analytics/PackageManagerWindowAnalytics.cs index 342a3df8f2..bacf2ba76b 100644 --- a/Modules/PackageManagerUI/Editor/Services/Analytics/PackageManagerWindowAnalytics.cs +++ b/Modules/PackageManagerUI/Editor/Services/Analytics/PackageManagerWindowAnalytics.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; @@ -13,6 +14,7 @@ internal struct PackageManagerWindowAnalytics { public string action; public string package_id; + public string[] package_ids; public string search_text; public string filter_name; public bool window_docked; @@ -30,7 +32,7 @@ public static void Setup() EditorAnalytics.RegisterEventWithLimit("packageManagerWindowUserAction", maxEventsPerHour, maxNumberOfElementInStruct, vendorKey); } - public static void SendEvent(string action, string packageId = null) + public static void SendEvent(string action, string packageId = null, IEnumerable packageIds = null) { // remove sensitive part of the id: file path or url is not tracked if (!string.IsNullOrEmpty(packageId)) @@ -51,6 +53,7 @@ public static void SendEvent(string action, string packageId = null) { action = action, package_id = packageId ?? string.Empty, + package_ids = packageIds?.ToArray() ?? new string[0], search_text = packageFiltering.currentSearchText, filter_name = filterName, window_docked = EditorWindow.GetWindowDontShow()?.docked ?? false, diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreClient.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreClient.cs index 31717a4c05..ea188aa589 100644 --- a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreClient.cs +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreClient.cs @@ -353,22 +353,30 @@ public virtual void CheckUpdate(IEnumerable productIds, Action doneCallb } else { + // If an asset store package is disabled, we won't get properly update info from the server (the id field will be transformed to something else) + // in the past we consider this case as `updateInfo` not checked and that causes the Package Manager to check update indefinitely. + // Now we want to mark all packages that we called `CheckUpdate` on as updateInfoFetched to avoid unnecessary calls on disabled packages. + foreach (var localInfo in productIds.Select(id => m_AssetStoreCache.GetLocalInfo(id)).Where(info => info != null)) + localInfo.updateInfoFetched = true; + var results = updateDetails.GetList>("results") ?? Enumerable.Empty>(); + var updatedLocalInfos = new List(); foreach (var updateDetail in results) { var id = updateDetail.GetString("id"); var localInfo = m_AssetStoreCache.GetLocalInfo(id); if (localInfo != null) { - localInfo.updateInfoFetched = true; var newValue = updateDetail.Get("can_update", 0L) != 0L; if (localInfo.canUpdate != newValue) { localInfo.canUpdate = newValue; - OnLocalInfosChanged(new[] { localInfo }, null); + updatedLocalInfos.Add(localInfo); } } } + if (updatedLocalInfos.Any()) + OnLocalInfosChanged(updatedLocalInfos, null); onUpdateChecked?.Invoke(productIds); } doneCallbackAction?.Invoke(); diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreDownloadOperation.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreDownloadOperation.cs index da2b142c1a..1317f65c01 100644 --- a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreDownloadOperation.cs +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreDownloadOperation.cs @@ -40,11 +40,11 @@ internal class AssetStoreDownloadOperation : IOperation public bool isOfflineMode => false; - public bool isInProgress => (state & DownloadState.InProgress) != 0; + public virtual bool isInProgress => (state & DownloadState.InProgress) != 0; public bool isInPause => (state & DownloadState.InPause) != 0; - public bool isProgressVisible => (state & ~DownloadState.DownloadRequested & (DownloadState.InPause | DownloadState.InProgress)) != 0; + public virtual bool isProgressVisible => (state & ~DownloadState.DownloadRequested & (DownloadState.InPause | DownloadState.InProgress)) != 0; public bool isProgressTrackable => true; @@ -65,7 +65,7 @@ internal class AssetStoreDownloadOperation : IOperation [SerializeField] private DownloadState m_State; - public DownloadState state => m_State; + public virtual DownloadState state => m_State; [SerializeField] private string m_ErrorMessage; @@ -90,7 +90,7 @@ public void ResolveDependencies(AssetStoreUtils assetStoreUtils, m_AssetStoreCachePathProxy = assetStoreCachePathProxy; } - private AssetStoreDownloadOperation() + public AssetStoreDownloadOperation() { } 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/Services/Packages/PackageDatabase.cs b/Modules/PackageManagerUI/Editor/Services/Packages/PackageDatabase.cs index ad963feeaf..314fa9ccec 100644 --- a/Modules/PackageManagerUI/Editor/Services/Packages/PackageDatabase.cs +++ b/Modules/PackageManagerUI/Editor/Services/Packages/PackageDatabase.cs @@ -383,20 +383,21 @@ private void OnDownloadProgress(IOperation operation) private void OnDownloadFinalized(IOperation operation) { + // We want to call RefreshLocal() before calling GetPackage(operation.packageUniqueId), + // because if we call GetPackage first, we might get an old instance of the package. + // This is due to RefreshLocal potentially replacing the instance in the database with a new one. + var downloadOperation = operation as AssetStoreDownloadOperation; + if (downloadOperation?.state == DownloadState.Completed) + m_AssetStoreClient.RefreshLocal(); + var package = GetPackage(operation.packageUniqueId); if (package == null) return; - var downloadOperation = operation as AssetStoreDownloadOperation; - if (downloadOperation != null) - { - if (downloadOperation.state == DownloadState.Error) - AddPackageError(package, new UIError(UIErrorCode.AssetStoreOperationError, downloadOperation.errorMessage, UIError.Attribute.IsClearable)); - else if (downloadOperation.state == DownloadState.Aborted) - AddPackageError(package, new UIError(UIErrorCode.AssetStoreOperationError, downloadOperation.errorMessage ?? L10n.Tr("Download aborted"), UIError.Attribute.IsWarning | UIError.Attribute.IsClearable)); - else if (downloadOperation.state == DownloadState.Completed) - m_AssetStoreClient.RefreshLocal(); - } + if (downloadOperation?.state == DownloadState.Error) + AddPackageError(package, new UIError(UIErrorCode.AssetStoreOperationError, downloadOperation.errorMessage, UIError.Attribute.IsClearable)); + else if (downloadOperation?.state == DownloadState.Aborted) + AddPackageError(package, new UIError(UIErrorCode.AssetStoreOperationError, downloadOperation.errorMessage ?? L10n.Tr("Download aborted"), UIError.Attribute.IsWarning | UIError.Attribute.IsClearable)); SetPackageProgress(package, PackageProgress.None); } diff --git a/Modules/PackageManagerUI/Editor/Services/Packages/PackageSample.cs b/Modules/PackageManagerUI/Editor/Services/Packages/PackageSample.cs index bead052ace..9ae7ef44b2 100644 --- a/Modules/PackageManagerUI/Editor/Services/Packages/PackageSample.cs +++ b/Modules/PackageManagerUI/Editor/Services/Packages/PackageSample.cs @@ -135,11 +135,16 @@ internal static IEnumerable FindByPackage(PackageInfo package, UpmCache string.IsNullOrEmpty(displayName) ? string.Empty : IOUtils.SanitizeFileName(displayName) ); return new Sample(ioProxy, assetDatabaseProxy, displayName, description, resolvedSamplePath, importPath, interactiveImport); - }) ?? Enumerable.Empty(); + }).ToArray() ?? Enumerable.Empty(); } catch (IOException e) { - Debug.Log($"[Package Manager] Cannot find samples for package {package.displayName}: {e.Message}"); + Debug.Log($"[Package Manager Window] Cannot find samples for package {package.displayName}: {e}"); + return Enumerable.Empty(); + } + catch (InvalidCastException e) + { + Debug.Log($"[Package Manager Window] Invalid sample data for package {package.displayName}: {e}"); return Enumerable.Empty(); } catch (Exception) diff --git a/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackageDocs.cs b/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackageDocs.cs index 7cb5c1b35f..9b168bfb86 100644 --- a/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackageDocs.cs +++ b/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackageDocs.cs @@ -16,12 +16,27 @@ internal class UpmPackageDocs // the link in the description text. internal const string k_BuiltinPackageDocsUrlKey = "Scripting API: "; - public static string[] SplitBuiltinDescription(UpmPackageVersion version) + public static string[] FetchUrlsFromDescription(UpmPackageVersion version) { - if (string.IsNullOrEmpty(version?.packageInfo?.description)) - return new string[] { string.Format(L10n.Tr("This built in package controls the presence of the {0} module."), version.displayName) }; - else - return version.packageInfo.description.Split(new[] { k_BuiltinPackageDocsUrlKey }, StringSplitOptions.None); + var applicationProxy = ServicesContainer.instance.Resolve(); + List urls = new List(); + + var descriptionSlitWithUrl = version.packageInfo.description.Split(new[] { $"{k_BuiltinPackageDocsUrlKey}https://docs.unity3d.com/" }, StringSplitOptions.None); + if (descriptionSlitWithUrl.Length > 1) + urls.Add($"https://docs.unity3d.com/{applicationProxy.shortUnityVersion}/Documentation/" + descriptionSlitWithUrl[1]); + + var descriptionSlitWithoutUrl = version.packageInfo.description.Split(new[] { k_BuiltinPackageDocsUrlKey }, StringSplitOptions.None); + if (descriptionSlitWithoutUrl.Length > 1) + urls.Add(descriptionSlitWithoutUrl[1]); + + return urls.ToArray(); + } + + public static string FetchBuiltinDescription(UpmPackageVersion version) + { + return string.IsNullOrEmpty(version?.packageInfo?.description) ? + string.Format(L10n.Tr("This built in package controls the presence of the {0} module."), version.displayName) : + version.packageInfo.description.Split(new[] { k_BuiltinPackageDocsUrlKey }, StringSplitOptions.None)[0]; } public static string GetOfflineDocumentation(IOProxy IOProxy, IPackageVersion version) @@ -47,22 +62,19 @@ public static string GetOfflineDocumentation(IOProxy IOProxy, IPackageVersion ve return string.Empty; } - public static string GetDocumentationUrl(IPackageVersion version) + public static string[] GetDocumentationUrl(IPackageVersion version) { var upmVersion = version as UpmPackageVersion; if (upmVersion == null) - return string.Empty; + return new string[] { }; if (!string.IsNullOrEmpty(upmVersion.documentationUrl)) - return upmVersion.documentationUrl; + return new string[] { upmVersion.documentationUrl }; if (upmVersion.HasTag(PackageTag.BuiltIn) && !string.IsNullOrEmpty(upmVersion.description)) - { - var split = SplitBuiltinDescription(upmVersion); - if (split.Length > 1) - return split[1]; - } - return $"https://docs.unity3d.com/Packages/{upmVersion.shortVersionId}/index.html"; + return FetchUrlsFromDescription(upmVersion); + + return new string[] { $"https://docs.unity3d.com/Packages/{upmVersion.shortVersionId}/index.html" }; } public static string GetChangelogUrl(IPackageVersion version) diff --git a/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackageVersion.cs b/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackageVersion.cs index b0b1fe4a50..f8e3e580ca 100644 --- a/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackageVersion.cs +++ b/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackageVersion.cs @@ -137,7 +137,7 @@ internal void UpdatePackageInfo(PackageInfo newPackageInfo, bool isUnityPackage) m_Author = this.isUnityPackage ? k_UnityAuthor : m_PackageInfo.author?.name ?? string.Empty; if (HasTag(PackageTag.BuiltIn)) - m_Description = UpmPackageDocs.SplitBuiltinDescription(this)[0]; + m_Description = UpmPackageDocs.FetchBuiltinDescription(this); if (m_IsFullyFetched) { diff --git a/Modules/PackageManagerUI/Editor/Services/UserSettings/PackageManagerUserSettingsProvider.cs b/Modules/PackageManagerUI/Editor/Services/UserSettings/PackageManagerUserSettingsProvider.cs index a6d96b96a3..6059a5d96e 100644 --- a/Modules/PackageManagerUI/Editor/Services/UserSettings/PackageManagerUserSettingsProvider.cs +++ b/Modules/PackageManagerUI/Editor/Services/UserSettings/PackageManagerUserSettingsProvider.cs @@ -86,7 +86,7 @@ private PackageManagerUserSettingsProvider(string path, IEnumerable keyw m_UpmCacheRootClient.onSetCacheRootOperationError -= OnPackagesSetCacheRootOperationError; m_UpmCacheRootClient.onSetCacheRootOperationResult -= OnPackagesSetCacheRootOperationResult; m_UpmCacheRootClient.onClearCacheRootOperationError -= OnPackagesClearCacheRootOperationError; - m_UpmCacheRootClient.onClearCacheRootOperationResult -= OnPackagesSetCacheRootOperationResult; + m_UpmCacheRootClient.onClearCacheRootOperationResult -= OnPackagesClearCacheRootOperationResult; } if (m_AssetStoreCachePathProxy != null) @@ -132,7 +132,7 @@ private void DisplayPackagesCacheSetting() m_UpmCacheRootClient.onSetCacheRootOperationError += OnPackagesSetCacheRootOperationError; m_UpmCacheRootClient.onSetCacheRootOperationResult += OnPackagesSetCacheRootOperationResult; m_UpmCacheRootClient.onClearCacheRootOperationError += OnPackagesClearCacheRootOperationError; - m_UpmCacheRootClient.onClearCacheRootOperationResult += OnPackagesSetCacheRootOperationResult; + m_UpmCacheRootClient.onClearCacheRootOperationResult += OnPackagesClearCacheRootOperationResult; if (!m_ApplicationProxy.isBatchMode && m_ApplicationProxy.isUpmRunning) { @@ -178,7 +178,15 @@ private void DisplayAssetStoreCachePathSetting() if (!CancelDownloadInProgress()) return; + var oldStatus = m_CurrentAssetStoreConfig?.status.ToString() ?? string.Empty; + var oldSource = m_CurrentAssetStoreConfig?.source.ToString() ?? string.Empty; var status = m_AssetStoreCachePathProxy.SetConfig(path); + + // Send analytics + PackageCacheManagementAnalytics.SendAssetStoreEvent("changePath", + new []{ oldSource, oldStatus }, + new []{ m_CurrentAssetStoreConfig.source.ToString(), m_CurrentAssetStoreConfig.status.ToString() }); + if (status == AssetStoreCachePathManager.ConfigStatus.Failed) DisplayAssetsCacheErrorBox(HelpBoxMessageType.Error, L10n.Tr($"Cannot set the Assets Cache location, \"{path}\" is invalid or inaccessible.")); } @@ -188,7 +196,15 @@ private void DisplayAssetStoreCachePathSetting() if (!CancelDownloadInProgress()) return; + var oldStatus = m_CurrentAssetStoreConfig?.status.ToString() ?? string.Empty; + var oldSource = m_CurrentAssetStoreConfig?.source.ToString() ?? string.Empty; var status = m_AssetStoreCachePathProxy.ResetConfig(); + + // Send analytics + PackageCacheManagementAnalytics.SendAssetStoreEvent("resetPath", + new []{ oldSource, oldStatus }, + new []{ m_CurrentAssetStoreConfig.source.ToString(), m_CurrentAssetStoreConfig.status.ToString()}); + if (status == AssetStoreCachePathManager.ConfigStatus.Failed) DisplayAssetsCacheErrorBox(HelpBoxMessageType.Error, L10n.Tr("Cannot reset the Assets Cache location to default.")); }, action => m_CurrentAssetStoreConfig.source == ConfigSource.User ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled, "resetLocation"); @@ -220,7 +236,7 @@ private void GetAssetStoreCacheConfig() private void RefreshAssetStoreCachePathConfig(CachePathConfig config) { m_CurrentAssetStoreConfig = config; - assetsCachePath.SetValueWithoutNotify(m_CurrentAssetStoreConfig.path.NormalizePath()); + assetsCachePath.SetValueWithoutNotify(m_CurrentAssetStoreConfig.path.NormalizePath().EscapeBackslashes()); UIUtils.SetElementDisplay(assetsCacheErrorBox, false); if (m_CurrentAssetStoreConfig.source == ConfigSource.Environment) @@ -262,6 +278,22 @@ private void OnPackagesGetCacheRootOperationResult(CacheRootConfig config) private void OnPackagesSetCacheRootOperationResult(CacheRootConfig config) { + // Send analytics + PackageCacheManagementAnalytics.SendUpmEvent("changePath", + new []{ m_CurrentPackagesConfig.source.ToString() }, + new []{ config.source.ToString()}); + + RefreshCurrentPackagesConfig(config); + m_ClientProxy.Resolve(); + } + + private void OnPackagesClearCacheRootOperationResult(CacheRootConfig config) + { + // Send analytics + PackageCacheManagementAnalytics.SendUpmEvent("resetPath", + new []{ m_CurrentPackagesConfig.source.ToString() }, + new []{ config.source.ToString()}); + RefreshCurrentPackagesConfig(config); m_ClientProxy.Resolve(); } @@ -269,7 +301,7 @@ private void OnPackagesSetCacheRootOperationResult(CacheRootConfig config) private void RefreshCurrentPackagesConfig(CacheRootConfig config) { m_CurrentPackagesConfig = config; - packagesCachePath.SetValueWithoutNotify(m_CurrentPackagesConfig.path.NormalizePath()); + packagesCachePath.SetValueWithoutNotify(m_CurrentPackagesConfig.path.NormalizePath().EscapeBackslashes()); packagesCacheDropdown.SetEnabled(true); UIUtils.SetElementDisplay(packagesCacheErrorBox, false); } diff --git a/Modules/PackageManagerUI/Editor/UI/Common/DictionaryExtensions.cs b/Modules/PackageManagerUI/Editor/UI/Common/DictionaryExtensions.cs index 4532ddff67..0cabe5e000 100644 --- a/Modules/PackageManagerUI/Editor/UI/Common/DictionaryExtensions.cs +++ b/Modules/PackageManagerUI/Editor/UI/Common/DictionaryExtensions.cs @@ -2,6 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -12,26 +13,38 @@ internal static class DictionaryExtensions { public static T Get(this IDictionary dict, string key) where T : class { - object result; - return dict.TryGetValue(key, out result) ? (T)result : null; + var result = dict.TryGetValue(key, out var value); + try + { + return result ? (T)value : null; + } + catch (InvalidCastException) + { + throw new IncorrectFieldTypeException(key, typeof(T), value.GetType()); + } } public static T Get(this IDictionary dict, string key, T fallbackValue = default(T)) where T : struct { - object result; - return dict.TryGetValue(key, out result) ? (T)result : fallbackValue; + var result = dict.TryGetValue(key, out var value); + try + { + return result ? (T)value: fallbackValue; + } + catch (InvalidCastException) + { + throw new IncorrectFieldTypeException(key, typeof(T), value.GetType()); + } } public static T Get(this IDictionary dict, string key) where T : class { - T result; - return dict.TryGetValue(key, out result) ? result : null; + return dict.TryGetValue(key, out var result) ? result : null; } public static T Get(this IDictionary dict, string key, T fallbackValue = default(T)) where T : struct { - T result; - return dict.TryGetValue(key, out result) ? result : fallbackValue; + return dict.TryGetValue(key, out var result) ? result : fallbackValue; } public static IDictionary GetDictionary(this IDictionary dict, string key) diff --git a/Modules/PackageManagerUI/Editor/UI/Common/IOUtils.cs b/Modules/PackageManagerUI/Editor/UI/Common/IOUtils.cs index 5587c99916..03e4daaf76 100644 --- a/Modules/PackageManagerUI/Editor/UI/Common/IOUtils.cs +++ b/Modules/PackageManagerUI/Editor/UI/Common/IOUtils.cs @@ -17,5 +17,10 @@ public static string SanitizeFileName(string name) name = name.Replace(c, '_'); return name.Trim(); } + + public static string EscapeBackslashes(this string path) + { + return path.Replace(@"\", @"\\"); + } } } diff --git a/Modules/PackageManagerUI/Editor/UI/Common/IncorrectFieldTypeException.cs b/Modules/PackageManagerUI/Editor/UI/Common/IncorrectFieldTypeException.cs new file mode 100644 index 0000000000..45f99b8a27 --- /dev/null +++ b/Modules/PackageManagerUI/Editor/UI/Common/IncorrectFieldTypeException.cs @@ -0,0 +1,16 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; + +namespace UnityEditor.PackageManager.UI.Internal +{ + internal class IncorrectFieldTypeException : InvalidCastException + { + public IncorrectFieldTypeException(string fieldName, Type expectedType, Type actualType) + : base($"Unable to cast {actualType.Name} to {expectedType.Name} for the field {fieldName}.") + { + } + } +} diff --git a/Modules/PackageManagerUI/Editor/UI/MultiSelect/CheckUpdateFoldout.cs b/Modules/PackageManagerUI/Editor/UI/MultiSelect/CheckUpdateFoldout.cs index 5264611cc0..8a86a3893c 100644 --- a/Modules/PackageManagerUI/Editor/UI/MultiSelect/CheckUpdateFoldout.cs +++ b/Modules/PackageManagerUI/Editor/UI/MultiSelect/CheckUpdateFoldout.cs @@ -10,7 +10,7 @@ internal class CheckUpdateFoldout : MultiSelectFoldout private AssetStoreCallQueue m_AssetStoreCallQueue; public CheckUpdateFoldout(PageManager pageManager, AssetStoreCache assetStoreCache, AssetStoreCallQueue assetStoreCallQueue) - : base(new PackageDeselectButton(pageManager)) + : base(new PackageDeselectButton(pageManager, "deselectCheckUpdate")) { m_AssetStoreCache = assetStoreCache; m_AssetStoreCallQueue = assetStoreCallQueue; diff --git a/Modules/PackageManagerUI/Editor/UI/MultiSelect/MultiSelectDetails.cs b/Modules/PackageManagerUI/Editor/UI/MultiSelect/MultiSelectDetails.cs index 8cb6ea8811..5f2b107ed7 100644 --- a/Modules/PackageManagerUI/Editor/UI/MultiSelect/MultiSelectDetails.cs +++ b/Modules/PackageManagerUI/Editor/UI/MultiSelect/MultiSelectDetails.cs @@ -200,6 +200,7 @@ private void Refresh() private void OnDeselectLockedSelectionsClicked() { m_PageManager.RemoveSelection(m_UnlockFoldout.versions.Select(s => new PackageAndVersionIdPair(s.packageUniqueId, s.uniqueId))); + PackageManagerWindowAnalytics.SendEvent("deselectLocked", packageIds: m_UnlockFoldout.versions.Select(v => v.packageUniqueId)); } private VisualElementCache cache { get; set; } diff --git a/Modules/PackageManagerUI/Editor/UI/MultiSelect/NoActionsFoldout.cs b/Modules/PackageManagerUI/Editor/UI/MultiSelect/NoActionsFoldout.cs index 87dcf7e434..bc2f0fedd9 100644 --- a/Modules/PackageManagerUI/Editor/UI/MultiSelect/NoActionsFoldout.cs +++ b/Modules/PackageManagerUI/Editor/UI/MultiSelect/NoActionsFoldout.cs @@ -7,7 +7,7 @@ namespace UnityEditor.PackageManager.UI.Internal internal class NoActionsFoldout : MultiSelectFoldout { public NoActionsFoldout(PageManager pageManager) - : base(new PackageDeselectButton(pageManager)) + : base(new PackageDeselectButton(pageManager, "deselectNoAction")) { headerTextTemplate = L10n.Tr("No common action available for {0}"); } diff --git a/Modules/PackageManagerUI/Editor/UI/PackageDetailsBody.cs b/Modules/PackageManagerUI/Editor/UI/PackageDetailsBody.cs index 1b0288e581..a3d01072d6 100644 --- a/Modules/PackageManagerUI/Editor/UI/PackageDetailsBody.cs +++ b/Modules/PackageManagerUI/Editor/UI/PackageDetailsBody.cs @@ -281,7 +281,7 @@ private void RefreshSourcePath() UIUtils.SetElementDisplay(detailSourcePathContainer, !string.IsNullOrEmpty(sourcePath)); if (!string.IsNullOrEmpty(sourcePath)) - detailSourcePath.SetValueWithoutNotify(sourcePath); + detailSourcePath.SetValueWithoutNotify(sourcePath.EscapeBackslashes()); } private VisualElementCache cache { get; set; } diff --git a/Modules/PackageManagerUI/Editor/UI/PackageDetailsHeader.cs b/Modules/PackageManagerUI/Editor/UI/PackageDetailsHeader.cs index 8d90d558cf..06bd6fac75 100644 --- a/Modules/PackageManagerUI/Editor/UI/PackageDetailsHeader.cs +++ b/Modules/PackageManagerUI/Editor/UI/PackageDetailsHeader.cs @@ -38,8 +38,8 @@ internal enum InfoBoxState private static readonly string[] k_InfoBoxReadMoreUrl = { - "/Documentation/Manual/pack-prerelease.html", - "/Documentation/Manual/pack-experimental.html", + "/Documentation/Manual/pack-preview.html", + "/Documentation/Manual/pack-exp.html", "/Documentation/Manual/pack-releasecandidate.html", "/Documentation/Manual/upm-scoped.html" }; @@ -76,6 +76,7 @@ public PackageDetailsHeader() Add(root); cache = new VisualElementCache(root); + m_PageManager.onVisualStateChange += OnVisualStateChange; detailAuthorLink.clickable.clicked += AuthorClick; scopedRegistryInfoBox.Q