diff --git a/Editor/Mono/BuildPipeline.bindings.cs b/Editor/Mono/BuildPipeline.bindings.cs index ac958b1ba9..b8fcd419f9 100644 --- a/Editor/Mono/BuildPipeline.bindings.cs +++ b/Editor/Mono/BuildPipeline.bindings.cs @@ -183,7 +183,10 @@ public enum BuildAssetBundleOptions //AssetBundleAllowEditorOnlyScriptableObjects = 1 << 14, //Removes the Unity Version number in the Archive File & Serialized File headers during the build. - AssetBundleStripUnityVersion = 32768 // 1 << 15 + AssetBundleStripUnityVersion = 32768, // 1 << 15 + + // Calculate bundle hash on the bundle content + UseContentHash = 65536 // 1 << 16 } // Keep in sync with CanAppendBuild in EditorUtility.h diff --git a/Editor/Mono/BuildPlayerWindow.cs b/Editor/Mono/BuildPlayerWindow.cs index e4c32ecc08..bcb1523211 100644 --- a/Editor/Mono/BuildPlayerWindow.cs +++ b/Editor/Mono/BuildPlayerWindow.cs @@ -47,6 +47,7 @@ class Styles public string noModuleLoaded = L10n.Tr("No {0} module loaded."); public GUIContent openDownloadPage = EditorGUIUtility.TrTextContent("Open Download Page"); public GUIContent installModuleWithHub = EditorGUIUtility.TrTextContent("Install with Unity Hub"); + public string EditorWillNeedToBeReloaded = L10n.Tr("Note: Editor will need to be restarted to load any newly installed modules"); public string infoText = L10n.Tr("{0} is not included in your Unity Pro license. Your {0} build will include a Unity Personal Edition splash screen.\n\nYou must be eligible to use Unity Personal Edition to use this build option. Please refer to our EULA for further information."); public GUIContent eula = EditorGUIUtility.TrTextContent("Eula"); public string addToYourPro = L10n.Tr("Add {0} to your Unity Pro license"); @@ -735,6 +736,7 @@ void ShowBuildTargetSettings() Help.BrowseURL(url); } } + GUILayout.Label(styles.EditorWillNeedToBeReloaded, EditorStyles.wordWrappedMiniLabel); GUIBuildButtons(false, false, false, platform, postprocessor); return; } diff --git a/Editor/Mono/ContainerWindow.cs b/Editor/Mono/ContainerWindow.cs index 031a192a6c..5f03089eab 100644 --- a/Editor/Mono/ContainerWindow.cs +++ b/Editor/Mono/ContainerWindow.cs @@ -387,6 +387,7 @@ public void Close() } DestroyImmediate(this, true); + EditorWindow.UpdateWindowMenuListing(); } [RequiredByNativeCode] diff --git a/Editor/Mono/EditorApplication.bindings.cs b/Editor/Mono/EditorApplication.bindings.cs index 6cbb3c970e..b9bc3cca03 100644 --- a/Editor/Mono/EditorApplication.bindings.cs +++ b/Editor/Mono/EditorApplication.bindings.cs @@ -297,7 +297,7 @@ public static extern bool isTemporaryProject internal static extern void UpdateSceneIfNeeded(); [StaticAccessor("GetApplication()", StaticAccessorType.Dot)] - internal static extern void UpdateMainWindowTitle(); + public static extern void UpdateMainWindowTitle(); // Plays system beep sound. [FreeFunction("UnityBeep")] diff --git a/Editor/Mono/EditorApplication.cs b/Editor/Mono/EditorApplication.cs index fd54d1b3b2..b76e0398d6 100644 --- a/Editor/Mono/EditorApplication.cs +++ b/Editor/Mono/EditorApplication.cs @@ -42,7 +42,7 @@ public enum PauseState Unpaused, } - internal class ApplicationTitleDescriptor + public class ApplicationTitleDescriptor { public ApplicationTitleDescriptor(string projectName, string unityVersion, string activeSceneName, string targetName, bool codeCoverageEnabled) { @@ -282,7 +282,7 @@ public static event Action playModeStateChanged // Global contextual menus for inspector values public static SerializedPropertyCallbackFunction contextualPropertyMenu; - internal static event Action updateMainWindowTitle; + public static event Action updateMainWindowTitle; internal static string GetDefaultMainWindowTitle(ApplicationTitleDescriptor desc) { diff --git a/Editor/Mono/EditorGUI.cs b/Editor/Mono/EditorGUI.cs index 1743e82c95..f5498740cf 100644 --- a/Editor/Mono/EditorGUI.cs +++ b/Editor/Mono/EditorGUI.cs @@ -7462,9 +7462,10 @@ internal static float GetPropertyHeightInternal(SerializedProperty property, GUI return ScriptAttributeUtility.GetHandler(property).GetHeight(property, label, includeChildren); } + [Obsolete("CanCacheInspectorGUI has been deprecated and is no longer used.", false)] public static bool CanCacheInspectorGUI(SerializedProperty property) { - return ScriptAttributeUtility.GetHandler(property).CanCacheInspectorGUI(property); + return false; } internal static bool HasVisibleChildFields(SerializedProperty property, bool isUIElements = false) diff --git a/Editor/Mono/EditorUserBuildSettings.bindings.cs b/Editor/Mono/EditorUserBuildSettings.bindings.cs index 3b2a60604e..6471365e65 100644 --- a/Editor/Mono/EditorUserBuildSettings.bindings.cs +++ b/Editor/Mono/EditorUserBuildSettings.bindings.cs @@ -242,6 +242,13 @@ public enum WSABuildAndRunDeployTarget DevicePortal = 2 } + [NativeType(Header = "Editor/Src/EditorUserBuildSettings.h")] + public enum WindowsBuildAndRunDeployTarget + { + LocalMachine = 0, + DevicePortal = 2 + } + [NativeType(Header = "Editor/Src/EditorUserBuildSettings.h")] public enum WSABuildType { @@ -334,7 +341,7 @@ public enum SwitchRomCompressionType [NativeType(Header = "Editor/Src/EditorUserBuildSettings.h")] public enum QNXOsVersion { - [UnityEngine.InspectorName("Neutrino RTOS 7.0")] + [System.Obsolete("Neutrino RTOS 7.0 has been removed in 2023.2")] Neutrino70 = 0, [UnityEngine.InspectorName("Neutrino RTOS 7.1")] @@ -347,13 +354,13 @@ public enum EmbeddedArchitecture [UnityEngine.InspectorName("Arm64")] Arm64 = 0, - [UnityEngine.InspectorName("Arm32")] + [System.Obsolete("Arm32 has been removed in 2023.2")] Arm32 = 1, [UnityEngine.InspectorName("X64")] X64 = 2, - [UnityEngine.InspectorName("X86")] + [System.Obsolete("X86 has been removed in 2023.2")] X86 = 3, } @@ -363,13 +370,13 @@ public enum QNXArchitecture [UnityEngine.InspectorName("Arm64")] Arm64 = EmbeddedArchitecture.Arm64, - [UnityEngine.InspectorName("Arm32")] + [System.Obsolete("Arm32 has been removed in 2023.2")] Arm32 = EmbeddedArchitecture.Arm32, [UnityEngine.InspectorName("X64")] X64 = EmbeddedArchitecture.X64, - [UnityEngine.InspectorName("X86")] + [System.Obsolete("X86 has been removed in 2023.2")] X86 = EmbeddedArchitecture.X86, } @@ -379,13 +386,13 @@ public enum EmbeddedLinuxArchitecture [UnityEngine.InspectorName("Arm64")] Arm64 = EmbeddedArchitecture.Arm64, - [UnityEngine.InspectorName("Arm32")] + [System.Obsolete("Arm32 has been removed in 2023.2")] Arm32 = EmbeddedArchitecture.Arm32, [UnityEngine.InspectorName("X64")] X64 = EmbeddedArchitecture.X64, - [UnityEngine.InspectorName("X86")] + [System.Obsolete("X86 has been removed in 2023.2")] X86 = EmbeddedArchitecture.X86, } @@ -619,7 +626,7 @@ public static extern WebGLTextureSubtarget webGLBuildSubtarget } public static extern string webGLClientBrowserPath { get; set; } - + public static extern WebGLClientBrowserType webGLClientBrowserType { [NativeMethod("GetWebGLClientBrowserType")] @@ -764,6 +771,13 @@ public static extern WSABuildAndRunDeployTarget wsaBuildAndRunDeployTarget set; } + public static extern WindowsBuildAndRunDeployTarget windowsBuildAndRunDeployTarget + { + [NativeMethod("GetSelectedWindowsBuildAndRunDeployTarget")] + get; + [NativeMethod("SetSelectedWindowsBuildAndRunDeployTarget")] + set; + } public static extern int overrideMaxTextureSize { get; set; } public static extern Build.OverrideTextureCompression overrideTextureCompression { get; set; } diff --git a/Editor/Mono/EditorWindow.cs b/Editor/Mono/EditorWindow.cs index b55a36f03b..78d11a454d 100644 --- a/Editor/Mono/EditorWindow.cs +++ b/Editor/Mono/EditorWindow.cs @@ -337,8 +337,7 @@ internal void DrawNotification() if (warningSize.y > targetHeight) warningSize.y = targetHeight; - Rect r = new Rect((position.width - warningSize.x) * .5f, 20 + (position.height - 20 - warningSize.y) * .7f, warningSize.x, warningSize.y); - + Rect r = new Rect((position.width - warningSize.x - EditorStyles.notificationText.margin.horizontal) * .5f, 20 + (position.height - 20 - warningSize.y) * .7f, warningSize.x + EditorStyles.notificationText.margin.horizontal, warningSize.y); double time = EditorApplication.timeSinceStartup; if (time > m_FadeoutTime) GUI.color = new Color(1, 1, 1, 1 - (float)((time - m_FadeoutTime) / kWarningFadeoutTime)); diff --git a/Editor/Mono/GI/DeviceContext.bindings.cs b/Editor/Mono/GI/DeviceContext.bindings.cs index b3f37bb87a..614082522c 100644 --- a/Editor/Mono/GI/DeviceContext.bindings.cs +++ b/Editor/Mono/GI/DeviceContext.bindings.cs @@ -7,7 +7,6 @@ using System.Runtime.InteropServices; using Unity.Collections; -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.RenderPipelines.Core.Editor")] namespace UnityEngine.LightTransport { internal struct BufferID @@ -50,6 +49,7 @@ internal interface IDeviceContext : IDisposable void ReadBuffer(BufferID id, NativeArray result); EventID EnqueueBufferRead(BufferID id, NativeArray result); bool IsAsyncOperationComplete(EventID id); + bool Flush(); } [StructLayout(LayoutKind.Sequential)] internal class ReferenceContext : IDeviceContext @@ -114,5 +114,10 @@ public NativeArray GetNativeArray(BufferID id) { return buffers[id]; } + + public bool Flush() + { + return true; + } } } diff --git a/Editor/Mono/GI/IntegrationContext.bindings.cs b/Editor/Mono/GI/IntegrationContext.bindings.cs index 3aa78eeb7c..b6f1150b47 100644 --- a/Editor/Mono/GI/IntegrationContext.bindings.cs +++ b/Editor/Mono/GI/IntegrationContext.bindings.cs @@ -6,7 +6,6 @@ using System.Runtime.InteropServices; using UnityEngine.Bindings; -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.RenderPipelines.Core.Editor")] namespace UnityEngine.LightBaking { [StructLayout(LayoutKind.Sequential)] diff --git a/Editor/Mono/GI/LightBaker.bindings.cs b/Editor/Mono/GI/LightBaker.bindings.cs index 4c1f1a7299..6e45048c28 100644 --- a/Editor/Mono/GI/LightBaker.bindings.cs +++ b/Editor/Mono/GI/LightBaker.bindings.cs @@ -10,7 +10,6 @@ using UnityEngine.LightTransport; using static UnityEditor.LightBaking.LightBaker; -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.RenderPipelines.Core.Editor")] namespace UnityEditor.LightBaking { [NativeHeader("Editor/Src/GI/LightBaker/LightBaker.Bindings.h")] diff --git a/Editor/Mono/GI/Lightmapping.bindings.cs b/Editor/Mono/GI/Lightmapping.bindings.cs index a73300a5dd..dff1226a83 100644 --- a/Editor/Mono/GI/Lightmapping.bindings.cs +++ b/Editor/Mono/GI/Lightmapping.bindings.cs @@ -140,14 +140,14 @@ public static float bounceBoost } [RequiredByNativeCode] - internal static bool GetIsInteractive() + internal static bool ShouldBakeInteractively() { - return SceneView.IsInteractiveBakingEnabled(); + return SceneView.NeedsInteractiveBaking(); } - internal static bool isInteractive + internal static bool shouldBakeInteractively { - get { return GetIsInteractive(); } + get { return ShouldBakeInteractively(); } } [RequiredByNativeCode] diff --git a/Editor/Mono/GI/PostProcessing.bindings.cs b/Editor/Mono/GI/PostProcessing.bindings.cs index c3bd3b35fd..d629392872 100644 --- a/Editor/Mono/GI/PostProcessing.bindings.cs +++ b/Editor/Mono/GI/PostProcessing.bindings.cs @@ -7,7 +7,6 @@ using UnityEngine.Bindings; using UnityEngine.Rendering; -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.RenderPipelines.Core.Editor")] namespace UnityEngine.LightTransport { namespace PostProcessing diff --git a/Editor/Mono/GI/ProbeIntegrator.bindings.cs b/Editor/Mono/GI/ProbeIntegrator.bindings.cs index 2d660541c9..029e4e6273 100644 --- a/Editor/Mono/GI/ProbeIntegrator.bindings.cs +++ b/Editor/Mono/GI/ProbeIntegrator.bindings.cs @@ -8,7 +8,6 @@ using UnityEditor.LightBaking; using UnityEngine.LightBaking; -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.RenderPipelines.Core.Editor")] namespace UnityEngine.LightTransport { internal interface IProbeIntegrator diff --git a/Editor/Mono/GI/ProgressState.bindings.cs b/Editor/Mono/GI/ProgressState.bindings.cs index 66927c4bf6..f6ba0ee684 100644 --- a/Editor/Mono/GI/ProgressState.bindings.cs +++ b/Editor/Mono/GI/ProgressState.bindings.cs @@ -6,7 +6,6 @@ using System.Runtime.InteropServices; using UnityEngine.Bindings; -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.RenderPipelines.Core.Editor")] namespace UnityEngine.LightBaking { [StructLayout(LayoutKind.Sequential)] diff --git a/Editor/Mono/GI/RadeonRaysDeviceContext.bindings.cs b/Editor/Mono/GI/RadeonRaysDeviceContext.bindings.cs index 979412add9..a0a2db23a1 100644 --- a/Editor/Mono/GI/RadeonRaysDeviceContext.bindings.cs +++ b/Editor/Mono/GI/RadeonRaysDeviceContext.bindings.cs @@ -8,7 +8,6 @@ using Unity.Collections.LowLevel.Unsafe; using UnityEngine.Bindings; -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.RenderPipelines.Core.Editor")] namespace UnityEngine.LightTransport { [StructLayout(LayoutKind.Sequential)] @@ -103,6 +102,9 @@ public unsafe void WriteBuffer(BufferID id, NativeArray data) [NativeMethod(IsThreadSafe = true)] public extern bool IsAsyncOperationComplete(EventID id); + [NativeMethod(IsThreadSafe = true)] + public extern bool Flush(); + [NativeMethod(IsThreadSafe = true)] internal static extern bool InitializePostProcessingInternal(RadeonRaysContext context); diff --git a/Editor/Mono/GI/WintermuteDeviceContext.bindings.cs b/Editor/Mono/GI/WintermuteDeviceContext.bindings.cs index 1412adbb45..e766d35203 100644 --- a/Editor/Mono/GI/WintermuteDeviceContext.bindings.cs +++ b/Editor/Mono/GI/WintermuteDeviceContext.bindings.cs @@ -8,7 +8,6 @@ using Unity.Collections; using UnityEngine.Bindings; -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.RenderPipelines.Core.Editor")] namespace UnityEngine.LightTransport { [StructLayout(LayoutKind.Sequential)] @@ -106,5 +105,10 @@ public NativeArray GetNativeArray(BufferID id) { return buffers[id]; } + + public bool Flush() + { + return true; + } } } diff --git a/Editor/Mono/GI/World.bindings.cs b/Editor/Mono/GI/World.bindings.cs index fc55321618..2a1575b442 100644 --- a/Editor/Mono/GI/World.bindings.cs +++ b/Editor/Mono/GI/World.bindings.cs @@ -4,7 +4,6 @@ using UnityEngine.LightBaking; -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.RenderPipelines.Core.Editor")] namespace UnityEngine.LightTransport { internal interface IWorld diff --git a/Editor/Mono/GUI/Actions/EditorAction.cs b/Editor/Mono/GUI/Actions/EditorAction.cs index 95526666be..6f1c40e610 100644 --- a/Editor/Mono/GUI/Actions/EditorAction.cs +++ b/Editor/Mono/GUI/Actions/EditorAction.cs @@ -17,9 +17,8 @@ public enum EditorActionResult public abstract class EditorAction { bool m_IsFinished; - internal event Action actionFinished; - public virtual Texture2D icon => null; + internal event Action actionFinished; public static T Start() where T : EditorAction, new() => Start(new T()); @@ -27,21 +26,17 @@ public static T Start(T action) where T : EditorAction { if (action == null) throw new ArgumentNullException(nameof(action)); + if (action.m_IsFinished) return action; - var tool = (EditorActionTool)ScriptableObject.CreateInstance(typeof(EditorActionTool), - x => - { - if (x is not EditorActionTool t) - return; - - t.action = action; - }); - ToolManager.SetActiveTool(tool); + + EditorToolManager.activeOverride = new EditorActionTool(action); + return action; } public virtual void OnSceneGUI(SceneView sceneView) {} + public void Finish(EditorActionResult result) { if (m_IsFinished) diff --git a/Editor/Mono/GUI/Actions/EditorActionTool.cs b/Editor/Mono/GUI/Actions/EditorActionTool.cs index 01083412b4..746f05cf8d 100644 --- a/Editor/Mono/GUI/Actions/EditorActionTool.cs +++ b/Editor/Mono/GUI/Actions/EditorActionTool.cs @@ -2,42 +2,58 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; using UnityEditor.EditorTools; using UnityEngine; namespace UnityEditor.Actions { - sealed class EditorActionTool : EditorTool + sealed class EditorActionTool : IDisposable { public EditorAction action { get; internal set; } + bool m_IsCancelled; - public override GUIContent toolbarIcon => new GUIContent(action?.icon); + EditorActionTool() { } - public override void OnActivated() + public EditorActionTool(EditorAction action) { - base.OnActivated(); - - if (action != null) - action.actionFinished += OnActionFinished; + this.action = action; + this.action.actionFinished += OnActionFinished; } - public override void OnToolGUI(EditorWindow window) + public void OnGUI(EditorWindow window) { - if (action != null) - action?.OnSceneGUI(window as SceneView); + if (!(window is SceneView sceneView)) + return; + + var evt = Event.current; + + if(evt.type == EventType.KeyDown && evt.keyCode == KeyCode.Escape) + { + Cancel(); + evt.Use(); + return; + } + + action?.OnSceneGUI(sceneView); } - void OnActionFinished(EditorActionResult result) + void OnActionFinished(EditorActionResult result) => Dispose(); + + public void Cancel() { - action.actionFinished -= OnActionFinished; - action = null; - DestroyImmediate(this); + m_IsCancelled = true; + Dispose(); } - public override void OnWillBeDeactivated() + public void Dispose() { - // If the tool is deactivate without being completed first - action?.Finish(EditorActionResult.Canceled); + if (action == null) + return; + action.actionFinished -= OnActionFinished; + action?.Finish(m_IsCancelled ? EditorActionResult.Canceled : EditorActionResult.Success); + action = null; + EditorToolManager.activeOverride = null; } } } diff --git a/Editor/Mono/GUI/DockArea.cs b/Editor/Mono/GUI/DockArea.cs index 4abccb2205..c10314db3d 100644 --- a/Editor/Mono/GUI/DockArea.cs +++ b/Editor/Mono/GUI/DockArea.cs @@ -156,7 +156,6 @@ protected override void OnDestroy() continue; UnityEngine.Object.DestroyImmediate(w, true); - EditorWindow.UpdateWindowMenuListing(); } m_Panes.Clear(); diff --git a/Editor/Mono/GUI/LazyLoadReferenceField.cs b/Editor/Mono/GUI/LazyLoadReferenceField.cs index 83e7b5dc10..abb202fea9 100644 --- a/Editor/Mono/GUI/LazyLoadReferenceField.cs +++ b/Editor/Mono/GUI/LazyLoadReferenceField.cs @@ -2,6 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using UnityEditor.UIElements; using UnityEngine; using UnityEngine.UIElements; @@ -35,12 +36,14 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) { ScriptAttributeUtility.GetFieldInfoFromProperty(property, out var fieldType); - var objectField = new UnityEditor.UIElements.ObjectField(preferredLabel); + var objectField = new ObjectField(preferredLabel); var genericType = fieldType.GetGenericArguments()[0]; objectField.objectType = genericType; objectField.value = property.objectReferenceValue; objectField.bindingPath = property.propertyPath; + PropertyField.ConfigureFieldStyles(objectField); + return objectField; } } diff --git a/Editor/Mono/GUI/Tools/EditorToolManager.cs b/Editor/Mono/GUI/Tools/EditorToolManager.cs index e2f8125831..1b69e5aaed 100644 --- a/Editor/Mono/GUI/Tools/EditorToolManager.cs +++ b/Editor/Mono/GUI/Tools/EditorToolManager.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using UnityEditor.Actions; using UnityEditor.SceneManagement; using UnityEngine; using UnityObject = UnityEngine.Object; @@ -20,6 +21,8 @@ sealed class EditorToolManager : ScriptableSingleton [SerializeField] EditorTool m_ActiveTool; + EditorActionTool m_ActiveOverride; + Tool m_PreviousTool = Tool.Move; [SerializeField] @@ -166,6 +169,8 @@ internal static EditorTool activeTool s_ChangingActiveTool = true; + activeOverride = null; + ToolManager.ActiveToolWillChange(); var previous = instance.m_ActiveTool; @@ -206,6 +211,20 @@ internal static EditorTool activeTool } } + // this tool will transparently override the `OnToolGUI` method of the active tool. + // do not expose this as public API with also considering how to handle lifecycle and active tool interop. + // currently this is only used for EditorToolAction. + internal static EditorActionTool activeOverride + { + get => instance.m_ActiveOverride; + + set + { + instance.m_ActiveOverride?.Dispose(); + instance.m_ActiveOverride = value; + } + } + [Serializable] struct ComponentToolCache : ISerializationCallbackReceiver { @@ -303,6 +322,7 @@ void OnEnable() void OnDisable() { + m_ActiveOverride = null; Undo.undoRedoEvent -= UndoRedoPerformed; ActiveEditorTracker.editorTrackerRebuilt -= TrackerRebuilt; Selection.selectedObjectWasDestroyed -= SelectedObjectWasDestroyed; @@ -382,6 +402,8 @@ void RestoreCustomEditorTool() // destroy invalid custom editor tools void ClearCustomEditorTools() { + m_ActiveOverride = null; + foreach (var customEditorTool in m_ComponentTools) { if (customEditorTool.editor == m_ActiveTool) @@ -512,10 +534,17 @@ internal static void OnToolGUI(EditorWindow window) if (!IsGizmoCulledBySceneCullingMasksOrFocusedScene(activeToolContext.target)) activeToolContext.OnToolGUI(window); + if (instance.m_ActiveOverride != null) + { + instance.m_ActiveOverride.OnGUI(window); + return; + } + if (Tools.s_Hidden || instance.m_ActiveTool == null) return; var current = instance.m_ActiveTool; + if (IsGizmoCulledBySceneCullingMasksOrFocusedScene(current.target)) return; diff --git a/Editor/Mono/GameView/GameView.cs b/Editor/Mono/GameView/GameView.cs index 30f58cf6b6..0787aec904 100644 --- a/Editor/Mono/GameView/GameView.cs +++ b/Editor/Mono/GameView/GameView.cs @@ -96,8 +96,8 @@ internal static class Styles public static GUIContent gizmosContent = EditorGUIUtility.TrTextContent("Gizmos"); public static GUIContent zoomSliderContent = EditorGUIUtility.TrTextContent("Scale", "Size of the game view on the screen."); public static GUIContent vsyncContent = EditorGUIUtility.TrTextContent("VSync"); - public static GUIContent muteOffContent = EditorGUIUtility.TrIconContent("SceneviewAudio On", "Mute Audio"); - public static GUIContent muteOnContent = EditorGUIUtility.TrIconContent("SceneviewAudio", "Mute Audio"); + public static GUIContent muteOffContent = EditorGUIUtility.TrIconContent("GameViewAudio On", "Mute Audio"); + public static GUIContent muteOnContent = EditorGUIUtility.TrIconContent("GameViewAudio", "Mute Audio"); public static GUIContent shortcutsOnContent = EditorGUIUtility.TrIconContent("Keyboard", "Unity Shortcuts"); public static GUIContent shortcutsOffContent = EditorGUIUtility.TrIconContent("KeyboardShortcutsDisabled", "Unity Shortcuts"); public static GUIContent statsContent = EditorGUIUtility.TrTextContent("Stats"); @@ -106,7 +106,7 @@ internal static class Styles public static GUIContent clearEveryFrameContextMenuContent = EditorGUIUtility.TrTextContent("Clear Every Frame in Edit Mode"); public static GUIContent lowResAspectRatiosContextMenuContent = EditorGUIUtility.TrTextContent("Low Resolution Aspect Ratios"); public static GUIContent metalFrameCaptureContent = EditorGUIUtility.TrIconContent("FrameCapture", "Capture the current view and open in Xcode frame debugger"); - public static GUIContent frameDebuggerContent = EditorGUIUtility.TrIconContent("Debug_Frame_d", "Opens the Frame Debugger"); + public static GUIContent frameDebuggerContent = EditorGUIUtility.TrIconContent("Debug", "Opens the Frame Debugger"); public const string k_StatsShortcutID = "Game View/Toggle Stats"; public const string k_StatsTooltip = "View general rendering information"; diff --git a/Editor/Mono/HandleUtility.cs b/Editor/Mono/HandleUtility.cs index d1d6d45ae1..3ddd567401 100644 --- a/Editor/Mono/HandleUtility.cs +++ b/Editor/Mono/HandleUtility.cs @@ -32,8 +32,7 @@ public static float CalcLineTranslation(Vector2 src, Vector2 dest, Vector3 srcPo // The constrained direction is facing towards the camera, THATS BAD when the handle is close to the camera // The srcPosition goes through to the other side of the camera float invert = 1.0F; - Camera cam = GetCurrentCamera(); - Vector3 cameraForward = cam == null ? Vector3.forward : cam.transform.forward; + Vector3 cameraForward = Camera.current == null ? Vector3.forward : Camera.current.transform.forward; if (Vector3.Dot(constraintDir, cameraForward) < 0.0F) invert = -1.0F; @@ -42,7 +41,7 @@ public static float CalcLineTranslation(Vector2 src, Vector2 dest, Vector3 srcPo // we then parametrise the perpendicular position of dest into the line (p1-p2) Vector3 cd = constraintDir; cd.y = -cd.y; - + Camera cam = Camera.current; // if camera is null, then we are drawing in OnGUI, where y-coordinate goes top-to-bottom Vector2 p1 = cam == null ? Vector2.Scale(srcPosition, new Vector2(1f, -1f)) @@ -65,16 +64,6 @@ public static float CalcLineTranslation(Vector2 src, Vector2 dest, Vector3 srcPo return output; } - internal static Camera GetCurrentCamera() - { - var camera = Camera.current; - - if (!camera && SceneView.lastActiveSceneView != null) - camera = SceneView.lastActiveSceneView.camera; - - return camera; - } - internal static float GetParametrization(Vector2 x0, Vector2 x1, Vector2 x2) { return -(Vector2.Dot(x1 - x0, x2 - x1) / (x2 - x1).sqrMagnitude); @@ -201,7 +190,7 @@ internal static float DistanceToLineInternal(Vector3 point, Vector3 p1, Vector3 public static float DistanceToCircle(Vector3 position, float radius) { Vector2 screenCenter = WorldToGUIPoint(position); - Camera cam = GetCurrentCamera(); + Camera cam = Camera.current; if (cam) { var screenEdge = WorldToGUIPoint(position + cam.transform.right * radius); @@ -217,7 +206,7 @@ public static float DistanceToCircle(Vector3 position, float radius) public static float DistanceToCircle(CameraProjectionCache projection, Vector3 position, float radius) { Vector2 screenCenter = projection.WorldToGUIPoint(position); - Camera cam = GetCurrentCamera(); + Camera cam = Camera.current; if (cam) { var screenEdge = projection.WorldToGUIPoint(position + cam.transform.right * radius); @@ -468,7 +457,7 @@ public static Vector3 ClosestPointToArc(Vector3 center, Vector3 normal, Vector3 public static float DistanceToPolyLine(params Vector3[] points) { Matrix4x4 handleMatrix = Handles.matrix; - CameraProjectionCache cam = new CameraProjectionCache(GetCurrentCamera()); + CameraProjectionCache cam = new CameraProjectionCache(Camera.current); Vector2 mouse = Event.current.mousePosition; Vector2 p1 = cam.WorldToGUIPoint(handleMatrix.MultiplyPoint3x4(points[0])); @@ -491,7 +480,7 @@ public static float DistanceToPolyLine(params Vector3[] points) internal static float DistanceToPolyLine(Vector3[] points, bool loop, out int index) { Matrix4x4 handleMatrix = Handles.matrix; - CameraProjectionCache cam = new CameraProjectionCache(GetCurrentCamera()); + CameraProjectionCache cam = new CameraProjectionCache(Camera.current); Vector2 mouse = Event.current.mousePosition; Vector2 p1 = cam.WorldToGUIPoint(handleMatrix.MultiplyPoint3x4(points[0])); @@ -639,7 +628,7 @@ static void CalcPointCloudConvexHull(Vector3[] points, List outHull) // project point cloud into 2D GUI space var handleMatrix = Handles.matrix; - var cam = new CameraProjectionCache(GetCurrentCamera()); + var cam = new CameraProjectionCache(Camera.current); for (var i = 0; i < points.Length; ++i) points[i] = cam.WorldToGUIPoint(handleMatrix.MultiplyPoint3x4(points[i])); @@ -651,7 +640,7 @@ static void CalcPointCloudConvexHull(Vector3[] points, List outHull) static readonly List s_PointCloudConvexHull = new List(); static float DistanceToPointCloudConvexHull(params Vector3[] points) { - if (points == null || points.Length == 0 || GetCurrentCamera() == null) + if (points == null || points.Length == 0 || Camera.current == null) return float.PositiveInfinity; var mousePos = Event.current.mousePosition; @@ -703,10 +692,9 @@ internal static void BeginHandles() Handles.zTest = CompareFunction.Always; s_CustomPickDistance = kPickDistance; - Camera cam = GetCurrentCamera(); - if (null != cam) + if (null != Camera.current) { - s_PreviousCamera = cam; + s_PreviousCamera = Camera.current; } Handles.Internal_SetCurrentCamera(null); @@ -739,7 +727,7 @@ internal static void EndHandles() // Get world space size of a manipulator handle at given position. public static float GetHandleSize(Vector3 position) { - Camera cam = GetCurrentCamera(); + Camera cam = Camera.current; position = Handles.matrix.MultiplyPoint(position); if (cam) { @@ -756,11 +744,7 @@ public static float GetHandleSize(Vector3 position) static float renderingViewHeight { - get - { - Camera cam = GetCurrentCamera(); - return cam == null ? Screen.height : cam.pixelHeight; - } + get { return Camera.current == null ? Screen.height : Camera.current.pixelHeight; } } // Convert world space point to a 2D GUI position. @@ -772,7 +756,7 @@ public static Vector2 WorldToGUIPoint(Vector3 world) // Convert world space point to a 2D GUI position. public static Vector3 WorldToGUIPointWithDepth(Vector3 world) { - return WorldToGUIPointWithDepth(GetCurrentCamera(), world); + return WorldToGUIPointWithDepth(Camera.current, world); } // Convert world space point to a 2D GUI position. @@ -808,7 +792,7 @@ public static Ray GUIPointToWorldRay(Vector2 position) private static Ray GUIPointToWorldRayPrecise(Vector2 position, float startZ = float.NegativeInfinity) { - Camera camera = GetCurrentCamera(); + Camera camera = Camera.current; if (!camera && SceneView.lastActiveSceneView != null) camera = SceneView.lastActiveSceneView.camera; @@ -932,7 +916,7 @@ public static GameObject[] PickRectObjects(Rect rect) // *undocumented* public static GameObject[] PickRectObjects(Rect rect, bool selectPrefabRootsOnly) { - Camera cam = GetCurrentCamera(); + Camera cam = Camera.current; rect = EditorGUIUtility.PointsToPixels(rect); rect.x /= cam.pixelWidth; rect.width /= cam.pixelWidth; @@ -954,7 +938,7 @@ public static bool FindNearestVertex(Vector2 guiPoint, Transform[] objectsToSear public static bool FindNearestVertex(Vector2 guiPoint, Transform[] objectsToSearch, Transform[] objectsToIgnore, out Vector3 vertex, out GameObject gameObject) { - Camera cam = GetCurrentCamera(); + Camera cam = Camera.current; var screenPoint = EditorGUIUtility.PointsToPixels(guiPoint); screenPoint.y = cam.pixelRect.yMax - screenPoint.y; gameObject = Internal_FindNearestVertex(cam, screenPoint, objectsToSearch, objectsToIgnore, out vertex, out bool found); @@ -973,7 +957,7 @@ public static bool FindNearestVertex(Vector2 guiPoint, Transform[] objectsToSear public static bool FindNearestVertex(Vector2 guiPoint, Transform[] objectsToSearch, Transform[] objectsToIgnore, out Vector3 vertex) { - Camera cam = GetCurrentCamera(); + Camera cam = Camera.current; var screenPoint = EditorGUIUtility.PointsToPixels(guiPoint); screenPoint.y = cam.pixelRect.yMax - screenPoint.y; Internal_FindNearestVertex(cam, screenPoint, objectsToSearch, objectsToIgnore, out vertex, out bool found); @@ -1095,7 +1079,7 @@ internal static PickingObject PickObject(Vector2 guiPosition, List ignore = null, List filter = null) { - Camera cam = GetCurrentCamera(); + Camera cam = Camera.current; int layers = cam.cullingMask; var screenPosition = GUIPointToScreenPixelCoordinate(guiPosition); var sceneView = SceneView.lastActiveSceneView; @@ -1458,7 +1442,7 @@ static Material handleWireMaterial get { InitHandleMaterials(); - return GetCurrentCamera() ? s_HandleWireMaterial : s_HandleWireMaterial2D; + return Camera.current ? s_HandleWireMaterial : s_HandleWireMaterial2D; } } @@ -1468,7 +1452,7 @@ static Material handleDottedWireMaterial get { InitHandleMaterials(); - return GetCurrentCamera() ? s_HandleDottedWireMaterial : s_HandleDottedWireMaterial2D; + return Camera.current ? s_HandleDottedWireMaterial : s_HandleDottedWireMaterial2D; } } @@ -1505,9 +1489,8 @@ internal static void ApplyWireMaterial([DefaultValue("UnityEngine.Rendering.Comp // for any later GL.Begin calls. mat.SetFloat("_HandleZTest", (float)zTest); mat.SetPass(0); - Camera cam = GetCurrentCamera(); - int textureIndex = cam ? s_HandleWireTextureIndex : s_HandleWireTextureIndex2D; - int samplerIndex = cam ? s_HandleWireTextureSamplerIndex : s_HandleWireTextureSamplerIndex2D; + int textureIndex = Camera.current ? s_HandleWireTextureIndex : s_HandleWireTextureIndex2D; + int samplerIndex = Camera.current ? s_HandleWireTextureSamplerIndex : s_HandleWireTextureSamplerIndex2D; Internal_SetHandleWireTextureIndex(textureIndex, samplerIndex); } @@ -1525,9 +1508,8 @@ internal static void ApplyDottedWireMaterial([DefaultValue("UnityEngine.Renderin // for any later GL.Begin calls. mat.SetFloat("_HandleZTest", (float)zTest); mat.SetPass(0); - Camera cam = GetCurrentCamera(); - int textureIndex = cam ? s_HandleDottedWireTextureIndex : s_HandleDottedWireTextureIndex2D; - int samplerIndex = cam ? s_HandleDottedWireTextureSamplerIndex : s_HandleDottedWireTextureSamplerIndex2D; + int textureIndex = Camera.current ? s_HandleDottedWireTextureIndex : s_HandleDottedWireTextureIndex2D; + int samplerIndex = Camera.current ? s_HandleDottedWireTextureSamplerIndex : s_HandleDottedWireTextureSamplerIndex2D; Internal_SetHandleWireTextureIndex(textureIndex, samplerIndex); } @@ -1615,7 +1597,7 @@ static bool TryRaySnap(Ray ray, out RaycastHit resultHit) // Casts /ray/ against the scene. public static object RaySnap(Ray ray) { - Camera cam = GetCurrentCamera(); + Camera cam = Camera.current; if (cam == null) return null; diff --git a/Editor/Mono/Handles.cs b/Editor/Mono/Handles.cs index 1fdd7bf8ea..ca6ee0c7e7 100644 --- a/Editor/Mono/Handles.cs +++ b/Editor/Mono/Handles.cs @@ -1146,15 +1146,6 @@ static void DoDrawAAPolyLine(Color[] colors, Vector3[] points, int actualNumberO HandleUtility.ApplyWireMaterial(zTest); Color defaultColor = new Color(1, 1, 1, alpha); - - if (colors != null) - { - for (int i = 0; i < colors.Length; i++) - colors[i] *= defaultColor; - } - else - defaultColor *= color; - Internal_DrawAAPolyLine(colors, points, defaultColor, actualNumberOfPoints, lineTex, width, matrix); } diff --git a/Editor/Mono/Inspector/EditorSettingsInspector.cs b/Editor/Mono/Inspector/EditorSettingsInspector.cs index 5d400296a5..50ba889cfe 100644 --- a/Editor/Mono/Inspector/EditorSettingsInspector.cs +++ b/Editor/Mono/Inspector/EditorSettingsInspector.cs @@ -47,7 +47,7 @@ class Content public static readonly GUIContent cacheServerLearnMore = new GUIContent("Learn more...", "Go to cacheserver documentation."); public static GUIContent assetSerialization = EditorGUIUtility.TrTextContent("Asset Serialization"); - public static GUIContent textSerializeMappingsOnOneLine = EditorGUIUtility.TrTextContent("Force Serialize References On One Line", "Forces Unity to write references and other inline mappings on one line, to help reduce version control noise"); + public static GUIContent textSerializeMappingsOnOneLine = EditorGUIUtility.TrTextContent("Reduce version control noise", "Forces Unity to write references and similar YAML structures on one line, which reduces version control noise."); public static GUIContent defaultBehaviorMode = EditorGUIUtility.TrTextContent("Default Behaviour Mode"); public static GUIContent buildPipelineHeader = EditorGUIUtility.TrTextContent("Build Pipeline"); @@ -438,7 +438,7 @@ public override void OnInspectorGUI() if (m_SerializationMode.intValue != (int)SerializationMode.ForceBinary) { EditorGUI.BeginChangeCheck(); - EditorGUILayout.PropertyField(m_SerializeInlineMappingsOnOneLine); + EditorGUILayout.PropertyField(m_SerializeInlineMappingsOnOneLine, Content.textSerializeMappingsOnOneLine); if (EditorGUI.EndChangeCheck() && m_IsGlobalSettings) { EditorSettings.serializeInlineMappingsOnOneLine = m_SerializeInlineMappingsOnOneLine.boolValue; diff --git a/Editor/Mono/Inspector/GraphicsSettingsInspectors/GraphicsSettingsUtils.cs b/Editor/Mono/Inspector/GraphicsSettingsInspectors/GraphicsSettingsUtils.cs index 962575e3eb..54260ff470 100644 --- a/Editor/Mono/Inspector/GraphicsSettingsInspectors/GraphicsSettingsUtils.cs +++ b/Editor/Mono/Inspector/GraphicsSettingsInspectors/GraphicsSettingsUtils.cs @@ -2,7 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Reflection; @@ -81,9 +81,8 @@ internal static List CollectRenderPipelineAssetsByGloba for (int i = 0; i < renderPipelineGlobalSettingsMap.arraySize; ++i) { var globalSettings = GetRenderPipelineGlobalSettingsByIndex(renderPipelineGlobalSettingsMap, i); - if (!TryCreateNewGlobalSettingsContainer(globalSettings, out var globalSettingsContainer)) - continue; - existedGlobalSettings.Add(globalSettingsContainer); + if (TryCreateNewGlobalSettingsContainer(globalSettings, out var globalSettingsContainer)) + existedGlobalSettings.Add(globalSettingsContainer); } return existedGlobalSettings; } @@ -91,28 +90,28 @@ internal static List CollectRenderPipelineAssetsByGloba internal static bool TryCreateNewGlobalSettingsContainer(RenderPipelineGlobalSettings globalSettings, out GlobalSettingsContainer globalSettingsContainer) { globalSettingsContainer = null; - if (globalSettings == null) - return false; - var settingsListInContainer = GetSettingsListFromRenderPipelineGlobalSettings(globalSettings, out var globalSettingsSO, out var settingsContainer); - if (settingsListInContainer.arraySize == 0) + if (globalSettings == null) return false; + + var globalSettingsSerializedObject = new SerializedObject(globalSettings); + var settingsContainer = globalSettingsSerializedObject.FindProperty("m_Settings"); - var globalSettingsType = globalSettings.GetType(); - if (!ExtractSupportedOnRenderPipelineAttribute(globalSettingsType, out var supportedOnRenderPipelineAttribute)) - return false; + var settingsListInContainer = settingsContainer != null + ? settingsContainer.FindPropertyRelative("m_SettingsList") + : null; - var tabName = CreateNewTabName(globalSettingsType, supportedOnRenderPipelineAttribute); - globalSettingsContainer = new GlobalSettingsContainer(tabName, supportedOnRenderPipelineAttribute.renderPipelineTypes[0], settingsContainer, globalSettingsSO); - return true; - } + if (settingsListInContainer != null && settingsListInContainer.arraySize != 0) + { + var globalSettingsType = globalSettings.GetType(); + if (ExtractSupportedOnRenderPipelineAttribute(globalSettingsType, out var supportedOnRenderPipelineAttribute)) + { + var tabName = CreateNewTabName(globalSettingsType, supportedOnRenderPipelineAttribute); + globalSettingsContainer = new GlobalSettingsContainer(tabName, supportedOnRenderPipelineAttribute.renderPipelineTypes[0], settingsContainer, globalSettingsSerializedObject); + } + } - internal static SerializedProperty GetSettingsListFromRenderPipelineGlobalSettings(RenderPipelineGlobalSettings globalSettings, out SerializedObject globalSettingsSO, out SerializedProperty settingsContainer) - { - globalSettingsSO = new SerializedObject(globalSettings); - settingsContainer = globalSettingsSO.FindProperty("m_Settings"); - var settingsListInContainer = settingsContainer.FindPropertyRelative("m_SettingsList"); - return settingsListInContainer; + return globalSettingsContainer != null; } internal static RenderPipelineGlobalSettings GetRenderPipelineGlobalSettingsByIndex(SerializedProperty srpDefaultSettings, int i) diff --git a/Editor/Mono/Inspector/MinMaxGradientPropertyDrawer.cs b/Editor/Mono/Inspector/MinMaxGradientPropertyDrawer.cs index 93152a3c1d..a1512e55e8 100644 --- a/Editor/Mono/Inspector/MinMaxGradientPropertyDrawer.cs +++ b/Editor/Mono/Inspector/MinMaxGradientPropertyDrawer.cs @@ -135,7 +135,9 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) { Init(property); - return new MinMaxGradientField(m_Property, preferredLabel); + var field = new MinMaxGradientField(m_Property, preferredLabel); + PropertyField.ConfigureFieldStyles(field); + return field; } } } diff --git a/Editor/Mono/Inspector/PreviewWindow.cs b/Editor/Mono/Inspector/PreviewWindow.cs index ef3608ec16..3ae0d02fd0 100644 --- a/Editor/Mono/Inspector/PreviewWindow.cs +++ b/Editor/Mono/Inspector/PreviewWindow.cs @@ -53,7 +53,7 @@ protected override void OnEnable() protected override void OnDisable() { base.OnDisable(); - if (m_ParentInspectorWindow != null) + if (m_ParentInspectorWindow != null && GetInspectors().Contains(m_ParentInspectorWindow)) { m_ParentInspectorWindow.hasFloatingPreviewWindow = false; m_ParentInspectorWindow.RebuildContentsContainers(); diff --git a/Editor/Mono/Inspector/PropertyEditor.cs b/Editor/Mono/Inspector/PropertyEditor.cs index b83da9de52..94b417e188 100644 --- a/Editor/Mono/Inspector/PropertyEditor.cs +++ b/Editor/Mono/Inspector/PropertyEditor.cs @@ -1550,7 +1550,7 @@ private Object[] GetInspectedAssets() // This is used if more than one asset is selected // Ideally the tracker should be refactored to track not just editors but also the selection that caused them, so we wouldn't need this - return Selection.objects.Where(IsOpenForEdit).ToArray(); + return Selection.objects.Where(EditorUtility.IsPersistent).ToArray(); } protected virtual bool BeginDrawPreviewAndLabels() { return true; } @@ -1759,7 +1759,7 @@ private void DrawFooter() GUILayout.BeginVertical(Styles.footer); if (hasLabels) { - using (new EditorGUI.DisabledScope(assets.Any(a => EditorUtility.IsPersistent(a) && !Editor.IsAppropriateFileOpenForEdit(a)))) + using (new EditorGUI.DisabledScope(assets.Any(a => !IsOpenForEdit(a) || !Editor.IsAppropriateFileOpenForEdit(a)))) { m_LabelGUI.OnLabelGUI(assets); } @@ -1767,7 +1767,10 @@ private void DrawFooter() if (hasBundleName) { - m_AssetBundleNameGUI.OnAssetBundleNameGUI(assets); + using (new EditorGUI.DisabledScope(assets.Any(a => !IsOpenForEdit(a)))) + { + m_AssetBundleNameGUI.OnAssetBundleNameGUI(assets); + } } GUILayout.EndVertical(); } diff --git a/Editor/Mono/Inspector/RendererLightingSettings.cs b/Editor/Mono/Inspector/RendererLightingSettings.cs index abf9592f80..e0e15413d6 100644 --- a/Editor/Mono/Inspector/RendererLightingSettings.cs +++ b/Editor/Mono/Inspector/RendererLightingSettings.cs @@ -564,7 +564,7 @@ void ShowAtlasGUI(int instanceID, bool isMeshRenderer, bool useInteractiveLightB return; // Hide preview lightmaps if we aren't in preview mode - if (useInteractiveLightBakingData && !Lightmapping.isInteractive) + if (useInteractiveLightBakingData && !Lightmapping.shouldBakeInteractively) return; Hash128 contentHash = LightmapVisualizationUtility.GetBakedGITextureHash(lightmapIndex, 0, GITextureType.Baked, useInteractiveLightBakingData); diff --git a/Editor/Mono/Inspector/SpriteFrameInspector.cs b/Editor/Mono/Inspector/SpriteFrameInspector.cs index 76a5c416f7..07ae7133f3 100644 --- a/Editor/Mono/Inspector/SpriteFrameInspector.cs +++ b/Editor/Mono/Inspector/SpriteFrameInspector.cs @@ -185,14 +185,18 @@ public static Texture2D BuildPreviewTexture(Sprite sprite, Material spriteRender if (!isPolygon) { // Try to have a minimum of 64 pixels for width and height, unless requested width and height is smaller - var minWidth = Mathf.Min(64, width); - var minHeight = Mathf.Min(64, height); + var minWidth = Mathf.Min(64f, width); + var minHeight = Mathf.Min(64f, height); PreviewHelpers.AdjustWidthAndHeightForStaticPreview((int) spriteWidth, (int) spriteHeight, ref width, ref height); - // Set minimum size for width and height to prevent small previews for small sprites - width = Mathf.Max(minWidth, width); - height = Mathf.Max(minHeight, height); + // Set minimum size for width/height to prevent small previews for small sprites + if (width < minWidth && height < minHeight) + { + var ratio = Mathf.Min( minWidth / width, minHeight / height); + width = Mathf.FloorToInt(width * ratio); + height = Mathf.FloorToInt(height * ratio); + } } SavedRenderTargetState savedRTState = new SavedRenderTargetState(); diff --git a/Editor/Mono/Inspector/Texture2DArrayPreview.cs b/Editor/Mono/Inspector/Texture2DArrayPreview.cs index 53dda5e406..4541c01d5e 100644 --- a/Editor/Mono/Inspector/Texture2DArrayPreview.cs +++ b/Editor/Mono/Inspector/Texture2DArrayPreview.cs @@ -104,7 +104,7 @@ public void OnPreviewGUI(Texture t, Rect r, GUIStyle background, float exposure, TextureUtil.SetFilterModeNoDirty(t, oldFilter); - int mipmapLimit = (t as Texture2DArray).activeMipmapLimit; + int mipmapLimit = GetMipmapLimit(t); int cpuMipLevel = Mathf.Min(TextureUtil.GetMipmapCount(t) - 1, (int)mipLevel + mipmapLimit); m_Pos = PreviewGUI.EndScrollView(); @@ -134,6 +134,15 @@ int GetEffectiveSlice(Texture t) } } + int GetMipmapLimit(Texture t) + { + var texture2DArray = t as Texture2DArray; + if (texture2DArray != null) + return texture2DArray.activeMipmapLimit; + + return 0; + } + void InitPreviewMaterialIfNeeded() { if (m_Material == null) diff --git a/Editor/Mono/Inspector/TextureInspector.cs b/Editor/Mono/Inspector/TextureInspector.cs index c67bb6b7ab..d12494a3c0 100644 --- a/Editor/Mono/Inspector/TextureInspector.cs +++ b/Editor/Mono/Inspector/TextureInspector.cs @@ -30,8 +30,8 @@ internal static void AdjustWidthAndHeightForStaticPreview(int textureWidth, int { // For textures larger than our wanted width and height we ensure to // keep aspect ratio of the texture and fit it to best match our wanted width and height. - float relWidth = height / (float)textureWidth; - float relHeight = width / (float)textureHeight; + float relWidth = width / (float)textureWidth; + float relHeight = height / (float)textureHeight; float scale = Mathf.Min(relHeight, relWidth); diff --git a/Editor/Mono/Menu.bindings.cs b/Editor/Mono/Menu.bindings.cs index a62b61854e..fbbf9db106 100644 --- a/Editor/Mono/Menu.bindings.cs +++ b/Editor/Mono/Menu.bindings.cs @@ -60,6 +60,9 @@ public sealed class Menu [FreeFunction("MenuController::SetMenuItemHotkey")] internal static extern void SetHotkey(string menuPath, string hotkey); + [FreeFunction("MenuController::GetMenuItemHotkey")] + internal static extern string GetHotkey(string menuPath); + [FreeFunction("MenuController::ExtractSubmenus")] internal static extern string[] ExtractSubmenus(string menuPath); diff --git a/Editor/Mono/MenuUtility.cs b/Editor/Mono/MenuUtility.cs index 340d0af9c8..ef1c15db16 100644 --- a/Editor/Mono/MenuUtility.cs +++ b/Editor/Mono/MenuUtility.cs @@ -34,9 +34,19 @@ public static void AddMenuItem(DropdownMenu menu, string menuItemPath, string co public static void AddMenuItemWithContext(DropdownMenu menu, IEnumerable context, string menuItemPath, string contextMenuPath = "") { var contextArray = ToArray(context); - bool enabled = Menu.GetEnabledWithContext(menuItemPath, contextArray); + var enabled = Menu.GetEnabledWithContext(menuItemPath, contextArray); + AddMenuItemWithContext(menu, context, enabled, menuItemPath, contextMenuPath); + } + + internal static void AddMenuItemWithContext(DropdownMenu menu, IEnumerable context, bool enabled, string menuItemPath, string contextMenuPath = "") + { + var contextArray = ToArray(context); string iconResource = Menu.GetIconResource(menuItemPath); - AddAction(menu, string.IsNullOrEmpty(contextMenuPath) ? menuItemPath : contextMenuPath, + string shortcut = Menu.GetHotkey(menuItemPath); + string path = (string.IsNullOrEmpty(contextMenuPath) ? menuItemPath : contextMenuPath) + + (string.IsNullOrEmpty(shortcut) ? "" : " " + shortcut); + + AddAction(menu, path, () => { ExecuteMenuItem(contextArray, menuItemPath); }, enabled, string.IsNullOrEmpty(iconResource) ? null : EditorGUIUtility.LoadIcon(iconResource), enabled ? string.Empty : Menu.GetDisabledTooltip(menuItemPath)); @@ -87,11 +97,18 @@ static void AddMenuItems(DropdownMenu menu, string componentName, ScriptingMenuI public static void AddClipboardEntriesTo(DropdownMenu menu) { - AddMenuItemWithContext(menu, null, "Edit/Cut", "Cut"); - AddMenuItemWithContext(menu, null, "Edit/Copy", "Copy"); - AddMenuItemWithContext(menu, null, "Edit/Paste", "Paste"); - AddMenuItemWithContext(menu, null, "Edit/Duplicate", "Duplicate"); - AddMenuItemWithContext(menu, null, "Edit/Delete", "Delete"); + var isThereGameObjectInSelection = Selection.gameObjects.Length > 0; + var isPasteEnabled = GUIUtility.systemCopyBuffer.Length > 0; + AddClipboardEntriesTo(menu, isThereGameObjectInSelection, isThereGameObjectInSelection, isPasteEnabled, isThereGameObjectInSelection, isThereGameObjectInSelection); + } + + public static void AddClipboardEntriesTo(DropdownMenu menu, bool cutEnabled, bool copyEnabled, bool pasteEnabled, bool duplicateEnabled, bool deleteEnabled) + { + AddMenuItemWithContext(menu, null, cutEnabled, "Edit/Cut", "Cut"); + AddMenuItemWithContext(menu, null, copyEnabled, "Edit/Copy", "Copy"); + AddMenuItemWithContext(menu, null, pasteEnabled, "Edit/Paste", "Paste"); + AddMenuItemWithContext(menu, null, duplicateEnabled, "Edit/Duplicate", "Duplicate"); + AddMenuItemWithContext(menu, null, deleteEnabled, "Edit/Delete", "Delete"); } public static void AddComponentEntriesTo(DropdownMenu menu) @@ -118,6 +135,7 @@ public static void AddComponentEntriesTo(DropdownMenu menu) public static void AddGameObjectEntriesTo(DropdownMenu menu) { bool hasSelectedGO = Selection.gameObjects.Length > 0; + bool hasSelectedExactlyOneGO = Selection.gameObjects.Length == 1; AddClipboardEntriesTo(menu); menu.AppendSeparator(); @@ -128,13 +146,31 @@ public static void AddGameObjectEntriesTo(DropdownMenu menu) menu.AppendSeparator(); - AddAction(menu, "Isolate", SceneVisibilityManager.ToggleIsolateSelectionShortcut, hasSelectedGO); + AddAction(menu, + SceneVisibilityState.isolation ? "Exit Isolation" : "Isolate", + SceneVisibilityManager.ToggleIsolateSelectionShortcut, + SceneVisibilityState.isolation || hasSelectedGO); menu.AppendSeparator(); AddMenuItemWithContext(menu, null, "Component/Add...", "Add Component..."); AddMenuItemWithContext(menu, null, "Assets/Properties...", "Properties..."); + // Prefab menu items that only make sense if a single object is selected. + var listOfSceneHierarchyWindows = SceneHierarchyWindow.GetAllSceneHierarchyWindows(); + if (hasSelectedExactlyOneGO && listOfSceneHierarchyWindows.Count > 0) + { + var sceneHierarchy = listOfSceneHierarchyWindows[0].sceneHierarchy; + if (sceneHierarchy is not null) + { + menu.AppendSeparator(); + var prefabIcon = PrefabUtility.GameObjectStyles.prefabIcon; + AddAction(menu, "Prefab/", null, icon: prefabIcon); + sceneHierarchy.PopulateDropdownMenuWithPrefabMenuItems(menu); + } + } + + // Component entries if (hasSelectedGO) { menu.AppendSeparator(); diff --git a/Editor/Mono/ObjectListLocalGroup.cs b/Editor/Mono/ObjectListLocalGroup.cs index 18cac0cf88..489cc06173 100644 --- a/Editor/Mono/ObjectListLocalGroup.cs +++ b/Editor/Mono/ObjectListLocalGroup.cs @@ -7,11 +7,8 @@ using UnityEditor.VersionControl; using UnityEditorInternal; using UnityEditorInternal.VersionControl; -using System.Collections; using System.Collections.Generic; -using System.Linq; using Math = System.Math; -using IndexOutOfRangeException = System.IndexOutOfRangeException; using AssetReference = UnityEditorInternal.InternalEditorUtility.AssetReference; namespace UnityEditor @@ -844,6 +841,8 @@ void DrawItem(Rect position, FilteredHierarchy.FilterResult filterItem, BuiltinR } else // Icon grid { + Texture previewImage = null; + // Get icon bool drawDropShadow = false; if (string.IsNullOrEmpty(assetReference.guid) && m_Owner.GetCreateAssetUtility().instanceID == assetReference.instanceID && m_Owner.GetCreateAssetUtility().icon != null) @@ -858,17 +857,16 @@ void DrawItem(Rect position, FilteredHierarchy.FilterResult filterItem, BuiltinR else { // Check for asset preview - Texture image = null; bool shouldGetAssetPreview = ShouldGetAssetPreview(assetReference); if (shouldGetAssetPreview) { if (assetReference.instanceID != 0) - image = AssetPreview.GetAssetPreview(assetReference.instanceID, m_Owner.GetAssetPreviewManagerID()); + previewImage = AssetPreview.GetAssetPreview(assetReference.instanceID, m_Owner.GetAssetPreviewManagerID()); else if (!string.IsNullOrEmpty(assetReference.guid)) - image = AssetPreview.GetAssetPreviewFromGUID(assetReference.guid, m_Owner.GetAssetPreviewManagerID()); + previewImage = AssetPreview.GetAssetPreviewFromGUID(assetReference.guid, m_Owner.GetAssetPreviewManagerID()); } - m_Content.image = image; + m_Content.image = previewImage; if (m_Content.image != null) drawDropShadow = true; @@ -957,9 +955,31 @@ void DrawItem(Rect position, FilteredHierarchy.FilterResult filterItem, BuiltinR if (isDropTarget) Styles.resultsLabel.Draw(new Rect(labelRect.x - 10, labelRect.y, labelRect.width + 20, labelRect.height), GUIContent.none, true, true, false, false); + + Texture2D typeIcon = null; + if (filterItem != null && previewImage != null) + { + Type type = InternalEditorUtility.GetTypeWithoutLoadingObject(filterItem.instanceID); + + if (type != typeof(Texture2D)) + { + typeIcon = filterItem.icon; + } + } + + if (builtinResource != null) + { + Type type = InternalEditorUtility.GetTypeWithoutLoadingObject(builtinResource.m_InstanceID); + + if (type != typeof(Texture2D)) + { + typeIcon = AssetPreview.GetMiniTypeThumbnail(type); + } + } + var orgClipping = Styles.resultsGridLabel.clipping; var orgAlignment = Styles.resultsLabel.alignment; - var size = Styles.resultsGridLabel.CalcSizeWithConstraints(GUIContent.Temp(labeltext), orgPosition.size); + var size = Styles.resultsGridLabel.CalcSizeWithConstraints(GUIContent.Temp(labeltext, typeIcon), orgPosition.size); size.x += Styles.resultsGridLabel.padding.horizontal; labelRect.x = orgPosition.x + (orgPosition.width - size.x) / 2.0f; labelRect.width = size.x; @@ -968,9 +988,33 @@ void DrawItem(Rect position, FilteredHierarchy.FilterResult filterItem, BuiltinR Styles.resultsGridLabel.clipping = TextClipping.Ellipsis; Styles.resultsGridLabel.alignment = TextAnchor.MiddleCenter; - Styles.resultsGridLabel.Draw(labelRect, labeltext, false, false, selected, m_Owner.HasFocus()); + Styles.resultsGridLabel.Draw(labelRect, GUIContent.Temp(labeltext, typeIcon), false, false, selected, m_Owner.HasFocus()); Styles.resultsGridLabel.clipping = orgClipping; Styles.resultsLabel.alignment = orgAlignment; + + // We only need to set the tooltip once, and not for every item. + if (labelRect.Contains(Event.current.mousePosition)) + { + string tooltip = null; + + if (filterItem != null) + { + //We use GetAssetPath to have the file extension as well + string path = AssetDatabase.GetAssetPath(filterItem.instanceID); + tooltip = path.Substring(path.LastIndexOf('/') + 1); + } + else if (builtinResource != null) + { + //We have a "None" item in the ObjectSelector that has a 0 instanceID + if (builtinResource.m_InstanceID != 0) + tooltip = builtinResource.m_Name + "\n" + "(Built-in Resource)"; + } + + if (tooltip != null) + { + GUI.Label(labelRect, GUIContent.Temp("", tooltip)); + } + } } } diff --git a/Editor/Mono/Overlays/Overlay.cs b/Editor/Mono/Overlays/Overlay.cs index 25b3939dd2..c880cbb3d4 100644 --- a/Editor/Mono/Overlays/Overlay.cs +++ b/Editor/Mono/Overlays/Overlay.cs @@ -720,7 +720,8 @@ void BuildContextMenu(ContextualMenuPopulateEvent evt) menu.AppendSeparator(); var layouts = supportedLayouts; - if ((layouts & Layout.Panel) != 0) + // Panel layout is always supported by default, we only add this option in the menu if other options are available + if ((layouts & Layout.HorizontalToolbar) != 0 || (layouts & Layout.VerticalToolbar) != 0) menu.AppendAction(L10n.Tr("Panel"), action => { layout = Layout.Panel; collapsed = false; }, GetMenuItemState(layout == Layout.Panel)); if ((layouts & Layout.HorizontalToolbar) != 0) menu.AppendAction(L10n.Tr("Horizontal"), action => { layout = Layout.HorizontalToolbar; collapsed = false; }, GetMenuItemState(layout == Layout.HorizontalToolbar)); diff --git a/Editor/Mono/PerformanceTools/FrameDebugger.cs b/Editor/Mono/PerformanceTools/FrameDebugger.cs index 370caea439..b4439cd664 100644 --- a/Editor/Mono/PerformanceTools/FrameDebugger.cs +++ b/Editor/Mono/PerformanceTools/FrameDebugger.cs @@ -435,7 +435,7 @@ private void DisableFrameDebugger() m_TreeView = null; } - private void RepaintOnLimitChange() + internal void RepaintOnLimitChange() { m_RepaintFrames = k_NeedToRepaintFrames; RepaintAllNeededThings(); diff --git a/Editor/Mono/PerformanceTools/FrameDebuggerStyles.cs b/Editor/Mono/PerformanceTools/FrameDebuggerStyles.cs index 5c7bfd751b..37b1421737 100644 --- a/Editor/Mono/PerformanceTools/FrameDebuggerStyles.cs +++ b/Editor/Mono/PerformanceTools/FrameDebuggerStyles.cs @@ -58,8 +58,13 @@ internal struct Window // Tree internal struct Tree { - internal static readonly GUIStyle s_RowText = "OL Label"; - internal static readonly GUIStyle s_RowTextRight = "OL RightLabel"; + internal static readonly GUIStyle s_RowText = new GUIStyle(EditorStyles.label); + internal static readonly GUIStyle s_RowTextBold = new GUIStyle(EditorStyles.boldLabel); + internal static readonly GUIStyle s_RowTextRight = new GUIStyle(EditorStyles.boldLabel) + { + alignment = TextAnchor.MiddleRight + }; + internal const string k_UnknownScopeString = ""; } diff --git a/Editor/Mono/PerformanceTools/FrameDebuggerTreeView.cs b/Editor/Mono/PerformanceTools/FrameDebuggerTreeView.cs index ea8b1c1574..088d282808 100644 --- a/Editor/Mono/PerformanceTools/FrameDebuggerTreeView.cs +++ b/Editor/Mono/PerformanceTools/FrameDebuggerTreeView.cs @@ -77,13 +77,9 @@ private void PingFrameEventObject(int selectedID) public void ReselectFrameEventIndex() { - int[] selection = m_TreeView.GetSelection(); - if (selection.Length > 0) - { - FrameDebuggerTreeViewItem item = m_TreeView.FindItem(selection[0]) as FrameDebuggerTreeViewItem; - if (item != null) - m_TreeView.SetSelection(new[] { item.m_EventIndex }, true); - } + FrameDebuggerTreeViewItem item = GetSelectedTreeViewItem(); + if (item != null) + SetSelection(item.m_EventIndex); } public void SelectFrameEventIndex(int eventIndex) @@ -92,14 +88,24 @@ public void SelectFrameEventIndex(int eventIndex) // different tree nodes could result in the same frame debugger event // limit, e.g. a hierarchy node sets last child event as the limit. // If the limit event is the same, then do not change the currently selected item. + FrameDebuggerTreeViewItem item = GetSelectedTreeViewItem(); + if (item == null || item.m_EventIndex != eventIndex) + SetSelection(eventIndex); + } + + private FrameDebuggerTreeViewItem GetSelectedTreeViewItem() + { int[] selection = m_TreeView.GetSelection(); if (selection.Length > 0) - { - FrameDebuggerTreeViewItem item = m_TreeView.FindItem(selection[0]) as FrameDebuggerTreeViewItem; - if (item != null && eventIndex == item.m_EventIndex) - return; - } + return m_TreeView.FindItem(selection[0]) as FrameDebuggerTreeViewItem; + + return null; + } + + private void SetSelection(int eventIndex) + { m_TreeView.SetSelection(new[] { eventIndex }, true); + m_FrameDebugger.RepaintOnLimitChange(); } public void DrawTree(Rect rect) @@ -148,17 +154,15 @@ protected override void OnContentGUI(Rect rect, int row, TreeViewItem itemRaw, s return; FrameDebuggerTreeViewItem item = (FrameDebuggerTreeViewItem)itemRaw; - string text; GUIContent tempContent; GUIStyle style; bool isParent = (item.hasChildren); - FontStyle fontStyle = (isParent) ? FontStyle.Bold : FontStyle.Normal; childCounter = (isParent) ? 1 : (childCounter + 1); // Draw background - style = FrameDebuggerStyles.Tree.s_RowText; tempContent = EditorGUIUtility.TempContent(""); + style = FrameDebuggerStyles.Tree.s_RowText; style.Draw(rect, tempContent, false, false, false, false); // indent @@ -169,30 +173,25 @@ protected override void OnContentGUI(Rect rect, int row, TreeViewItem itemRaw, s // child event count if (isParent) { - text = item.m_ChildEventCount.ToString(CultureInfo.InvariantCulture); - tempContent = EditorGUIUtility.TempContent(text); - - style = FrameDebuggerStyles.Tree.s_RowTextRight; - style.fontStyle = fontStyle; + tempContent = EditorGUIUtility.TempContent(item.m_ChildEventCount.ToString(CultureInfo.InvariantCulture)); Rect r = rect; r.width -= kSmallMargin; + style = FrameDebuggerStyles.Tree.s_RowTextRight; style.Draw(r, tempContent, false, false, false, false); // reduce width of available space for the name, so that it does not overlap event count rect.width -= style.CalcSize(tempContent).x + kSmallMargin * 2; } - style = FrameDebuggerStyles.Tree.s_RowText; - style.fontStyle = fontStyle; // draw event name - text = item.displayName; - - if (string.IsNullOrEmpty(text)) - text = FrameDebuggerStyles.Tree.k_UnknownScopeString; + if (string.IsNullOrEmpty(item.displayName)) + tempContent = EditorGUIUtility.TempContent(FrameDebuggerStyles.Tree.k_UnknownScopeString); + else + tempContent = EditorGUIUtility.TempContent(item.displayName); - tempContent = EditorGUIUtility.TempContent(text); + style = isParent ? FrameDebuggerStyles.Tree.s_RowTextBold : FrameDebuggerStyles.Tree.s_RowText; style.Draw(rect, tempContent, false, false, false, selected && focused); } diff --git a/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesWindow.cs b/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesWindow.cs index 8d3bca32e8..54888bc6a9 100644 --- a/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesWindow.cs +++ b/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesWindow.cs @@ -20,6 +20,7 @@ internal class PrefabOverridesWindow : PopupWindowContent RectOffset k_TreeViewPadding = new RectOffset(0, 0, 4, 4); const float k_HeaderHeight = 60f; const float k_ButtonWidth = 120; + const float k_ButtonWidthVariant = 201; const float k_ButtonWidthPadding = 10; //Padding in case the text is long to give the window a bit of margins around the buttons const float k_HeaderLeftMargin = 6; const float k_NoOverridesLabelHeight = 26f; @@ -629,7 +630,7 @@ void UpdateText(Texture assetIcon, string assetName) if (stage is PrefabStage && PrefabUtility.IsPartOfVariantPrefab(AssetDatabase.LoadAssetAtPath(stage.assetPath))) { applyAllContent = Styles.applyAllToBaseContent; - m_ApplyButtonWidth = GUI.skin.button.CalcSize(applyAllContent).x; + m_ApplyButtonWidth = k_ButtonWidthVariant; applySelectedContent = Styles.applySelectedToBaseContent; } @@ -800,7 +801,7 @@ void DrawRemoveButton(Rect rect) if (GUILayout.Button(Styles.buttonContent, EditorStyles.miniButton, GUILayout.Width(k_ButtonWidth))) { - PrefabUtility.RemovePrefabInstanceUnusedOverrides(m_InstanceOverridesInfos); + PrefabUtility.RemovePrefabInstanceUnusedOverrides(m_InstanceOverridesInfos, InteractionMode.UserAction); editorWindow.Close(); m_Owner.RefreshStatus(); GUIUtility.ExitGUI(); diff --git a/Editor/Mono/Prefabs/PrefabUtility.cs b/Editor/Mono/Prefabs/PrefabUtility.cs index e0720d1fcb..47f510dda3 100644 --- a/Editor/Mono/Prefabs/PrefabUtility.cs +++ b/Editor/Mono/Prefabs/PrefabUtility.cs @@ -372,6 +372,26 @@ public static void ApplyPrefabInstances(GameObject[] instanceRoots, InteractionM Undo.FlushTrackedObjects(); // Needs to be called after StopAssetEditing() to fix UUM-6917 } + public static void RemoveUnusedOverrides(GameObject[] prefabInstances, InteractionMode action) + { + if (prefabInstances == null) + throw new ArgumentNullException(nameof(prefabInstances)); + + HashSet rootSet = new HashSet(prefabInstances.Length); + foreach (var gameObject in prefabInstances) + { + if (gameObject == null) + throw new ArgumentException("Input array contains null elements.", nameof(prefabInstances)); + if (!PrefabUtility.IsPartOfPrefabInstance(gameObject)) + throw new ArgumentException("Input array contains objects which are not part of a Prefab instance.", nameof(prefabInstances)); + + rootSet.Add(PrefabUtility.GetOutermostPrefabInstanceRoot(gameObject)); + } + + InstanceOverridesInfo[] instanceOverridesInfos = rootSet.Select(PrefabUtility.GetPrefabInstanceOverridesInfo_Internal).ToArray(); + PrefabUtility.RemovePrefabInstanceUnusedOverrides(instanceOverridesInfos, action); + } + private static void MapObjectReferencePropertyToSourceIfApplicable(SerializedProperty property, Object prefabSourceObject) { var referencedObject = property.objectReferenceValue; @@ -3514,7 +3534,7 @@ internal static bool HavePrefabInstancesUnusedOverrides(GameObject[] gameObjects internal static InstanceOverridesInfo[] GetPrefabInstancesOverridesInfos(GameObject[] selectedGameObjects) { - if (selectedGameObjects == null || !selectedGameObjects.Any()) + if (selectedGameObjects == null || selectedGameObjects.Length == 0) return new InstanceOverridesInfo[] {}; List allInstanceMods = new List(); @@ -3634,18 +3654,46 @@ internal static bool DoRemovePrefabInstanceUnusedOverridesDialog(InstanceOverrid return false; } - internal static void RemovePrefabInstanceUnusedOverrides(InstanceOverridesInfo[] instanceOverridesInfos) + internal static void RemovePrefabInstanceUnusedOverrides(InstanceOverridesInfo[] instanceOverridesInfos, InteractionMode interactionMode = InteractionMode.UserAction) { - bool updatedEditorLog = false; - foreach (InstanceOverridesInfo ipmods in instanceOverridesInfos) - updatedEditorLog |= PrefabUtility.RemovePrefabInstanceUnusedOverrides(ipmods); + { + if (ipmods.unusedOverrideCount != 0 && interactionMode == InteractionMode.UserAction) + { + Undo.RegisterCompleteObjectUndo(ipmods.instance, "Remove unused overrides"); + } - if (updatedEditorLog) - System.Console.WriteLine(""); + PrefabUtility.RemovePrefabInstanceUnusedOverrides(ipmods, out RemovedModifications removedModificationsCount); + + if (interactionMode == InteractionMode.UserAction) + { + if (removedModificationsCount.unusedMods > 0) + { + LogRemovedPropertyOverrides(ipmods.instance, ipmods.unusedMods); + } + if (removedModificationsCount.unusedRemovedGameObjectCount > 0) + { + LogRemovedUnusedRemovedGameObjects(ipmods.instance, ipmods.unusedRemovedGameObjectCount); + } + if (removedModificationsCount.unusedRemovedComponentCount > 0) + { + LogRemovedUnusedRemovedComponents(ipmods.instance, ipmods.unusedRemovedComponentCount); + } + } + } + + if (interactionMode == InteractionMode.UserAction) + Undo.FlushTrackedObjects(); + } + + private struct RemovedModifications + { + public int unusedMods; + public int unusedRemovedGameObjectCount; + public int unusedRemovedComponentCount; } - private static bool RemovePrefabInstanceUnusedOverrides(InstanceOverridesInfo iovInfo) + private static bool RemovePrefabInstanceUnusedOverrides(InstanceOverridesInfo iovInfo, out RemovedModifications removedModificationsCount) { if (iovInfo.instance == null) throw new ArgumentNullException(nameof(iovInfo), "InstanceOverridesInfo.instance was null"); @@ -3654,30 +3702,32 @@ private static bool RemovePrefabInstanceUnusedOverrides(InstanceOverridesInfo io else if (iovInfo.usedMods == null) throw new ArgumentNullException(nameof(iovInfo), "InstanceOverridesInfo.usedMods was null"); - bool updatedEditorLog = false; - if (iovInfo.unusedOverrideCount != 0) + removedModificationsCount = new RemovedModifications { - Undo.RegisterCompleteObjectUndo(iovInfo.instance, "Remove unused overrides"); + unusedMods = iovInfo.unusedMods.Length, + unusedRemovedGameObjectCount = iovInfo.unusedRemovedGameObjectCount, + unusedRemovedComponentCount = iovInfo.unusedRemovedComponentCount + }; - if (iovInfo.unusedMods.Any()) + bool removedAny = iovInfo.unusedOverrideCount != 0; + if (removedAny) + { + if (iovInfo.unusedMods.Length > 0) { SetPropertyModifications(iovInfo.instance, iovInfo.usedMods); - updatedEditorLog |= PrefabUtility.LogRemovedPropertyOverrides(iovInfo.instance, iovInfo.unusedMods); } if (iovInfo.unusedRemovedGameObjectCount > 0) { PrefabUtility.RemoveRemovedGameObjectOverridesWhichAreNull(iovInfo.instance); - updatedEditorLog |= PrefabUtility.LogRemovedUnusedRemovedGameObjects(iovInfo.instance, iovInfo.unusedRemovedGameObjectCount); } if (iovInfo.unusedRemovedComponentCount > 0) { PrefabUtility.RemoveRemovedComponentOverridesWhichAreInvalid(iovInfo.instance); - updatedEditorLog |= PrefabUtility.LogRemovedUnusedRemovedComponents(iovInfo.instance, iovInfo.unusedRemovedComponentCount); } } - return updatedEditorLog; + return removedAny; } internal static bool LogRemovedPropertyOverrides(GameObject instance, PropertyModification[] mods) diff --git a/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs b/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs index 137e477d0d..9f2620570a 100644 --- a/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs +++ b/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs @@ -88,7 +88,6 @@ class GeneralProperties public static readonly GUIContent enableExtendedLogging = EditorGUIUtility.TrTextContent("Timestamp Editor log entries", "Adds timestamp and thread Id to Editor.log messages."); public static readonly GUIContent enableHelperBar = EditorGUIUtility.TrTextContent("Enable Helper Bar", "Enables Helper Bar in the status bar at the bottom of the main Unity Editor window."); public static readonly GUIContent enablePlayModeTooltips = EditorGUIUtility.TrTextContent("Enable PlayMode Tooltips", "Enables tooltips in the editor while in play mode."); - public static readonly GUIContent contextMenuAutoExpandDelay = EditorGUIUtility.TrTextContent("Context Menu Auto Expand Delay"); } class ExternalProperties @@ -585,7 +584,6 @@ private void ShowGeneral(string searchContext) DrawEnableHelperBar(); DrawEnableTooltipsInPlayMode(); - DrawContextMenuAutoExpandDelay(); EditorGUILayout.Space(); GUILayout.Label(GeneralProperties.hierarchyHeader, EditorStyles.boldLabel); @@ -734,19 +732,6 @@ void DrawEnableTooltipsInPlayMode() } } - void DrawContextMenuAutoExpandDelay() - { - var delay = EditorPrefs.GetFloat(EditorMenuExtensions.k_AutoExpandDelayKeyName, - EditorMenuExtensions.k_SubmenuExpandDelay); - - EditorGUI.BeginChangeCheck(); - delay = EditorGUILayout.Slider(GeneralProperties.contextMenuAutoExpandDelay, delay, 0f, 1.0f); - if (EditorGUI.EndChangeCheck()) - { - EditorPrefs.SetFloat(EditorMenuExtensions.k_AutoExpandDelayKeyName, delay); - } - } - public void ApplyChangesToPrefs(bool force = false) { if (GUI.changed || force) diff --git a/Editor/Mono/Progress/Progress.bindings.cs b/Editor/Mono/Progress/Progress.bindings.cs index ecadd07db8..15f265adf7 100644 --- a/Editor/Mono/Progress/Progress.bindings.cs +++ b/Editor/Mono/Progress/Progress.bindings.cs @@ -479,7 +479,7 @@ internal static float GetMaxElapsedTime() return maxElapsedTime; } - // For testing purposes only. + // Anything below this line is for testing purposes only. internal static void ClearProgressItems() { s_ProgressItems.Clear(); @@ -513,6 +513,21 @@ internal static ExplicitLoggingState warningLoggingState [NativeMethod(IsFreeFunction = true, IsThreadSafe = false, Name = "Editor::Progress::Internal_SetExplicitWarningLoggingState")] static extern void SetExplicitWarningLoggingState(ExplicitLoggingState state); + + internal static bool manualUpdate + { + get => IsManualUpdate(); + set => SetManualUpdate(value); + } + + [NativeMethod(IsFreeFunction = true, IsThreadSafe = false, Name = "Editor::Progress::Internal_IsManualUpdate")] + static extern bool IsManualUpdate(); + + [NativeMethod(IsFreeFunction = true, IsThreadSafe = false, Name = "Editor::Progress::Internal_SetManualUpdate")] + static extern void SetManualUpdate(bool manualUpdate); + + [NativeMethod(IsFreeFunction = true, IsThreadSafe = false, Name = "Editor::Progress::ForceUpdateProgress")] + internal static extern void ForceUpdate(); } static class ProgressEnumExtensions diff --git a/Editor/Mono/ProjectWindow/AssetClipboardUtility.cs b/Editor/Mono/ProjectWindow/AssetClipboardUtility.cs index 93b462d693..92900234ac 100644 --- a/Editor/Mono/ProjectWindow/AssetClipboardUtility.cs +++ b/Editor/Mono/ProjectWindow/AssetClipboardUtility.cs @@ -93,72 +93,55 @@ static IEnumerable PasteCopiedAssets(string destination = null) Object firstDuplicatedObjectToFail = null; List pastedObjects = new List(); - try + foreach (var item in assetClipboard) { - // StartAssetEditing begins a batch operation on the AssetDatabase - // such that we don't do any actual imports until - // AssetDatabase.StopAssetImporting is called. - // This means that while function calls are allowed to happen - // imports won't actually happen until AssetDatabase.StopAssetImporting - // is called below. - AssetDatabase.StartAssetEditing(); - - // We batch the copies here - foreach (var item in assetClipboard) - { - var asset = ObjectIdentifier.ToObject(item); - var assetPath = AssetDatabase.GetAssetPath(asset); + var asset = ObjectIdentifier.ToObject(item); + var assetPath = AssetDatabase.GetAssetPath(asset); - // if duplicating a sub-asset, then create a copy next to the main asset file - if (asset != null && !String.IsNullOrEmpty(assetPath) && AssetDatabase.IsSubAsset(asset)) + // if duplicating a sub-asset, then create a copy next to the main asset file + if (asset != null && !String.IsNullOrEmpty(assetPath) && AssetDatabase.IsSubAsset(asset)) + { + if (asset is ISubAssetNotDuplicatable || asset is GameObject) { - if (asset is ISubAssetNotDuplicatable || asset is GameObject) - { - firstDuplicatedObjectToFail = firstDuplicatedObjectToFail ? firstDuplicatedObjectToFail : asset; - continue; - } - - var extension = NativeFormatImporterUtility.GetExtensionForAsset(asset); - - // We dot sanitize or block unclean the asset filename (asset.name) - // since the assertdb will do it for us and has a whole tailored logic for that. - - // It feels wrong that the asset name (that can apparently contain any char) - // is conflated with the orthogonal notion of filename. From the user's POV - // it will force an asset dup but with mangled names if the original name contained - // "invalid chars" for filenames. - // Path.Combine is not used here to avoid blocking asset names that might - // contain chars not allowed in filenames. - if ((new HashSet(Path.GetInvalidFileNameChars())).Intersect(asset.name).Count() != 0) - { - Debug.LogWarning(string.Format("Duplicated asset name '{0}' contains invalid characters. Those will be replaced in the duplicated asset name.", asset.name)); - } - - var newPath = AssetDatabase.GenerateUniqueAssetPath( - string.Format("{0}{1}{2}.{3}", - Path.GetDirectoryName(assetPath), - Path.DirectorySeparatorChar, - asset.name, - extension) - ); - - assetPath = GetValidPath(newPath, destination); - AssetDatabase.CreateAsset(Object.Instantiate(asset), assetPath); - pastedObjects.Add(assetPath); + firstDuplicatedObjectToFail = firstDuplicatedObjectToFail ? firstDuplicatedObjectToFail : asset; + continue; } - // Otherwise duplicate the main asset file - else if (EditorUtility.IsPersistent(asset)) + + var extension = NativeFormatImporterUtility.GetExtensionForAsset(asset); + + // We dot sanitize or block unclean the asset filename (asset.name) + // since the assertdb will do it for us and has a whole tailored logic for that. + + // It feels wrong that the asset name (that can apparently contain any char) + // is conflated with the orthogonal notion of filename. From the user's POV + // it will force an asset dup but with mangled names if the original name contained + // "invalid chars" for filenames. + // Path.Combine is not used here to avoid blocking asset names that might + // contain chars not allowed in filenames. + if ((new HashSet(Path.GetInvalidFileNameChars())).Intersect(asset.name).Count() != 0) { - var newPath = AssetDatabase.GenerateUniqueAssetPath(GetValidPath(assetPath, destination)); - if (newPath.Length > 0 && AssetDatabase.CopyAsset(assetPath, newPath)) - pastedObjects.Add(newPath); + Debug.LogWarning(string.Format("Duplicated asset name '{0}' contains invalid characters. Those will be replaced in the duplicated asset name.", asset.name)); } + + var newPath = AssetDatabase.GenerateUniqueAssetPath( + string.Format("{0}{1}{2}.{3}", + Path.GetDirectoryName(assetPath), + Path.DirectorySeparatorChar, + asset.name, + extension) + ); + + assetPath = GetValidPath(newPath, destination); + AssetDatabase.CreateAsset(Object.Instantiate(asset), assetPath); + pastedObjects.Add(assetPath); + } + // Otherwise duplicate the main asset file + else if (EditorUtility.IsPersistent(asset)) + { + var newPath = AssetDatabase.GenerateUniqueAssetPath(GetValidPath(assetPath, destination)); + if (newPath.Length > 0 && AssetDatabase.CopyAsset(assetPath, newPath)) + pastedObjects.Add(newPath); } - } - finally - { - // Batch import all assets that were created & copied in the try{} block above - AssetDatabase.StopAssetEditing(); } if (firstDuplicatedObjectToFail != null) diff --git a/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs b/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs index 34f46ae95c..3a487fca90 100644 --- a/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs +++ b/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs @@ -382,7 +382,7 @@ static void CreatePrefabVariant() string sourcePath = AssetDatabase.GetAssetPath(go); string sourceDir = Path.GetDirectoryName(sourcePath).ConvertSeparatorsToUnity(); - string variantPath = GetPrefabVariantPath(sourceDir, go.name); + string variantPath = GetPrefabVariantPath(sourceDir, go); StartNameEditingIfProjectWindowExists( 0, @@ -413,7 +413,7 @@ static GameObject[] CreatePrefabVariants(GameObject[] gameObjects) { string sourcePath = AssetDatabase.GetAssetPath(go); string sourceDir = Path.GetDirectoryName(sourcePath).ConvertSeparatorsToUnity(); - string variantPath = GetPrefabVariantPath(sourceDir, go.name); + string variantPath = GetPrefabVariantPath(sourceDir, go); variantPath = AssetDatabase.GenerateUniqueAssetPath(variantPath); var variant = PrefabUtility.CreateVariant(go, variantPath); @@ -430,9 +430,12 @@ static GameObject[] CreatePrefabVariants(GameObject[] gameObjects) return createdVariants.ToArray(); } - static string GetPrefabVariantPath(string folder, string gameObjectName) + static string GetPrefabVariantPath(string folder, GameObject gameObject) { - return string.Format("{0}/{1} Variant.prefab", folder, gameObjectName); + if (PrefabUtility.IsPartOfModelPrefab(gameObject)) + return string.Format("{0}/{1}.prefab", folder, gameObject.name); + else + return string.Format("{0}/{1} Variant.prefab", folder, gameObject.name); } public static void CreateAssetWithContent(string filename, string content, Texture2D icon = null) diff --git a/Editor/Mono/RenderPipelineGraphicsSettingsContainerPropertyDrawer.cs b/Editor/Mono/RenderPipelineGraphicsSettingsContainerPropertyDrawer.cs deleted file mode 100644 index 6307133582..0000000000 --- a/Editor/Mono/RenderPipelineGraphicsSettingsContainerPropertyDrawer.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using System.Collections.Generic; -using System.ComponentModel; -using System.Reflection; -using UnityEditor.UIElements; -using UnityEngine.Rendering; -using UnityEngine.UIElements; - -namespace UnityEditor.Rendering -{ - [CustomPropertyDrawer(typeof(RenderPipelineGraphicsSettingsContainer))] - class RenderPipelineGraphicsSettingsContainerPropertyDrawer : PropertyDrawer - { - //internal is for tests - internal static SortedDictionary> Categorize(SerializedProperty property) - { - SortedDictionary> categories = new(); - foreach(SerializedProperty prop in property.Copy()) - { - var type = prop.boxedValue.GetType(); - - //remove array length property - if (!typeof(IRenderPipelineGraphicsSettings).IsAssignableFrom(type)) - continue; - - var typeName = ObjectNames.NicifyVariableName(type.Name); - string name = type.GetCustomAttribute()?.Category ?? typeName; - - //sort per type in category - if (categories.TryGetValue(name, out var categoryElement)) - { - if (categoryElement.ContainsKey(typeName)) - UnityEngine.Debug.LogWarning($"{nameof(IRenderPipelineGraphicsSettings)} {typeName} is duplicated. Only showing first one."); - else - categoryElement.Add(typeName, new PropertyField(prop)); - continue; - } - - //sort per category - categories.Add(name, new SortedDictionary() - { - { typeName, new PropertyField(prop) } - }); - } - return categories; - } - - - public override VisualElement CreatePropertyGUI(SerializedProperty property) - { - var root = new VisualElement { name = "GlobalSettingsList" }; - var graphicsSettings = property.FindPropertyRelative("m_SettingsList"); - UnityEngine.Debug.Assert(graphicsSettings != null); - - foreach (var category in Categorize(graphicsSettings)) - { - var foldout = new Foldout() { text = category.Key }; - foreach(var element in category.Value) - foldout.Add(element.Value); - root.Add(foldout); - } - - return root; - } - } - - //The purpose is to remove the foldout drown from the ISRPGraphicsSetting itself, - //only if there is no dedicated CustomPropertyDrawer. - [CustomPropertyDrawer(typeof(IRenderPipelineGraphicsSettings), useForChildren: true)] - class ISRPGraphicsSettingPropertyDrawer : PropertyDrawer - { - public override VisualElement CreatePropertyGUI(SerializedProperty property) - { - VisualElement root = new(); - - bool atLeastOneChild = false; - foreach(SerializedProperty prop in property.Copy()) - { - atLeastOneChild = true; - root.Add(new PropertyField(prop)); - } - - if (!atLeastOneChild) - root.Add(new Label($"This {nameof(IRenderPipelineGraphicsSettings)} is empty.")); - - return root; - } - } -} diff --git a/Editor/Mono/SceneHierarchy.cs b/Editor/Mono/SceneHierarchy.cs index db5d4f70a7..95d344bba6 100644 --- a/Editor/Mono/SceneHierarchy.cs +++ b/Editor/Mono/SceneHierarchy.cs @@ -14,6 +14,7 @@ using UnityEditorInternal; using UnityEngine.Assertions; using System.Text; +using UnityEngine.UIElements; namespace UnityEditor { @@ -78,7 +79,7 @@ public bool hasCustomScenes const int kInvalidSceneHandle = 0; bool m_RectSelectInProgress; - TreeViewController m_TreeView; + IMGUI.Controls.TreeViewController m_TreeView; [SerializeField] TreeViewState m_TreeViewState; @@ -219,7 +220,7 @@ internal TreeViewState treeViewState get { return m_TreeViewState; } } - internal TreeViewController treeView + internal IMGUI.Controls.TreeViewController treeView { get { @@ -262,7 +263,7 @@ void Init() if (m_SortingObjects == null) SetUpSortMethodLists(); - m_TreeView = new TreeViewController(m_EditorWindow, m_TreeViewState); + m_TreeView = new IMGUI.Controls.TreeViewController(m_EditorWindow, m_TreeViewState); m_TreeView.itemDoubleClickedCallback += TreeViewItemDoubleClicked; m_TreeView.selectionChangedCallback += TreeViewSelectionChanged; m_TreeView.onGUIRowCallback += OnRowGUICallback; @@ -1159,89 +1160,8 @@ void CreateSubSceneGameObjectContextClick(GenericMenu menu, int contextClickedIt menu.AddItem(EditorGUIUtility.TrTextContent("Delete GameObject"), false, DeleteGO); } - void CreateGameObjectContextClick(GenericMenu menu, int contextClickedItemID) + void PopulateGenericMenuWithPrefabMenuItems(GenericMenu menu, int contextClickedItemID) { - bool itemIsSelected = Selection.gameObjects.Length > 0; - // If the custom parent for new objects is set we don't allow cut, copy or duplicate any ancestors of this object - // as it might be context objects (as in Prefab Mode in Context). - bool allowCutCopyAndDuplicate = m_CustomParentForNewGameObjects == null - || !IsChildOfSelectionOrSelected(m_CustomParentForNewGameObjects.parent); - - if (itemIsSelected && allowCutCopyAndDuplicate) - menu.AddItem(EditorGUIUtility.TrTextContent("Cut"), false, ClipboardUtility.CutGO); - else - menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Cut")); - if (itemIsSelected && allowCutCopyAndDuplicate) - menu.AddItem(EditorGUIUtility.TrTextContent("Copy"), false, ClipboardUtility.CopyGO); - else - menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Copy")); - if (CutBoard.CanGameObjectsBePasted() || Unsupported.CanPasteGameObjectsFromPasteboard()) - menu.AddItem(EditorGUIUtility.TrTextContent("Paste"), false, PasteGO); - else - menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Paste")); - if (ClipboardUtility.CanPasteAsChild()) - menu.AddItem(EditorGUIUtility.TrTextContent("Paste As Child"), false, ClipboardUtility.PasteGOAsChild); - else - menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Paste As Child")); - - menu.AddSeparator(""); - - if (itemIsSelected && !hasSearchFilter && m_TreeViewState.selectedIDs.Count == 1 && !GetIsNotEditable()) - menu.AddItem(EditorGUIUtility.TrTextContent("Rename"), false, RenameGO); - else - menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Rename")); - - if (itemIsSelected && allowCutCopyAndDuplicate) - menu.AddItem(EditorGUIUtility.TrTextContent("Duplicate"), false, DuplicateGO); - else - menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Duplicate")); - - if (m_CustomParentForNewGameObjects != null && IsChildOfSelectionOrSelected(m_CustomParentForNewGameObjects) || !itemIsSelected) - menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Delete")); - else - menu.AddItem(EditorGUIUtility.TrTextContent("Delete"), false, DeleteGO); - - - menu.AddSeparator(""); - - if (IsSelectChildrenAvailable()) - menu.AddItem(EditorGUIUtility.TrTextContent("Select Children"), false, SelectChildren); - else - menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Select Children")); - - menu.AddSeparator(""); - if (Selection.activeGameObject) - { - menu.AddItem(EditorGUIUtility.TrTextContent("Find References in Scene"), false, FindReferenceInScene); - } - else - { - menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Find References in Scene")); - } - - menu.AddSeparator(""); - - GameObject selectedObject = null; - if (Selection.objects.Length > 0) - selectedObject = Selection.objects[Selection.objects.Length - 1] as GameObject; - - if (Selection.count == 0 && treeView.hoveredItem == null || selectedObject && (selectedObject.name == PrefabUtility.kDummyPrefabStageRootObjectName || PrefabStageUtility.IsGameObjectThePrefabRootInAnyPrefabStage(selectedObject))) - { - menu.AddDisabledItem(Styles.setOriginLabel); - } - else if (selectedObject && (selectedObject.GetInstanceID() != GetDefaultParentForSession(selectedObject.scene.guid) || EditorSceneManager.GetActiveScene().guid != selectedObject.scene.guid)) - { - menu.AddItem(Styles.setOriginLabel, false, () => - { - SetDefaultParentObject(false); - }); - } - else - { - menu.AddItem(Styles.clearOriginLabel, false, () => { ClearDefaultParentObject(); }); - } - - // Prefab menu items that only make sense if a single object is selected. GameObject go = null; string assetPath = null; GameObject prefabAsset = null; @@ -1367,6 +1287,92 @@ void CreateGameObjectContextClick(GenericMenu menu, int contextClickedItemID) menu.AddSeparator("Prefab/"); menu.AddItem(EditorGUIUtility.TrTextContent("Prefab/Remove Unused Overrides..."), false, RemoveSelectedPrefabInstanceUnusedOverrides); } + } + + void CreateGameObjectContextClick(GenericMenu menu, int contextClickedItemID) + { + bool itemIsSelected = Selection.gameObjects.Length > 0; + // If the custom parent for new objects is set we don't allow cut, copy or duplicate any ancestors of this object + // as it might be context objects (as in Prefab Mode in Context). + bool allowCutCopyAndDuplicate = m_CustomParentForNewGameObjects == null + || !IsChildOfSelectionOrSelected(m_CustomParentForNewGameObjects.parent); + + if (itemIsSelected && allowCutCopyAndDuplicate) + menu.AddItem(EditorGUIUtility.TrTextContent("Cut"), false, ClipboardUtility.CutGO); + else + menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Cut")); + if (itemIsSelected && allowCutCopyAndDuplicate) + menu.AddItem(EditorGUIUtility.TrTextContent("Copy"), false, ClipboardUtility.CopyGO); + else + menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Copy")); + if (CutBoard.CanGameObjectsBePasted() || Unsupported.CanPasteGameObjectsFromPasteboard()) + menu.AddItem(EditorGUIUtility.TrTextContent("Paste"), false, PasteGO); + else + menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Paste")); + if (ClipboardUtility.CanPasteAsChild()) + menu.AddItem(EditorGUIUtility.TrTextContent("Paste As Child"), false, ClipboardUtility.PasteGOAsChild); + else + menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Paste As Child")); + + menu.AddSeparator(""); + + if (itemIsSelected && !hasSearchFilter && m_TreeViewState.selectedIDs.Count == 1 && !GetIsNotEditable()) + menu.AddItem(EditorGUIUtility.TrTextContent("Rename"), false, RenameGO); + else + menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Rename")); + + if (itemIsSelected && allowCutCopyAndDuplicate) + menu.AddItem(EditorGUIUtility.TrTextContent("Duplicate"), false, DuplicateGO); + else + menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Duplicate")); + + if (m_CustomParentForNewGameObjects != null && IsChildOfSelectionOrSelected(m_CustomParentForNewGameObjects) || !itemIsSelected) + menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Delete")); + else + menu.AddItem(EditorGUIUtility.TrTextContent("Delete"), false, DeleteGO); + + + menu.AddSeparator(""); + + if (IsSelectChildrenAvailable()) + menu.AddItem(EditorGUIUtility.TrTextContent("Select Children"), false, SelectChildren); + else + menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Select Children")); + + menu.AddSeparator(""); + if (Selection.activeGameObject) + { + menu.AddItem(EditorGUIUtility.TrTextContent("Find References in Scene"), false, FindReferenceInScene); + } + else + { + menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Find References in Scene")); + } + + menu.AddSeparator(""); + + GameObject selectedObject = null; + if (Selection.objects.Length > 0) + selectedObject = Selection.objects[Selection.objects.Length - 1] as GameObject; + + if (Selection.count == 0 && treeView.hoveredItem == null || selectedObject && (selectedObject.name == PrefabUtility.kDummyPrefabStageRootObjectName || PrefabStageUtility.IsGameObjectThePrefabRootInAnyPrefabStage(selectedObject))) + { + menu.AddDisabledItem(Styles.setOriginLabel); + } + else if (selectedObject && (selectedObject.GetInstanceID() != GetDefaultParentForSession(selectedObject.scene.guid) || EditorSceneManager.GetActiveScene().guid != selectedObject.scene.guid)) + { + menu.AddItem(Styles.setOriginLabel, false, () => + { + SetDefaultParentObject(false); + }); + } + else + { + menu.AddItem(Styles.clearOriginLabel, false, () => { ClearDefaultParentObject(); }); + } + + // Prefab menu items that only make sense if a single object is selected. + PopulateGenericMenuWithPrefabMenuItems(menu, contextClickedItemID); GameObject[] selectedGameObjects = Selection.transforms.Select(t => t.gameObject).ToArray(); @@ -1400,6 +1406,28 @@ void CreateGameObjectContextClick(GenericMenu menu, int contextClickedItemID) } } + internal void PopulateDropdownMenuWithPrefabMenuItems(DropdownMenu menu) + { + if (m_TreeViewState.selectedIDs.Count != 1) return; + + int selectedGameObjectInstanceID = m_TreeViewState.selectedIDs[0]; + GenericMenu genericMenuForGettingPrefabItems = new GenericMenu(); + PopulateGenericMenuWithPrefabMenuItems(genericMenuForGettingPrefabItems, selectedGameObjectInstanceID); + + for (int i = 1, prefabMenuItemsCount = genericMenuForGettingPrefabItems.menuItems.Count; i < prefabMenuItemsCount; ++i) + { + GenericMenu.MenuItem menuItem = genericMenuForGettingPrefabItems.menuItems[i]; + if (menuItem.separator) + menu.AppendSeparator(menuItem.content.text); + else if (menuItem.func2 != null && menuItem.userData != null) + menu.AppendAction(menuItem.content.text, action => { menuItem.func2(menuItem.userData); }); + else if (menuItem.func != null) + menu.AppendAction(menuItem.content.text, action => { menuItem.func(); }); + else + menu.AppendAction(menuItem.content.text, action => { }, DropdownMenuAction.Status.Disabled); + } + } + private void FindReferenceInScene() { var selectedObject = Selection.activeObject; @@ -1754,7 +1782,7 @@ bool AskUserToRemovePrefabInstanceUnusedOverrides(PrefabUtility.InstanceOverride { if (PrefabUtility.DoRemovePrefabInstanceUnusedOverridesDialog(instanceOverridesInfos)) { - PrefabUtility.RemovePrefabInstanceUnusedOverrides(instanceOverridesInfos); + PrefabUtility.RemovePrefabInstanceUnusedOverrides(instanceOverridesInfos, InteractionMode.UserAction); return true; } diff --git a/Editor/Mono/SceneModeWindows/LightingWindow.cs b/Editor/Mono/SceneModeWindows/LightingWindow.cs index d045c6b5ed..31d2f958f5 100644 --- a/Editor/Mono/SceneModeWindows/LightingWindow.cs +++ b/Editor/Mono/SceneModeWindows/LightingWindow.cs @@ -553,7 +553,7 @@ void DrawBottomBarGUI(Mode selectedMode) { // Bake button if we are not currently baking - bool showBakeButton = Lightmapping.isInteractive || !Lightmapping.isRunning; + bool showBakeButton = Lightmapping.shouldBakeInteractively || !Lightmapping.isRunning; if (showBakeButton) { bool anythingCompiling = ShaderUtil.anythingCompiling; diff --git a/Editor/Mono/SceneModeWindows/LightingWindowLightmapPreviewTab.cs b/Editor/Mono/SceneModeWindows/LightingWindowLightmapPreviewTab.cs index c43e9cf357..35ab499d37 100644 --- a/Editor/Mono/SceneModeWindows/LightingWindowLightmapPreviewTab.cs +++ b/Editor/Mono/SceneModeWindows/LightingWindowLightmapPreviewTab.cs @@ -136,7 +136,7 @@ public void OnGUI() else { var interactiveLightmaps = InteractiveLightBaking.GetLightmapData(); - if (interactiveLightmaps.numLightmaps > 0 && Lightmapping.isInteractive) // Hide preview lightmaps when not in preview mode + if (interactiveLightmaps.numLightmaps > 0 && Lightmapping.shouldBakeInteractively) // Hide preview lightmaps when not in preview mode { m_ShowPreviewLightmaps.value = EditorGUILayout.Foldout(m_ShowPreviewLightmaps.value, Styles.PreviewLightmapsFoldoutTitle); diff --git a/Editor/Mono/SceneView/SceneOrientationGizmo.cs b/Editor/Mono/SceneView/SceneOrientationGizmo.cs index 56d1a591ca..7d08cf7ce4 100644 --- a/Editor/Mono/SceneView/SceneOrientationGizmo.cs +++ b/Editor/Mono/SceneView/SceneOrientationGizmo.cs @@ -566,7 +566,6 @@ public override void OnGUI() // Usually this does nothing, but when dragging the window between monitors that have different scaling // it can resize the RT as necessary. SetupRenderTexture(); - var prevCamera = Camera.current; SetupCamera(view); //Handle transparency when rotation is locked @@ -601,8 +600,6 @@ public override void OnGUI() labelRect.y += gizmoRect.height; DrawLabels(view, labelRect); } - - Camera.SetupCurrent(prevCamera); } void DoOrientationHandles(SceneView view, Camera camera, bool isMouseHovering) diff --git a/Editor/Mono/SceneView/SceneView.cs b/Editor/Mono/SceneView/SceneView.cs index b1fdd5c203..5a1db3bcce 100644 --- a/Editor/Mono/SceneView/SceneView.cs +++ b/Editor/Mono/SceneView/SceneView.cs @@ -94,7 +94,7 @@ internal static DrawCameraMode[] GetInteractiveDrawCameraModeValues() return drawCameraModes.ToArray(); } - internal static bool IsInteractiveBakingEnabled() + internal static bool NeedsInteractiveBaking() { foreach (SceneView sceneView in SceneView.sceneViews) { @@ -2495,7 +2495,6 @@ void DoOnGUI() EditorGUIUtility.labelWidth = 100; - var prevCamera = Camera.current; SetupCamera(); RenderingPath oldRenderingPath = m_Camera.renderingPath; @@ -2667,8 +2666,6 @@ void DoOnGUI() onGUIEnded?.Invoke(this); if (m_StageHandling != null) m_StageHandling.EndOnGUI(); - - Camera.SetupCurrent(prevCamera); } // This will eventually be modified to use the mouse right-click. diff --git a/Editor/Mono/ScriptAttributeGUI/DecoratorDrawer.cs b/Editor/Mono/ScriptAttributeGUI/DecoratorDrawer.cs index e639d6b154..88c1be6474 100644 --- a/Editor/Mono/ScriptAttributeGUI/DecoratorDrawer.cs +++ b/Editor/Mono/ScriptAttributeGUI/DecoratorDrawer.cs @@ -2,6 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; using UnityEngine; using UnityEngine.UIElements; @@ -34,6 +35,7 @@ public virtual float GetHeight() return EditorGUI.kSingleLineHeight; } + [Obsolete("CanCacheInspectorGUI has been deprecated and is no longer used.", false)] public virtual bool CanCacheInspectorGUI() { return true; diff --git a/Editor/Mono/ScriptAttributeGUI/PropertyDrawer.cs b/Editor/Mono/ScriptAttributeGUI/PropertyDrawer.cs index 5dc8cc3d84..6740a3c378 100644 --- a/Editor/Mono/ScriptAttributeGUI/PropertyDrawer.cs +++ b/Editor/Mono/ScriptAttributeGUI/PropertyDrawer.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.Reflection; using UnityEngine; using UnityEngine.UIElements; @@ -59,14 +60,7 @@ public virtual float GetPropertyHeight(SerializedProperty property, GUIContent l return EditorGUI.kSingleLineHeight; } - internal bool CanCacheInspectorGUISafe(SerializedProperty property) - { - ScriptAttributeUtility.s_DrawerStack.Push(this); - bool canCache = CanCacheInspectorGUI(property); - ScriptAttributeUtility.s_DrawerStack.TryPop(out _); - return canCache; - } - + [Obsolete("CanCacheInspectorGUI has been deprecated and is no longer used.", false)] public virtual bool CanCacheInspectorGUI(SerializedProperty property) { return true; diff --git a/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs b/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs index 39e08689a1..dd56d8058e 100644 --- a/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs +++ b/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs @@ -373,45 +373,6 @@ public float GetHeight(SerializedProperty property, GUIContent label, bool inclu return height; } - public bool CanCacheInspectorGUI(SerializedProperty property) - { - if (m_DecoratorDrawers != null && - !isCurrentlyNested && - m_DecoratorDrawers.Any(decorator => !decorator.CanCacheInspectorGUI())) - return false; - - if (propertyDrawer != null) - { - // Retrieve drawer BEFORE increasing nesting. - PropertyDrawer drawer = propertyDrawer; - using (var nestingContext = IncrementNestingContext()) - { - return drawer.CanCacheInspectorGUISafe(property.Copy()); - } - } - - property = property.Copy(); - - bool childrenAreExpanded = property.isExpanded && EditorGUI.HasVisibleChildFields(property); - - // Loop through all child properties - if (childrenAreExpanded) - { - PropertyHandler handler = null; - SerializedProperty endProperty = property.GetEndProperty(); - while (property.NextVisible(childrenAreExpanded) && !SerializedProperty.EqualContents(property, endProperty)) - { - if (handler == null) - handler = ScriptAttributeUtility.GetHandler(property); - if (!handler.CanCacheInspectorGUI(property)) - return false; - childrenAreExpanded = false; - } - } - - return true; - } - public void AddMenuItems(SerializedProperty property, GenericMenu menu) { if (contextMenuItems != null) diff --git a/Editor/Mono/ScriptAttributeGUI/PropertyTrait.cs b/Editor/Mono/ScriptAttributeGUI/PropertyTrait.cs index 5fa7049c2c..aec6b76330 100644 --- a/Editor/Mono/ScriptAttributeGUI/PropertyTrait.cs +++ b/Editor/Mono/ScriptAttributeGUI/PropertyTrait.cs @@ -28,10 +28,5 @@ public override float GetPropertyHeight(SerializedProperty property, GUIContent { return EditorGUI.GetPropertyHeight(property, true); } - - public override bool CanCacheInspectorGUI(SerializedProperty property) - { - return EditorGUI.CanCacheInspectorGUI(property); - } } } diff --git a/Editor/Mono/ScriptableSingleton.cs b/Editor/Mono/ScriptableSingleton.cs index 609e3a4ec3..23aa91478a 100644 --- a/Editor/Mono/ScriptableSingleton.cs +++ b/Editor/Mono/ScriptableSingleton.cs @@ -111,7 +111,9 @@ private static void CreateAndLoad() { // Create T t = CreateInstance(); - t.hideFlags = HideFlags.HideAndDontSave; + + // Editing should be allowed, but the user is responsible for calling Save() (case uum-40767) + t.hideFlags = HideFlags.HideAndDontSave & ~HideFlags.NotEditable; } System.Diagnostics.Debug.Assert(s_Instance != null); diff --git a/Editor/Mono/UIElements/Bindings/BindingStyleHelpers.cs b/Editor/Mono/UIElements/Bindings/BindingStyleHelpers.cs index 531ed1d4ba..b04821390e 100644 --- a/Editor/Mono/UIElements/Bindings/BindingStyleHelpers.cs +++ b/Editor/Mono/UIElements/Bindings/BindingStyleHelpers.cs @@ -34,10 +34,10 @@ private static void UpdateElementRecursively(VisualElement element, SerializedPr { VisualElement elementToUpdate = element; - if (element is Foldout) + if (element is Foldout foldout) { // We only want to apply override styles onto the Foldout header, not the entire contents. - elementToUpdate = element.Q(className: Foldout.toggleUssClassName); + elementToUpdate = foldout.toggle; } else if (element.ClassListContains(BaseCompositeField.ussClassName) || element is BoundsField || element is BoundsIntField) @@ -53,7 +53,10 @@ private static void UpdateElementRecursively(VisualElement element, SerializedPr var compositeField = element; // The element we style in the main pass is going to be just the label. - elementToUpdate = element.Q(className: BaseField.labelUssClassName); + if (element is IPrefixLabel prefixLabel) + elementToUpdate = prefixLabel.labelElement; + else + elementToUpdate = element.Q(className: BaseField.labelUssClassName); // Go through the inputs and find any that match the names of the child PropertyFields. var propCopy = prop.Copy(); diff --git a/Editor/Mono/UIElements/Controls/EnumFlagsField.cs b/Editor/Mono/UIElements/Controls/EnumFlagsField.cs index e259a87a6d..7d167644ec 100644 --- a/Editor/Mono/UIElements/Controls/EnumFlagsField.cs +++ b/Editor/Mono/UIElements/Controls/EnumFlagsField.cs @@ -259,9 +259,15 @@ internal void PopulateDataFromType(Type enumType) internal override string GetNothingName() { - if (m_EnumData.flagValues != null && m_EnumData.flagValues.Length > 0 && m_EnumData.flagValues[0] == 0) + if (m_EnumData.flagValues is not { Length: > 0 }) + return base.GetNothingName(); + + for (var i = 0; i < m_EnumData.flagValues.Length; i++) { - return m_EnumData.displayNames[0]; + if (m_EnumData.flagValues[i] == 0) + { + return m_EnumData.displayNames[i]; + } } return base.GetNothingName(); @@ -269,10 +275,15 @@ internal override string GetNothingName() internal override string GetEverythingName() { - if (m_EnumData.flagValues != null && m_EnumData.flagValues.Length > 0 - && m_EnumData.flagValues[^1] == ~0) + if (m_EnumData.flagValues is not { Length: > 0 }) + return base.GetEverythingName(); + + for (var i = 0; i < m_EnumData.flagValues.Length; i++) { - return m_EnumData.displayNames[^1]; + if (m_EnumData.flagValues[i] == ~0) + { + return m_EnumData.displayNames[i]; + } } return base.GetEverythingName(); diff --git a/Editor/Mono/UIElements/Controls/MaskField.cs b/Editor/Mono/UIElements/Controls/MaskField.cs index 3edb9bcf7a..20198f4cfb 100644 --- a/Editor/Mono/UIElements/Controls/MaskField.cs +++ b/Editor/Mono/UIElements/Controls/MaskField.cs @@ -257,7 +257,7 @@ internal string GetDisplayedValue(int itemIndex) // Find the actual index of the selected choice... foreach (int itemMask in m_UserChoicesMasks) { - if ((itemMask & itemIndex) == itemIndex) + if (itemMask != ~0 && ((itemMask & itemIndex) == itemIndex)) { indexOfValue = m_UserChoicesMasks.IndexOf(itemMask); break; diff --git a/Editor/Mono/UIElements/Controls/ObjectField.cs b/Editor/Mono/UIElements/Controls/ObjectField.cs index 3dac07bbf8..80991fd969 100644 --- a/Editor/Mono/UIElements/Controls/ObjectField.cs +++ b/Editor/Mono/UIElements/Controls/ObjectField.cs @@ -136,7 +136,7 @@ internal virtual void UpdateDisplay() m_ObjectFieldDisplay.Update(); } - private class ObjectFieldDisplay : VisualElement + internal class ObjectFieldDisplay : VisualElement { private readonly ObjectField m_ObjectField; private readonly Image m_ObjectIcon; @@ -144,7 +144,7 @@ private class ObjectFieldDisplay : VisualElement static readonly string ussClassName = "unity-object-field-display"; static readonly string iconUssClassName = ussClassName + "__icon"; - static readonly string labelUssClassName = ussClassName + "__label"; + internal static readonly string labelUssClassName = ussClassName + "__label"; static readonly string acceptDropVariantUssClassName = ussClassName + "--accept-drop"; internal void ShowMixedValue(bool show) @@ -281,6 +281,7 @@ private void OnKeyboardEnter() private void OnKeyboardDelete() { + m_ObjectField.SetProperty(serializedPropertyKey, null); m_ObjectField.value = null; } diff --git a/Editor/Mono/UIElements/Controls/PropertyField.cs b/Editor/Mono/UIElements/Controls/PropertyField.cs index 83cfdf1f8f..bca86a8c84 100644 --- a/Editor/Mono/UIElements/Controls/PropertyField.cs +++ b/Editor/Mono/UIElements/Controls/PropertyField.cs @@ -19,6 +19,7 @@ public class PropertyField : VisualElement, IBindable private static readonly Regex s_MatchPPtrTypeName = new Regex(@"PPtr\<(\w+)\>"); internal static readonly string foldoutTitleBoundLabelProperty = "unity-foldout-bound-title"; internal static readonly string decoratorDrawersContainerClassName = "unity-decorator-drawers-container"; + internal static readonly string listViewBoundFieldProperty = "unity-list-view-property-field-bound"; static readonly string listViewNamePrefix = "unity-list-"; [UnityEngine.Internal.ExcludeFromDocs, Serializable] @@ -675,6 +676,13 @@ private TField ConfigureField(TField field, SerializedProperty p field.name = "unity-input-" + property.propertyPath; field.label = fieldLabel; + ConfigureFieldStyles(field); + + return field; + } + + internal static void ConfigureFieldStyles(TField field) where TField : BaseField + { field.labelElement.AddToClassList(labelUssClassName); field.visualInput.AddToClassList(inputUssClassName); field.AddToClassList(BaseField.alignedFieldUssClassName); @@ -686,9 +694,6 @@ private TField ConfigureField(TField field, SerializedProperty p { x.AddToClassList(BaseField.alignedFieldUssClassName); }); - - - return field; } VisualElement ConfigureListView(ListView listView, SerializedProperty property, Func factory) @@ -712,6 +717,7 @@ VisualElement ConfigureListView(ListView listView, SerializedProperty property, listView.bindingPath = property.propertyPath; listView.viewDataKey = listViewName; listView.name = listViewName; + listView.SetProperty(listViewBoundFieldProperty, this); // Make list view foldout react even when disabled, like EditorGUILayout.Foldout. var toggle = listView.Q(className: Foldout.toggleUssClassName); @@ -796,12 +802,17 @@ private VisualElement CreateOrUpdateFieldFromProperty(SerializedProperty propert { if (!objectTypes.Name.Equals(targetTypeName, StringComparison.OrdinalIgnoreCase)) continue; + + // We ignore C# types as they can can be confused with a built-in type with the same name, + // we can use the FieldInfo to find MonoScript types. (UUM-29499) + if (typeof(MonoBehaviour).IsAssignableFrom(objectTypes) || typeof(ScriptableObject).IsAssignableFrom(objectTypes)) + continue; + requiredType = objectTypes; break; } } - field.SetProperty(ObjectField.serializedPropertyKey, property); field.SetObjectTypeWithoutDisplayUpdate(requiredType); field.UpdateDisplay(); diff --git a/Editor/Mono/UIElements/EditorMenuExtensions.cs b/Editor/Mono/UIElements/EditorMenuExtensions.cs index 9c8aaa8b04..766df003d2 100644 --- a/Editor/Mono/UIElements/EditorMenuExtensions.cs +++ b/Editor/Mono/UIElements/EditorMenuExtensions.cs @@ -32,8 +32,7 @@ public class DropdownMenuDescriptor public static class EditorMenuExtensions { const float k_MaxMenuWidth = 512.0f; - internal const string k_AutoExpandDelayKeyName = "ContextMenuAutoExpandDelay"; - internal const float k_SubmenuExpandDelay = 0.4f; + internal const float k_SubmenuExpandDelay = 0.35f; internal const string k_SearchShortcutId = "Main Menu/Edit/Find"; internal static readonly Rect k_InvalidRect = new(0, 0, -1, -1); @@ -433,14 +432,17 @@ void ResetHighlighting(GenericDropdownMenu.MenuItem root) } var menu = search.userData as GenericDropdownMenu; + var newValue = Regex.Replace(e.newValue, "[^\\w ]+", ""); + search.SetValueWithoutNotify(newValue); + ResetHighlighting(menu.root); if (string.IsNullOrWhiteSpace(menu.current.name)) menu.NavigateBack(false); // Allow whitespace so we can search for spaces too - if (!string.IsNullOrEmpty(e.newValue)) - menu.NavigateTo(BuildSearchMenu(e.newValue, menu.current)); + if (!string.IsNullOrEmpty(newValue)) + menu.NavigateTo(BuildSearchMenu(newValue, menu.current)); // Workaround for getting window content stretching artifacts // when resizing to fit search results on Mac. @@ -644,8 +646,7 @@ bool ValidateExpansion() menu.m_OnBeforePerformAction?.Invoke(item.isSubmenu, menu.autoClose); item.PerformAction(); } - }, EditorPrefs.GetFloat(EditorMenuExtensions.k_AutoExpandDelayKeyName, - EditorMenuExtensions.k_SubmenuExpandDelay)); + }, k_SubmenuExpandDelay); if (!item.isCustomContent) s_CachedRect = GUIUtility.GUIToScreenRect(item.element.worldBound); diff --git a/Modules/Accessibility/AssemblyInfo.cs b/Modules/Accessibility/AssemblyInfo.cs index 3ed396d7e0..2a71def2ea 100644 --- a/Modules/Accessibility/AssemblyInfo.cs +++ b/Modules/Accessibility/AssemblyInfo.cs @@ -5,6 +5,7 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("UnityEngine.AccessibilityModule.EditModeTests")] +[assembly: InternalsVisibleTo("UnityEditor.AccessibilityModule")] // TOLERATED: modules or core packages evolving in lockstep with this module // Reducing this list means to improve the API design of this module. diff --git a/Modules/Accessibility/Bindings/AccessibilityManager.bindings.cs b/Modules/Accessibility/Bindings/AccessibilityManager.bindings.cs index 44ef3cead2..8ce97bfc9d 100644 --- a/Modules/Accessibility/Bindings/AccessibilityManager.bindings.cs +++ b/Modules/Accessibility/Bindings/AccessibilityManager.bindings.cs @@ -15,6 +15,7 @@ namespace UnityEngine.Accessibility /// platform. /// [NativeHeader("Modules/Accessibility/Native/AccessibilityManager.h")] + [VisibleToOtherModules("UnityEditor.AccessibilityModule")] internal static class AccessibilityManager { public struct NotificationContext @@ -83,7 +84,8 @@ public NotificationContext(ref AccessibilityNotificationContext nativeNotificati internal static extern void SetApplicationAccessibilityLanguage(SystemLanguage languageCode); [RequiredByNativeCode] - static void Internal_Initialize() + [VisibleToOtherModules("UnityEditor.AccessibilityModule")] + internal static void Internal_Initialize() { AssistiveSupport.Initialize(); } diff --git a/Modules/Accessibility/Managed/AssistiveSupport.cs b/Modules/Accessibility/Managed/AssistiveSupport.cs index 5a87f4519a..6be0e2030c 100644 --- a/Modules/Accessibility/Managed/AssistiveSupport.cs +++ b/Modules/Accessibility/Managed/AssistiveSupport.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using UnityEngine.Bindings; namespace UnityEngine.Accessibility { @@ -159,11 +160,25 @@ public static AccessibilityHierarchy activeHierarchy if (hierarchyService != null) { hierarchyService.hierarchy = value; + s_ActiveHierarchyChanged?.Invoke(value); } } get => GetService()?.hierarchy; } + private static event Action s_ActiveHierarchyChanged; + + /// + /// Event sent when the active hierarchy is changed. + /// + internal static event Action activeHierarchyChanged + { + [VisibleToOtherModules("UnityEditor.AccessibilityModule")] + add { s_ActiveHierarchyChanged += value; } + [VisibleToOtherModules("UnityEditor.AccessibilityModule")] + remove { s_ActiveHierarchyChanged -= value; } + } + internal static void OnHierarchyNodeFramesRefreshed(AccessibilityHierarchy hierarchy) { if (activeHierarchy == hierarchy) diff --git a/Modules/Accessibility/Managed/Hierarchy/AccessibilityHierarchy.cs b/Modules/Accessibility/Managed/Hierarchy/AccessibilityHierarchy.cs index 6c7ef1af9f..9c1dc21858 100644 --- a/Modules/Accessibility/Managed/Hierarchy/AccessibilityHierarchy.cs +++ b/Modules/Accessibility/Managed/Hierarchy/AccessibilityHierarchy.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using UnityEngine.Bindings; namespace UnityEngine.Accessibility { @@ -42,6 +43,19 @@ public class AccessibilityHierarchy /// readonly IDictionary m_Nodes; + event Action m_Changed; + + /// + /// Event sent when the hierarchy changes. + /// + internal event Action changed + { + [VisibleToOtherModules("UnityEditor.AccessibilityModule")] + add => m_Changed += value; + [VisibleToOtherModules("UnityEditor.AccessibilityModule")] + remove => m_Changed -= value; + } + /// /// Initializes and returns an instance of an AccessibilityHierarchy. /// @@ -54,6 +68,11 @@ public AccessibilityHierarchy() m_RootNodes = new List(); } + internal void NotifyHierarchyChanged() + { + m_Changed?.Invoke(this); + } + /// /// Resets the hierarchy to an empty state, removing all the nodes and removing focus. /// @@ -126,6 +145,7 @@ public AccessibilityNode InsertNode(int childIndex, string label, AccessibilityN SetParent(node, parent, null, parent == null ? m_RootNodes : parent.childList, childIndex); } + NotifyHierarchyChanged(); return node; } @@ -184,6 +204,7 @@ public bool MoveNode(AccessibilityNode node, AccessibilityNode newParent, int ne // Update node relationships (SetParent checks for loops) CheckForLoopsAndSetParent(node, newParent, newChildIndex); + NotifyHierarchyChanged(); return true; } @@ -228,6 +249,7 @@ void removeFromNodes(AccessibilityNode child) } node.Destroy(removeChildren); + NotifyHierarchyChanged(); } /// diff --git a/Modules/Accessibility/Managed/Hierarchy/AccessibilityNode.cs b/Modules/Accessibility/Managed/Hierarchy/AccessibilityNode.cs index 129d16cc4c..1777e13a39 100644 --- a/Modules/Accessibility/Managed/Hierarchy/AccessibilityNode.cs +++ b/Modules/Accessibility/Managed/Hierarchy/AccessibilityNode.cs @@ -5,6 +5,7 @@ using System; using System.Collections; using System.Collections.Generic; +using UnityEngine.Bindings; namespace UnityEngine.Accessibility { @@ -14,12 +15,19 @@ namespace UnityEngine.Accessibility /// public class AccessibilityNode { - private class ObservableList : IList, IReadOnlyList + private class ObservableList : IList, IReadOnlyList, IList { readonly List m_Items; public int Count => m_Items.Count; - public bool IsReadOnly => false; + public bool IsSynchronized => (m_Items as IList)?.IsSynchronized ?? false; + public object SyncRoot => (m_Items as IList)?.SyncRoot ?? false; + public bool IsReadOnly => (m_Items as IList)?.IsReadOnly ?? false; + object IList.this[int index] + { + get => m_Items[index]; + set => throw new NotImplementedException(); + } public ObservableList() { @@ -31,6 +39,8 @@ public ObservableList(IEnumerable enumerable) m_Items = new List(enumerable); } + public void CopyTo(Array array, int index) => (m_Items as IList)?.CopyTo(array, index); + public void Add(T item) { m_Items.Add(item); @@ -61,18 +71,45 @@ bool ICollection.Remove(T item) return removed; } + public void Remove(object value) + { + throw new NotImplementedException(); + } + public void RemoveAt(int index) { m_Items.RemoveAt(index); listChanged?.Invoke(); } + public bool IsFixedSize { get; } + + public int Add(object value) + { + throw new NotImplementedException(); + } + public void Clear() { m_Items.Clear(); listChanged?.Invoke(); } + public bool Contains(object value) + { + throw new NotImplementedException(); + } + + public int IndexOf(object value) + { + throw new NotImplementedException(); + } + + public void Insert(int index, object value) + { + throw new NotImplementedException(); + } + public T this[int index] { get => m_Items[index]; @@ -382,19 +419,21 @@ public Rect frame return m_Frame; } - set + set => SetFrame(value); + } + + void SetFrame(Rect frame) + { + if (m_Frame == frame) { - if (m_Frame == value) - { - return; - } + return; + } - m_Frame = value; + m_Frame = frame; - if (IsInActiveHierarchy()) - { - AccessibilityNodeManager.SetFrame(id, value); - } + if (IsInActiveHierarchy()) + { + AccessibilityNodeManager.SetFrame(id, frame); } } @@ -409,7 +448,7 @@ public Rect frame internal void CalculateFrame() { - m_Frame = frameGetter?.Invoke() ?? Rect.zero; + SetFrame(frameGetter?.Invoke() ?? Rect.zero); } /// diff --git a/Modules/Accessibility/Managed/Services/ServiceManager.cs b/Modules/Accessibility/Managed/Services/ServiceManager.cs index fad1830c71..ecfd932909 100644 --- a/Modules/Accessibility/Managed/Services/ServiceManager.cs +++ b/Modules/Accessibility/Managed/Services/ServiceManager.cs @@ -18,7 +18,10 @@ public ServiceManager() var isScreenReaderEnabled = AccessibilityManager.IsScreenReaderEnabled(); screenReaderStatusChanged?.Invoke(isScreenReaderEnabled); - UpdateServices(isScreenReaderEnabled); + // In the Editor context, we always initialize services in order to allow users to build, + // activate and debug (using the Accessibility Hierarchy Viewer for instance) their Accessibility hierarchies in play mode + // even if Screen Reader is off and the Accessibility backend is not supported. + UpdateServices(true); } public T GetService() where T : IService diff --git a/Modules/AccessibilityEditor/Managed/AccessibilityEditor.cs b/Modules/AccessibilityEditor/Managed/AccessibilityEditor.cs new file mode 100644 index 0000000000..6896e498df --- /dev/null +++ b/Modules/AccessibilityEditor/Managed/AccessibilityEditor.cs @@ -0,0 +1,17 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using UnityEngine.Accessibility; + +namespace UnityEditor.Accessibility +{ + internal static class AccessibilityEditor + { + [InitializeOnLoadMethod] + public static void Initialize() + { + AccessibilityManager.Internal_Initialize(); + } + } +} diff --git a/Modules/AccessibilityEditor/Managed/AccessibilityHierarchyTreeView.cs b/Modules/AccessibilityEditor/Managed/AccessibilityHierarchyTreeView.cs new file mode 100644 index 0000000000..29b6678623 --- /dev/null +++ b/Modules/AccessibilityEditor/Managed/AccessibilityHierarchyTreeView.cs @@ -0,0 +1,246 @@ +// 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 Unity.Properties; +using UnityEngine; +using UnityEngine.Accessibility; +using UnityEngine.UIElements; +using TreeView = UnityEngine.UIElements.TreeView; + +namespace UnityEditor.Accessibility +{ + class AccessibilityHierarchyTreeViewItem : VisualElement + { + public static readonly string s_UssClassName = AccessibilityHierarchyTreeView.s_UssClassName + "__item"; + public static readonly string s_RootUssClassName = s_UssClassName + "--root"; + public static readonly string s_InactiveUssClassName = s_UssClassName + "--inactive"; + public static readonly string s_IdTextUssClassName = s_UssClassName + "__id"; + public static readonly string s_LabelTextUssClassName = s_UssClassName + "__label"; + public static readonly string s_RoleTextUssClassName = s_UssClassName + "__role"; + + [Serializable] + public new class UxmlSerializedData : VisualElement.UxmlSerializedData + { +#pragma warning disable 649 + [SerializeField] private bool isRoot; + [SerializeField] private int id; + [SerializeField] private string label; + [SerializeField] private AccessibilityRole role; + [SerializeField] private bool isActive; +#pragma warning restore 649 + public override object CreateInstance() => new AccessibilityHierarchyTreeViewItem(); + public override void Deserialize(object obj) + { + base.Deserialize(obj); + + var item = (AccessibilityHierarchyTreeViewItem) obj; + item.isRoot = isRoot; + item.isActive = isActive; + item.id = id; + item.label = label; + item.role = role; + } + } + + private readonly SearchableLabel m_IdText; + private readonly SearchableLabel m_LabelText; + private readonly SearchableLabel m_RoleText; + private int m_Id; + private bool m_IsRoot; + private bool m_IsActive; + private string m_Label; + private AccessibilityRole m_Role; + + [CreateProperty] + public bool isRoot + { + get => m_IsRoot; + set + { + if (m_IsRoot == value) + return; + m_IsRoot = value; + UpdateTexts(); + } + } + + [CreateProperty] + public int id + { + get => m_Id; + set + { + if (m_Id == value) + return; + m_Id = value; + UpdateTexts(); + } + } + + [CreateProperty] + public string label + { + get => m_Label; + set + { + if (m_Label == value) + return; + m_Label = value; + UpdateTexts(); + } + } + + [CreateProperty] + public AccessibilityRole role + { + get => m_Role; + set + { + if (m_Role == value) + return; + m_Role = value; + UpdateTexts(); + } + } + + [CreateProperty] + public bool isActive + { + get => m_IsActive; + set + { + if (m_IsActive == value) + return; + m_IsActive = value; + UpdateTexts(); + } + } + + public AccessibilityHierarchyTreeViewItem() + { + AddToClassList(s_UssClassName); + m_IdText = new SearchableLabel(); + m_IdText.AddToClassList(s_IdTextUssClassName); + m_LabelText = new SearchableLabel(); + m_LabelText.AddToClassList(s_LabelTextUssClassName); + m_RoleText = new SearchableLabel(); + m_RoleText.AddToClassList(s_RoleTextUssClassName); + Add(m_IdText); + Add(m_LabelText); + Add(m_RoleText); + UpdateTexts(); + } + + public static string GetDisplayRoleText(AccessibilityRole role) + { + return role.ToString() + (role == AccessibilityRole.None ? L10n.Tr(" (Role)") : null); + } + + public static string GetDisplayLabelText(bool active, string label) + { + return "\"" + ReplaceNewLines(label) + "\"" + (!active ? L10n.Tr(" (Inactive)") : null); + } + + private void UpdateTexts() + { + m_IdText.text = isRoot ? null : id.ToString(); + m_LabelText.text = isRoot ? label : GetDisplayLabelText(isActive, label); + m_RoleText.text = isRoot ? null : GetDisplayRoleText(role); + EnableInClassList(s_InactiveUssClassName, !isActive); + } + + /// + /// Helper method used to replace all '\n' characters (new line) by "\n" string in the specified multiline text in order to display it in a single-line label. + /// + /// The source text to transform + /// Returns the source text with all '\n' characters replaced by "\n". + private static string ReplaceNewLines(string text) + { + if (string.IsNullOrEmpty(text)) + return text; + return text.Replace("\n", "\\n"); + } + } + + /// + /// Tree view that displays an accessibility hierarchy. + /// + class AccessibilityHierarchyTreeView : VisualElement + { + [Serializable] + public new class UxmlSerializedData : VisualElement.UxmlSerializedData + { + public override object CreateInstance() => new AccessibilityHierarchyTreeView(); + } + + internal static readonly string s_UssClassName = "hierarchy-tree-view"; + + private TreeView m_TreeView; + private TreeViewSearchBar m_SearchBar; + public TreeView treeView => m_TreeView; + + /// + /// The selected node. + /// + public AccessibilityViewModelNode selectedNode => (m_TreeView.selectedItem != null) ? (AccessibilityViewModelNode) m_TreeView.selectedItem : default; + + /// + /// Constructor. + /// + public AccessibilityHierarchyTreeView() + { + var asset = EditorGUIUtility.Load("Accessibility/HierarchyTreeView.uxml") as VisualTreeAsset; + + asset.CloneTree(this); + + AddToClassList(s_UssClassName); + + m_TreeView = this.Q("treeView"); + m_SearchBar = this.Q("searchBar"); + m_SearchBar.treeView = m_TreeView; + // Ensure that all items (including recycled items) have a non null data source by default to avoid binding errors. + m_TreeView.dataSource ??= default(AccessibilityViewModelNode); + m_TreeView.bindItem = (element, index) => + { + var id = m_TreeView.GetIdForIndex(index); + var node = m_TreeView.GetItemDataForId(id); + + // Cannot do element.dataSource = null as there seems to be an issue with Binding not updating the inherited data source properly. + element.Q().dataSource = node; + + if (node.isRoot) + { + element.parent.parent.AddToClassList(AccessibilityHierarchyTreeViewItem.s_RootUssClassName); + element.Query().ForEach((label) => label.ClearHighlight()); + } + else + { + element.parent.parent.RemoveFromClassList(AccessibilityHierarchyTreeViewItem.s_RootUssClassName); + + schedule.Execute(() => + { + // Apply the current search query to the labels. + element.Query().ForEach((label) => label.HighlightText(m_SearchBar.currentQuery)); + }).ExecuteLater(200); // Delay to give time for the label.text to update first + } + }; + } + + /// + /// Sets the root nodes + /// + /// + /// + public void SetRootItems(List> rootItems) + { + m_TreeView.ClearSelection(); + m_TreeView.SetRootItems(rootItems); + m_TreeView.Rebuild(); + m_TreeView.ExpandAll(); + m_SearchBar.PerformSearch(); + } + } +} diff --git a/Modules/AccessibilityEditor/Managed/AccessibilityHierarchyViewModel.cs b/Modules/AccessibilityEditor/Managed/AccessibilityHierarchyViewModel.cs new file mode 100644 index 0000000000..e2b9eda999 --- /dev/null +++ b/Modules/AccessibilityEditor/Managed/AccessibilityHierarchyViewModel.cs @@ -0,0 +1,266 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections; +using Unity.Properties; +using UnityEngine; +using UnityEngine.Accessibility; + +namespace UnityEditor.Accessibility +{ + /// + /// A view model of an accessibility hierarchy + /// + internal class AccessibilityHierarchyViewModel + { + private AccessibilityHierarchy m_Hierarchy; + + /// + /// The underlying accessibility hierarchy. + /// + public AccessibilityHierarchy accessibilityHierarchy + { + get => m_Hierarchy; + set + { + if (m_Hierarchy == value) + return; + + if (m_Hierarchy != null) + { + m_Hierarchy.changed -= OnHierarchyChanged; + } + + m_Hierarchy = value; + + if (m_Hierarchy != null) + { + m_Hierarchy.changed += OnHierarchyChanged; + } + + Reset(); + } + } + + /// + /// The root node. + /// + public AccessibilityViewModelNode root { get; } + + /// + /// Sent when the model is reset, causing a rebuild of the attached views. + /// + public event Action modelReset; + + /// + /// Constructor. + /// + public AccessibilityHierarchyViewModel() + { + root = CreateRootNode(); + } + + private void OnHierarchyChanged(AccessibilityHierarchy hierarchy) + { + Reset(); + } + + /// + /// Resets the model. + /// + public void Reset() + { + modelReset?.Invoke(); + } + + /// + /// Creates a model node from an accessibility node + /// + /// The accessibility node + /// The created model node + public AccessibilityViewModelNode CreateNode(AccessibilityNode node) + { + return new AccessibilityViewModelNode(node, this); + } + + /// + /// Tries to get the model node with the specified node id. + /// + /// The node id + /// The node found + /// Returns true if a node is found with the specified id and false otherwise. + public bool TryGetNode(int nodeId, out AccessibilityViewModelNode modelNode) + { + modelNode = default; + + if (m_Hierarchy == null) + return false; + + if (m_Hierarchy.TryGetNode(nodeId, out var node)) + { + modelNode = CreateNode(node); + return true; + } + + return false; + } + + private AccessibilityViewModelNode CreateRootNode() + { + return new AccessibilityViewModelNode(null, this, true); + } + } + + /// + /// A view model node of an accessibility node. + /// + internal struct AccessibilityViewModelNode : IEquatable + { + const int k_RootNodeId = int.MaxValue; + + private AccessibilityHierarchyViewModel m_Model; + private AccessibilityNode m_Node; + internal bool m_Root; + + /// + /// Indicates whether the model node is the root node of the model. + /// + [CreateProperty] + public bool isRoot => m_Root; + + /// + /// Indicates whether the model node is null. + /// + public bool isNull => m_Node == null && !isRoot; + + /// + /// The id of the node. + /// + [CreateProperty] + public int id => isRoot ? k_RootNodeId : (m_Node?.id ?? 0); + + /// + /// The label of the node. + /// + [CreateProperty] + public string label + { + get + { + if (isRoot) + { + if (m_Model.accessibilityHierarchy != null) + return m_Model.accessibilityHierarchy == AssistiveSupport.activeHierarchy ? L10n.Tr("Active Hierarchy") : L10n.Tr("Inactive Hierarchy"); + return L10n.Tr("No Active Hierarchy"); + } + return m_Node?.label; + } + } + + /// + /// The value of the node. + /// + [CreateProperty] + public string value => m_Node?.value; + + /// + /// Indicates whether the node is active. + /// + [CreateProperty] + public bool isActive => m_Node?.isActive ?? false; + + /// + /// The frame of the node. + /// + [CreateProperty] + public Rect frame => m_Node?.frame ?? Rect.zero; + + /// + /// The role of the node. + /// + [CreateProperty] + public AccessibilityRole role => m_Node?.role ?? default; + + // Indicates whether the node allows direct interaction. + [CreateProperty] + public bool allowsDirectInteraction => m_Node?.allowsDirectInteraction ?? default; + + /// + /// The state of the node. + /// + [CreateProperty] + public AccessibilityState state => m_Node?.state ?? default; + + /// + /// The extra data of the node. + /// + [CreateProperty] + public IList extraData => m_Node?.extraData as IList; + + /// + /// The number of child model nodes. + /// + [CreateProperty] + public int childNodeCount => isRoot ? (m_Model.accessibilityHierarchy?.rootNodes.Count ?? 0) : m_Node?.children.Count ?? 0; + + /// + /// The accessibility node. + /// + public AccessibilityNode accessibilityNode => m_Node; + + /// + /// The child model node at the specified index. + /// + /// The index + /// The node at the index. + public AccessibilityViewModelNode GetChildNode(int index) + { + return m_Model?.CreateNode(isRoot ? m_Model.accessibilityHierarchy.rootNodes[index] : m_Node.children[index]) ?? default; + } + + /// + /// Constructs a model node from an accessibility node, a view model and a value that indicates whether is the root node of the model. + /// + /// The accessibility node. + /// The parent view model. + /// Indicates whether the node is the root of the view model. + internal AccessibilityViewModelNode(AccessibilityNode accessibilityNode, AccessibilityHierarchyViewModel model, bool isRootNode = false) + { + m_Model = model; + m_Node = accessibilityNode; + m_Root = isRootNode; + } + + public override string ToString() + { + return id.ToString(); + } + + public bool Equals(AccessibilityViewModelNode other) + { + return Equals(m_Root, other.m_Root) && Equals(m_Model, other.m_Model) && Equals(m_Node, other.m_Node); + } + + public override bool Equals(object obj) + { + return obj is AccessibilityViewModelNode other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(m_Model, m_Node, m_Root); + } + + public static bool operator ==(AccessibilityViewModelNode obj1, AccessibilityViewModelNode obj2) + { + return obj1.Equals(obj2); + } + + public static bool operator !=(AccessibilityViewModelNode obj1, AccessibilityViewModelNode obj2) + { + return !(obj1 == obj2); + } + } +} diff --git a/Modules/AccessibilityEditor/Managed/AccessibilityHierarchyViewer.cs b/Modules/AccessibilityEditor/Managed/AccessibilityHierarchyViewer.cs new file mode 100644 index 0000000000..604e923ea1 --- /dev/null +++ b/Modules/AccessibilityEditor/Managed/AccessibilityHierarchyViewer.cs @@ -0,0 +1,160 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.Collections.Generic; +using UnityEngine.Accessibility; +using UnityEngine.UIElements; + +namespace UnityEditor.Accessibility +{ + /// + /// View that displays an accessibility hierarchy and properties of its nodes. + /// + internal class AccessibilityHierarchyViewer : VisualElement + { + private static readonly string s_UssClassName = "hierarchy-viewer"; + private static readonly string s_NoSelectionUssClassName = s_UssClassName + "--no-selection"; + + /// Controls + + // Hierarchy view + private AccessibilityHierarchyTreeView m_HierarchyView; + + // Inspector + private readonly VisualElement m_InspectorContainer; + private readonly MultiColumnListView m_InspectorField_ExtraData; + + private AccessibilityHierarchyViewModel m_HierarchyModel; + + private IVisualElementScheduledItem m_RebuildScheduledItem; + + /// + /// The accessibility hierarchy view model + /// + public AccessibilityHierarchyViewModel hierarchyModel + { + get => m_HierarchyModel; + set + { + if (m_HierarchyModel == value) + return; + + if (m_HierarchyModel != null) + { + m_HierarchyModel.modelReset -= RebuildHierarchy; + } + + m_HierarchyModel = value; + + if (m_HierarchyModel != null) + { + m_HierarchyModel.modelReset += RebuildHierarchy; + } + + RebuildHierarchy(); + } + } + + /// + /// Constructor. + /// + public AccessibilityHierarchyViewer() + { + var asset = EditorGUIUtility.Load("Accessibility/HierarchyViewer.uxml") as VisualTreeAsset; + var themeUssFilePath = $"Accessibility/HierarchyViewer{(EditorGUIUtility.isProSkin ? "Dark" : "Light")}.uss"; + var themeUss = EditorGUIUtility.Load(themeUssFilePath) as StyleSheet; + + asset.CloneTree(this); + styleSheets.Add(themeUss); + + AddToClassList(s_UssClassName); + + m_HierarchyView = this.Q("hierarchyView"); + + // Inspector + + m_InspectorContainer = this.Q("inspectorContainer"); + + this.Q("isActiveField").SetEnabled(false); + this.Q("allowsDirectInteractionField").SetEnabled(false); + // Make all input fields of the frame field readonly + this.Q("frameField").Query().ForEach(frameSubField => frameSubField.isReadOnly = true); + + m_InspectorField_ExtraData = this.Q("extraDataView"); + m_InspectorField_ExtraData.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight; + m_InspectorField_ExtraData.columns[0].bindCell = (element, i) => + { + var extraData = (AccessibilityExtraData) m_InspectorField_ExtraData.itemsSource[i]; + (element as Label).text = extraData.label; + }; + m_InspectorField_ExtraData.columns[1].bindCell = (element, i) => + { + var extraData = (AccessibilityExtraData) m_InspectorField_ExtraData.itemsSource[i]; + (element as Label).text = extraData.description; + }; + + m_HierarchyView.treeView.selectionChanged += (_) => OnSelectionChanged(); + OnSelectionChanged(); + } + + private void RebuildHierarchy() + { + if (m_HierarchyView == null) + return; + + if (m_RebuildScheduledItem == null) + { + m_RebuildScheduledItem = schedule.Execute(HandleRebuildHierarchy); + } + else + { + m_RebuildScheduledItem.Resume(); + } + } + + private void HandleRebuildHierarchy() + { + m_HierarchyView.SetRootItems(hierarchyModel != null ? new List>() { RebuildHierarchy(hierarchyModel.root)} : null); + } + + private static TreeViewItemData RebuildHierarchy(AccessibilityViewModelNode modelNode) + { + var childList = new List>(modelNode.childNodeCount); + + for (var i = 0; i < modelNode.childNodeCount; ++i) + { + childList.Add(RebuildHierarchy(modelNode.GetChildNode(i))); + } + return new TreeViewItemData(modelNode.id, modelNode, childList); + } + + private void RefreshInspector(AccessibilityViewModelNode node) + { + m_InspectorContainer.dataSource = node; + schedule.Execute(() => m_InspectorField_ExtraData.RefreshItems()).ExecuteLater(250); + } + + void OnSelectionChanged() + { + bool hasSelection = false; + + if (m_HierarchyView.treeView.selectedItem != null) + { + var node = (AccessibilityViewModelNode) m_HierarchyView.treeView.selectedItem; + + if (!node.isRoot) + { + RefreshInspector(node); + hasSelection = true; + } + } + else + { + RefreshInspector(default); + } + + EnableInClassList(s_NoSelectionUssClassName, !hasSelection); + } + } +} diff --git a/Modules/AccessibilityEditor/Managed/AccessibilityHierarchyViewerWindow.cs b/Modules/AccessibilityEditor/Managed/AccessibilityHierarchyViewerWindow.cs new file mode 100644 index 0000000000..2c06d1ad27 --- /dev/null +++ b/Modules/AccessibilityEditor/Managed/AccessibilityHierarchyViewerWindow.cs @@ -0,0 +1,60 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using UnityEngine; +using UnityEngine.Accessibility; +using UnityEngine.UIElements; + +namespace UnityEditor.Accessibility +{ + /// + /// A window that displays the active accessibility hierarchy. + /// + internal class AccessibilityHierarchyViewerWindow : EditorWindow + { + private static string s_WindowTitle = "Accessibility Hierarchy Viewer"; + + private AccessibilityHierarchyViewModel m_ActiveHierarchyModel; + + [MenuItem("Window/Accessibility/Hierarchy Viewer", false, 3006)] + public static void ShowWindow() + { + GetWindow(); + } + + private void OnEnable() + { + minSize = new Vector2(200, 200); + titleContent = new GUIContent(L10n.Tr(s_WindowTitle)); + AssistiveSupport.activeHierarchyChanged += OnActiveHierarchyChanged; + } + + private void OnDisable() + { + AssistiveSupport.activeHierarchyChanged -= OnActiveHierarchyChanged; + } + + public void CreateGUI() + { + var root = rootVisualElement; + var viewer = new AccessibilityHierarchyViewer(); + + m_ActiveHierarchyModel = new AccessibilityHierarchyViewModel(); + viewer.hierarchyModel = m_ActiveHierarchyModel; + + root.Add(viewer); + viewer.StretchToParentSize(); + + OnActiveHierarchyChanged(AssistiveSupport.activeHierarchy); + } + + private void OnActiveHierarchyChanged(AccessibilityHierarchy hierarchy) + { + if (m_ActiveHierarchyModel == null) + return; + + m_ActiveHierarchyModel.accessibilityHierarchy = hierarchy; + } + } +} diff --git a/Modules/AccessibilityEditor/Managed/SearchableLabel.cs b/Modules/AccessibilityEditor/Managed/SearchableLabel.cs new file mode 100644 index 0000000000..421527417d --- /dev/null +++ b/Modules/AccessibilityEditor/Managed/SearchableLabel.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 System.Collections.Generic; +using Unity.Properties; +using UnityEditor; +using UnityEditor.Accessibility; +using UnityEngine; +using UnityEngine.Accessibility; +using UnityEngine.UIElements; +using TreeView = UnityEngine.UIElements.TreeView; + +namespace UnityEditor.Accessibility +{ + class SearchableLabel : VisualElement + { + [Serializable] + public new class UxmlSerializedData : VisualElement.UxmlSerializedData + { +#pragma warning disable 649 + [SerializeField] private string text; +#pragma warning restore 649 + + public override object CreateInstance() => new SearchableLabel(); + + public override void Deserialize(object obj) + { + base.Deserialize(obj); + + var e = (SearchableLabel) obj; + e.text = text; + } + } + + private static readonly string s_UssClassName = "searchable-label"; + private static readonly string s_LabelTextUssClassName = s_UssClassName + "__text"; + private static readonly string s_HighlightUssClassName = s_UssClassName + "__highlight"; + + private readonly Label m_Label; + private readonly VisualElement m_Highlight; + + [CreateProperty] + public string text + { + get => m_Label.text; + set => m_Label.text = value; + } + + public SearchableLabel() + { + AddToClassList(s_UssClassName); + m_Label = new Label(); + m_Label.AddToClassList(s_LabelTextUssClassName); + m_Highlight = new VisualElement(); + m_Highlight.AddToClassList(s_HighlightUssClassName); + Add(m_Label); + Add(m_Highlight); + ClearHighlight(); + } + + public void ClearHighlight() + { + m_Highlight.style.display = DisplayStyle.None; + } + + public void HighlightText(string query) + { + ClearHighlight(); + + if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(query)) + return; + + var indexOf = text.IndexOf(query, StringComparison.OrdinalIgnoreCase); + + if (indexOf < 0) + return; + + var startPos = m_Label.MeasureTextSize(text[..indexOf], 0, MeasureMode.Undefined, 0, MeasureMode.Undefined); + var endPos = m_Label.MeasureTextSize(text[..(indexOf + query.Length)], 0, MeasureMode.Undefined, 0, MeasureMode.Undefined); + + m_Highlight.style.width = endPos.x - startPos.x; + m_Highlight.style.left = startPos.x; + m_Highlight.style.display = DisplayStyle.Flex; + } + } +} diff --git a/Modules/AccessibilityEditor/Managed/TreeViewSearchBar.cs b/Modules/AccessibilityEditor/Managed/TreeViewSearchBar.cs new file mode 100644 index 0000000000..cc9ad2eab6 --- /dev/null +++ b/Modules/AccessibilityEditor/Managed/TreeViewSearchBar.cs @@ -0,0 +1,239 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UIElements; + +namespace UnityEditor.Accessibility +{ + /// + /// Search field used to search items by id, label or role in a accessibility hierarchy tree view. + /// + internal class TreeViewSearchBar : VisualElement + { + private static readonly string s_UssClassName = "hierarchy-tree-view__search-bar"; + private static readonly string s_HiddenElementUssClassName = "hierarchy-tree-view__search-bar__element-hidden"; + private static readonly string s_SearchLabelUssClassName = s_UssClassName + "__label"; + private static readonly string s_SearchLabelHelpUssClassName = s_UssClassName + "__label-help"; + private static readonly string s_SearchFieldUssClassName = s_UssClassName + "__field"; + private static readonly string s_SearchButtonUssClassName = s_UssClassName + "__button"; + private static readonly string s_SearchPrevButtonUssClassName = s_SearchButtonUssClassName + "-prev"; + private static readonly string s_SearchNextButtonUssClassName = s_UssClassName + "-next"; + + class SearchResultItem + { + public int itemId; + } + + [Serializable] + public new class UxmlSerializedData : VisualElement.UxmlSerializedData + { + public override object CreateInstance() => new TreeViewSearchBar(); + } + + private List m_FoundItems; + private int m_SelectedIndex; + private string m_CurrentQuery; + + private TextField m_SearchTextField; + + private TreeView m_TreeView; + private Label m_CountLabel; + private Label m_FieldHelpLabel; + + private List m_SearchResultsHightlights; + + public string currentQuery => m_CurrentQuery; + + /// + /// The tree view on which search is performed. + /// + public TreeView treeView + { + get => m_TreeView; + set => m_TreeView = value; + } + + /// + /// Constructor. + /// + public TreeViewSearchBar() + { + m_FoundItems = new List(); + m_SearchResultsHightlights = new List(); + + AddToClassList(s_UssClassName); + + m_FieldHelpLabel = new Label(L10n.Tr("Search by id, label, role")); + m_FieldHelpLabel.pickingMode = PickingMode.Ignore; + m_FieldHelpLabel.AddToClassList(s_SearchLabelUssClassName); + m_FieldHelpLabel.AddToClassList(s_SearchLabelHelpUssClassName); + Add(m_FieldHelpLabel); + + m_SearchTextField = new TextField(); + m_SearchTextField.AddToClassList(s_SearchFieldUssClassName); + m_SearchTextField.RegisterValueChangedCallback((e) => PerformSearch()); + m_SearchTextField.RegisterCallback((e) => + { + var targetField = m_SearchTextField; + if (e.keyCode == KeyCode.F3 || e.keyCode == KeyCode.Return || e.keyCode == KeyCode.KeypadEnter) + { + if (e.modifiers.HasFlag(EventModifiers.Shift)) + SelectPrev(); + else + SelectNext(); + } + else if (e.keyCode == KeyCode.Escape) + { + targetField.value = string.Empty; + ClearSearchResults(); + treeView.Focus(); + } + }, TrickleDown.TrickleDown); + Add(m_SearchTextField); + + m_CountLabel = new Label(); + m_CountLabel.AddToClassList(s_SearchLabelUssClassName); + m_CountLabel.AddToClassList(s_HiddenElementUssClassName); + Add(m_CountLabel); + + var prevButton = new Button(SelectPrev) {text = "<"}; + prevButton.AddToClassList(s_SearchButtonUssClassName); + prevButton.AddToClassList(s_SearchPrevButtonUssClassName); + Add(prevButton); + + var nextButton = new Button(SelectNext) {text = ">"}; + nextButton.AddToClassList(s_SearchButtonUssClassName); + nextButton.AddToClassList(s_SearchNextButtonUssClassName); + Add(nextButton); + } + + private IEnumerable> GetAllItems() + { + if (m_TreeView?.viewController == null) + { + yield break; + } + + var treeViewController = m_TreeView?.viewController as DefaultTreeViewController; + + foreach (var itemId in m_TreeView.viewController.GetAllItemIds()) + { + yield return treeViewController.GetTreeViewItemDataForId(itemId); + } + } + + /// + /// Clears the text of the search field. + /// + public void ClearSearch() + { + m_SearchTextField.value = string.Empty; + } + + private void SelectNext() + { + if (m_FoundItems.Count == 0) + return; + + m_SelectedIndex = (m_SelectedIndex + 1) % m_FoundItems.Count; + SelectElement(m_FoundItems[m_SelectedIndex].itemId, m_CurrentQuery); + m_CountLabel.text = $"{m_SelectedIndex + 1} of {m_FoundItems.Count}"; + } + + private void SelectPrev() + { + if (m_FoundItems.Count == 0) + return; + + var count = m_FoundItems.Count; + m_SelectedIndex--; + m_SelectedIndex = (m_SelectedIndex % count + count) % count; + + SelectElement(m_FoundItems[m_SelectedIndex].itemId, m_CurrentQuery); + m_CountLabel.text = $"{m_SelectedIndex + 1} of {m_FoundItems.Count}"; + } + + /// + /// Performs a search. + /// + public void PerformSearch() + { + m_FoundItems.Clear(); + m_SelectedIndex = 0; + + m_CountLabel.text = string.Empty; + m_CountLabel.AddToClassList(s_HiddenElementUssClassName); + + ClearSearchResults(); + + m_FieldHelpLabel.AddToClassList(s_HiddenElementUssClassName); + + m_CurrentQuery = m_SearchTextField.text; + + m_TreeView.RefreshItems(); + + if (string.IsNullOrEmpty(m_CurrentQuery)) + { + m_FieldHelpLabel.RemoveFromClassList(s_HiddenElementUssClassName); + return; + } + + var items = GetAllItems(); + if (items == null) + return; + + foreach (var treeItem in items) + { + var element = treeItem.data; + + if (element.isRoot) + continue; + + var idText = element.id.ToString(); + var labelText = AccessibilityHierarchyTreeViewItem.GetDisplayLabelText(element.isActive, element.label); + var roleText = AccessibilityHierarchyTreeViewItem.GetDisplayRoleText(element.role); + + if ((idText.IndexOf(m_CurrentQuery, StringComparison.OrdinalIgnoreCase) >= 0) + || (labelText?.IndexOf(m_CurrentQuery, StringComparison.OrdinalIgnoreCase) >= 0) + || (roleText.IndexOf(m_CurrentQuery, StringComparison.OrdinalIgnoreCase) >= 0)) + { + m_FoundItems.Add(new SearchResultItem() {itemId = treeItem.id}); + } + } + + if (m_FoundItems.Count == 0) + return; + + m_CountLabel.RemoveFromClassList(s_HiddenElementUssClassName); + m_CountLabel.text = $"{m_SelectedIndex + 1} of {m_FoundItems.Count}"; + + var firstItem = m_FoundItems[0]; + SelectElement(firstItem.itemId, m_CurrentQuery); + } + + private void ClearSearchResults() + { + foreach (var hl in m_SearchResultsHightlights) + hl.RemoveFromHierarchy(); + + m_SearchResultsHightlights.Clear(); + } + + private void SelectElement(int itemId, string query) + { + ClearSearchResults(); + + var node = m_TreeView.GetItemDataForId(itemId); + + if (node.isNull || node.isRoot) + return; + + m_TreeView.SetSelectionById(itemId); + m_TreeView.ScrollToItemById(itemId); + } + } +} diff --git a/Modules/AndroidJNI/AndroidApplicationExitInfo.cs b/Modules/AndroidJNI/AndroidApplicationExitInfo.cs new file mode 100644 index 0000000000..ba881c4057 --- /dev/null +++ b/Modules/AndroidJNI/AndroidApplicationExitInfo.cs @@ -0,0 +1,315 @@ +// 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 Unity.Collections; + +namespace UnityEngine.Android +{ + public enum ProcessImportance + { + /// + /// This process is running the foreground UI; that is, it is the thing currently at the top of the screen that the user is interacting with. + /// developer.android.com + /// + Foreground = 100, + + /// + /// This process is running a foreground service, for example to perform music playback even while the user is not immediately in the app. This generally indicates that the process is doing something the user actively cares about. + /// developer.android.com + /// + ForeGroundService = 125, + + /// + /// This process is running something that is actively visible to the user, though not in the immediate foreground. This may be running a window that is behind the current foreground (so paused and with its state saved, not interacting with the user, but visible to them to some degree); it may also be running other services under the system's control that it inconsiders important. + /// developer.android.com + /// + Visible = 200, + + /// + /// This process is not something the user is directly aware of, but is otherwise perceptible to them to some degree. + /// developer.android.com + /// + Perceptible = 230, + + /// + /// This process is running the foreground UI, but the device is asleep so it is not visible to the user. Though the system will try hard to keep its process from being killed, in all other ways we consider it a kind of cached process, with the limitations that go along with that state: network access, running background services, etc. + /// developer.android.com + /// + TopSleeping = 325, + + /// + /// This process is running an application that can not save its state, and thus can't be killed while in the background. This will be used with apps that have R.attr.cantSaveState set on their application tag. + /// developer.android.com + /// + CantSaveState = 350, + + /// + /// This process contains services that should remain running. These are background services apps have started, not something the user is aware of, so they may be killed by the system relatively freely (though it is generally desired that they stay running as long as they want to). + /// developer.android.com + /// + Service = 300, + + /// + /// This process process contains cached code that is expendable, not actively running any app components we care about. + /// developer.android.com + /// + Cached = 400, + + /// + /// This process does not exist. + /// developer.android.com + /// + Gone = 1000 + } + + public enum ExitReason + { + /// + /// Application process died due to unknown reason. + /// developer.android.com + /// + Unknown = 0, + + /// + /// Application process exit normally by itself, for example, via System.exit(int); getStatus() will specify the exit code. + /// developer.android.com + /// + ExitSelf = 1, + + /// + /// Application process died due to the result of an OS signal; for example, OsConstants.SIGKILL; getStatus() will specify the signal number. + /// developer.android.com + /// + Signaled = 2, + + /// + /// Application process was killed by the system low memory killer, meaning the system was under memory pressure at the time of kill. + /// developer.android.com + /// + LowMemory = 3, + + /// + /// Application process died because of an unhandled exception in Java code. + /// developer.android.com + /// + Crash = 4, + + /// + /// Application process died because of a native code crash. + /// developer.android.com + /// + CrashNative = 5, + + /// + /// Application process was killed due to being unresponsive (ANR). + /// developer.android.com + /// + ANR = 6, + + /// + /// Application process was killed because of initialization failure, for example, it took too long to attach to the system during the start, or there was an error during initialization. + /// developer.android.com + /// + InititalizationFailure = 7, + + /// + /// Application process was killed due to a runtime permission change. + /// developer.android.com + /// + PermissionChange = 8, + + /// + /// Application process was killed by the system due to excessive resource usage. + /// developer.android.com + /// + ExcessiveResourceUsage = 9, + + /// + /// Application process was killed because of the user request, for example, user clicked the "Force stop" button of the application in the Settings, or removed the application away from Recents. + /// developer.android.com + /// + UserRequested = 10, + + /// + /// Application process was killed, because the user it is running as on devices with mutlple users, was stopped. + /// developer.android.com + /// + UserStopped = 11, + + /// + /// Application process was killed because its dependency was going away, for example, a stable content provider connection's client will be killed if the provider is killed. + /// developer.android.com + /// + DependencyDied = 12, + + /// + /// Application process was killed by the system for various other reasons which are not by problems in apps and not actionable by apps, for example, the system just finished updates; getDescription() will specify the cause given by the system. + /// developer.android.com + /// + Other = 13, + + /// + /// Application process was killed by App Freezer, for example, because it receives sync binder transactions while being frozen. + /// developer.android.com + /// + Freezer = 14, + + /// + /// Application process was killed because the app was disabled, or any of its component states have changed without PackageManager.DONT_KILL_APP + /// developer.android.com + /// + PackageStateChange = 15, + + /// + /// Application process was killed because it was updated. + /// developer.android.com + /// + PackageUpdated = 16 + } + + public interface IApplicationExitInfo + { + /// + /// The human readable description of the process's death, given by the system; could be null. + /// developer.android.com + /// + /// string + string description { get; } + + /// + /// Describe the kinds of special objects contained in this Parcelable instance's marshaled representation. + /// developer.android.com + /// + /// a bitmask indicating the set of special object types marshaled by this Parcelable object instance. Value is either 0 or CONTENTS_FILE_DESCRIPTOR + int describeContents { get; } + + /// + /// Return the defining kernel user identifier, maybe different from getRealUid() and getPackageUid(), if an external service has the android:useAppZygote set to true and was bound with the flag Context.BIND_EXTERNAL_SERVICE - in this case, this field here will be the kernel user identifier of the external service provider. + /// developer.android.com + /// + /// int + int definingUid { get; } + + /// + /// The importance of the process that it used to have before the death. + /// developer.android.com + /// + /// ProcessImportance + ProcessImportance importance { get; } + + /// + /// Similar to getRealUid(), it's the kernel user identifier that is assigned at the package installation time. + /// developer.android.com + /// + /// int + int packageUid { get; } + + /// + /// The process id of the process that died. + /// developer.android.com + /// + /// int + int pid { get; } + + /// + /// The actual process name it was running with. + /// developer.android.com + /// + /// String + String processName { get; } + + /// + /// Return the state data set by calling ApplicationExitInfoProvider.setProcessStateSummary(byte[]) from the process before its death. + /// developer.android.com + /// + /// byte[] containing the process-customized data. This value may be null. + sbyte[] processStateSummary { get; } + + /// + /// Last proportional set size of the memory that the process had used in kB. + /// developer.android.com + /// + /// long + long pss { get; } + + /// + /// The kernel user identifier of the process, most of the time the system uses this to do access control checks. + /// developer.android.com + /// + /// int + int realUid { get; } + + /// + /// The reason code of the process's death. + /// developer.android.com + /// + /// ExitReason + ExitReason reason { get; } + + /// + /// Last resident set size of the memory that the process had used in kB. + /// developer.android.com + /// + /// long + long rss { get; } + + /// + /// The exit status argument of exit() if the application calls it, or the signal number if the application is signaled. + /// developer.android.com + /// + /// int + int status { get; } + + /// + /// The timestamp of the process's death, in milliseconds since the epoch, as returned by System.currentTimeMillis(). Value is a non-negative timestamp measured as the number of milliseconds since 1970-01-01T00:00:00Z. + /// developer.android.com + /// + /// long Value is a non-negative timestamp measured as the number of milliseconds since 1970-01-01T00:00:00Z. + long timestamp { get; } + + /// + /// Return the traces that was taken by the system prior to the death of the process; typically it'll be available when the reason is REASON_ANR, though if the process gets an ANR but recovers, and dies for another reason later, this trace will be included in the record of ApplicationExitInfo still. + /// developer.android.com + /// + /// byte[] + byte[] trace { get; } + + /// + /// Return the trace data in string format + /// + /// string + public String traceAsString { get; } + } + + + public static class ApplicationExitInfoProvider + { + /// + /// Return a list of ApplicationExitInfo records containing the reasons for the most recent app deaths. + /// developer.android.com + /// + /// Optional, a null value means match all packages belonging to the caller's UID. If this package belongs to another UID, you must hold android.Manifest.permission.DUMP in order to retrieve it. + /// A process ID that used to belong to this package but died later; a value of 0 means to ignore this parameter and return all matching records. Value is 0 or greater + /// The maximum number of results to be returned; a value of 0 means to ignore this parameter and return all matching records Value is 0 or greater. + /// IApplicationExitInfo[] a list of ApplicationExitInfo records matching the criteria, sorted in the order from most recent to least recent. This value cannot be null. + public static IApplicationExitInfo[] GetHistoricalProcessExitInfo(string packageName = null, int pid = 0, int maxNum = 0) + { + IApplicationExitInfo[] result = null; + if (result == null) + result = new IApplicationExitInfo[0]; + + return result; + } + + /// + ///Set custom state data for this process.It will be included in the record of ApplicationExitInfo on the death of the current calling process; the new process of the app can retrieve this state data by calling processStateSummary on the IApplicationExitInfo record returned by ApplicationExitInfoProvider.getHistoricalProcessExitReasons(String, int, int). + ///developer.android.com + /// + /// The state data.To be advised, DO NOT include sensitive information/data (PII, SPII, or other sensitive user data) here.Maximum length is 128 bytes. + public static void SetProcessStateSummary(SByte[] buffer) + { + } + } +} diff --git a/Modules/AssetDatabase/Editor/ScriptBindings/AssetDatabase.bindings.cs b/Modules/AssetDatabase/Editor/ScriptBindings/AssetDatabase.bindings.cs index 3095d79711..07d7a90650 100644 --- a/Modules/AssetDatabase/Editor/ScriptBindings/AssetDatabase.bindings.cs +++ b/Modules/AssetDatabase/Editor/ScriptBindings/AssetDatabase.bindings.cs @@ -367,7 +367,10 @@ public unsafe static void InstanceIDsToGUIDs(NativeArray instanceIDs, Nativ extern public static System.Type GetMainAssetTypeAtPath(string assetPath); + extern public static System.Type GetMainAssetTypeFromGUID(GUID guid); + extern public static System.Type GetTypeFromPathAndFileID(string assetPath, long localIdentifierInFile); + extern public static bool IsMainAssetAtPathLoaded(string assetPath); [PreventExecutionInState(AssetDatabasePreventExecution.kGatheringDependenciesFromSourceFile, PreventExecutionSeverity.PreventExecution_ManagedException, "Assets may not be loaded while dependencies are being gathered, as these assets may not have been imported yet.")] diff --git a/Modules/IMGUI/TextSelectingUtilities.cs b/Modules/IMGUI/TextSelectingUtilities.cs index 5bab1e609d..6ebfee6816 100644 --- a/Modules/IMGUI/TextSelectingUtilities.cs +++ b/Modules/IMGUI/TextSelectingUtilities.cs @@ -911,7 +911,7 @@ enum Direction CharacterType ClassifyChar(int index) { - char c = m_TextElementInfos[index].character; + char c = (char)m_TextElementInfos[index].character; if (c == kNewLineChar) return CharacterType.NewLine; if (char.IsWhiteSpace(c)) diff --git a/Modules/PackageManagerUI/Editor/Services/Common/PackageManagerPrefs.cs b/Modules/PackageManagerUI/Editor/Services/Common/PackageManagerPrefs.cs index 2aa61e3a76..bdea21c566 100644 --- a/Modules/PackageManagerUI/Editor/Services/Common/PackageManagerPrefs.cs +++ b/Modules/PackageManagerUI/Editor/Services/Common/PackageManagerPrefs.cs @@ -14,7 +14,8 @@ internal class PackageManagerPrefs private const string k_SkipRemoveConfirmationPrefs = "PackageManager.SkipRemoveConfirmation"; private const string k_SkipMultiSelectRemoveConfirmationPrefs = "PackageManager.SkipMultiSelectRemoveConfirmation"; private const string k_SkipDisableConfirmationPrefs = "PackageManager.SkipDisableConfirmation"; - private const string k_SplitterFlexGrowPrefs = "PackageManager.SplitterFlexGrowPrefs"; + private const string k_LeftContainerWidthPrefs = "PackageManager.LeftContainerWidthPrefs"; + private const string k_SidebarWidthPrefs = "PackageManager.SidebarWidthPrefs"; private const string k_LastActivePageIdPrefsPrefix = "PackageManager.PageId_"; public const int k_DefaultPageSize = 25; @@ -40,14 +41,25 @@ public virtual bool skipDisableConfirmation set => EditorPrefs.SetBool(k_SkipDisableConfirmationPrefs, value); } - public virtual float splitterFlexGrow + public virtual float leftContainerWidth { - get => EditorPrefs.GetFloat(k_SplitterFlexGrowPrefs, 0.3f); + get => EditorPrefs.GetFloat(k_LeftContainerWidthPrefs, 300); set { if (float.IsNaN(value) || float.IsInfinity(value)) return; - EditorPrefs.SetFloat(k_SplitterFlexGrowPrefs, value); + EditorPrefs.SetFloat(k_LeftContainerWidthPrefs, value); + } + } + + public virtual float sidebarWidth + { + get => EditorPrefs.GetFloat(k_SidebarWidthPrefs, 225); + set + { + if (float.IsNaN(value) || float.IsInfinity(value)) + return; + EditorPrefs.SetFloat(k_SidebarWidthPrefs, value); } } diff --git a/Modules/PackageManagerUI/Editor/Services/Packages/Actions/ImportActionBase.cs b/Modules/PackageManagerUI/Editor/Services/Packages/Actions/ImportActionBase.cs index 5fc1d8472c..800b9705a9 100644 --- a/Modules/PackageManagerUI/Editor/Services/Packages/Actions/ImportActionBase.cs +++ b/Modules/PackageManagerUI/Editor/Services/Packages/Actions/ImportActionBase.cs @@ -11,12 +11,17 @@ internal abstract class ImportActionBase : PackageAction protected readonly PackageOperationDispatcher m_OperationDispatcher; protected readonly AssetStoreDownloadManager m_AssetStoreDownloadManager; protected readonly ApplicationProxy m_Application; + protected readonly UnityConnectProxy m_UnityConnect; - protected ImportActionBase(PackageOperationDispatcher operationDispatcher, AssetStoreDownloadManager assetStoreDownloadManager, ApplicationProxy application) + protected ImportActionBase(PackageOperationDispatcher operationDispatcher, + AssetStoreDownloadManager assetStoreDownloadManager, + ApplicationProxy application, + UnityConnectProxy unityConnect) { m_OperationDispatcher = operationDispatcher; m_AssetStoreDownloadManager = assetStoreDownloadManager; m_Application = application; + m_UnityConnect = unityConnect; } protected abstract string analyticEventName { get; } @@ -30,10 +35,11 @@ protected override bool TriggerActionImplementation(IPackageVersion version) public override bool IsVisible(IPackageVersion version) { - return version?.HasTag(PackageTag.LegacyFormat) == true - && version.package.versions.importAvailable != null - && version.package.progress == PackageProgress.None - && m_AssetStoreDownloadManager.GetDownloadOperation(version.package.product?.id)?.isProgressVisible != true; + return m_UnityConnect.isUserLoggedIn + && version.HasTag(PackageTag.LegacyFormat) + && version.package.versions.importAvailable != null + && version.package.progress == PackageProgress.None + && m_AssetStoreDownloadManager.GetDownloadOperation(version.package.product?.id)?.isProgressVisible != true; } public override bool IsInProgress(IPackageVersion version) => false; diff --git a/Modules/PackageManagerUI/Editor/Services/Packages/Actions/ImportNewAction.cs b/Modules/PackageManagerUI/Editor/Services/Packages/Actions/ImportNewAction.cs index 024e596de5..508d971f7b 100644 --- a/Modules/PackageManagerUI/Editor/Services/Packages/Actions/ImportNewAction.cs +++ b/Modules/PackageManagerUI/Editor/Services/Packages/Actions/ImportNewAction.cs @@ -6,8 +6,8 @@ namespace UnityEditor.PackageManager.UI.Internal; internal class ImportNewAction : ImportActionBase { - public ImportNewAction(PackageOperationDispatcher operationDispatcher, AssetStoreDownloadManager assetStoreDownloadManager, ApplicationProxy application) - : base(operationDispatcher, assetStoreDownloadManager, application) + public ImportNewAction(PackageOperationDispatcher operationDispatcher, AssetStoreDownloadManager assetStoreDownloadManager, ApplicationProxy application, UnityConnectProxy unityConnect) + : base(operationDispatcher, assetStoreDownloadManager, application, unityConnect) { } diff --git a/Modules/PackageManagerUI/Editor/Services/Packages/Actions/ImportUpdateAction.cs b/Modules/PackageManagerUI/Editor/Services/Packages/Actions/ImportUpdateAction.cs index 6ecab135ee..bd958cd4df 100644 --- a/Modules/PackageManagerUI/Editor/Services/Packages/Actions/ImportUpdateAction.cs +++ b/Modules/PackageManagerUI/Editor/Services/Packages/Actions/ImportUpdateAction.cs @@ -6,8 +6,8 @@ namespace UnityEditor.PackageManager.UI.Internal; internal class ImportUpdateAction : ImportActionBase { - public ImportUpdateAction(PackageOperationDispatcher operationDispatcher, AssetStoreDownloadManager assetStoreDownloadManager, ApplicationProxy application) - : base(operationDispatcher, assetStoreDownloadManager, application) + public ImportUpdateAction(PackageOperationDispatcher operationDispatcher, AssetStoreDownloadManager assetStoreDownloadManager, ApplicationProxy application, UnityConnectProxy unityConnect) + : base(operationDispatcher, assetStoreDownloadManager, application, unityConnect) { } diff --git a/Modules/PackageManagerUI/Editor/Services/Packages/Actions/ReImportAction.cs b/Modules/PackageManagerUI/Editor/Services/Packages/Actions/ReImportAction.cs index 4f5f67f574..c999095b21 100644 --- a/Modules/PackageManagerUI/Editor/Services/Packages/Actions/ReImportAction.cs +++ b/Modules/PackageManagerUI/Editor/Services/Packages/Actions/ReImportAction.cs @@ -6,8 +6,8 @@ namespace UnityEditor.PackageManager.UI.Internal; internal class ReImportAction : ImportActionBase { - public ReImportAction(PackageOperationDispatcher operationDispatcher, AssetStoreDownloadManager assetStoreDownloadManager, ApplicationProxy application) - : base(operationDispatcher, assetStoreDownloadManager, application) + public ReImportAction(PackageOperationDispatcher operationDispatcher, AssetStoreDownloadManager assetStoreDownloadManager, ApplicationProxy application, UnityConnectProxy unityConnect) + : base(operationDispatcher, assetStoreDownloadManager, application, unityConnect) { } diff --git a/Modules/PackageManagerUI/Editor/UI/PackageManagerWindow.cs b/Modules/PackageManagerUI/Editor/UI/PackageManagerWindow.cs index b16cb1c397..59ec240d9d 100644 --- a/Modules/PackageManagerUI/Editor/UI/PackageManagerWindow.cs +++ b/Modules/PackageManagerUI/Editor/UI/PackageManagerWindow.cs @@ -78,6 +78,7 @@ void OnEnable() titleContent = GetLocalizedTitleContent(); + minSize = new Vector2(1050, 250); BuildGUI(); Events.registeredPackages += OnRegisteredPackages; @@ -302,7 +303,7 @@ private static void OnRegisteredPackages(PackageRegistrationEventArgs args) internal static void SelectPackageAndPageStatic(string packageToSelect = null, string pageId = null, bool refresh = false, string searchText = "") { instance = GetWindow(); - instance.minSize = new Vector2(800, 250); + instance.minSize = new Vector2(1050, 250); instance.m_Root.SelectPackageAndPage(packageToSelect, pageId, refresh, searchText); instance.Show(); } diff --git a/Modules/PackageManagerUI/Editor/UI/PackageManagerWindowRoot.cs b/Modules/PackageManagerUI/Editor/UI/PackageManagerWindowRoot.cs index dca471f34e..3fb5486bfc 100644 --- a/Modules/PackageManagerUI/Editor/UI/PackageManagerWindowRoot.cs +++ b/Modules/PackageManagerUI/Editor/UI/PackageManagerWindowRoot.cs @@ -93,8 +93,8 @@ public void OnEnable() RegisterEventsToAdaptFocus(); - leftColumnContainer.style.flexGrow = m_PackageManagerPrefs.splitterFlexGrow; - rightColumnContainer.style.flexGrow = 1 - m_PackageManagerPrefs.splitterFlexGrow; + globalSplitter.fixedPaneInitialDimension = m_PackageManagerPrefs.sidebarWidth; + mainContainerSplitter.fixedPaneInitialDimension = m_PackageManagerPrefs.leftContainerWidth; m_PageRefreshHandler.onRefreshOperationFinish += OnRefreshOperationFinish; m_UnityConnectProxy.onUserLoginStateChange += OnUserLoginStateChange; @@ -234,7 +234,8 @@ public void OnDisable() EditorApplication.focusChanged -= OnFocusChanged; m_Selection.onSelectionChanged -= RefreshSelectedInInspectorClass; - m_PackageManagerPrefs.splitterFlexGrow = leftColumnContainer.resolvedStyle.flexGrow; + m_PackageManagerPrefs.sidebarWidth = sidebar.layout.width; + m_PackageManagerPrefs.leftContainerWidth = leftColumnContainer.layout.width; } private void OnUserLoginStateChange(bool userInfoReady, bool loggedIn) @@ -469,9 +470,10 @@ public PackageSelectionArgs activeSelection public PackageManagerToolbar packageManagerToolbar => cache.Get("topMenuToolbar"); public PackageStatusBar packageStatusbar => cache.Get("packageStatusBar"); private VisualElement leftColumnContainer => cache.Get("leftColumnContainer"); - private VisualElement rightColumnContainer => cache.Get("rightColumnContainer"); private VisualElement rightContainer => cache.Get("rightSideContainer"); private Sidebar sidebar => cache.Get("sidebar"); + private TwoPaneSplitView globalSplitter => cache.Get("globalSplitter"); + private TwoPaneSplitView mainContainerSplitter => cache.Get("mainContainer"); } internal class PackageAndPageSelectionArgs diff --git a/Modules/PackageManagerUI/Editor/UI/PackageManifestImporterEditor.cs b/Modules/PackageManagerUI/Editor/UI/PackageManifestImporterEditor.cs index fefdf22441..908935346e 100644 --- a/Modules/PackageManagerUI/Editor/UI/PackageManifestImporterEditor.cs +++ b/Modules/PackageManagerUI/Editor/UI/PackageManifestImporterEditor.cs @@ -341,18 +341,35 @@ private void DoPackageInformationLayout() } } + private void DoPackageDescriptionLabel() + { + var descriptionStyle = EditorStyles.textArea; + var description = m_Description.stringValue ?? ""; + var descriptionRect = GUILayoutUtility.GetRect(EditorGUIUtility.TempContent(description), descriptionStyle, GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true)); + EditorGUI.SelectableLabel(descriptionRect, description, descriptionStyle); + } + private void DoPackageDescriptionLayout() { + var previousEnabled = GUI.enabled; + GUI.enabled = true; + using (new EditorGUILayout.VerticalScope(GUI.skin.box, GUILayout.ExpandWidth(true))) { - using (var scrollView = new EditorGUILayout.VerticalScrollViewScope(descriptionScrollViewPosition, - GUILayout.MinHeight(kMinHeightDescriptionScrollView))) + using (var scrollView = new EditorGUILayout.VerticalScrollViewScope(descriptionScrollViewPosition, GUILayout.MinHeight(kMinHeightDescriptionScrollView))) { descriptionScrollViewPosition = scrollView.scrollPosition; - m_Description.stringValue = EditorGUILayout.TextArea(m_Description.stringValue ?? "" , + + // We want to have text we can edit instead of selectable label when it's in Edit mode + if (previousEnabled == true) + m_Description.stringValue = EditorGUILayout.TextArea(m_Description.stringValue ?? "", GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true)); + else + DoPackageDescriptionLabel(); } } + + GUI.enabled = previousEnabled; } private void PerformValidation() diff --git a/Modules/PackageManagerUI/Editor/UI/ToolBar/LegacyFormatDropdownButton.cs b/Modules/PackageManagerUI/Editor/UI/ToolBar/LegacyFormatDropdownButton.cs index c193a972b6..105cac7c19 100644 --- a/Modules/PackageManagerUI/Editor/UI/ToolBar/LegacyFormatDropdownButton.cs +++ b/Modules/PackageManagerUI/Editor/UI/ToolBar/LegacyFormatDropdownButton.cs @@ -43,9 +43,9 @@ public LegacyFormatDropdownButton(PackageOperationDispatcher operationDispatcher { new DownloadNewAction(operationDispatcher, assetStoreDownloadManager, unityConnect, application), new DownloadUpdateAction(operationDispatcher, assetStoreDownloadManager, unityConnect, application), - new ImportNewAction(operationDispatcher, assetStoreDownloadManager, application), - new ImportUpdateAction(operationDispatcher, assetStoreDownloadManager, application), - new ReImportAction(operationDispatcher, assetStoreDownloadManager, application), + new ImportNewAction(operationDispatcher, assetStoreDownloadManager, application, unityConnect), + new ImportUpdateAction(operationDispatcher, assetStoreDownloadManager, application, unityConnect), + new ReImportAction(operationDispatcher, assetStoreDownloadManager, application, unityConnect), new RemoveImportedAction(operationDispatcher, application), new ReDownloadAction(operationDispatcher, assetStoreDownloadManager, unityConnect, application), }; diff --git a/Modules/QuickSearch/Editor/Indexing/AssetIndexer.cs b/Modules/QuickSearch/Editor/Indexing/AssetIndexer.cs index e404399df9..e757fe2fd5 100644 --- a/Modules/QuickSearch/Editor/Indexing/AssetIndexer.cs +++ b/Modules/QuickSearch/Editor/Indexing/AssetIndexer.cs @@ -75,7 +75,7 @@ public string GetPartialPath(string path) public void IndexTypes(Type objType, int documentIndex, bool isPrefabDocument, string name = "t", bool exact = false) { - while (objType != null && objType != typeof(Object)) + while (objType != null && objType != typeof(Object) && objType != typeof(MonoBehaviour) && objType != typeof(Behaviour)) { if (isPrefabDocument && objType == typeof(GameObject)) IndexProperty(documentIndex, name, "prefab", saveKeyword: true, exact: true); @@ -204,7 +204,7 @@ public override void IndexDocument(string path, bool checkIfDocumentExists) var c = gocs[componentIndex]; if (!c || (c.hideFlags & (HideFlags.DontSave | HideFlags.HideInInspector)) != 0) continue; - IndexType(c.GetType(), documentIndex); + IndexTypes(c.GetType(), documentIndex, false); } } } diff --git a/Modules/QuickSearch/Editor/QueryBuilder/QuerySelector.cs b/Modules/QuickSearch/Editor/QueryBuilder/QuerySelector.cs index 0a12969377..906d2e17b7 100644 --- a/Modules/QuickSearch/Editor/QueryBuilder/QuerySelector.cs +++ b/Modules/QuickSearch/Editor/QueryBuilder/QuerySelector.cs @@ -247,11 +247,12 @@ protected override AdvancedDropdownItem BuildRoot() prefix = path.Substring(0, ls); } + var displayName = formatNames ? ObjectNames.NicifyVariableName(name) : name; var newItem = new AdvancedDropdownItem(path) { - displayName = formatNames ? ObjectNames.NicifyVariableName(name) : name, + displayName = displayName, icon = p.icon ?? Icons.quicksearch, - tooltip = p.help, + tooltip = string.IsNullOrEmpty(p.help) ? $"Search {displayName}" : p.help, userData = p }; diff --git a/Modules/QuickSearch/Editor/SearchQuery/SearchQueryAsset.cs b/Modules/QuickSearch/Editor/SearchQuery/SearchQueryAsset.cs index 5568e9fa97..36ed5fe308 100644 --- a/Modules/QuickSearch/Editor/SearchQuery/SearchQueryAsset.cs +++ b/Modules/QuickSearch/Editor/SearchQuery/SearchQueryAsset.cs @@ -19,6 +19,7 @@ namespace UnityEditor.Search [HelpURL("search-usage")] class SearchQueryAsset : ScriptableObject, ISearchQuery { + static bool s_ListeningToAssetChanges = false; static List s_SavedQueries; private long m_CreationTime; @@ -137,13 +138,28 @@ public string tooltip } } + internal static void ContentRefreshed(string[] updated, string[] removed, string[] moved) + { + if (updated.Any(p => p.EndsWith(".asset")) || removed.Any(p => p.EndsWith(".asset"))) + { + ResetSearchQueryItems(); + Dispatcher.Emit(SearchEvent.ProjectQueryListChanged, new SearchEventPayload((ISearchElement)null)); + } + } + internal static IEnumerable savedQueries { get { - if (s_SavedQueries == null || s_SavedQueries.Any(qs => !qs)) - s_SavedQueries = EnumerateAll().Where(asset => asset != null).ToList(); - + if (s_SavedQueries == null) + { + if (!s_ListeningToAssetChanges) + { + SearchMonitor.contentRefreshed += ContentRefreshed; + s_ListeningToAssetChanges = true; + } + s_SavedQueries = EnumerateAll().Where(asset => asset).ToList(); + } return s_SavedQueries.Where(s => s); } } diff --git a/Modules/QuickSearch/Editor/SearchSettings.cs b/Modules/QuickSearch/Editor/SearchSettings.cs index 7cc42ee725..11f655eb44 100644 --- a/Modules/QuickSearch/Editor/SearchSettings.cs +++ b/Modules/QuickSearch/Editor/SearchSettings.cs @@ -93,7 +93,7 @@ class ObjectSelectorsSettings : BaseDictionarySettings public bool active; public int priority; - public override int Count => 3; + public override int Count => 2; public override ICollection Keys => new string[] { nameof(active), nameof(priority) }; public override ICollection Values => new object[] { active, priority }; @@ -129,38 +129,39 @@ public override IDictionaryEnumerator GetEnumerator() } } - public static class SearchSettings + class SearchSettingsStorage { - internal static readonly string projectLocalSettingsFolder = Utils.CleanPath(new DirectoryInfo("UserSettings").FullName); - internal static readonly string projectLocalSettingsPath = $"{projectLocalSettingsFolder}/Search.settings"; + public string settingsFolder { get; set; } + public string settingsPath { get; set; } - const string k_ItemIconSizePrefKey = "Search.ItemIconSize"; - internal const string settingsPreferencesKey = "Preferences/Search"; + public string itemIconSizePrefKey { get; set; }= "Search.ItemIconSize"; + public string favoritesQueryPrefKey { get; set; } = "SearchQuery.Favorites"; + public string ignoredPropertiesCustomDependency { get; set; } = "SearchIndexIgnoredProperties"; // Per project settings - internal static bool trackSelection { get; set; } - internal static bool fetchPreview { get; set; } - internal static SearchFlags defaultFlags { get; set; } - internal static bool keepOpen { get; set; } - internal static string queryFolder { get; set; } - internal static bool onBoardingDoNotAskAgain { get; set; } - internal static bool showPackageIndexes { get; set; } - internal static bool showStatusBar { get; set; } - internal static bool hideTabs { get; set; } - internal static SearchQuerySortOrder savedSearchesSortOrder { get; set; } - internal static bool showSavedSearchPanel { get; set; } - internal static Dictionary scopes { get; private set; } - internal static Dictionary providers { get; private set; } - internal static Dictionary objectSelectors { get; private set; } - internal static bool queryBuilder { get; set; } - internal static string ignoredProperties { get; set; } - internal static string helperWidgetCurrentArea { get; set; } - internal static bool refreshSearchWindowsInPlayMode { get; set; } - internal static int minIndexVariations { get; set; } - internal static bool findProviderIndexHelper { get; set; } - internal static int[] expandedQueries { get; set; } - - internal static bool wantsMore + public bool trackSelection { get; set; } + public bool fetchPreview { get; set; } + public SearchFlags defaultFlags { get; set; } + public bool keepOpen { get; set; } + public string queryFolder { get; set; } + public bool onBoardingDoNotAskAgain { get; set; } + public bool showPackageIndexes { get; set; } + public bool showStatusBar { get; set; } + public bool hideTabs { get; set; } + public SearchQuerySortOrder savedSearchesSortOrder { get; set; } + public bool showSavedSearchPanel { get; set; } + public Dictionary scopes { get; private set; } = new(); + public Dictionary providers { get; private set; } = new(); + public Dictionary objectSelectors { get; private set; } = new(); + public bool queryBuilder { get; set; } + public string ignoredProperties { get; set; } + public string helperWidgetCurrentArea { get; set; } + public bool refreshSearchWindowsInPlayMode { get; set; } + public int minIndexVariations { get; set; } + public bool findProviderIndexHelper { get; set; } + public int[] expandedQueries { get; set; } = Array.Empty(); + + public bool wantsMore { get => defaultFlags.HasAny(SearchFlags.WantsMore); set @@ -173,61 +174,55 @@ internal static bool wantsMore } // User editor pref - internal static float itemIconSize { get; set; } = (float)DisplayMode.List; + public float itemIconSize { get; set; } = (float)DisplayMode.List; - internal const int k_RecentSearchMaxCount = 20; - internal static List recentSearches = new List(k_RecentSearchMaxCount); + int m_RecentSearchMaxCount = 20; + public int recentSearchMaxCount + { + get => m_RecentSearchMaxCount; + set + { + m_RecentSearchMaxCount = value; + ApplyRecentSearchCapacity(); + } + } + public List recentSearches = new(); - static string s_DisabledIndexersString; - static HashSet s_DisabledIndexers; - internal static HashSet disabledIndexers + string m_DisabledIndexersString; + HashSet m_DisabledIndexers; + public HashSet disabledIndexers { get { - if (s_DisabledIndexers == null) + if (m_DisabledIndexers == null) { - var entries = s_DisabledIndexersString ?? string.Empty; - s_DisabledIndexers = new HashSet(entries.Split(new string[] { ";;;" }, StringSplitOptions.RemoveEmptyEntries)); + var entries = m_DisabledIndexersString ?? string.Empty; + m_DisabledIndexers = new HashSet(entries.Split(new string[] { ";;;" }, StringSplitOptions.RemoveEmptyEntries)); } - return s_DisabledIndexers; + return m_DisabledIndexers; } } - public static HashSet searchItemFavorites = new HashSet(); - internal static HashSet searchQueryFavorites = new HashSet(); - - internal static event Action providerActivationChanged; + public HashSet searchItemFavorites = new(); + public HashSet searchQueryFavorites = new(); - internal static int debounceMs + public int debounceMs { get { return UnityEditor.SearchUtils.debounceThresholdMs; } set { UnityEditor.SearchUtils.debounceThresholdMs = value; } } - static SearchSettings() + public void Load() { - expandedQueries = new int[0]; - Load(); - } - - private static void Load() - { - if (Application.HasARGV("cleanTestPrefs") || !File.Exists(projectLocalSettingsPath)) - { - if (!Directory.Exists("UserSettings/")) - Directory.CreateDirectory("UserSettings/"); - Utils.WriteTextFileToDisk(projectLocalSettingsPath, "{}"); - } - IDictionary settings = null; try { - settings = (IDictionary)SJSON.Load(projectLocalSettingsPath); + settings = (IDictionary)SJSON.Load(settingsPath); } catch (Exception ex) { - Debug.LogError($"We weren't able to parse search user settings at {projectLocalSettingsPath}. We will fallback to default settings.\n{ex}"); + Debug.LogError($"We weren't able to parse search user settings at {settingsPath}. We will fallback to default settings.\n{ex}"); } trackSelection = ReadSetting(settings, nameof(trackSelection), true); @@ -244,12 +239,12 @@ private static void Load() queryBuilder = ReadSetting(settings, nameof(queryBuilder), false); ignoredProperties = ReadSetting(settings, nameof(ignoredProperties), "id;name;classname;imagecontentshash"); helperWidgetCurrentArea = ReadSetting(settings, nameof(helperWidgetCurrentArea), "all"); - s_DisabledIndexersString = ReadSetting(settings, nameof(disabledIndexers), ""); + m_DisabledIndexersString = ReadSetting(settings, nameof(disabledIndexers), ""); refreshSearchWindowsInPlayMode = ReadSetting(settings, nameof(refreshSearchWindowsInPlayMode), false); minIndexVariations = ReadSetting(settings, nameof(minIndexVariations), 2); findProviderIndexHelper = ReadSetting(settings, nameof(findProviderIndexHelper), true); - itemIconSize = EditorPrefs.GetFloat(k_ItemIconSizePrefKey, itemIconSize); + itemIconSize = EditorPrefs.GetFloat(itemIconSizePrefKey, itemIconSize); var searches = ReadSetting(settings, nameof(recentSearches)); @@ -269,9 +264,11 @@ private static void Load() objectSelectors = ReadPickerSettings(settings, nameof(objectSelectors)); LoadFavorites(); + + RegisterIgnoredPropertiesCustomDependencies(); } - internal static void Save() + public void Save() { var settings = new Dictionary { @@ -292,7 +289,7 @@ internal static void Save() [nameof(savedSearchesSortOrder)] = (int)savedSearchesSortOrder, [nameof(showSavedSearchPanel)] = showSavedSearchPanel, [nameof(hideTabs)] = hideTabs, - [nameof(expandedQueries)] = expandedQueries, + [nameof(expandedQueries)] = expandedQueries ?? Array.Empty(), [nameof(queryBuilder)] = queryBuilder, [nameof(ignoredProperties)] = ignoredProperties, [nameof(helperWidgetCurrentArea)] = helperWidgetCurrentArea, @@ -300,47 +297,67 @@ internal static void Save() [nameof(minIndexVariations)] = minIndexVariations, [nameof(findProviderIndexHelper)] = findProviderIndexHelper, - }; + }; RetriableOperation.Execute(() => { - SJSON.Save(settings, projectLocalSettingsPath); + CreateFolderIfNeeded(); + SJSON.Save(settings, settingsPath); }, 5, TimeSpan.FromMilliseconds(10)); SaveFavorites(); - EditorPrefs.SetFloat(k_ItemIconSizePrefKey, itemIconSize); + EditorPrefs.SetFloat(itemIconSizePrefKey, itemIconSize); - AssetDatabaseAPI.RegisterCustomDependency("SearchIndexIgnoredProperties", Hash128.Compute(ignoredProperties)); + RegisterIgnoredPropertiesCustomDependencies(); } - internal static void SetScopeValue(string prefix, int hash, string value) + public void RegisterIgnoredPropertiesCustomDependencies() + { + if (AssetDatabase.IsAssetImportWorkerProcess() || EditorApplication.isUpdating) + return; + AssetDatabaseAPI.RegisterCustomDependency(ignoredPropertiesCustomDependency, Hash128.Compute(ignoredProperties)); + } + + public void ClearSettingsFile() + { + CreateFolderIfNeeded(); + Utils.WriteTextFileToDisk(settingsPath, "{}"); + } + + void CreateFolderIfNeeded() + { + if (!Directory.Exists(settingsFolder)) + Directory.CreateDirectory(settingsFolder); + } + + public void SetScopeValue(string prefix, int hash, string value) { scopes[$"{prefix}.{hash:X8}"] = value; } - internal static void SetScopeValue(string prefix, int hash, int value) + public void SetScopeValue(string prefix, int hash, int value) { scopes[$"{prefix}.{hash:X8}"] = value.ToString(); } - internal static void SetScopeValue(string prefix, int hash, float value) + public void SetScopeValue(string prefix, int hash, float value) { scopes[$"{prefix}.{hash:X8}"] = value.ToString(); } - internal static void SetScopeValue(string prefix, int hash, Rect rect) + public void SetScopeValue(string prefix, int hash, Rect rect) { scopes[$"{prefix}.{hash:X8}"] = $"{rect.x};{rect.y};{rect.width};{rect.height}"; } - internal static string GetScopeValue(string prefix, int hash, string defaultValue) + public string GetScopeValue(string prefix, int hash, string defaultValue) { if (scopes.TryGetValue($"{prefix}.{hash:X8}", out var value)) return value; return defaultValue; } - internal static int GetScopeValue(string prefix, int hash, int defaultValue) + public int GetScopeValue(string prefix, int hash, int defaultValue) { if (scopes.TryGetValue($"{prefix}.{hash:X8}", out var value)) { @@ -349,7 +366,7 @@ internal static int GetScopeValue(string prefix, int hash, int defaultValue) return defaultValue; } - internal static float GetScopeValue(string prefix, int hash, float defaultValue) + public float GetScopeValue(string prefix, int hash, float defaultValue) { if (scopes.TryGetValue($"{prefix}.{hash:X8}", out var value)) { @@ -358,7 +375,7 @@ internal static float GetScopeValue(string prefix, int hash, float defaultValue) return defaultValue; } - internal static Rect GetScopeValue(string prefix, int hash, Rect defaultValue) + public Rect GetScopeValue(string prefix, int hash, Rect defaultValue) { if (scopes.TryGetValue($"{prefix}.{hash:X8}", out var value)) { @@ -369,6 +386,393 @@ internal static Rect GetScopeValue(string prefix, int hash, Rect defaultValue) return defaultValue; } + public void AddRecentSearch(string search) + { + recentSearches.Insert(0, search); + ApplyRecentSearchCapacity(); + recentSearches = recentSearches.Distinct().ToList(); + } + + void ApplyRecentSearchCapacity() + { + if (recentSearches.Count > recentSearchMaxCount) + recentSearches.RemoveRange(recentSearchMaxCount, recentSearches.Count - recentSearchMaxCount); + } + + public ObjectSelectorsSettings GetObjectSelectorSettings(string selectorId, bool defaultActive, int defaultPriority) + { + if (TryGetObjectSelectorSettings(selectorId, out var settings)) + return settings; + + objectSelectors[selectorId] = new ObjectSelectorsSettings() { active = defaultActive, priority = defaultPriority }; + return objectSelectors[selectorId]; + } + + public bool TryGetObjectSelectorSettings(string selectorId, out ObjectSelectorsSettings settings) + { + return objectSelectors.TryGetValue(selectorId, out settings); + } + + public void ResetObjectSelectorSettings() + { + objectSelectors.Clear(); + } + + public SearchProviderSettings GetProviderSettings(string providerId, bool defaultActive, int defaultPriority, string defaultAction, bool addToSettings = true) + { + if (TryGetProviderSettings(providerId, out var settings)) + return settings; + + var newSettings = new SearchProviderSettings() { active = defaultActive, priority = defaultPriority, defaultAction = defaultAction }; + if (addToSettings) + providers[providerId] = newSettings; + return newSettings; + } + + public bool TryGetProviderSettings(string providerId, out SearchProviderSettings settings) + { + return providers.TryGetValue(providerId, out settings); + } + + public void ResetProviderSettings() + { + providers.Clear(); + } + + public void ToggleCustomIndexer(string name, bool enable) + { + if (enable) + disabledIndexers.Remove(name); + else + disabledIndexers.Add(name); + Save(); + } + + public void AddItemFavorite(string itemId) + { + searchItemFavorites.Add(itemId); + } + + public void RemoveItemFavorite(string itemId) + { + searchItemFavorites.Remove(itemId); + } + + public void LoadFavorites() + { + var favoriteString = EditorPrefs.GetString(favoritesQueryPrefKey, ""); + searchQueryFavorites.UnionWith(favoriteString.Split(new string[] { ";;;" }, StringSplitOptions.RemoveEmptyEntries)); + } + + public void SaveFavorites() + { + EditorPrefs.SetString(favoritesQueryPrefKey, string.Join(";;;", searchQueryFavorites)); + } + + static T ReadSetting(IDictionary settings, string key, T defaultValue = default) + { + try + { + if (SJSON.TryGetValue(settings, key, out var value)) + return (T)value; + } + catch (Exception) + { + // Any error will return the default value. + } + + return defaultValue; + } + + static float ReadSetting(IDictionary settings, string key, float defaultValue = 0) + { + return (float)ReadSetting(settings, key, (double)defaultValue); + } + + static int ReadSetting(IDictionary settings, string key, int defaultValue = 0) + { + return (int)ReadSetting(settings, key, (double)defaultValue); + } + + static Dictionary ReadProviderSettings(IDictionary settings, string fieldName) + { + return ReadDictionary(settings, fieldName, vdict => new SearchProviderSettings() + { + active = Convert.ToBoolean(vdict[nameof(SearchProviderSettings.active)]), + priority = (int)(double)vdict[nameof(SearchProviderSettings.priority)], + defaultAction = vdict[nameof(SearchProviderSettings.defaultAction)] as string, + }); + } + + static Dictionary ReadPickerSettings(IDictionary settings, string fieldName) + { + return ReadDictionary(settings, fieldName, vdict => new ObjectSelectorsSettings() + { + active = Convert.ToBoolean(vdict[nameof(SearchProviderSettings.active)]), + priority = (int)(double)vdict[nameof(SearchProviderSettings.priority)] + }); + } + + static Dictionary ReadProperties(IDictionary settings, string fieldName) + { + return ReadDictionary(settings, fieldName, o => (T)o); + } + + static Dictionary ReadDictionary(IDictionary settings, string fieldName, Func valueCreator) + { + return ReadDictionary(settings, fieldName, e => true, valueCreator); + } + + static Dictionary ReadDictionary(IDictionary settings, string fieldName, Func valueCreator) + where T : BaseDictionarySettings + { + return ReadDictionary(settings, fieldName, e => e.Value is IDictionary, o => + { + var vdict = o as IDictionary; + return (T)valueCreator(vdict); + }); + } + + static Dictionary ReadDictionary(IDictionary settings, string fieldName, Func extraPredicate, Func valueCreator) + { + var d = new Dictionary(); + if (SJSON.TryGetValue(settings, fieldName, out var _data) && _data is IDictionary dataDict) + { + foreach (var p in dataDict) + { + try + { + if (p is DictionaryEntry e && extraPredicate(e)) + d[(string)e.Key] = valueCreator(e.Value); + } + catch + { + // ignore copy + } + } + } + return d; + } + } + + public static class SearchSettings + { + internal static readonly string projectLocalSettingsFolder = Utils.CleanPath(new DirectoryInfo("UserSettings").FullName); + internal static readonly string projectLocalSettingsPath = $"{projectLocalSettingsFolder}/Search.settings"; + internal const string settingsPreferencesKey = "Preferences/Search"; + + static SearchSettingsStorage s_SettingsStorage; + + // Per project settings + internal static bool trackSelection + { + get => s_SettingsStorage.trackSelection; + set => s_SettingsStorage.trackSelection = value; + } + + internal static bool fetchPreview + { + get => s_SettingsStorage.fetchPreview; + set => s_SettingsStorage.fetchPreview = value; + } + + internal static SearchFlags defaultFlags + { + get => s_SettingsStorage.defaultFlags; + set => s_SettingsStorage.defaultFlags = value; + } + + internal static bool keepOpen + { + get => s_SettingsStorage.keepOpen; + set => s_SettingsStorage.keepOpen = value; + } + + internal static string queryFolder + { + get => s_SettingsStorage.queryFolder; + set => s_SettingsStorage.queryFolder = value; + } + + internal static bool onBoardingDoNotAskAgain + { + get => s_SettingsStorage.onBoardingDoNotAskAgain; + set => s_SettingsStorage.onBoardingDoNotAskAgain = value; + } + + internal static bool showPackageIndexes + { + get => s_SettingsStorage.showPackageIndexes; + set => s_SettingsStorage.showPackageIndexes = value; + } + + internal static bool showStatusBar + { + get => s_SettingsStorage.showStatusBar; + set => s_SettingsStorage.showStatusBar = value; + } + + internal static bool hideTabs + { + get => s_SettingsStorage.hideTabs; + set => s_SettingsStorage.hideTabs = value; + } + + internal static SearchQuerySortOrder savedSearchesSortOrder + { + get => s_SettingsStorage.savedSearchesSortOrder; + set => s_SettingsStorage.savedSearchesSortOrder = value; + } + + internal static bool showSavedSearchPanel + { + get => s_SettingsStorage.showSavedSearchPanel; + set => s_SettingsStorage.showSavedSearchPanel = value; + } + + internal static Dictionary scopes => s_SettingsStorage.scopes; + internal static Dictionary providers => s_SettingsStorage.providers; + internal static Dictionary objectSelectors => s_SettingsStorage.objectSelectors; + + internal static bool queryBuilder + { + get => s_SettingsStorage.queryBuilder; + set => s_SettingsStorage.queryBuilder = value; + } + + internal static string ignoredProperties + { + get => s_SettingsStorage.ignoredProperties; + set => s_SettingsStorage.ignoredProperties = value; + } + + internal static string helperWidgetCurrentArea + { + get => s_SettingsStorage.helperWidgetCurrentArea; + set => s_SettingsStorage.helperWidgetCurrentArea = value; + } + + internal static bool refreshSearchWindowsInPlayMode + { + get => s_SettingsStorage.refreshSearchWindowsInPlayMode; + set => s_SettingsStorage.refreshSearchWindowsInPlayMode = value; + } + + internal static int minIndexVariations + { + get => s_SettingsStorage.minIndexVariations; + set => s_SettingsStorage.minIndexVariations = value; + } + + internal static bool findProviderIndexHelper + { + get => s_SettingsStorage.findProviderIndexHelper; + set => s_SettingsStorage.findProviderIndexHelper = value; + } + + internal static int[] expandedQueries + { + get => s_SettingsStorage.expandedQueries; + set => s_SettingsStorage.expandedQueries = value; + } + + internal static bool wantsMore + { + get => s_SettingsStorage.wantsMore; + set => s_SettingsStorage.wantsMore = value; + } + + const int k_RecentSearchMaxCount = 20; + internal static IReadOnlyList recentSearches => s_SettingsStorage.recentSearches; + + // User editor pref + internal static float itemIconSize + { + get => s_SettingsStorage.itemIconSize; + set => s_SettingsStorage.itemIconSize = value; + } + + internal static HashSet disabledIndexers => s_SettingsStorage.disabledIndexers; + + // TODO: That's not good that it is public like that. Should have been a property. + public static HashSet searchItemFavorites = new(); + + internal static event Action providerActivationChanged; + + internal static int debounceMs + { + get => s_SettingsStorage.debounceMs; + set => s_SettingsStorage.debounceMs = value; + } + + static SearchSettings() + { + s_SettingsStorage = new SearchSettingsStorage() + { + settingsFolder = projectLocalSettingsFolder, + settingsPath = projectLocalSettingsPath, + recentSearchMaxCount = k_RecentSearchMaxCount, + itemIconSize = (float)DisplayMode.List, + expandedQueries = Array.Empty() + }; + Load(); + } + + internal static void Load() + { + if (Application.HasARGV("cleanTestPrefs") || !File.Exists(projectLocalSettingsPath)) + { + s_SettingsStorage.ClearSettingsFile(); + } + s_SettingsStorage.Load(); + searchItemFavorites = s_SettingsStorage.searchItemFavorites; + } + + internal static void Save() + { + s_SettingsStorage?.Save(); + } + + internal static void SetScopeValue(string prefix, int hash, string value) + { + s_SettingsStorage.SetScopeValue(prefix, hash, value); + } + + internal static void SetScopeValue(string prefix, int hash, int value) + { + s_SettingsStorage.SetScopeValue(prefix, hash, value); + } + + internal static void SetScopeValue(string prefix, int hash, float value) + { + s_SettingsStorage.SetScopeValue(prefix, hash, value); + } + + internal static void SetScopeValue(string prefix, int hash, Rect rect) + { + s_SettingsStorage.SetScopeValue(prefix, hash, rect); + } + + internal static string GetScopeValue(string prefix, int hash, string defaultValue) + { + return s_SettingsStorage.GetScopeValue(prefix, hash, defaultValue); + } + + internal static int GetScopeValue(string prefix, int hash, int defaultValue) + { + return s_SettingsStorage.GetScopeValue(prefix, hash, defaultValue); + } + + internal static float GetScopeValue(string prefix, int hash, float defaultValue) + { + return s_SettingsStorage.GetScopeValue(prefix, hash, defaultValue); + } + + internal static Rect GetScopeValue(string prefix, int hash, Rect defaultValue) + { + return s_SettingsStorage.GetScopeValue(prefix, hash, defaultValue); + } + internal static SearchFlags GetContextOptions() { return SearchFlags.Default | defaultFlags; @@ -386,10 +790,7 @@ internal static void ApplyContextOptions(SearchContext context) internal static void AddRecentSearch(string search) { - recentSearches.Insert(0, search); - if (recentSearches.Count > k_RecentSearchMaxCount) - recentSearches.RemoveRange(k_RecentSearchMaxCount, recentSearches.Count - k_RecentSearchMaxCount); - recentSearches = recentSearches.Distinct().ToList(); + s_SettingsStorage.AddRecentSearch(search); } [SettingsProvider] @@ -513,11 +914,7 @@ static void DrawAdvancedObjectSelectorsSettings() internal static ObjectSelectorsSettings GetObjectSelectorSettings(AdvancedObjectSelector selector) { - if (TryGetObjectSelectorSettings(selector.id, out var settings)) - return settings; - - objectSelectors[selector.id] = new ObjectSelectorsSettings() { active = selector.active, priority = selector.priority }; - return objectSelectors[selector.id]; + return s_SettingsStorage.GetObjectSelectorSettings(selector.id, selector.active, selector.priority); } internal static ObjectSelectorsSettings GetObjectSelectorSettings(string selectorId) @@ -531,7 +928,7 @@ internal static ObjectSelectorsSettings GetObjectSelectorSettings(string selecto static void ResetObjectSelectorSettings() { SearchAnalytics.SendEvent(null, SearchAnalytics.GenericEventType.ObjectSelectorSettingsReset); - objectSelectors.Clear(); + s_SettingsStorage.ResetObjectSelectorSettings(); SearchService.RefreshObjectSelectors(); } @@ -602,7 +999,7 @@ internal static void IncreaseObjectSelectorPriority(AdvancedObjectSelector selec internal static bool TryGetObjectSelectorSettings(string selectorId, out ObjectSelectorsSettings settings) { - return objectSelectors.TryGetValue(selectorId, out settings); + return s_SettingsStorage.TryGetObjectSelectorSettings(selectorId, out settings); } private static void DrawSearchSettings(string searchContext) @@ -690,11 +1087,7 @@ private static void OpenCustomIndexerMenu() private static void ToggleCustomIndexer(string name, bool enable) { - if (enable) - disabledIndexers.Remove(name); - else - disabledIndexers.Add(name); - Save(); + s_SettingsStorage.ToggleCustomIndexer(name, enable); } private static bool Toggle(GUIContent content, string propertyName, bool value) @@ -705,91 +1098,6 @@ private static bool Toggle(GUIContent content, string propertyName, bool value) return newValue; } - private static T ReadSetting(IDictionary settings, string key, T defaultValue = default) - { - try - { - if (SJSON.TryGetValue(settings, key, out var value)) - return (T)value; - } - catch (Exception) - { - // Any error will return the default value. - } - - return defaultValue; - } - - private static float ReadSetting(IDictionary settings, string key, float defaultValue = 0) - { - return (float)ReadSetting(settings, key, (double)defaultValue); - } - - private static int ReadSetting(IDictionary settings, string key, int defaultValue = 0) - { - return (int)ReadSetting(settings, key, (double)defaultValue); - } - - private static Dictionary ReadProviderSettings(IDictionary settings, string fieldName) - { - return ReadDictionary(settings, fieldName, vdict => new SearchProviderSettings() - { - active = Convert.ToBoolean(vdict[nameof(SearchProviderSettings.active)]), - priority = (int)(double)vdict[nameof(SearchProviderSettings.priority)], - defaultAction = vdict[nameof(SearchProviderSettings.defaultAction)] as string, - }); - } - - private static Dictionary ReadPickerSettings(IDictionary settings, string fieldName) - { - return ReadDictionary(settings, fieldName, vdict => new ObjectSelectorsSettings() - { - active = Convert.ToBoolean(vdict[nameof(SearchProviderSettings.active)]), - priority = (int)(double)vdict[nameof(SearchProviderSettings.priority)] - }); - } - - private static Dictionary ReadProperties(IDictionary settings, string fieldName) - { - return ReadDictionary(settings, fieldName, o => (T)o); - } - - static Dictionary ReadDictionary(IDictionary settings, string fieldName, Func valueCreator) - { - return ReadDictionary(settings, fieldName, e => true, valueCreator); - } - - static Dictionary ReadDictionary(IDictionary settings, string fieldName, Func valueCreator) - where T : BaseDictionarySettings - { - return ReadDictionary(settings, fieldName, e => e.Value is IDictionary, o => - { - var vdict = o as IDictionary; - return (T)valueCreator(vdict); - }); - } - - static Dictionary ReadDictionary(IDictionary settings, string fieldName, Func extraPredicate, Func valueCreator) - { - var d = new Dictionary(); - if (SJSON.TryGetValue(settings, fieldName, out var _data) && _data is IDictionary dataDict) - { - foreach (var p in dataDict) - { - try - { - if (p is DictionaryEntry e && extraPredicate(e)) - d[(string)e.Key] = valueCreator(e.Value); - } - catch - { - // ignore copy - } - } - } - return d; - } - private static void DrawProviderSettings() { EditorGUILayout.LabelField(L10n.Tr("Provider Settings"), EditorStyles.largeLabel); @@ -860,26 +1168,23 @@ private static void DrawProviderSettings() internal static SearchProviderSettings GetProviderSettings(string providerId) { - if (TryGetProviderSettings(providerId, out var settings)) - return settings; - var provider = SearchService.GetProvider(providerId); + SearchProviderSettings defaultSettings = null; if (provider == null) - return new SearchProviderSettings(); + defaultSettings = new SearchProviderSettings(); - providers[providerId] = new SearchProviderSettings() { active = provider.active, priority = provider.priority, defaultAction = null }; - return providers[providerId]; + return s_SettingsStorage.GetProviderSettings(providerId, provider?.active ?? defaultSettings.active, provider?.priority ?? defaultSettings.priority, null, provider != null); } internal static bool TryGetProviderSettings(string providerId, out SearchProviderSettings settings) { - return providers.TryGetValue(providerId, out settings); + return s_SettingsStorage.TryGetProviderSettings(providerId, out settings); } private static void ResetProviderSettings() { SearchAnalytics.SendEvent(null, SearchAnalytics.GenericEventType.PreferenceReset); - providers.Clear(); + s_SettingsStorage.ResetProviderSettings(); SearchService.Refresh(); } @@ -1014,21 +1319,10 @@ class Styles public static GUIContent decreaseObjectSelectorPriorityContent = EditorGUIUtility.TrTextContent("\u2193", "Decrease the object selector's priority"); } - internal static void AddSearchFavorite(string searchText) - { - searchQueryFavorites.Add(searchText); - SaveFavorites(); - } - - internal static void RemoveSearchFavorite(string searchText) - { - searchQueryFavorites.Remove(searchText); - SaveFavorites(); - } - public static void AddItemFavorite(SearchItem item) { searchItemFavorites.Add(item.id); + s_SettingsStorage.AddItemFavorite(item.id); Dispatcher.Emit(SearchEvent.ItemFavoriteStateChanged, new SearchEventPayload(item.context, item.id)); SearchAnalytics.SendEvent(null, SearchAnalytics.GenericEventType.QuickSearchAddFavoriteItem, item.provider.id); } @@ -1036,19 +1330,9 @@ public static void AddItemFavorite(SearchItem item) public static void RemoveItemFavorite(SearchItem item) { searchItemFavorites.Remove(item.id); + s_SettingsStorage.RemoveItemFavorite(item.id); Dispatcher.Emit(SearchEvent.ItemFavoriteStateChanged, new SearchEventPayload(item.context, item.id)); SearchAnalytics.SendEvent(null, SearchAnalytics.GenericEventType.QuickSearchRemoveFavoriteItem, item.provider.id); } - - internal static void LoadFavorites() - { - var favoriteString = EditorPrefs.GetString("SearchQuery.Favorites", ""); - searchQueryFavorites.UnionWith(favoriteString.Split(new string[] { ";;;" }, StringSplitOptions.RemoveEmptyEntries)); - } - - internal static void SaveFavorites() - { - EditorPrefs.SetString("SearchQuery.Favorites", string.Join(";;;", searchQueryFavorites)); - } } } diff --git a/Modules/QuickSearch/Editor/UITK/SearchDetailView.cs b/Modules/QuickSearch/Editor/UITK/SearchDetailView.cs index b27d9d2414..16e522fd18 100644 --- a/Modules/QuickSearch/Editor/UITK/SearchDetailView.cs +++ b/Modules/QuickSearch/Editor/UITK/SearchDetailView.cs @@ -203,6 +203,7 @@ private void Refresh() if (selectionCount == 0) { + m_EditorContainer?.Clear(); return; } diff --git a/Modules/QuickSearch/Editor/UITK/SearchElement.cs b/Modules/QuickSearch/Editor/UITK/SearchElement.cs index 45cb53b68a..1db7245dd8 100644 --- a/Modules/QuickSearch/Editor/UITK/SearchElement.cs +++ b/Modules/QuickSearch/Editor/UITK/SearchElement.cs @@ -28,6 +28,7 @@ static class SearchEvent public const string UserQueryRemoved = "search-user-query-removed"; public const string ProjectQueryAdded = "search-project-query-added"; public const string ProjectQueryRemoved = "search-project-query-removed"; + public const string ProjectQueryListChanged = "search-project-query-list-changed"; public const string ActiveQueryChanged = "search-active-query-changed"; public const string SaveActiveSearchQuery = "search-save-active-search-query"; public const string SearchQueryChanged = "search-query-changed"; diff --git a/Modules/QuickSearch/Editor/UITK/SearchQueryPanelView.cs b/Modules/QuickSearch/Editor/UITK/SearchQueryPanelView.cs index fe4c5643cc..1c90037027 100644 --- a/Modules/QuickSearch/Editor/UITK/SearchQueryPanelView.cs +++ b/Modules/QuickSearch/Editor/UITK/SearchQueryPanelView.cs @@ -197,9 +197,9 @@ protected override void OnAttachToPanel(AttachToPanelEvent evt) SetActiveQuery(m_ViewModel.state.activeQuery); SetExpandedState(SearchSettings.expandedQueries); - SearchMonitor.contentRefreshed += CheckForDeletedQueries; OnAll(SearchEvent.UserQueryAdded, HandleUserQueriesChanged); OnAll(SearchEvent.UserQueryRemoved, HandleUserQueriesChanged); + OnAll(SearchEvent.ProjectQueryListChanged, HandleProjectQueriesChanged); OnAll(SearchEvent.ProjectQueryAdded, HandleProjectQueriesChanged); OnAll(SearchEvent.ProjectQueryRemoved, HandleProjectQueriesChanged); OnAll(SearchEvent.SearchQueryChanged, HandleSearchQueryChanged); @@ -208,9 +208,9 @@ protected override void OnAttachToPanel(AttachToPanelEvent evt) protected override void OnDetachFromPanel(DetachFromPanelEvent evt) { - SearchMonitor.contentRefreshed -= CheckForDeletedQueries; Off(SearchEvent.UserQueryAdded, HandleUserQueriesChanged); Off(SearchEvent.UserQueryRemoved, HandleUserQueriesChanged); + Off(SearchEvent.ProjectQueryListChanged, HandleProjectQueriesChanged); Off(SearchEvent.ProjectQueryAdded, HandleProjectQueriesChanged); Off(SearchEvent.ProjectQueryRemoved, HandleProjectQueriesChanged); Off(SearchEvent.SearchQueryChanged, HandleSearchQueryChanged); @@ -271,34 +271,6 @@ void SetActiveQuery(ISearchQuery activeQuery) UpdateListViewSelection(listView, m_LastSelectedQuery); } - void CheckForDeletedQueries(string[] updated, string[] removed, string[] moved) - { - if (removed == null || removed.Length == 0) - return; - var potentialDeletedQueries = false; - foreach (var path in removed) - { - if (path.EndsWith(".asset")) - { - potentialDeletedQueries = true; - break; - } - } - - if (!potentialDeletedQueries) - return; - - var queries = GetListView(k_ProjectQueryId).itemSource; - foreach(var q in queries) - { - if (q == null || q.filePath == "") - { - HandleProjectQueriesChanged(null); - break; - } - } - } - void HandleProjectQueriesChanged(ISearchEvent evt) { var source = GetSearchQuerySource(k_ProjectQueryId); @@ -529,7 +501,7 @@ void HandleSortClicked(MouseDownEvent evt) var sortOrder = (SearchQuerySortOrder)i; dropDownMenu.AddItem(options[i].text, sortOrder == currentSortingOrder, () => SortQueries(sortOrder)); } - dropDownMenu.DropDown(new Rect(worldPosition, Vector2.one), sortButton); + DropdownUtility.ShowDropdown(dropDownMenu, worldPosition, sortButton); } internal void SortQueries(SearchQuerySortOrder sortOrder) diff --git a/Modules/SceneTemplateEditor/SceneTemplateAssetInspectorWindow.cs b/Modules/SceneTemplateEditor/SceneTemplateAssetInspectorWindow.cs index 16af98df18..7665de3862 100644 --- a/Modules/SceneTemplateEditor/SceneTemplateAssetInspectorWindow.cs +++ b/Modules/SceneTemplateEditor/SceneTemplateAssetInspectorWindow.cs @@ -24,6 +24,7 @@ internal class SceneTemplateAssetInspectorWindow : Editor private const string k_ThumbnailAreaName = "scene-template-asset-inspector-thumbnail-area"; private const string k_DependencyListView = "scene-template-asset-inspector-list-view"; private const string k_NoLabelRowName = "scene-template-asset-inspector-no-label-row"; + private const string k_DynamicResize = "scene-template-control-dynamic-resize"; static readonly string k_SceneTemplateInfo = L10n.Tr("Scene Template Pipeline must be a Mono Script whose main class derives from ISceneTemplatePipeline or SceneTemplatePipelineAdapter. The main class and the script must have the same name."); @@ -188,6 +189,8 @@ public override VisualElement CreateInspectorGUI() // Dependencies root.Add(CreateFoldoutInspector(BuildDependencyRows(), L10n.Tr("Dependencies"), "SceneTemplateDependenciesFoldout")); + + root.RegisterCallback(OnInspectorFieldGeometryChanged); return root; } @@ -201,6 +204,33 @@ protected override bool ShouldHideOpenButton() return true; } + private void OnInspectorFieldGeometryChanged(GeometryChangedEvent e) + { + var foldouts = Root.Query(); + if (foldouts == null) + return; + Foldout openFoldout = null; + foreach (var foldout in foldouts.ToList()) + { + if (foldout.value) + { + openFoldout = foldout; + break; + } + } + if (openFoldout == null) + return; + + var referenceLabel = openFoldout.Q /// - [MenuItem("CONTEXT/Font Asset/Update Atlas Texture...", priority = 2000)] + [MenuItem("CONTEXT/FontAsset/Update Atlas Texture...", priority = 2000)] static void RegenerateFontAsset(MenuCommand command) { FontAsset fontAsset = command.context as FontAsset; @@ -322,13 +322,13 @@ static void RegenerateFontAsset(MenuCommand command) /// Clear Dynamic Font Asset data such as glyph, character and font features. /// /// - [MenuItem("CONTEXT/Font Asset/Reset", validate = true)] + [MenuItem("CONTEXT/FontAsset/Reset", validate = true)] static bool ClearFontAssetDataValidate(MenuCommand command) { return AssetDatabase.IsOpenForEdit(command.context); } - [MenuItem("CONTEXT/Font Asset/Reset", priority = 2100)] + [MenuItem("CONTEXT/FontAsset/Reset", priority = 2100)] static void ClearFontAssetData(MenuCommand command) { FontAsset fontAsset = command.context as FontAsset; @@ -339,13 +339,20 @@ static void ClearFontAssetData(MenuCommand command) if (Selection.activeObject != fontAsset) Selection.activeObject = fontAsset; + ClearFontAssetData(fontAsset); + } + + internal static void ClearFontAssetData(FontAsset fontAsset) + { + if (fontAsset == null) + return; + fontAsset.ClearFontAssetData(true); TextResourceManager.RebuildFontAssetCache(); TextEventManager.ON_FONT_PROPERTY_CHANGED(true, fontAsset); } - // Context Menus for TMPro Font Assets //This function is used for debugging and fixing potentially broken font atlas links. [MenuItem("CONTEXT/Font Asset/Extract Atlas", priority = 2101)] static void ExtractAtlas(MenuCommand command) @@ -375,17 +382,18 @@ static void ExtractAtlas(MenuCommand command) AssetDatabase.Refresh(); DestroyImmediate(tex); } + /// /// Clear Character and Glyph data (only). /// /// - [MenuItem("CONTEXT/Font Asset/Clear Dynamic Data", validate = true)] + [MenuItem("CONTEXT/FontAsset/Clear Dynamic Data", validate = true, priority = 2102)] static bool ClearFontCharacterDataValidate(MenuCommand command) { return AssetDatabase.IsOpenForEdit(command.context); } - [MenuItem("CONTEXT/Font Asset/Clear Dynamic Data", priority = 2102)] + [MenuItem("CONTEXT/FontAsset/Clear Dynamic Data", priority = 2102)] static void ClearFontCharacterData(MenuCommand command) { FontAsset fontAsset = command.context as FontAsset; @@ -396,6 +404,14 @@ static void ClearFontCharacterData(MenuCommand command) if (Selection.activeObject != fontAsset) Selection.activeObject = fontAsset; + ClearFontCharacterData(fontAsset); + } + + internal static void ClearFontCharacterData(FontAsset fontAsset) + { + if (fontAsset == null) + return; + fontAsset.ClearCharacterAndGlyphTablesInternal(); TextEventManager.ON_FONT_PROPERTY_CHANGED(true, fontAsset); @@ -405,13 +421,13 @@ static void ClearFontCharacterData(MenuCommand command) /// Import all font features /// /// - [MenuItem("CONTEXT/Font Asset/Import Font Features", validate = true)] + [MenuItem("CONTEXT/FontAsset/Import Font Features", validate = true, priority = 2110)] static bool ReimportFontFeaturesValidate(MenuCommand command) { return AssetDatabase.IsOpenForEdit(command.context); } - [MenuItem("CONTEXT/Font Asset/Import Font Features", priority = 2110)] + [MenuItem("CONTEXT/FontAsset/Import Font Features", priority = 2110)] static void ReimportFontFeatures(MenuCommand command) { FontAsset fontAsset = command.context as FontAsset; diff --git a/Modules/TextCoreTextEngineEditor/Managed/TextEditorResourceManager.cs b/Modules/TextCoreTextEngineEditor/Managed/TextEditorResourceManager.cs index ab22a16bc9..bc10180705 100644 --- a/Modules/TextCoreTextEngineEditor/Managed/TextEditorResourceManager.cs +++ b/Modules/TextCoreTextEngineEditor/Managed/TextEditorResourceManager.cs @@ -21,6 +21,7 @@ internal static void InitializeFontAssetResourceChangeCallBacks() FontAsset.SetAtlasTextureIsReadable += FontEngineEditorUtilities.SetAtlasTextureIsReadable; FontAsset.GetSourceFontRef += TextEditorResourceManager.GetSourceFontRef; FontAsset.SetSourceFontGUID += TextEditorResourceManager.SetSourceFontGUID; + FontAsset.EditorApplicationIsUpdating += () => EditorApplication.isUpdating; // Callback to handle clearing dynamic font asset data when closing the Editor EditorApplication.quitting += () => diff --git a/Modules/UIBuilder/Editor/Builder/Document/BuilderDocumentOpenUXML.cs b/Modules/UIBuilder/Editor/Builder/Document/BuilderDocumentOpenUXML.cs index 15a2beb09d..71b5f80b19 100644 --- a/Modules/UIBuilder/Editor/Builder/Document/BuilderDocumentOpenUXML.cs +++ b/Modules/UIBuilder/Editor/Builder/Document/BuilderDocumentOpenUXML.cs @@ -883,7 +883,7 @@ VisualElement ReloadChildToCanvas(BuilderDocumentOpenUXML childOpenUXML, VisualE var targetTemplateAsset = parentOpenUXML.visualTreeAsset.templateAssets[childOpenUXML.openSubDocumentParentSourceTemplateAssetIndex]; var templateContainerQuery = rootElement.Query().Where(container => container.GetProperty(BuilderConstants.ElementLinkedVisualElementAssetVEPropertyName) as TemplateAsset == targetTemplateAsset); - var foundTemplateContainer = templateContainerQuery.ToList().First(); + var foundTemplateContainer = templateContainerQuery.First(); childRootElement = foundTemplateContainer; } diff --git a/Modules/UIBuilder/Editor/Builder/Inspector/Binding/BuilderBindingUxmlAtttributesView.cs b/Modules/UIBuilder/Editor/Builder/Inspector/Binding/BuilderBindingUxmlAtttributesView.cs index 13bbad22ad..2d8362087c 100644 --- a/Modules/UIBuilder/Editor/Builder/Inspector/Binding/BuilderBindingUxmlAtttributesView.cs +++ b/Modules/UIBuilder/Editor/Builder/Inspector/Binding/BuilderBindingUxmlAtttributesView.cs @@ -3,8 +3,10 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Collections.Generic; using UnityEditor; using UnityEditor.UIElements; +using UnityEngine.Pool; using UnityEngine.UIElements; using UIEHelpBox = UnityEngine.UIElements.HelpBox; @@ -150,6 +152,62 @@ protected override void UpdateAttribute(VisualElement target, string bindingAttr UpdateConverterCompleter(); } + internal override void UnsetAllAttributes() + { + var undoGroup = Undo.GetCurrentGroup(); + UndoRecordDocument(BuilderConstants.ChangeAttributeValueUndoMessage); + var builder = Builder.ActiveWindow; + + var styleRows = fieldsContainer.Query().ToList(); + foreach (var styleRow in styleRows) + { + var fields = styleRow.GetLinkedFieldElements(); + + // needed for data source and data source type that are sharing a style row + foreach (var fieldElement in fields) + { + var attributeName = GetAttributeName(fieldElement); + + var currentAttributesUxmlOwner = attributesUxmlOwner; + var currentSerializedData = uxmlSerializedData; + + if (fieldElement.GetFirstAncestorOfType() is { } dataRoot && dataRoot.dataDescription.isUxmlObject) + { + SynchronizePath(dataRoot.rootPath, false, out var uxmlOwner, out var serializedData, out var _); + currentAttributesUxmlOwner = uxmlOwner as UxmlAsset; + currentSerializedData = serializedData as UxmlSerializedData; + + if (currentAttributesUxmlOwner != null) + { + var entry = uxmlDocument.GetUxmlObjectEntry(currentAttributesUxmlOwner.id); + if (entry.uxmlObjectAssets?.Count > 0) + { + for (var i = entry.uxmlObjectAssets.Count - 1 ; i >= 0; i--) + { + uxmlDocument.RemoveUxmlObject(entry.uxmlObjectAssets[i].id); + } + } + } + } + + if (currentSerializedData == null) + continue; + + currentAttributesUxmlOwner.RemoveAttribute(attributeName); + var description = fieldElement.GetLinkedAttributeDescription() as UxmlSerializedAttributeDescription; + description.SetSerializedValue(currentSerializedData, description.defaultValue); + CallDeserializeOnElement(); + UnsetEnumValue(attributeName, false); + } + } + + // Notify of changes. + NotifyAttributesChanged(); + Refresh(); + builder.inspector.headerSection.Refresh(); + Undo.CollapseUndoOperations(undoGroup); + } + /// protected override void GenerateSerializedAttributeFields() { @@ -233,7 +291,7 @@ internal override void UpdateAttributeOverrideStyle(VisualElement fieldElement) void UpdateConverterCompleter() { - // Make sure we have all the required fields available. + // Make sure we have all the required fields available. if (m_ConvertersToUi == null || m_ConvertersToSource == null || m_DataSourceField == null || m_DataSourceTypeField == null || m_DataSourcePathField == null) return; @@ -316,7 +374,7 @@ void CopyAttributesRecursively(string path, UxmlAsset origin, UxmlAsset destinat { if (!attributeDescription.isUxmlObject) continue; - + var attributePath = $"{path}.{attributeDescription.serializedField.Name}"; if (attributeDescription.isList) { @@ -330,7 +388,7 @@ void CopyAttributesRecursively(string path, UxmlAsset origin, UxmlAsset destinat targetView.SynchronizePath(arrayPath, true, out var newUxmlAsset, out _, out _); CopyAttributesRecursively(arrayPath, (UxmlAsset)uxmlAsset, (UxmlAsset)newUxmlAsset, targetView); - + arrayProperty = targetView.m_CurrentElementSerializedObject.FindProperty($"{attributePath}.Array.data[{++i}]"); } } diff --git a/Modules/UIBuilder/Editor/Builder/Inspector/Binding/BuilderDataSourceAndPathView.cs b/Modules/UIBuilder/Editor/Builder/Inspector/Binding/BuilderDataSourceAndPathView.cs index b52e497091..885130ceb6 100644 --- a/Modules/UIBuilder/Editor/Builder/Inspector/Binding/BuilderDataSourceAndPathView.cs +++ b/Modules/UIBuilder/Editor/Builder/Inspector/Binding/BuilderDataSourceAndPathView.cs @@ -432,6 +432,14 @@ protected override void BuildAttributeFieldContextualMenu(DropdownMenu menu, Bui { var fieldElement = styleRow.GetLinkedFieldElements()[0]; var desc = fieldElement.GetLinkedAttributeDescription(); + + var currentUxmlAttributeOwner = attributesUxmlOwner; + + if (isBinding && SynchronizePath(bindingSerializedPropertyPathRoot, false, out var uxmlAsset, out _, out _)) + { + currentUxmlAttributeOwner = uxmlAsset as UxmlAsset; + } + if (desc.name is k_BindingAttr_DataSource or k_BindingAttr_DataSourceType) { menu.AppendAction( @@ -447,7 +455,7 @@ protected override void BuildAttributeFieldContextualMenu(DropdownMenu menu, Bui attributeName); var canUnsetBinding = !isInTemplateInstance && DataBindingUtility.TryGetBinding(currentElement, new PropertyPath(bindingProperty), out _); - return (attributesUxmlOwner != null && attributesUxmlOwner.HasAttribute(attributeName)) || isAttributeOverrideAttribute || canUnsetBinding + return (attributesUxmlOwner != null && currentUxmlAttributeOwner.HasAttribute(attributeName)) || isAttributeOverrideAttribute || canUnsetBinding ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled; }, @@ -464,7 +472,7 @@ protected override void BuildAttributeFieldContextualMenu(DropdownMenu menu, Bui && BuilderAssetUtilities.HasAttributeOverrideInRootTemplate(currentElement, k_BindingAttr_DataSourceType); var canUnsetBinding = !isInTemplateInstance && DataBindingUtility.TryGetBinding(currentElement, new PropertyPath(bindingProperty), out _); - return (attributesUxmlOwner != null && attributesUxmlOwner.HasAttribute(k_BindingAttr_DataSourceType)) || isAttributeOverrideAttribute || canUnsetBinding + return (attributesUxmlOwner != null && currentUxmlAttributeOwner.HasAttribute(k_BindingAttr_DataSourceType)) || isAttributeOverrideAttribute || canUnsetBinding ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled; }, diff --git a/Modules/UIBuilder/Editor/Builder/Inspector/BuilderInspector.cs b/Modules/UIBuilder/Editor/Builder/Inspector/BuilderInspector.cs index ae44dc17f7..8ea35089d7 100644 --- a/Modules/UIBuilder/Editor/Builder/Inspector/BuilderInspector.cs +++ b/Modules/UIBuilder/Editor/Builder/Inspector/BuilderInspector.cs @@ -14,6 +14,7 @@ using UnityEngine.Pool; using UnityEditor.UIElements.Debugger; using UnityEngine.UIElements.StyleSheets; +using System.Collections; namespace Unity.UI.Builder { @@ -584,8 +585,6 @@ public void UpdateFieldStatus(VisualElement field, StyleProperty property) SetFieldsEnabled(field, !isResolvedVariable); } } - - m_LocalStylesSection.UpdateStyleCategoryFoldoutOverrides(); } } @@ -777,8 +776,25 @@ internal static void UpdateFieldTooltip(VisualElement field, FieldValueInfo valu internal static Label GetDraggerLabel(VisualElement field) { - var labelDraggers = field.Query