diff --git a/Editor/Mono/2D/SpriteAtlas/EditorSpriteAtlas.bindings.cs b/Editor/Mono/2D/SpriteAtlas/EditorSpriteAtlas.bindings.cs index b3b86711c5..490e284a30 100644 --- a/Editor/Mono/2D/SpriteAtlas/EditorSpriteAtlas.bindings.cs +++ b/Editor/Mono/2D/SpriteAtlas/EditorSpriteAtlas.bindings.cs @@ -96,5 +96,6 @@ public static class SpriteAtlasExtensions extern internal static Texture2D[] GetPreviewAlphaTextures(this SpriteAtlas spriteAtlas); extern internal static TextureFormat GetTextureFormat(this SpriteAtlas spriteAtlas, BuildTarget target); extern internal static Sprite[] GetPackedSprites(this SpriteAtlas spriteAtlas); + extern internal static Hash128 GetStoredHash(this SpriteAtlas spriteAtlas); } } diff --git a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasInspector.cs b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasInspector.cs index f25505ad8a..a253110a68 100644 --- a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasInspector.cs +++ b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasInspector.cs @@ -194,12 +194,12 @@ void OnEnable() m_VariantScale = serializedObject.FindProperty("m_EditorData.variantMultiplier"); m_Packables = serializedObject.FindProperty("m_EditorData.packables"); - m_PackableList = new ReorderableList(serializedObject, m_Packables, true, true, true, true); + m_PackableList = new ReorderableList(serializedObject, m_Packables, true, false, true, true); m_PackableList.onAddCallback = AddPackable; m_PackableList.onRemoveCallback = RemovePackable; m_PackableList.drawElementCallback = DrawPackableElement; m_PackableList.elementHeight = EditorGUIUtility.singleLineHeight; - m_PackableList.headerHeight = 0f; + m_PackableList.headerHeight = 3f; SyncPlatformSettings(); diff --git a/Editor/Mono/Animation/AnimationUtility.bindings.cs b/Editor/Mono/Animation/AnimationUtility.bindings.cs index 65d1f17da4..768efad696 100644 --- a/Editor/Mono/Animation/AnimationUtility.bindings.cs +++ b/Editor/Mono/Animation/AnimationUtility.bindings.cs @@ -242,19 +242,19 @@ private static void Internal_InvokeOnCurveWasModified(AnimationClip clip, Editor extern internal static void UpdateTangentsFromMode([NotNull] AnimationCurve curve); - [NativeThrows] + [NativeThrows, ThreadSafe] extern public static TangentMode GetKeyLeftTangentMode([NotNull] AnimationCurve curve, int index); - [NativeThrows] + [NativeThrows, ThreadSafe] extern public static TangentMode GetKeyRightTangentMode([NotNull] AnimationCurve curve, int index); [NativeThrows] extern public static bool GetKeyBroken([NotNull] AnimationCurve curve, int index); - [NativeThrows] + [NativeThrows, ThreadSafe] extern public static void SetKeyLeftTangentMode([NotNull] AnimationCurve curve, int index, TangentMode tangentMode); - [NativeThrows] + [NativeThrows, ThreadSafe] extern public static void SetKeyRightTangentMode([NotNull] AnimationCurve curve, int index, TangentMode tangentMode); [NativeThrows] diff --git a/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyDataSource.cs b/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyDataSource.cs index 5bea4058b3..17491a11bd 100644 --- a/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyDataSource.cs +++ b/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyDataSource.cs @@ -54,7 +54,7 @@ public override void FetchData() private TreeViewItem AddGameObjectToHierarchy(GameObject gameObject, GameObject rootGameObject, AnimationClip animationClip, TreeViewItem parent) { string path = AnimationUtility.CalculateTransformPath(gameObject.transform, rootGameObject.transform); - TreeViewItem node = new AddCurvesPopupGameObjectNode(gameObject, parent, gameObject.name); + AddCurvesPopupGameObjectNode node = new AddCurvesPopupGameObjectNode(gameObject, parent, gameObject.name); List childNodes = new List(); if (m_RootItem == null) @@ -111,6 +111,20 @@ private TreeViewItem AddGameObjectToHierarchy(GameObject gameObject, GameObject } } + var animator = rootGameObject.GetComponent(); + if (animator != null) + { + //If the Animator has a human avatar, we need to check if the avatar's hierarchy matches that of the current GameObject. If they do not match, disable the node. + if (animator.avatarRoot != null && animator.isHuman) + { + if (animator.avatarRoot.Find(path) == null) + { + node.disabled = true; + } + } + } + + if (showEntireHierarchy) { // Iterate over all child GOs @@ -220,6 +234,7 @@ public void UpdateData() internal class AddCurvesPopupGameObjectNode : TreeViewItem { + internal bool disabled = false; public AddCurvesPopupGameObjectNode(GameObject gameObject, TreeViewItem parent, string displayName) : base(gameObject.GetInstanceID(), parent != null ? parent.depth + 1 : -1, parent, displayName) { diff --git a/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyGUI.cs b/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyGUI.cs index 939b21cf85..7ca2d0a06c 100644 --- a/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyGUI.cs +++ b/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyGUI.cs @@ -6,6 +6,7 @@ using UnityEditor.IMGUI.Controls; using UnityEngine; using System.Collections.Generic; +using UnityEditor.ShortcutManagement; namespace UnityEditorInternal { @@ -13,22 +14,44 @@ internal class AddCurvesPopupHierarchyGUI : TreeViewGUI { public EditorWindow owner; public bool showPlusButton { get; set; } - private GUIStyle plusButtonStyle = "OL Plus"; + private GUIStyle buttonStyle = "IconButton"; + private GUIContent plusIcon = EditorGUIUtility.TrIconContent("Toolbar Plus"); private GUIStyle plusButtonBackgroundStyle = "Tag MenuItem"; private GUIContent addPropertiesContent = EditorGUIUtility.TrTextContent("Add Properties"); private const float plusButtonWidth = 17; + static Texture2D warningIcon = (Texture2D)EditorGUIUtility.LoadRequired("Icons/ShortcutManager/alertDialog.png"); + public static GUIStyle warningIconStyle; + public AddCurvesPopupHierarchyGUI(TreeViewController treeView, EditorWindow owner) : base(treeView, true) { this.owner = owner; + warningIconStyle = new GUIStyle(); + warningIconStyle.margin = new RectOffset(15, 15, 15, 15); } public override void OnRowGUI(Rect rowRect, TreeViewItem node, int row, bool selected, bool focused) { - base.OnRowGUI(rowRect, node, row, selected, focused); - DoAddCurveButton(rowRect, node); - HandleContextMenu(rowRect, node); + bool propertyPathMismatchWithHumanAvatar = false; + AddCurvesPopupGameObjectNode addCurvesPopupNode = node as AddCurvesPopupGameObjectNode; + if (addCurvesPopupNode != null) + { + propertyPathMismatchWithHumanAvatar = addCurvesPopupNode.disabled; + } + + using (new EditorGUI.DisabledScope(propertyPathMismatchWithHumanAvatar)) + { + base.OnRowGUI(rowRect, node, row, selected, focused); + DoAddCurveButton(rowRect, node); + HandleContextMenu(rowRect, node); + } + + if (propertyPathMismatchWithHumanAvatar) + { + Rect iconRect = new Rect(rowRect.width - plusButtonWidth, rowRect.yMin, plusButtonWidth, buttonStyle.fixedHeight); + GUI.Label(iconRect, new GUIContent(warningIcon, "The Avatar definition does not match the property path. Please author using a hierarchy the Avatar was built with."), warningIconStyle); + } } private void DoAddCurveButton(Rect rowRect, TreeViewItem node) @@ -38,14 +61,14 @@ private void DoAddCurveButton(Rect rowRect, TreeViewItem node) if (hierarchyNode == null || hierarchyNode.curveBindings == null || hierarchyNode.curveBindings.Length == 0) return; - Rect buttonRect = new Rect(rowRect.width - plusButtonWidth, rowRect.yMin, plusButtonWidth, plusButtonStyle.fixedHeight); + Rect buttonRect = new Rect(rowRect.width - plusButtonWidth, rowRect.yMin, plusButtonWidth, buttonStyle.fixedHeight); // TODO Make a style for add curves popup // Draw background behind plus button to prevent text overlapping GUI.Box(buttonRect, GUIContent.none, plusButtonBackgroundStyle); // Check if the curve already exists and remove plus button - if (GUI.Button(buttonRect, GUIContent.none, plusButtonStyle)) + if (GUI.Button(buttonRect, plusIcon, buttonStyle)) { AddCurvesPopup.AddNewCurve(hierarchyNode); diff --git a/Editor/Mono/Animation/AnimationWindow/AnimEditor.cs b/Editor/Mono/Animation/AnimationWindow/AnimEditor.cs index e5e27f7ca1..00120ddf0a 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimEditor.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimEditor.cs @@ -94,7 +94,7 @@ static private Color filterBySelectionColor } } - internal const int kSliderThickness = 15; + internal const int kSliderThickness = 13; internal const int kIntFieldWidth = 35; internal const int kHierarchyMinWidth = 300; internal const int kToggleButtonWidth = 80; @@ -102,7 +102,7 @@ static private Color filterBySelectionColor private int layoutRowHeight { - get { return (int)EditorGUI.kWindowToolbarHeight + 1; } + get { return (int)EditorGUI.kWindowToolbarHeight; } } internal struct FrameRateMenuEntry @@ -227,9 +227,10 @@ public void OnAnimEditorGUI(EditorWindow parent, Rect position) HierarchyOnGUI(); // Bottom row of controls - GUILayout.BeginHorizontal(AnimationWindowStyles.miniToolbar); - TabSelectionOnGUI(); - GUILayout.EndHorizontal(); + using (new GUILayout.HorizontalScope(AnimationWindowStyles.toolbarBottom)) + { + TabSelectionOnGUI(); + } GUILayout.EndVertical(); @@ -254,7 +255,10 @@ public void OnAnimEditorGUI(EditorWindow parent, Rect position) OverlayOnGUI(contentLayoutRect); if (Event.current.type == EventType.Repaint) + { OptionsOnGUI(optionsID); + AnimationWindowStyles.separator.Draw(new Rect(hierarchyWidth, 0, 1, position.height), false, false, false, false); + } RenderEventTooltip(); } @@ -525,7 +529,7 @@ private void TabSelectionOnGUI() GUILayout.FlexibleSpace(); EditorGUI.BeginChangeCheck(); GUILayout.Toggle(!m_State.showCurveEditor, AnimationWindowStyles.dopesheet, AnimationWindowStyles.miniToolbarButton, GUILayout.Width(kToggleButtonWidth)); - GUILayout.Toggle(m_State.showCurveEditor, AnimationWindowStyles.curves, AnimationWindowStyles.miniToolbarButton, GUILayout.Width(kToggleButtonWidth)); + GUILayout.Toggle(m_State.showCurveEditor, AnimationWindowStyles.curves, EditorStyles.toolbarButtonRight, GUILayout.Width(kToggleButtonWidth)); if (EditorGUI.EndChangeCheck()) { SwitchBetweenCurvesAndDopesheet(); @@ -571,7 +575,7 @@ private void FrameRateInputFieldOnGUI() using (new EditorGUI.DisabledScope(!selection.animationIsEditable)) { - GUILayout.Label(AnimationWindowStyles.samples, EditorStyles.miniLabel); + GUILayout.Label(AnimationWindowStyles.samples, EditorStyles.toolbarLabel); EditorGUI.BeginChangeCheck(); int clipFrameRate = EditorGUILayout.DelayedIntField((int)m_State.clipFrameRate, EditorStyles.toolbarTextField, GUILayout.Width(kIntFieldWidth)); @@ -666,7 +670,6 @@ private void TimeRulerOnGUI(Rect timeRulerRect) Rect timeRulerRectNoScrollbar = new Rect(timeRulerRect.xMin, timeRulerRect.yMin, timeRulerRect.width - kSliderThickness, timeRulerRect.height); Rect timeRulerBackgroundRect = timeRulerRectNoScrollbar; - timeRulerBackgroundRect.y += 2; GUI.Box(timeRulerBackgroundRect, GUIContent.none, AnimationWindowStyles.timeRulerBackground); if (!m_State.disabled) @@ -717,14 +720,14 @@ private GenericMenu GenerateOptionsMenu() private void OptionsOnGUI(int controlID) { - Rect layoutRect = new Rect(hierarchyWidth - 1f, 1f, contentWidth, layoutRowHeight); + Rect layoutRect = new Rect(hierarchyWidth, 0f, contentWidth, layoutRowHeight); GUI.BeginGroup(layoutRect); - Vector2 optionsSize = EditorStyles.toolbarButton.CalcSize(AnimationWindowStyles.optionsContent); - Rect optionsRect = new Rect(layoutRect.width - optionsSize.x, 0f, optionsSize.x, optionsSize.y); - - if (EditorGUI.DropdownButton(controlID, optionsRect, AnimationWindowStyles.optionsContent, EditorStyles.toolbarButton)) + Vector2 optionsSize = EditorStyles.toolbarButtonRight.CalcSize(AnimationWindowStyles.optionsContent); + Rect optionsRect = new Rect(layoutRect.width - kSliderThickness, 0f, optionsSize.x, optionsSize.y); + GUI.Box(optionsRect, GUIContent.none, AnimationWindowStyles.animPlayToolBar); + if (EditorGUI.DropdownButton(controlID, optionsRect, AnimationWindowStyles.optionsContent, AnimationWindowStyles.optionsButton)) { var menu = GenerateOptionsMenu(); menu.ShowAsContext(); diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowControl.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowControl.cs index 50bdf86e29..7df495c23e 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowControl.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowControl.cs @@ -558,6 +558,7 @@ private void RebuildGraph(Animator animator) m_ClipPlayable.SetApplyFootIK(false); m_CandidateClipPlayable = AnimationClipPlayable.Create(m_Graph, m_CandidateClip); + m_CandidateClipPlayable.SetApplyFootIK(false); IAnimationWindowPreview[] previewComponents = FetchPostProcessComponents(); bool requiresDefaultPose = previewComponents != null && previewComponents.Length > 0; @@ -590,6 +591,7 @@ private void RebuildGraph(Animator animator) AnimationWindowUtility.CreateDefaultCurves(state, m_DefaultPose, streamBindings); m_DefaultPosePlayable = AnimationClipPlayable.Create(m_Graph, m_DefaultPose); + m_DefaultPosePlayable.SetApplyFootIK(false); mixer.ConnectInput(inputIndex++, m_DefaultPosePlayable, 0, 1.0f); } diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowState.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowState.cs index f99df8e428..2945f0ca6d 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowState.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowState.cs @@ -347,11 +347,18 @@ public void ForceRefresh() refresh = RefreshType.Everything; } + private void PurgeSelection() + { + Object.DestroyImmediate(m_Selection); + m_Selection = null; + } + public void OnEnable() { hideFlags = HideFlags.HideAndDontSave; AnimationUtility.onCurveWasModified += CurveWasModified; Undo.undoRedoPerformed += UndoRedoPerformed; + AssemblyReloadEvents.beforeAssemblyReload += PurgeSelection; // NoOps... onStartLiveEdit += () => {}; @@ -367,6 +374,7 @@ public void OnDisable() { AnimationUtility.onCurveWasModified -= CurveWasModified; Undo.undoRedoPerformed -= UndoRedoPerformed; + AssemblyReloadEvents.beforeAssemblyReload -= PurgeSelection; m_ControlInterface.OnDisable(); } diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowStyles.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowStyles.cs index cb785b822a..03e4d97dcd 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowStyles.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowStyles.cs @@ -38,7 +38,7 @@ internal class AnimationWindowStyles public static GUIContent readOnlyPropertiesLabel = EditorGUIUtility.TrTextContent("Animation Clip is Read-Only"); public static GUIContent readOnlyPropertiesButton = EditorGUIUtility.TrTextContent("Show Read-Only Properties"); - public static GUIContent optionsContent = EditorGUIUtility.IconContent("_Popup"); + public static GUIContent optionsContent = EditorGUIUtility.IconContent("_Menu"); public static GUIStyle playHead = "AnimationPlayHead"; @@ -60,14 +60,19 @@ internal class AnimationWindowStyles public static GUIStyle popupCurveEditorBackground = "PopupCurveEditorBackground"; public static GUIStyle popupCurveEditorSwatch = "PopupCurveEditorSwatch"; public static GUIStyle popupCurveSwatchBackground = "PopupCurveSwatchBackground"; + public static GUIStyle separator = new GUIStyle("AnimLeftPaneSeparator"); - public static GUIStyle miniToolbar = "PreToolbar"; + public static GUIStyle toolbarBottom = "ToolbarBottom"; + public static GUIStyle optionsButton = new GUIStyle(EditorStyles.toolbarButtonRight); public static GUIStyle miniToolbarButton = new GUIStyle(EditorStyles.toolbarButton); public static GUIStyle toolbarLabel = new GUIStyle(AnimationWindowStyles.animClipToolbarPopup); public static void Initialize() { toolbarLabel.normal.background = null; + optionsButton.padding = new RectOffset(); + optionsButton.imagePosition = ImagePosition.ImageOnly; + optionsButton.contentOffset = new Vector2(-7, 0); } } } diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowUtility.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowUtility.cs index 64a1704e92..114a343176 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowUtility.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowUtility.cs @@ -13,7 +13,6 @@ using Object = UnityEngine.Object; using TangentMode = UnityEditor.AnimationUtility.TangentMode; -using CurveModifiedType = UnityEditor.AnimationUtility.CurveModifiedType; namespace UnityEditorInternal { @@ -827,29 +826,32 @@ public static string GetPropertyGroupName(string propertyName) public static float GetNextKeyframeTime(AnimationWindowCurve[] curves, float currentTime, float frameRate) { - float candidate = float.MaxValue; + AnimationKeyTime candidateKeyTime = AnimationKeyTime.Frame(int.MaxValue, frameRate); AnimationKeyTime time = AnimationKeyTime.Time(currentTime, frameRate); AnimationKeyTime nextTime = AnimationKeyTime.Frame(time.frame + 1, frameRate); - bool found = false; foreach (AnimationWindowCurve curve in curves) { foreach (AnimationWindowKeyframe keyframe in curve.m_Keyframes) { - if (keyframe.time < candidate && keyframe.time >= nextTime.time) + AnimationKeyTime keyTime = AnimationKeyTime.Time(keyframe.time, frameRate); + if (keyTime.frame <= candidateKeyTime.frame && keyTime.frame >= nextTime.frame) { - candidate = keyframe.time; - found = true; + if (keyframe.time <= candidateKeyTime.time) + { + candidateKeyTime = keyTime; + found = true; + } } } } - return found ? candidate : time.time; + return found ? candidateKeyTime.time : time.time; } public static float GetPreviousKeyframeTime(AnimationWindowCurve[] curves, float currentTime, float frameRate) { - float candidate = float.MinValue; + AnimationKeyTime candidateKeyTime = AnimationKeyTime.Time(float.MinValue, frameRate); AnimationKeyTime time = AnimationKeyTime.Time(currentTime, frameRate); AnimationKeyTime previousTime = AnimationKeyTime.Frame(time.frame - 1, frameRate); @@ -859,14 +861,19 @@ public static float GetPreviousKeyframeTime(AnimationWindowCurve[] curves, float { foreach (AnimationWindowKeyframe keyframe in curve.m_Keyframes) { - if (keyframe.time > candidate && keyframe.time <= previousTime.time) + AnimationKeyTime keyTime = AnimationKeyTime.Time(keyframe.time, frameRate); + if (keyTime.frame >= candidateKeyTime.frame && keyTime.frame <= previousTime.frame) { - candidate = keyframe.time; - found = true; + if (keyTime.time >= candidateKeyTime.time) + { + candidateKeyTime = keyTime; + found = true; + } } } } - return found ? candidate : time.time; + + return found ? candidateKeyTime.time : time.time; } // Add animator, controller and clip to gameobject if they are missing to make this gameobject animatable diff --git a/Editor/Mono/Animation/AnimationWindow/Deprecated/EditorGUIExt.cs b/Editor/Mono/Animation/AnimationWindow/Deprecated/EditorGUIExt.cs index a6e2691b3f..eef0565036 100644 --- a/Editor/Mono/Animation/AnimationWindow/Deprecated/EditorGUIExt.cs +++ b/Editor/Mono/Animation/AnimationWindow/Deprecated/EditorGUIExt.cs @@ -47,8 +47,7 @@ static bool DoRepeatButton(Rect position, GUIContent content, GUIStyle style, Fo } return false; case EventType.Repaint: - style.Draw(position, content, id); - // Handles.Repaint (); + style.Draw(position, content, id, false, position.Contains(Event.current.mousePosition)); return id == GUIUtility.hotControl && position.Contains(Event.current.mousePosition); } return false; diff --git a/Editor/Mono/Animation/AnimationWindow/MinMaxCurveEditorWindow.cs b/Editor/Mono/Animation/AnimationWindow/MinMaxCurveEditorWindow.cs index a4c7c7ac65..5b44306564 100644 --- a/Editor/Mono/Animation/AnimationWindow/MinMaxCurveEditorWindow.cs +++ b/Editor/Mono/Animation/AnimationWindow/MinMaxCurveEditorWindow.cs @@ -96,7 +96,7 @@ void InitCurvePresets() if (m_CurvePresets == null) { AnimationCurve max = m_CurveEditor.animationCurves[0].curve; - AnimationCurve min = m_CurveEditor.animationCurves.Length > 1 ? m_CurveEditor.animationCurves[1].curve : null; + AnimationCurve min = m_CurveEditor.animationCurves.Length > 1 ? m_CurveEditor.animationCurves[1].curve : new AnimationCurve(); // Selection callback for library window System.Action presetSelectedCallback = delegate(DoubleCurve presetCurve) @@ -110,6 +110,9 @@ void InitCurvePresets() doubleCurve.maxCurve.postWrapMode = presetCurve.maxCurve.postWrapMode; doubleCurve.maxCurve.preWrapMode = presetCurve.maxCurve.preWrapMode; + m_MinCurve = doubleCurve.minCurve; + m_MaxCurve = doubleCurve.maxCurve; + m_CurveEditor.SelectNone(); RefreshShownCurves(); SendEvent("CurveChanged", true); diff --git a/Editor/Mono/Animation/ZoomableArea.cs b/Editor/Mono/Animation/ZoomableArea.cs index 3c1858328a..5d79ed8a82 100644 --- a/Editor/Mono/Animation/ZoomableArea.cs +++ b/Editor/Mono/Animation/ZoomableArea.cs @@ -258,12 +258,12 @@ public Styles(bool minimalGUI) if (minimalGUI) { visualSliderWidth = 0; - sliderWidth = 12; + sliderWidth = 13; } else { - visualSliderWidth = 12; - sliderWidth = 12; + visualSliderWidth = 13; + sliderWidth = 13; } } @@ -518,6 +518,20 @@ private Rect shownAreaInsideMarginsInternal } } + float GetWidthInsideMargins(float widthWithMargins, bool substractSliderWidth = false) + { + float width = (widthWithMargins < kMinWidth) ? kMinWidth : widthWithMargins; + float widthInsideMargins = width - leftmargin - rightmargin - (substractSliderWidth ? (m_VSlider ? styles.visualSliderWidth : 0) : 0); + return Mathf.Max(widthInsideMargins, kMinWidth); + } + + float GetHeightInsideMargins(float heightWithMargins, bool substractSliderHeight = false) + { + float height = (heightWithMargins < kMinHeight) ? kMinHeight : heightWithMargins; + float heightInsideMargins = height - topmargin - bottommargin - (substractSliderHeight ? (m_HSlider ? styles.visualSliderWidth : 0) : 0); + return Mathf.Max(heightInsideMargins, kMinHeight); + } + public virtual Bounds drawingBounds { get @@ -739,10 +753,11 @@ void SliderGUI() } min = shownXMin; max = shownXMin + shownXRange; + float rectWidthWithinMargins = GetWidthInsideMargins(rect.width, true); if (min > area.xMin) - min = Mathf.Min(min, max - rect.width / m_HScaleMax); + min = Mathf.Min(min, max - rectWidthWithinMargins / m_HScaleMax); if (max < area.xMax) - max = Mathf.Max(max, min + rect.width / m_HScaleMax); + max = Mathf.Max(max, min + rectWidthWithinMargins / m_HScaleMax); SetShownHRangeInsideMargins(min, max); } @@ -773,10 +788,11 @@ void SliderGUI() } min = -(shownYMin + shownYRange); max = -shownYMin; + float rectHeightWithinMargins = GetHeightInsideMargins(rect.height, true); if (min > area.yMin) - min = Mathf.Min(min, max - rect.height / m_VScaleMax); + min = Mathf.Min(min, max - rectHeightWithinMargins / m_VScaleMax); if (max < area.yMax) - max = Mathf.Max(max, min + rect.height / m_VScaleMax); + max = Mathf.Max(max, min + rectHeightWithinMargins / m_VScaleMax); SetShownVRangeInsideMargins(min, max); } else @@ -802,10 +818,11 @@ void SliderGUI() } min = shownYMin; max = shownYMin + shownYRange; + float rectHeightWithinMargins = GetHeightInsideMargins(rect.height, true); if (min > area.yMin) - min = Mathf.Min(min, max - rect.height / m_VScaleMax); + min = Mathf.Min(min, max - rectHeightWithinMargins / m_VScaleMax); if (max < area.yMax) - max = Mathf.Max(max, min + rect.height / m_VScaleMax); + max = Mathf.Max(max, min + rectHeightWithinMargins / m_VScaleMax); SetShownVRangeInsideMargins(min, max); } } @@ -906,59 +923,80 @@ public void SetTransform(Vector2 newTranslation, Vector2 newScale) public void EnforceScaleAndRange() { - // Minimum scale might also be constrained by maximum range - float constrainedHScaleMin = rect.width / m_HScaleMin; - float constrainedVScaleMin = rect.height / m_VScaleMin; - if (hRangeMax != Mathf.Infinity && hRangeMin != Mathf.NegativeInfinity) - constrainedHScaleMin = Mathf.Min(constrainedHScaleMin, hRangeMax - hRangeMin); - if (vRangeMax != Mathf.Infinity && vRangeMin != Mathf.NegativeInfinity) - constrainedVScaleMin = Mathf.Min(constrainedVScaleMin, vRangeMax - vRangeMin); - Rect oldArea = m_LastShownAreaInsideMargins; Rect newArea = shownAreaInsideMargins; if (newArea == oldArea) return; - float epsilon = 0.00001f; + float minChange = 0.01f; - if (newArea.width < oldArea.width - epsilon) - { - float xLerp = Mathf.InverseLerp(oldArea.width, newArea.width, rect.width / m_HScaleMax); - newArea = new Rect( - Mathf.Lerp(oldArea.x, newArea.x, xLerp), - newArea.y, - Mathf.Lerp(oldArea.width, newArea.width, xLerp), - newArea.height - ); - } - if (newArea.height < oldArea.height - epsilon) - { - float yLerp = Mathf.InverseLerp(oldArea.height, newArea.height, rect.height / m_VScaleMax); - newArea = new Rect( - newArea.x, - Mathf.Lerp(oldArea.y, newArea.y, yLerp), - newArea.width, - Mathf.Lerp(oldArea.height, newArea.height, yLerp) - ); - } - if (newArea.width > oldArea.width + epsilon) + if (!Mathf.Approximately(newArea.width, oldArea.width)) { - float xLerp = Mathf.InverseLerp(oldArea.width, newArea.width, constrainedHScaleMin); + float constrainedWidth = newArea.width; + if (newArea.width < oldArea.width) + { + // The shown area decreasing in size means the scale is increasing. This happens e.g. while zooming in. + // Only the max scale restricts the shown area size here, range has no influence. + constrainedWidth = GetWidthInsideMargins(drawRect.width / m_HScaleMax, false); + } + else + { + constrainedWidth = GetWidthInsideMargins(drawRect.width / m_HScaleMin, false); + + if (hRangeMax != Mathf.Infinity && hRangeMin != Mathf.NegativeInfinity) + { + // range only has an influence if it is enforced, i.e. not infinity + float denum = hRangeMax - hRangeMin; + if (denum < kMinWidth) denum = kMinWidth; + + constrainedWidth = Mathf.Min(constrainedWidth, denum); + } + } + + float xLerp = Mathf.InverseLerp(oldArea.width, newArea.width, constrainedWidth); + float newWidth = Mathf.Lerp(oldArea.width, newArea.width, xLerp); + float widthChange = Mathf.Abs(newWidth - newArea.width); newArea = new Rect( - Mathf.Lerp(oldArea.x, newArea.x, xLerp), + // only affect the position if there was any significant change in width + // this fixes an issue where if width was only different due to rounding issues, position changes are ignored as xLerp comes back 0 (or very nearly 0) + widthChange > minChange ? Mathf.Lerp(oldArea.x, newArea.x, xLerp) : newArea.x, newArea.y, - Mathf.Lerp(oldArea.width, newArea.width, xLerp), + newWidth, newArea.height ); } - if (newArea.height > oldArea.height + epsilon) + if (!Mathf.Approximately(newArea.height, oldArea.height)) { - float yLerp = Mathf.InverseLerp(oldArea.height, newArea.height, constrainedVScaleMin); + float constrainedHeight = newArea.height; + if (newArea.height < oldArea.height) + { + // The shown area decreasing in size means the scale is increasing. This happens e.g. while zooming in. + // Only the max scale restricts the shown area size here, range has no influence. + constrainedHeight = GetHeightInsideMargins(drawRect.height / m_VScaleMax, false); + } + else + { + constrainedHeight = GetHeightInsideMargins(drawRect.height / m_VScaleMin, false); + + if (vRangeMax != Mathf.Infinity && vRangeMin != Mathf.NegativeInfinity) + { + // range only has an influence if it is enforced, i.e. not infinity + float denum = vRangeMax - vRangeMin; + if (denum < kMinHeight) denum = kMinHeight; + constrainedHeight = Mathf.Min(constrainedHeight, denum); + } + } + + float yLerp = Mathf.InverseLerp(oldArea.height, newArea.height, constrainedHeight); + float newHeight = Mathf.Lerp(oldArea.height, newArea.height, yLerp); + float heightChange = Mathf.Abs(newHeight - newArea.height); newArea = new Rect( newArea.x, - Mathf.Lerp(oldArea.y, newArea.y, yLerp), + // only affect the position if there was any significant change in height + // this fixes an issue where if height was only different due to rounding issues, position changes are ignored as yLerp comes back 0 (or very nearly 0) + heightChange > minChange ? Mathf.Lerp(oldArea.y, newArea.y, yLerp) : newArea.y, newArea.width, - Mathf.Lerp(oldArea.height, newArea.height, yLerp) + newHeight ); } @@ -973,7 +1011,7 @@ public void EnforceScaleAndRange() newArea.y = vRangeMax - newArea.height; shownAreaInsideMarginsInternal = newArea; - m_LastShownAreaInsideMargins = newArea; + m_LastShownAreaInsideMargins = shownAreaInsideMargins; } public float PixelToTime(float pixelX, Rect rect) diff --git a/Editor/Mono/Annotation/AnnotationWindow.cs b/Editor/Mono/Annotation/AnnotationWindow.cs index 8514315499..9ea4109d8b 100644 --- a/Editor/Mono/Annotation/AnnotationWindow.cs +++ b/Editor/Mono/Annotation/AnnotationWindow.cs @@ -78,7 +78,6 @@ private enum EnabledState readonly string textGizmoVisible = L10n.Tr("Show/Hide Gizmo"); GUIContent iconToggleContent = EditorGUIUtility.TrTextContent("", "Show/Hide Icon"); GUIContent iconSelectContent = EditorGUIUtility.TrTextContent("", "Select Icon"); - GUIContent icon3dGizmoContent = EditorGUIUtility.TrTextContent("3D Icons"); GUIContent showOutlineContent = EditorGUIUtility.TrTextContent("Selection Outline"); GUIContent showWireframeContent = EditorGUIUtility.TrTextContent("Selection Wire"); @@ -144,7 +143,7 @@ static public void IconChanged() float GetTopSectionHeight() { const int numberOfControls = 3; - return EditorGUI.kSingleLineHeight * numberOfControls + EditorGUI.kControlVerticalSpacing * numberOfControls; + return EditorGUI.kSingleLineHeight * numberOfControls + EditorGUI.kControlVerticalSpacing * (numberOfControls - 1) + EditorStyles.inspectorBig.padding.bottom; } void OnEnable() @@ -537,7 +536,7 @@ void DrawListHeader(string header, List elements, Rect rect, ref bool hea // Column headers Rect columnRect = rect; - columnRect.y -= gizmoTextSize.y - 3; + columnRect.y -= gizmoTextSize.y + 3; columnRect.x = rect.width - gizmoTextRightAlign; GUI.Label(columnRect, gizmoText, m_Styles.columnHeaderStyle); @@ -601,21 +600,24 @@ void DrawListElement(Rect rect, bool even, AInfo ainfo) GUI.DrawTexture(div, EditorGUIUtility.whiteTexture, ScaleMode.StretchToFill); GUI.color = Color.white; - Rect arrowRect = iconRect; - arrowRect.x += 18; - arrowRect.y += 0; - arrowRect.width = 9; - - if (GUI.Button(arrowRect, iconSelectContent, m_Styles.iconDropDown)) + if (!ainfo.IsDisabled()) { - Object script = EditorGUIUtility.GetScript(ainfo.m_ScriptClass); - if (script != null) + Rect arrowRect = iconRect; + arrowRect.x += 18; + arrowRect.y += 0; + arrowRect.width = 9; + + if (GUI.Button(arrowRect, iconSelectContent, m_Styles.iconDropDown)) { - m_LastScriptThatHasShownTheIconSelector = ainfo.m_ScriptClass; - if (IconSelector.ShowAtPosition(script, arrowRect, true)) + Object script = EditorGUIUtility.GetScript(ainfo.m_ScriptClass); + if (script != null) { - IconSelector.SetMonoScriptIconChangedCallback(MonoScriptIconChanged); - GUIUtility.ExitGUI(); + m_LastScriptThatHasShownTheIconSelector = ainfo.m_ScriptClass; + if (IconSelector.ShowAtPosition(script, arrowRect, true)) + { + IconSelector.SetMonoScriptIconChangedCallback(MonoScriptIconChanged); + GUIUtility.ExitGUI(); + } } } } @@ -688,7 +690,7 @@ void SetGizmoState(AInfo ainfo, bool addToMostRecentChanged = true) internal class AInfo : System.IComparable, System.IEquatable { // Similar values as in Annotation (in AnnotationManager.h) - public enum Flags { kHasIcon = 1, kHasGizmo = 2 }; + public enum Flags { kHasIcon = 1, kHasGizmo = 2, kIsDisabled = 4 }; public AInfo(bool gizmoEnabled, bool iconEnabled, int flags, int classID, string scriptClass) { @@ -720,6 +722,11 @@ public bool HasIcon() return (m_Flags & (int)Flags.kHasIcon) > 0; } + public bool IsDisabled() + { + return (m_Flags & (int)Flags.kIsDisabled) > 0; + } + public int CompareTo(object obj) { AInfo other = obj as AInfo; diff --git a/Editor/Mono/Annotation/SceneRenderModeWindow.cs b/Editor/Mono/Annotation/SceneRenderModeWindow.cs index 2867b1cc56..01f186edea 100644 --- a/Editor/Mono/Annotation/SceneRenderModeWindow.cs +++ b/Editor/Mono/Annotation/SceneRenderModeWindow.cs @@ -167,7 +167,7 @@ private float windowHeight modes = Styles.sBuiltinCameraModes.Length + SceneView.userDefinedModes.Count(mode => m_SceneView.IsCameraDrawModeEnabled(mode)); } - int separators = headers - 2; + int separators = headers - 1; return ((headers + modes) * EditorGUI.kSingleLineHeight) + (kSeparatorHeight * separators) + kShowLightmapResolutionHeight; } } diff --git a/Editor/Mono/Annotation/SceneViewCameraWindow.cs b/Editor/Mono/Annotation/SceneViewCameraWindow.cs index 72f1f9dab4..b38fd4a299 100644 --- a/Editor/Mono/Annotation/SceneViewCameraWindow.cs +++ b/Editor/Mono/Annotation/SceneViewCameraWindow.cs @@ -109,7 +109,7 @@ private void Draw(Rect rect) // fov isn't applicable in orthographic mode, and orthographic size is controlled by the user zoom using (new EditorGUI.DisabledScope(m_SceneView.orthographic)) { - settings.fieldOfView = EditorGUILayout.Slider(m_FieldOfView, settings.fieldOfView, 4f, 179f); + settings.fieldOfView = EditorGUILayout.Slider(m_FieldOfView, settings.fieldOfView, 4f, 120f); } settings.dynamicClip = EditorGUILayout.Toggle(m_DynamicClip, settings.dynamicClip); diff --git a/Editor/Mono/AssemblyHelper.cs b/Editor/Mono/AssemblyHelper.cs index 56651f2bc5..ea64eff0fa 100644 --- a/Editor/Mono/AssemblyHelper.cs +++ b/Editor/Mono/AssemblyHelper.cs @@ -263,6 +263,12 @@ public static string[] GetDefaultAssemblySearchPaths() foreach (var asm in precompiledAssemblies) searchPaths.Add(Path.GetDirectoryName(asm.Path)); + // Add Unity compiled assembly output directory. + // Required for MonoBehaviour derived types like UIBehaviour that + // were previous in a precompiled UnityEngine.UI.dll, but are now + // compiled in a package. + searchPaths.Add("Library/ScriptAssemblies"); + return searchPaths.ToArray(); } @@ -327,44 +333,12 @@ struct GetAssemblyResolverData public string[] SearchDirs; } - static GetAssemblyResolverData GetAssemblyResolver(BuildTarget targetPlatform, bool isEditor, string assemblyPathName, string[] searchDirs) - { - var target = ModuleManager.GetTargetStringFromBuildTarget(targetPlatform); - var extension = ModuleManager.GetCompilationExtension(target); - var extraPaths = extension.GetCompilerExtraAssemblyPaths(isEditor, assemblyPathName); - if (extraPaths != null && extraPaths.Length > 0) - { - var dirs = new List(searchDirs); - dirs.AddRange(extraPaths); - searchDirs = dirs.ToArray(); - } - - var assemblyResolver = extension.GetAssemblyResolver(isEditor, assemblyPathName, searchDirs); - - return new GetAssemblyResolverData - { - Resolver = assemblyResolver, - SearchDirs = searchDirs - }; - } - /// Extract information about all types in the specified assembly, searchDirs might be used to resolve dependencies. static public AssemblyTypeInfoGenerator.ClassInfo[] ExtractAssemblyTypeInfo(BuildTarget targetPlatform, bool isEditor, string assemblyPathName, string[] searchDirs) { try { - var assemblyResolverData = GetAssemblyResolver(targetPlatform, isEditor, assemblyPathName, searchDirs); - - AssemblyTypeInfoGenerator gen; - if (assemblyResolverData.Resolver == null) - { - gen = new AssemblyTypeInfoGenerator(assemblyPathName, assemblyResolverData.SearchDirs); - } - else - { - gen = new AssemblyTypeInfoGenerator(assemblyPathName, assemblyResolverData.Resolver); - } - + AssemblyTypeInfoGenerator gen = new AssemblyTypeInfoGenerator(assemblyPathName, searchDirs); return gen.GatherClassInfo(); } catch (System.Exception ex) @@ -426,16 +400,12 @@ public static RuntimeInitializeOnLoadMethodsData ExtractPlayerRuntimeInitializeO { try { - var assemblyResolverData = GetAssemblyResolver(targetPlatform, false, assemblyPath, searchDirs); - - if (assemblyResolverData.Resolver == null) - { - var resolver = new DefaultAssemblyResolver(); - foreach (var searchDir in searchDirs) - resolver.AddSearchDirectory(searchDir); + var assemblyResolverData = new GetAssemblyResolverData { SearchDirs = searchDirs, }; + var resolver = new DefaultAssemblyResolver(); + foreach (var searchDir in searchDirs) + resolver.AddSearchDirectory(searchDir); - assemblyResolverData.Resolver = resolver; - } + assemblyResolverData.Resolver = resolver; var assembly = AssemblyDefinition.ReadAssembly(assemblyPath, new ReaderParameters { diff --git a/Editor/Mono/AssetDatabase/AssetDatabase.cs b/Editor/Mono/AssetDatabase/AssetDatabase.cs index 712d81c13c..92bbe630a5 100644 --- a/Editor/Mono/AssetDatabase/AssetDatabase.cs +++ b/Editor/Mono/AssetDatabase/AssetDatabase.cs @@ -4,7 +4,9 @@ using System; using System.Collections.Generic; +using UnityEditor.VersionControl; using UnityEngine.Scripting; +using uei = UnityEngine.Internal; namespace UnityEditor { @@ -56,7 +58,7 @@ private static void Internal_CallImportPackageFailed(string packageName, string importPackageFailed(packageName, errorMessage); } - public static void IsOpenForEdit(string[] assetOrMetaFilePaths, List outNotEditablePaths, StatusQueryOptions statusQueryOptions = StatusQueryOptions.UseCachedIfPossible) + public static void IsOpenForEdit(string[] assetOrMetaFilePaths, List outNotEditablePaths, [uei.DefaultValue("StatusQueryOptions.UseCachedIfPossible")] StatusQueryOptions statusQueryOptions = StatusQueryOptions.UseCachedIfPossible) { if (assetOrMetaFilePaths == null) throw new ArgumentNullException(nameof(assetOrMetaFilePaths)); @@ -66,5 +68,24 @@ public static void IsOpenForEdit(string[] assetOrMetaFilePaths, List out AssetModificationProcessorInternal.IsOpenForEdit(assetOrMetaFilePaths, outNotEditablePaths, statusQueryOptions); UnityEngine.Profiling.Profiler.EndSample(); } + + public static bool MakeEditable(string path) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + return MakeEditable(new[] {path}); + } + + public static bool MakeEditable(string[] paths, string prompt = null, List outNotEditablePaths = null) + { + if (paths == null) + throw new ArgumentNullException(nameof(paths)); + + ChangeSet changeSet = null; + if (!Provider.HandlePreCheckoutCallback(ref paths, ref changeSet)) + return false; + + return Provider.MakeEditableImpl(paths, prompt, changeSet, outNotEditablePaths); + } } } diff --git a/Editor/Mono/AssetDatabase/AssetMoveInfo.cs b/Editor/Mono/AssetDatabase/AssetMoveInfo.cs index 2dd2efc3c4..deb4007b65 100644 --- a/Editor/Mono/AssetDatabase/AssetMoveInfo.cs +++ b/Editor/Mono/AssetDatabase/AssetMoveInfo.cs @@ -6,7 +6,7 @@ namespace UnityEditor.Experimental { - internal struct AssetMoveInfo : IEquatable + public struct AssetMoveInfo : IEquatable { public AssetMoveInfo(string sourceAssetPath, string destinationAssetPath) { diff --git a/Editor/Mono/AssetDatabase/AssetPreview.bindings.cs b/Editor/Mono/AssetDatabase/AssetPreview.bindings.cs index 44d04e28d4..4cca1c5fc8 100644 --- a/Editor/Mono/AssetDatabase/AssetPreview.bindings.cs +++ b/Editor/Mono/AssetDatabase/AssetPreview.bindings.cs @@ -33,6 +33,14 @@ internal static Texture2D GetAssetPreview(int instanceID) [FreeFunction("AssetPreviewBindings::HasAssetPreview")] internal static extern bool HasAssetPreview(int instanceID, int clientID); + internal static Texture2D GetAssetPreviewFromGUID(string guid) + { + return GetAssetPreviewFromGUID(guid, kSharedClientID); + } + + [FreeFunction("AssetPreviewBindings::GetAssetPreviewFromGUID")] + internal static extern Texture2D GetAssetPreviewFromGUID(string guid, int clientID); + public static bool IsLoadingAssetPreview(int instanceID) { return IsLoadingAssetPreview(instanceID, kSharedClientID); diff --git a/Editor/Mono/AssetDatabase/AssetsModifiedProcessor.cs b/Editor/Mono/AssetDatabase/AssetsModifiedProcessor.cs index a7e33890e5..84164ffc05 100644 --- a/Editor/Mono/AssetDatabase/AssetsModifiedProcessor.cs +++ b/Editor/Mono/AssetDatabase/AssetsModifiedProcessor.cs @@ -7,9 +7,9 @@ namespace UnityEditor.Experimental { - internal abstract class AssetsModifiedProcessor + public abstract class AssetsModifiedProcessor { - internal HashSet assetsReportedChanged { get; set; } + public HashSet assetsReportedChanged { get; set; } protected void ReportAssetChanged(string assetChanged) { diff --git a/Editor/Mono/AssetModificationProcessor.cs b/Editor/Mono/AssetModificationProcessor.cs index ec42dbf5d3..cbd83165f0 100644 --- a/Editor/Mono/AssetModificationProcessor.cs +++ b/Editor/Mono/AssetModificationProcessor.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using UnityEngine; using UnityEditor.VersionControl; using UnityEditorInternal; @@ -111,22 +112,26 @@ static void OnWillCreateAsset(string path) } } + // ReSharper disable once UnusedMember.Local - invoked from native code static void FileModeChanged(string[] assets, UnityEditor.VersionControl.FileMode mode) { - // Make sure that all assets are checked out in version control and - // that we have the most recent status - if (Provider.enabled) - { - var editableAssets = new string[assets.Length]; - if (Provider.MakeEditable(assets, editableAssets)) - { - // TODO: handle partial results from MakeEditable i.e. editableassets - Provider.SetFileMode(assets, mode); - } - } + if (!Provider.enabled) + return; + + // if we happen to be disconnected or work offline, there's not much we can do; + // just ignore the file mode and hope that VCS client/project is setup to handle + // appropriate file types correctly + if (!Provider.isActive) + return; + + // we'll want to re-serialize these assets in different (text vs binary) mode; + // make sure they are editable first + AssetDatabase.MakeEditable(assets); + Provider.SetFileMode(assets, mode); } // Postprocess on all assets once an automatic import has completed + // ReSharper disable once UnusedMember.Local - invoked from native code static void OnWillSaveAssets(string[] assets, out string[] assetsThatShouldBeSaved, out string[] assetsThatShouldBeReverted, bool explicitlySaveAsset) { assetsThatShouldBeReverted = new string[0]; @@ -169,16 +174,15 @@ static void OnWillSaveAssets(string[] assets, out string[] assetsThatShouldBeSav AssetDatabase.IsOpenForEdit(assetsThatShouldBeSaved, assetsNotOpened, StatusQueryOptions.ForceUpdate); assets = assetsNotOpened.ToArray(); - // Try to checkout if needed. This may fail but is caught below. - var editableAssets = new string[assets.Length]; - if (assets.Length != 0 && !Provider.MakeEditable(assets, editableAssets)) + // Try to checkout if needed + var notEditableAssets = new List(); + if (assets.Length != 0 && !AssetDatabase.MakeEditable(assets, null, notEditableAssets)) { // only save assets that can be made editable (not locked by someone else, etc.), // unless we are in the behavior mode that just overwrites everything anyway if (!EditorUserSettings.overwriteFailedCheckoutAssets) { - editableAssets = editableAssets.Where(a => a != null).ToArray(); - assetsThatShouldBeReverted = assets.Except(editableAssets).ToArray(); + assetsThatShouldBeReverted = notEditableAssets.ToArray(); assetsThatShouldBeSaved = assetsThatShouldBeSaved.Except(assetsThatShouldBeReverted).ToArray(); } } @@ -274,11 +278,26 @@ static MethodInfo[] GetIsOpenForEditMethods() return isOpenForEditMethods; } - static bool IsAssetInReadOnlyFolder(string assetPath) + enum Editability + { + Never, + Always, + Maybe + } + + static Editability GetPathEditability(string assetPath) { + // read-only asset locations (e.g. shared packages) are considered not editable bool rootFolder, readOnly; bool validPath = AssetDatabase.GetAssetFolderInfo(assetPath, out rootFolder, out readOnly); - return validPath && readOnly; + if (validPath && readOnly) + return Editability.Never; + + // other paths that are not know to asset database, and not versioned, are considered always editable + if (!Provider.PathIsVersioned(assetPath)) + return Editability.Always; + + return Editability.Maybe; } static bool IsOpenForEditViaScriptCallbacks(string assetPath, ref string message) @@ -301,7 +320,10 @@ internal static bool IsOpenForEdit(string assetPath, out string message, StatusQ if (string.IsNullOrEmpty(assetPath)) return true; // treat empty/null paths as editable (might be under Library folders etc.) - if (IsAssetInReadOnlyFolder(assetPath)) + var editability = GetPathEditability(assetPath); + if (editability == Editability.Always) + return true; + if (editability == Editability.Never) return false; if (!AssetModificationHook.IsOpenForEdit(assetPath, out message, statusOptions)) return false; @@ -322,7 +344,10 @@ internal static void IsOpenForEdit(string[] assetOrMetaFilePaths, List o { if (string.IsNullOrEmpty(path)) continue; // treat empty/null paths as editable (might be under Library folders etc.) - if (IsAssetInReadOnlyFolder(path)) + var editability = GetPathEditability(path); + if (editability == Editability.Always) + continue; + if (editability == Editability.Never) { outNotEditablePaths.Add(path); continue; diff --git a/Editor/Mono/AssetPipeline/AssetImportContext.bindings.cs b/Editor/Mono/AssetPipeline/AssetImportContext.bindings.cs index 96d819c81a..b9af2bcd7e 100644 --- a/Editor/Mono/AssetPipeline/AssetImportContext.bindings.cs +++ b/Editor/Mono/AssetPipeline/AssetImportContext.bindings.cs @@ -30,6 +30,8 @@ public class AssetImportContext AssetImportContext() {} public extern string assetPath { get; internal set; } + public extern string GetResultPath(string extension); + public extern BuildTarget selectedBuildTarget { get; } extern void LogMessage(string msg, string file, int line, UnityEngine.Object obj, bool isAnError); @@ -78,6 +80,19 @@ internal void DependsOnImportedAsset(string path) [NativeName("DependsOnImportedAsset")] private extern void DependsOnImportedAssetInternal(string path); + public void DependsOnCustomDependency(string dependency) + { + if (string.IsNullOrEmpty(dependency)) + { + throw new ArgumentNullException("dependency", "Cannot add custom dependency on an empty custom dependency."); + } + + DependsOnCustomDependencyInternal(dependency); + } + + [NativeName("DependsOnCustomDependency")] + private extern void DependsOnCustomDependencyInternal(string path); + public void LogImportError(string msg, UnityEngine.Object obj = null) { AddToLog(msg, true, obj); diff --git a/Editor/Mono/AssetPipeline/AssetImporter.bindings.cs b/Editor/Mono/AssetPipeline/AssetImporter.bindings.cs index 3c1e6b5f00..76ad684c0c 100644 --- a/Editor/Mono/AssetPipeline/AssetImporter.bindings.cs +++ b/Editor/Mono/AssetPipeline/AssetImporter.bindings.cs @@ -139,7 +139,7 @@ public Dictionary GetExternalObjectMap() } [FreeFunction("AssetImporterBindings::RegisterImporter")] - extern internal static void RegisterImporter(Type importer, int importerVersion, int queuePos, string fileExt, bool supportsImportDependencyHinting, bool autoSelect); + extern internal static void RegisterImporter(Type importer, int importerVersion, int queuePos, string fileExt, bool supportsImportDependencyHinting, bool autoSelect, bool allowCaching); [FreeFunction("AssetImporterBindings::SupportsRemappedAssetType", HasExplicitThis = true, IsThreadSafe = true)] public extern bool SupportsRemappedAssetType(Type type); diff --git a/Editor/Mono/AssetPipeline/TextureImporterTypes.bindings.cs b/Editor/Mono/AssetPipeline/TextureImporterTypes.bindings.cs index 67188cb7e1..7e673f9128 100644 --- a/Editor/Mono/AssetPipeline/TextureImporterTypes.bindings.cs +++ b/Editor/Mono/AssetPipeline/TextureImporterTypes.bindings.cs @@ -24,6 +24,7 @@ public struct SpriteMetaData [System.Serializable] [StructLayout(LayoutKind.Sequential)] [NativeAsStruct] + [NativeType(CodegenOptions.Custom, "TextureImporterSettings")] [NativeHeader("Editor/Src/AssetPipeline/TextureImporting/TextureImporter.bindings.h")] [NativeHeader("Editor/Src/AssetPipeline/TextureImporting/TextureImporterTypes.h")] public sealed partial class TextureImporterSettings @@ -166,6 +167,10 @@ public sealed partial class TextureImporterSettings [SerializeField] private int m_TextureFormatSet; + //For backward compatibility for an incorrectly applied gamma decoding step (bug) + [SerializeField] + int m_ApplyGammaDecoding; + public TextureImporterType textureType { get {return (TextureImporterType)m_TextureType; } diff --git a/Editor/Mono/Audio/Mixer/GUI/AudioMixerDrawUtils.cs b/Editor/Mono/Audio/Mixer/GUI/AudioMixerDrawUtils.cs index edc4cd5118..85a8a76580 100644 --- a/Editor/Mono/Audio/Mixer/GUI/AudioMixerDrawUtils.cs +++ b/Editor/Mono/Audio/Mixer/GUI/AudioMixerDrawUtils.cs @@ -182,7 +182,7 @@ public static void DrawRegionBg(Rect rect, out Rect headerRect, out Rect content const float headerIndent = 2f; headerRect = new Rect(rect.x + headerIndent, rect.y, rect.width - headerIndent, kSectionHeaderHeight); contentRect = new Rect(rect.x, headerRect.yMax, rect.width, rect.height - kSectionHeaderHeight); - GUI.Label(new RectOffset(1, 1, 1, 1).Add(contentRect), GUIContent.none, EditorStyles.helpBox); + GUI.Label(new RectOffset(1, 1, 1, 1).Add(contentRect), GUIContent.none, EditorStyles.frameBox); } public static void HeaderLabel(Rect r, GUIContent text, Texture2D icon) diff --git a/Editor/Mono/Audio/Mixer/GUI/AudioMixerGroupTreeView.cs b/Editor/Mono/Audio/Mixer/GUI/AudioMixerGroupTreeView.cs index 2c00705901..0623c8f43d 100644 --- a/Editor/Mono/Audio/Mixer/GUI/AudioMixerGroupTreeView.cs +++ b/Editor/Mono/Audio/Mixer/GUI/AudioMixerGroupTreeView.cs @@ -152,7 +152,8 @@ public override bool IsRenamingItemAllowed(TreeViewItem node) internal class AudioGroupTreeViewGUI : TreeViewGUI { readonly float column1Width = 20f; - readonly Texture2D k_VisibleON = EditorGUIUtility.FindTexture("VisibilityOn"); + readonly GUIContent k_VisibleON = EditorGUIUtility.TrIconContent("animationvisibilitytoggleon"); + readonly GUIContent k_VisibleOFF = EditorGUIUtility.TrIconContent("animationvisibilitytoggleoff"); public Action NodeWasToggled; public AudioMixerController m_Controller = null; @@ -206,9 +207,10 @@ override public void OnRowGUI(Rect rowRect, TreeViewItem node, int row, bool sel if (colorIndex > 0) EditorGUI.DrawRect(new Rect(rowRect.x, iconBgRect.y, 2, iconBgRect.height), AudioMixerColorCodes.GetColor(colorIndex)); - EditorGUI.DrawRect(iconBgRect, new Color(0.5f, 0.5f, 0.5f, 0.2f)); if (oldSelected) - GUI.DrawTexture(iconRect, k_VisibleON); + GUI.DrawTexture(iconRect, k_VisibleON.image); + else + GUI.DrawTexture(iconRect, k_VisibleOFF.image); Rect toggleRect = new Rect(2, rowRect.y, rowRect.height, rowRect.height); if (evt.type == EventType.MouseUp && evt.button == 0 && toggleRect.Contains(evt.mousePosition)) @@ -290,7 +292,7 @@ internal class AudioMixerGroupTreeView class Styles { public GUIContent header = EditorGUIUtility.TrTextContent("Groups", "An Audio Mixer Group is used by e.g Audio Sources to modify the audio output before it reaches the Audio Listener. An Audio Mixer Group will route its output to another Audio Mixer Group if it is made a child of that group. The Master Group will route its output to the Audio Listener if it doesn't route its output into another Mixer."); - public GUIContent addText = EditorGUIUtility.TrTextContent("+", "Add child group"); + public GUIContent addButton = EditorGUIUtility.TrIconContent("CreateAddNew", "Add child group"); public Texture2D audioMixerGroupIcon = EditorGUIUtility.FindTexture(typeof(UnityEngine.Audio.AudioMixerGroup)); } static Styles s_Styles; @@ -567,7 +569,7 @@ public void OnGUI(Rect rect) AudioMixerGroupController parent = (m_Controller.CachedSelection.Count == 1) ? m_Controller.CachedSelection[0] : m_Controller.masterGroup; using (new EditorGUI.DisabledScope(EditorApplication.isPlaying)) { - if (GUI.Button(new Rect(headerRect.xMax - 15f, headerRect.y + 3f, 15f, 15f), s_Styles.addText, EditorStyles.label)) + if (GUI.Button(new Rect(headerRect.xMax - 17f, headerRect.y + 3f, 16f, 16f), s_Styles.addButton, EditorStyles.iconButton)) AddAudioMixerGroup(parent); } diff --git a/Editor/Mono/Audio/Mixer/GUI/AudioMixerGroupViewList.cs b/Editor/Mono/Audio/Mixer/GUI/AudioMixerGroupViewList.cs index baab4b27d8..4f62c30298 100644 --- a/Editor/Mono/Audio/Mixer/GUI/AudioMixerGroupViewList.cs +++ b/Editor/Mono/Audio/Mixer/GUI/AudioMixerGroupViewList.cs @@ -22,7 +22,7 @@ internal class AudioMixerGroupViewList class Styles { public GUIContent header = EditorGUIUtility.TrTextContent("Views", "A view is the saved visibility state of the current Mixer Groups. Use views to setup often used combinations of Mixer Groups."); - public GUIContent addButton = new GUIContent("+"); + public GUIContent addButton = EditorGUIUtility.TrIconContent("CreateAddNew"); public Texture2D viewsIcon = EditorGUIUtility.FindTexture("AudioMixerView Icon"); } static Styles s_Styles; @@ -118,7 +118,7 @@ public void OnGUI(Rect rect) m_ReorderableListWithRenameAndScrollView.OnGUI(contentRect); // Call after list to prevent id mismatch - if (GUI.Button(new Rect(headerRect.xMax - 15f, headerRect.y + 3f, 15f, 15f), s_Styles.addButton, EditorStyles.label)) + if (GUI.Button(new Rect(headerRect.xMax - 17f, headerRect.y + 3f, 16f, 16f), s_Styles.addButton, EditorStyles.iconButton)) Add(); } } diff --git a/Editor/Mono/Audio/Mixer/GUI/AudioMixerSnapshotView.cs b/Editor/Mono/Audio/Mixer/GUI/AudioMixerSnapshotView.cs index 5140a0f16c..c91bc03148 100644 --- a/Editor/Mono/Audio/Mixer/GUI/AudioMixerSnapshotView.cs +++ b/Editor/Mono/Audio/Mixer/GUI/AudioMixerSnapshotView.cs @@ -21,9 +21,9 @@ internal class AudioMixerSnapshotListView class Styles { - public GUIContent starIcon = new GUIContent(EditorGUIUtility.FindTexture("Favorite"), "Start snapshot"); + public GUIContent starIcon = EditorGUIUtility.TrIconContent("Favorite", "Start snapshot"); public GUIContent header = EditorGUIUtility.TrTextContent("Snapshots", "A snapshot is a set of values for all parameters in the mixer. When using the mixer, you modify parameters in the selected snapshot. Blend between multiple snapshots at runtime."); - public GUIContent addButton = new GUIContent("+"); + public GUIContent addButton = EditorGUIUtility.TrIconContent("CreateAddNew"); public Texture2D snapshotsIcon = EditorGUIUtility.FindTexture(typeof(UnityEngine.Audio.AudioMixerSnapshot)); } static Styles s_Styles; @@ -106,7 +106,7 @@ public void CustomDrawElement(Rect r, int index, bool isActive, bool isFocused) evt.Use(); } - const float iconSize = 14f; + const float iconSize = 16f; const float spacing = 5f; bool isSelected = (index == m_ReorderableListWithRenameAndScrollView.list.index) && !m_ReorderableListWithRenameAndScrollView.IsRenamingIndex(index); @@ -155,7 +155,7 @@ public void OnGUI(Rect rect) } m_ReorderableListWithRenameAndScrollView.OnGUI(contentRect); - if (GUI.Button(new Rect(headerRect.xMax - 15f, headerRect.y + 3f, 15f, 15f), s_Styles.addButton, EditorStyles.label)) + if (GUI.Button(new Rect(headerRect.xMax - 17f, headerRect.y + 3f, 16f, 16f), s_Styles.addButton, EditorStyles.iconButton)) Add(); } } diff --git a/Editor/Mono/Audio/Mixer/GUI/AudioMixersTreeView.cs b/Editor/Mono/Audio/Mixer/GUI/AudioMixersTreeView.cs index 2d63b05b59..cc85e4fa5a 100644 --- a/Editor/Mono/Audio/Mixer/GUI/AudioMixersTreeView.cs +++ b/Editor/Mono/Audio/Mixer/GUI/AudioMixersTreeView.cs @@ -529,7 +529,7 @@ internal class AudioMixersTreeView class Styles { public GUIContent header = EditorGUIUtility.TrTextContent("Mixers", "All mixers in the project are shown here. By default, a mixer outputs to the AudioListener but mixers can also route their output to other mixers. Each mixer shows where it outputs (in parenthesis). To reroute a mixer simply drag the mixer upon another mixer and select a group from the popup."); - public GUIContent addText = EditorGUIUtility.TrTextContent("+", "Add mixer asset. The asset will be saved in the same folder as the current selected mixer or, if none is selected, saved in the Assets folder."); + public GUIContent addButton = EditorGUIUtility.TrIconContent("CreateAddNew", "Add mixer asset. The asset will be saved in the same folder as the current selected mixer or, if none is selected, saved in the Assets folder."); public Texture2D audioMixerIcon = EditorGUIUtility.FindTexture(typeof(AudioMixerController)); } static Styles s_Styles; @@ -604,7 +604,7 @@ public void OnGUI(Rect rect) AudioMixerDrawUtils.DrawRegionBg(rect, out headerRect, out contentRect); AudioMixerDrawUtils.HeaderLabel(headerRect, s_Styles.header, s_Styles.audioMixerIcon); - if (GUI.Button(new Rect(headerRect.xMax - 15f, headerRect.y + 3f, 15f, 15f), s_Styles.addText, EditorStyles.label)) + if (GUI.Button(new Rect(headerRect.xMax - 17f, headerRect.y + 3f, 16f, 16f), s_Styles.addButton, EditorStyles.iconButton)) { AudioMixersTreeViewGUI gui = m_TreeView.gui as AudioMixersTreeViewGUI; gui.BeginCreateNewMixer(); diff --git a/Editor/Mono/Audio/Mixer/GUI/ReorderableListWithRenameAndScrollView.cs b/Editor/Mono/Audio/Mixer/GUI/ReorderableListWithRenameAndScrollView.cs index 6fd26ab210..35856f88a7 100644 --- a/Editor/Mono/Audio/Mixer/GUI/ReorderableListWithRenameAndScrollView.cs +++ b/Editor/Mono/Audio/Mixer/GUI/ReorderableListWithRenameAndScrollView.cs @@ -146,7 +146,7 @@ public void DrawElementText(Rect r, int index, bool isActive, bool isSelected, b { if (Event.current.type == EventType.Repaint && onGetNameAtIndex != null) { - elementStyle.Draw(r, onGetNameAtIndex(index), false, false, isSelected, true); + elementStyle.Draw(r, onGetNameAtIndex(index), false, false, isSelected, isFocused); } } diff --git a/Editor/Mono/BugReportingTools.bindings.cs b/Editor/Mono/BugReportingTools.bindings.cs new file mode 100644 index 0000000000..0dc4429cb5 --- /dev/null +++ b/Editor/Mono/BugReportingTools.bindings.cs @@ -0,0 +1,21 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using UnityEngine; +using UnityEngine.Bindings; +using uei = UnityEngine.Internal; + +namespace UnityEditor.BugReporting +{ + // Keep in sync with "Editor/Platform/Interface/BugReportingTools.h" + internal enum BugReportMode { ManualOpen, CrashBug, FatalError, CocoaExceptionOrAssertion, ManualSimple } + + [NativeHeader("Editor/Mono/BugReportingTools.bindings.h")] + internal sealed class BugReportingTools + { + [StaticAccessor("BugReportingToolsBindings", StaticAccessorType.DoubleColon)] + [NativeMethod("LaunchBugReportingTool")] + public static extern void LaunchBugReporter(BugReportMode mode, string[] additionalArguments); + } +} diff --git a/Editor/Mono/BuildPipeline/AssemblyStripper.cs b/Editor/Mono/BuildPipeline/AssemblyStripper.cs index ea642b36a5..b8ca6f1a6e 100644 --- a/Editor/Mono/BuildPipeline/AssemblyStripper.cs +++ b/Editor/Mono/BuildPipeline/AssemblyStripper.cs @@ -46,7 +46,7 @@ private static string UnityLinkerPath { get { - return Path.Combine(IL2CPPUtils.GetIl2CppFolder(), "build/UnityLinker.exe"); + return Path.Combine(IL2CPPUtils.GetIl2CppFolder(), "build/deploy/net471/UnityLinker.exe"); } } diff --git a/Editor/Mono/BuildPipeline/DesktopStandaloneBuildWindowExtension.cs b/Editor/Mono/BuildPipeline/DesktopStandaloneBuildWindowExtension.cs index 5a064f383e..75ffe3218d 100644 --- a/Editor/Mono/BuildPipeline/DesktopStandaloneBuildWindowExtension.cs +++ b/Editor/Mono/BuildPipeline/DesktopStandaloneBuildWindowExtension.cs @@ -16,8 +16,8 @@ internal abstract class DesktopStandaloneBuildWindowExtension : DefaultBuildWind private BuildTarget[] m_StandaloneSubtargets; private GUIContent[] m_StandaloneSubtargetStrings; - private bool m_HasIl2CppPlayers; - private bool m_IsRunningOnHostPlatform; + protected bool m_HasIl2CppPlayers; + protected bool m_IsRunningOnHostPlatform; public DesktopStandaloneBuildWindowExtension(bool hasIl2CppPlayers) { diff --git a/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs b/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs index 41666532b1..f20ead802f 100644 --- a/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs +++ b/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs @@ -92,24 +92,19 @@ public override void UpdateBootConfig(BuildTarget target, BootConfigData config, config.AddKey("nolog"); } - private void CopyNativePlugins(BuildPostProcessArgs args, out List cppPlugins) + private void CopyNativePlugins(BuildPostProcessArgs args, BuildTarget buildTarget, out List cppPlugins) { - string buildTargetName = BuildPipeline.GetBuildTargetName(args.target); + string buildTargetName = BuildPipeline.GetBuildTargetName(buildTarget); IPluginImporterExtension pluginImpExtension = new DesktopPluginImporterExtension(); string pluginsFolder = GetStagingAreaPluginsFolder(args); - string subDir32Bit = Path.Combine(pluginsFolder, "x86"); - string subDir64Bit = Path.Combine(pluginsFolder, "x86_64"); bool haveCreatedPluginsFolder = false; - bool haveCreatedSubDir32Bit = false; - bool haveCreatedSubDir64Bit = false; + var createdFolders = new HashSet(); cppPlugins = new List(); - foreach (PluginImporter imp in PluginImporter.GetImporters(args.target)) + foreach (PluginImporter imp in PluginImporter.GetImporters(buildTarget)) { - BuildTarget t = args.target; - // Skip .cpp files. They get copied to il2cpp output folder just before code compilation if (DesktopPluginImporterExtension.IsCppPluginFile(imp.assetPath)) { @@ -124,7 +119,7 @@ private void CopyNativePlugins(BuildPostProcessArgs args, out List cppPl // HACK: This should never happen. if (string.IsNullOrEmpty(imp.assetPath)) { - UnityEngine.Debug.LogWarning("Got empty plugin importer path for " + args.target.ToString()); + UnityEngine.Debug.LogWarning("Got empty plugin importer path for " + buildTarget); continue; } @@ -134,39 +129,8 @@ private void CopyNativePlugins(BuildPostProcessArgs args, out List cppPl haveCreatedPluginsFolder = true; } + bool isDirectory = Directory.Exists(imp.assetPath); - string cpu = imp.GetPlatformData(t, "CPU"); - switch (cpu) - { - case "x86": - if (t == BuildTarget.StandaloneWindows64 || - t == BuildTarget.StandaloneLinux64) - { - continue; - } - if (!haveCreatedSubDir32Bit) - { - Directory.CreateDirectory(subDir32Bit); - haveCreatedSubDir32Bit = true; - } - break; - case "x86_64": - if (t != BuildTarget.StandaloneOSX && - t != BuildTarget.StandaloneWindows64 && - t != BuildTarget.StandaloneLinux64) - { - continue; - } - if (!haveCreatedSubDir64Bit) - { - Directory.CreateDirectory(subDir64Bit); - haveCreatedSubDir64Bit = true; - } - break; - // This is a special case for CPU targets, means no valid CPU is selected - case "None": - continue; - } string destinationPath = pluginImpExtension.CalculateFinalPluginPath(buildTargetName, imp); if (string.IsNullOrEmpty(destinationPath)) @@ -174,9 +138,17 @@ private void CopyNativePlugins(BuildPostProcessArgs args, out List cppPl string finalDestinationPath = Path.Combine(pluginsFolder, destinationPath); + var finalDestinationFolder = Path.GetDirectoryName(finalDestinationPath); + if (!createdFolders.Contains(finalDestinationFolder)) + { + Directory.CreateDirectory(finalDestinationFolder); + createdFolders.Add(finalDestinationFolder); + } + if (isDirectory) { - FileUtil.CopyDirectoryRecursive(imp.assetPath, finalDestinationPath); + // Since we may be copying from Assets make sure to not include .meta files to the build + FileUtil.CopyDirectoryRecursive(imp.assetPath, finalDestinationPath, overwrite: false, ignoreMeta: true); } else { @@ -185,7 +157,7 @@ private void CopyNativePlugins(BuildPostProcessArgs args, out List cppPl } // TODO: Move all plugins using GetExtensionPlugins to GetImporters and remove GetExtensionPlugins - foreach (UnityEditorInternal.PluginDesc pluginDesc in PluginImporter.GetExtensionPlugins(args.target)) + foreach (UnityEditorInternal.PluginDesc pluginDesc in PluginImporter.GetExtensionPlugins(buildTarget)) { if (!haveCreatedPluginsFolder) { @@ -224,7 +196,18 @@ private void CopyCppPlugins(BuildPostProcessArgs args, string cppOutputDir, IEnu private void SetupStagingArea(BuildPostProcessArgs args, HashSet filesToNotOverwrite) { List cppPlugins; - CopyNativePlugins(args, out cppPlugins); + + if (GetCreateSolution(args) && (args.target == BuildTarget.StandaloneWindows || args.target == BuildTarget.StandaloneWindows64)) + { + // For Windows Standalone player solution build, we want to copy plugins for all architectures as + // the ultimate CPU architecture choice can be made from Visual Studio + CopyNativePlugins(args, BuildTarget.StandaloneWindows, out cppPlugins); + CopyNativePlugins(args, BuildTarget.StandaloneWindows64, out cppPlugins); + } + else + { + CopyNativePlugins(args, args.target, out cppPlugins); + } CreateApplicationData(args); @@ -286,8 +269,13 @@ private void ProcessIl2CppOutputForBinary(BuildPostProcessArgs args) var dataBackupFolder = Path.Combine(args.stagingArea, GetIl2CppDataBackupFolderName(args)); FileUtil.CreateOrCleanDirectory(dataBackupFolder); + var il2cppOutputFolder = Path.Combine(args.stagingAreaData, "il2cppOutput"); + + // Delete duplicate il2cpp_data that was created in il2cppOutput directory (case 1198179) + FileUtil.DeleteFileOrDirectory(Path.Combine(il2cppOutputFolder, "Data")); + // Move generated C++ code out of Data directory - FileUtil.MoveFileOrDirectory(Path.Combine(args.stagingAreaData, "il2cppOutput"), Path.Combine(dataBackupFolder, "il2cppOutput")); + FileUtil.MoveFileOrDirectory(il2cppOutputFolder, Path.Combine(dataBackupFolder, "il2cppOutput")); if (IL2CPPUtils.UseIl2CppCodegenWithMonoBackend(BuildPipeline.GetBuildTargetGroup(args.target))) { @@ -385,8 +373,8 @@ private static void CreateApplicationData(BuildPostProcessArgs args) protected bool CopyPlayerFilter(string path, BuildPostProcessArgs args) { - // Don't copy UnityEngine mdb files - return Path.GetExtension(path) != ".mdb" || !Path.GetFileName(path).StartsWith("UnityEngine."); + // Don't copy UnityEngine mdb/pdb files + return (Path.GetExtension(path) != ".mdb" && Path.GetExtension(path) != ".pdb") || !Path.GetFileName(path).StartsWith("UnityEngine."); } private static uint StringToFourCC(string literal) @@ -408,21 +396,6 @@ protected string GetVariationFolder(BuildPostProcessArgs args) => protected static void RecordCommonFiles(BuildPostProcessArgs args, string variationSourceFolder, string monoFolderRoot) { - // Mark all the managed DLLs in Data/Managed as engine API assemblies - // Data/Managed may already contain managed DLLs in the UnityEngine.*.dll naming scheme from the extensions - // So we find the files in the source Variations directory and mark the corresponding files in the output - var path = Path.Combine(variationSourceFolder, "Data/Managed"); - foreach (var file in Directory.GetFiles(path, "*.dll") - .Concat(Directory.GetFiles(path, "*.pdb"))) - { - var filename = Path.GetFileName(file); - if (!filename.StartsWith("UnityEngine")) - continue; - - var targetFilePath = Path.Combine(args.stagingArea, "Data/Managed/" + filename); - args.report.RecordFileAdded(targetFilePath, CommonRoles.managedEngineApi); - } - // Mark the default resources file args.report.RecordFileAdded(Path.Combine(args.stagingArea, "Data/Resources/unity default resources"), CommonRoles.builtInResources); diff --git a/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs b/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs index 814a6c2978..8c0a9fba34 100644 --- a/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs +++ b/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs @@ -368,9 +368,14 @@ public void RunCompileAndLink() var compilerConfiguration = PlayerSettings.GetIl2CppCompilerConfiguration(buildTargetGroup); var arguments = Il2CppNativeCodeBuilderUtils.AddBuilderArguments(il2CppNativeCodeBuilder, OutputFileRelativePath(), m_PlatformProvider.includePaths, m_PlatformProvider.libraryPaths, compilerConfiguration).ToList(); + var additionalArgs = IL2CPPUtils.GetAdditionalArguments(); + if (!string.IsNullOrEmpty(additionalArgs)) + arguments.Add(additionalArgs); + arguments.Add($"--map-file-parser={CommandLineFormatter.PrepareFileName(GetMapFileParserPath())}"); arguments.Add($"--generatedcppdir={CommandLineFormatter.PrepareFileName(Path.GetFullPath(GetCppOutputDirectoryInStagingArea()))}"); arguments.Add(string.Format("--dotnetprofile=\"{0}\"", IL2CPPUtils.ApiCompatibilityLevelToDotNetProfileArgument(PlayerSettings.GetApiCompatibilityLevel(buildTargetGroup)))); + arguments.AddRange(IL2CPPUtils.GetDebuggerIL2CPPArguments(m_PlatformProvider, buildTargetGroup)); Action setupStartInfo = il2CppNativeCodeBuilder.SetupStartInfo; var managedDir = Path.GetFullPath(Path.Combine(m_StagingAreaData, "Managed")); @@ -440,12 +445,6 @@ private void ConvertPlayerDlltoCpp(Il2CppBuildPipelineData data, string outputDi if (m_BuildForMonoRuntime) arguments.Add("--mono-runtime"); - // Working around gcc bug 41091 - if (m_PlatformProvider.target == BuildTarget.StandaloneLinux64) - { - arguments.Add("--disable-aggressive-inlining"); - } - var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(m_PlatformProvider.target); var apiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(buildTargetGroup); arguments.Add(string.Format("--dotnetprofile=\"{0}\"", IL2CPPUtils.ApiCompatibilityLevelToDotNetProfileArgument(apiCompatibilityLevel))); @@ -526,12 +525,12 @@ private void RunIl2CppWithArguments(List arguments, Action Signature(BuildTarget target); + static IEnumerable Signature(BuildTarget target) { throw new InvalidOperationException(); } } [RequiredByNativeCode] @@ -46,7 +46,7 @@ public sealed partial class PostProcessBuildAttribute : CallbackOrderAttribute public PostProcessBuildAttribute(int callbackOrder) { m_CallbackOrder = callbackOrder; } [RequiredSignature] - extern static void Signature(BuildTarget target, string pathToBuiltProject); + static void Signature(BuildTarget target, string pathToBuiltProject) { throw new InvalidOperationException(); } } [RequiredByNativeCode] @@ -62,7 +62,7 @@ public sealed partial class PostProcessSceneAttribute : CallbackOrderAttribute public PostProcessSceneAttribute(int callbackOrder, int version) { m_CallbackOrder = callbackOrder; m_version = version; } [RequiredSignature] - extern static void Signature(); + static void Signature() { throw new InvalidOperationException(); } } [RequiredByNativeCode] @@ -73,7 +73,7 @@ public sealed partial class DidReloadScripts : CallbackOrderAttribute public DidReloadScripts(int callbackOrder) { m_CallbackOrder = callbackOrder; } [RequiredSignature] - extern static void Signature(); + static void Signature() { throw new InvalidOperationException(); } } // Add this attribute to a static method to get a callback for opening an asset inside Unity before trying to open it with an external tool @@ -84,10 +84,10 @@ public sealed partial class OnOpenAssetAttribute : CallbackOrderAttribute public OnOpenAssetAttribute(int callbackOrder) { m_CallbackOrder = callbackOrder; } [RequiredSignature] - extern static bool SignatureLine(int instanceID, int line); + static bool SignatureLine(int instanceID, int line) { throw new InvalidOperationException(); } [RequiredSignature] - extern static bool SignatureLineColumn(int instanceID, int line, int column); + static bool SignatureLineColumn(int instanceID, int line, int column) { throw new InvalidOperationException(); } } } } diff --git a/Editor/Mono/Camera/CameraProjectionCache.cs b/Editor/Mono/Camera/CameraProjectionCache.cs new file mode 100644 index 0000000000..0b5d032a04 --- /dev/null +++ b/Editor/Mono/Camera/CameraProjectionCache.cs @@ -0,0 +1,49 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using UnityEngine; + +namespace UnityEditor +{ + struct CameraProjectionCache + { + Matrix4x4 worldToClip; + Rect viewport; + float screen; + + public CameraProjectionCache(Camera camera, float screenHeight) + { + worldToClip = camera.projectionMatrix * camera.worldToCameraMatrix; + viewport = camera.pixelRect; + screen = screenHeight; + } + + public Vector2 WorldToScreenPoint(Vector3 point) + { + Vector3 clip = worldToClip.MultiplyPoint(point); + + return new Vector2( + viewport.x + (1.0f + clip.x) * viewport.width * 0.5f, + viewport.y + (1.0f + clip.y) * viewport.height * 0.5f); + } + + public Vector2 WorldToGUIPoint(Vector3 point) + { + return ScreenToGUIPoint(WorldToScreenPoint(point)); + } + + public Vector2 GUIToScreenPoint(Vector2 point) + { + var pixels = EditorGUIUtility.PointsToPixels(point); + pixels.y = screen - pixels.y; + return pixels; + } + + public Vector2 ScreenToGUIPoint(Vector2 point) + { + point.y = screen - point.y; + return GUIClip.Clip(EditorGUIUtility.PixelsToPoints(point)); + } + } +} diff --git a/Editor/Mono/CodeEditor/CodeEditor.cs b/Editor/Mono/CodeEditor/CodeEditor.cs index 2d7004c744..83628e4db9 100644 --- a/Editor/Mono/CodeEditor/CodeEditor.cs +++ b/Editor/Mono/CodeEditor/CodeEditor.cs @@ -2,15 +2,12 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License -using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; using System.Text; using UnityEditor; using UnityEditor.Callbacks; -using UnityEditor.PlatformSupport; using UnityEditorInternal; using UnityEngine; using UnityEngine.Scripting; @@ -22,6 +19,7 @@ public class CodeEditor internal static CodeEditor Editor { get; } = new CodeEditor(); List m_ExternalCodeEditors = new List(); IExternalCodeEditor m_DefaultEditor = new DefaultExternalCodeEditor(); + internal const string SystemDefaultPath = ""; public struct Installation { @@ -30,7 +28,7 @@ public struct Installation } [RequiredByNativeCode] - static bool OpenFileAtLineColumn(string path, int line, int column) + static bool OpenProject(string path, int line, int column) { return Editor.Current.OpenProject(path, line, column); } @@ -60,13 +58,20 @@ internal Installation EditorInstallation { get { - var editorPath = EditorPrefs.GetString("kScriptsDefaultApp"); - if (string.IsNullOrEmpty(editorPath)) + var editorPath = CurrentEditorInstallation.Trim(); + if (editorPath == CodeEditor.SystemDefaultPath) { + // If no script editor is set, try to use first found supported one. + var editorPaths = GetFoundScriptEditorPaths(); + if (editorPaths.Count > 0) + { + return new Installation { Path = editorPaths.Keys.First() }; + } + return new Installation { Name = "Internal", - Path = "", + Path = editorPath, }; } @@ -79,17 +84,7 @@ internal Installation EditorInstallation } } - // This is supporting legacy script editors until they are moved to packages - if (!string.IsNullOrEmpty(editorPath)) - return new Installation { Path = editorPath }; - - // If no script editor is set, try to use first found supported one. - var editorPaths = GetFoundScriptEditorPaths(); - - if (editorPaths.Count > 0) - return new Installation { Path = editorPaths.Keys.ToArray()[0] }; - - return new Installation(); + return new Installation { Path = editorPath }; } } @@ -97,8 +92,8 @@ internal IExternalCodeEditor Current { get { - var editorPath = EditorPrefs.GetString("kScriptsDefaultApp"); - if (string.IsNullOrEmpty(editorPath)) + var editorPath = CurrentEditorInstallation.Trim(); + if (editorPath == CodeEditor.SystemDefaultPath) { return m_DefaultEditor; } @@ -154,7 +149,12 @@ public static void Unregister(IExternalCodeEditor externalCodeEditor) public static IExternalCodeEditor CurrentEditor => Editor.Current; - public static string CurrentEditorInstallation => Editor.EditorInstallation.Path; + public static string CurrentEditorInstallation => EditorPrefs.GetString("kScriptsDefaultApp"); + + public static bool OSOpenFile(string appPath, string arguments) + { + return ExternalEditor.OSOpenFileWithArgument(appPath, arguments); + } public static string ParseArgument(string arguments, string path, int line, int column) { diff --git a/Editor/Mono/CodeEditor/DefaultExternalCodeEditor.cs b/Editor/Mono/CodeEditor/DefaultExternalCodeEditor.cs index e35b8f24a4..7698d48ef3 100644 --- a/Editor/Mono/CodeEditor/DefaultExternalCodeEditor.cs +++ b/Editor/Mono/CodeEditor/DefaultExternalCodeEditor.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.IO; +using System.Linq; using Unity.CodeEditor; using UnityEngine; @@ -12,7 +13,10 @@ namespace UnityEditor internal class DefaultExternalCodeEditor : IExternalCodeEditor { static readonly GUIContent k_ResetArguments = EditorGUIUtility.TrTextContent("Reset argument"); + static bool IsOSX => Application.platform == RuntimePlatform.OSXEditor; + string m_ChosenInstallation; + const string k_ArgumentKey = "kScriptEditorArgs"; const string k_DefaultArgument = "$(File)"; @@ -31,25 +35,40 @@ string Arguments // The year 2021: Delete mac hack. if (Application.platform == RuntimePlatform.OSXEditor) { - var oldMac = EditorPrefs.GetString("kScriptEditorArgs_" + m_ChosenInstallation); + var oldMac = EditorPrefs.GetString("kScriptEditorArgs_" + Installation); if (!string.IsNullOrEmpty(oldMac)) { EditorPrefs.SetString(k_ArgumentKey, oldMac); } } - return EditorPrefs.GetString(k_ArgumentKey + m_ChosenInstallation, k_DefaultArgument); + return EditorPrefs.GetString(k_ArgumentKey + Installation, k_DefaultArgument); } set { if (Application.platform == RuntimePlatform.OSXEditor) { - EditorPrefs.SetString("kScriptEditorArgs_" + m_ChosenInstallation, value); + EditorPrefs.SetString("kScriptEditorArgs_" + Installation, value); } - EditorPrefs.SetString(k_ArgumentKey + m_ChosenInstallation, value); + EditorPrefs.SetString(k_ArgumentKey + Installation, value); } } + + string Installation + { + get + { + if (m_ChosenInstallation == null) + m_ChosenInstallation = CodeEditor.CurrentEditorInstallation; + return m_ChosenInstallation; + } + set + { + m_ChosenInstallation = value; + } + } + public CodeEditor.Installation[] Installations { get; } public bool TryGetInstallationForPath(string editorPath, out CodeEditor.Installation installation) { @@ -80,18 +99,52 @@ public void SyncAll() public void Initialize(string editorInstallationPath) { - m_ChosenInstallation = editorInstallationPath; + } + + static string[] defaultExtensions + { + get + { + var customExtensions = new[] {"json", "asmdef", "log"}; + return EditorSettings.projectGenerationBuiltinExtensions + .Concat(EditorSettings.projectGenerationUserExtensions) + .Concat(customExtensions) + .Distinct().ToArray(); + } + } + + static bool SupportsExtension(string path) + { + var extension = Path.GetExtension(path); + if (string.IsNullOrEmpty(extension)) + return false; + return defaultExtensions.Contains(extension.TrimStart('.')); } public bool OpenProject(string path, int line, int column) { - string applicationPath = EditorPrefs.GetString("kScriptsDefaultApp"); + if (path != "" && !SupportsExtension(path)) // Assets - Open C# Project passes empty path here + { + return false; + } + + string applicationPath = CodeEditor.CurrentEditorInstallation.Trim(); + if (applicationPath == CodeEditor.SystemDefaultPath) + { + return false; + } + + if (IsOSX) + { + return CodeEditor.OSOpenFile(applicationPath, CodeEditor.ParseArgument(Arguments, path, line, column)); + } + var process = new Process { StartInfo = new ProcessStartInfo { FileName = applicationPath, - Arguments = string.IsNullOrEmpty(applicationPath) ? "" : CodeEditor.ParseArgument(Arguments, path, line, column), + Arguments = CodeEditor.ParseArgument(Arguments, path, line, column), WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true, UseShellExecute = true, diff --git a/Editor/Mono/CodeEditor/ExternalEditor.bindings.cs b/Editor/Mono/CodeEditor/ExternalEditor.bindings.cs new file mode 100644 index 0000000000..4e94506f0d --- /dev/null +++ b/Editor/Mono/CodeEditor/ExternalEditor.bindings.cs @@ -0,0 +1,15 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using UnityEngine.Bindings; + +namespace Unity.CodeEditor +{ + [NativeHeader("Editor/Platform/Interface/ExternalEditor.h")] + internal class ExternalEditor + { + [FreeFunction("PlatformSpecificOpenFileAtLine")] + internal static extern bool OSOpenFileWithArgument(string appPath, string arguments); + } +} diff --git a/Editor/Mono/CollectImportedDependenciesAttribute.cs b/Editor/Mono/CollectImportedDependenciesAttribute.cs index de888b2f5f..81cc0080ba 100644 --- a/Editor/Mono/CollectImportedDependenciesAttribute.cs +++ b/Editor/Mono/CollectImportedDependenciesAttribute.cs @@ -28,7 +28,7 @@ public CollectImportedDependenciesAttribute(Type importerType, uint version) public uint version { get { return m_Version; } } [RequiredSignature] - static extern string[] CollectImportedDependenciesSignature(string assetPath); + static string[] CollectImportedDependenciesSignature(string assetPath) { throw new InvalidOperationException(); } } static class ImportedDependenciesApi diff --git a/Editor/Mono/ConsoleWindow.cs b/Editor/Mono/ConsoleWindow.cs index e40715eb6b..8ba7fd451d 100644 --- a/Editor/Mono/ConsoleWindow.cs +++ b/Editor/Mono/ConsoleWindow.cs @@ -25,6 +25,7 @@ internal class Constants private static bool ms_Loaded; private static int ms_logStyleLineCount; public static GUIStyle Box; + public static GUIStyle MiniButtonLeft; public static GUIStyle MiniButton; public static GUIStyle MiniButtonRight; public static GUIStyle LogStyle; @@ -79,6 +80,7 @@ public static void Init() Box = "CN Box"; + MiniButtonLeft = "ToolbarButtonLeft"; MiniButton = "ToolbarButton"; MiniButtonRight = "ToolbarButtonRight"; Toolbar = "Toolbar"; @@ -148,6 +150,9 @@ static class Content public ConsoleAttachToPlayerState(EditorWindow parentWindow, Action connectedCallback = null) : base(parentWindow, connectedCallback) { + // This is needed to force initialize the instance and the state so that messages from players are received and printed to the console (if that is the serialized state) + // on creation of the ConsoleWindow UI instead of when the uer first clicks on the dropdown, and triggers AddItemsToMenu. + PlayerConnectionLogReceiver.instance.State = PlayerConnectionLogReceiver.instance.State; } bool IsConnected() @@ -300,6 +305,9 @@ internal void OnEnable() if (m_ConsoleAttachToPlayerState == null) m_ConsoleAttachToPlayerState = new ConsoleAttachToPlayerState(this); + // Update the filter on enable for DomainReload(keep current filter) and window opening(reset filter because m_searchText is null) + SetFilter(LogEntries.GetFilteringText()); + titleContent = GetLocalizedTitleContent(); ms_ConsoleWindow = this; m_DevBuild = Unsupported.IsDeveloperMode(); @@ -473,7 +481,7 @@ internal void OnGUI() GUILayout.BeginHorizontal(Constants.Toolbar); - if (GUILayout.Button(Constants.ClearLabel, Constants.MiniButton)) + if (GUILayout.Button(Constants.ClearLabel, Constants.MiniButtonLeft)) { LogEntries.Clear(); GUIUtility.keyboardControl = 0; @@ -735,10 +743,7 @@ private void SearchField(Event e) var filteringText = EditorGUI.ToolbarSearchField(rect, searchText, false); if (m_SearchText != filteringText) { - m_SearchText = filteringText; - LogEntries.SetFilteringText(filteringText); - // Reset the active entry when we change the filtering text - SetActiveEntry(null); + SetFilter(filteringText); } } @@ -911,6 +916,21 @@ internal void AddMessageWithDoubleClickCallback(string message, string file, int var outputEntry = new LogEntry {message = message, file = file, mode = mode, instanceID = instanceID}; LogEntries.AddMessageWithDoubleClickCallback(outputEntry); } + + private void SetFilter(string filteringText) + { + if (filteringText == null) + { + m_SearchText = ""; + LogEntries.SetFilteringText(""); + } + else + { + m_SearchText = filteringText; + LogEntries.SetFilteringText(filteringText); // Reset the active entry when we change the filtering text + } + SetActiveEntry(null); + } } internal struct GettingLogEntriesScope : IDisposable diff --git a/Editor/Mono/ContainerWindow.cs b/Editor/Mono/ContainerWindow.cs index 15b6e96207..80d5ce94f9 100644 --- a/Editor/Mono/ContainerWindow.cs +++ b/Editor/Mono/ContainerWindow.cs @@ -30,6 +30,7 @@ internal partial class ContainerWindow : ScriptableObject const float kTitleHeight = 24f, kButtonWidth = 16f, kButtonHeight = 16f; static internal bool macEditor => Application.platform == RuntimePlatform.OSXEditor; + static internal bool s_Modal = false; private static class Styles { @@ -198,7 +199,7 @@ internal bool IsNotDocked() return ( // hallelujah (m_ShowMode == (int)ShowMode.Utility || m_ShowMode == (int)ShowMode.AuxWindow) || - (m_ShowMode == (int)ShowMode.MainWindow && (rootView is HostView || rootView is MainView)) || + (m_ShowMode == (int)ShowMode.MainWindow && rootView is HostView) || (rootView is SplitView && rootView.children.Length == 1 && rootView.children[0] is DockArea && @@ -337,6 +338,8 @@ public SplitView rootSplitView { if (m_ShowMode == (int)ShowMode.MainWindow && rootView && rootView.children.Length == 3) return rootView.children[1] as SplitView; + if (m_ShowMode == (int)ShowMode.MainWindow && rootView && rootView.children.Length == 2) + return rootView.children[0] as SplitView; else return rootView as SplitView; } @@ -364,7 +367,7 @@ public void HandleWindowDecorationEnd(Rect windowPosition) public void HandleWindowDecorationStart(Rect windowPosition) { - bool hasTitleBar = (windowPosition.y == 0 && showMode != ShowMode.Utility && !isPopup); + bool hasTitleBar = (windowPosition.y == 0 && (showMode != ShowMode.Utility && showMode != ShowMode.MainWindow) && !isPopup); if (!hasTitleBar) return; @@ -444,7 +447,7 @@ private void DragTitleBar(Rect titleBarRect) // If the mouse is inside the title bar rect, we say that we're the hot control if (titleBarRect.Contains(evt.mousePosition) && GUIUtility.hotControl == 0 && evt.button == 0) { - if (Application.platform == RuntimePlatform.WindowsEditor) + if (Application.platform != RuntimePlatform.LinuxEditor) { Event.current.Use(); m_DraggingNativeTitleBarCaption = true; diff --git a/Editor/Mono/Delayer.cs b/Editor/Mono/Delayer.cs new file mode 100644 index 0000000000..dde2dfdbbb --- /dev/null +++ b/Editor/Mono/Delayer.cs @@ -0,0 +1,81 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; + +namespace UnityEditor +{ + internal class Delayer + { + private double m_LastExecutionTime; + private readonly Action m_Action; + private readonly double m_DebounceDelay; + private object m_Context; + private readonly bool m_IsThrottle; + + public static Delayer Throttle(Action action, double delay = 0.2) + { + return new Delayer(action, delay, true); + } + + public static Delayer Debounce(Action action, double delay = 0.2) + { + return new Delayer(action, delay, false); + } + + public void Execute(object context = null) + { + m_Context = context; + if (m_IsThrottle) + { + if (m_LastExecutionTime == 0) + Throttle(); + } + else + { + m_LastExecutionTime = EditorApplication.timeSinceStartup; + Debounce(); + } + } + + private Delayer(Action action, double delay, bool isThrottle) + { + m_Action = action; + m_DebounceDelay = delay; + m_IsThrottle = isThrottle; + } + + private void Debounce() + { + EditorApplication.delayCall -= Debounce; + var currentTime = EditorApplication.timeSinceStartup; + if (m_LastExecutionTime != 0 && currentTime - m_LastExecutionTime > m_DebounceDelay) + { + m_Action(m_Context); + m_LastExecutionTime = 0; + } + else + { + EditorApplication.delayCall += Debounce; + } + } + + private void Throttle() + { + EditorApplication.delayCall -= Throttle; + var currentTime = EditorApplication.timeSinceStartup; + if (m_LastExecutionTime != 0 && currentTime - m_LastExecutionTime > m_DebounceDelay) + { + m_Action(m_Context); + m_LastExecutionTime = 0; + } + else + { + if (m_LastExecutionTime == 0) + m_LastExecutionTime = currentTime; + EditorApplication.delayCall += Throttle; + } + } + } +} diff --git a/Editor/Mono/EditorApplication.bindings.cs b/Editor/Mono/EditorApplication.bindings.cs index 0e31a7bb63..74af8a8d67 100644 --- a/Editor/Mono/EditorApplication.bindings.cs +++ b/Editor/Mono/EditorApplication.bindings.cs @@ -63,7 +63,7 @@ public static void OpenProject(string projectPath, params string[] args) // Saves all serializable assets that have not yet been written to disk (eg. Materials) [System.Obsolete("Use AssetDatabase.SaveAssets instead (UnityUpgradable) -> AssetDatabase.SaveAssets()", true)] - public static extern void SaveAssets(); + public static void SaveAssets() {} // Is editor currently in play mode? public static extern bool isPlaying @@ -238,5 +238,13 @@ internal static extern string windowTitle internal static extern void CloseAndRelaunch(string[] arguments); internal static extern void RequestCloseAndRelaunchWithCurrentArguments(); + // Triggers the editor to restart, after which all scripts will be recompiled. + internal static void RestartEditorAndRecompileScripts() + { + // Clear the script assemblies so we compile after the restart. + EditorCompilationInterface.Instance.CleanScriptAssemblies(); + + RequestCloseAndRelaunchWithCurrentArguments(); + } } } diff --git a/Editor/Mono/EditorAssemblies.cs b/Editor/Mono/EditorAssemblies.cs index 22b31ce5d9..c6848d1635 100644 --- a/Editor/Mono/EditorAssemblies.cs +++ b/Editor/Mono/EditorAssemblies.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; -using Unity.Profiling; using UnityEngine; using UnityEngine.Scripting; @@ -33,6 +32,12 @@ public class InitializeOnLoadMethodAttribute : Attribute { } + [RequiredByNativeCode] + [AttributeUsage(AttributeTargets.Method)] + public class InitializeOnEnterPlayModeAttribute : Attribute + { + } + /// /// Holds information about the current set of editor assemblies. /// diff --git a/Editor/Mono/EditorGUI.cs b/Editor/Mono/EditorGUI.cs index 6b78ada58f..ec0d3a772d 100644 --- a/Editor/Mono/EditorGUI.cs +++ b/Editor/Mono/EditorGUI.cs @@ -17,6 +17,7 @@ using Event = UnityEngine.Event; using UnityEditor.Build; using UnityEditor.StyleSheets; +using UnityEditor.VersionControl; using UnityEngine.Internal; namespace UnityEditor @@ -553,27 +554,28 @@ public void SendEvent() } } - private static void ShowTextEditorPopupMenu() + static void ShowTextEditorPopupMenu() { GenericMenu pm = new GenericMenu(); - if (s_RecycledEditor.hasSelection && !s_RecycledEditor.isPasswordField) + var enabled = GUI.enabled; + + // Cut + if (RecycledTextEditor.s_AllowContextCutOrPaste) { - if (RecycledTextEditor.s_AllowContextCutOrPaste) - { + if (s_RecycledEditor.hasSelection && !s_RecycledEditor.isPasswordField && enabled) pm.AddItem(EditorGUIUtility.TrTextContent("Cut"), false, new PopupMenuEvent(EventCommandNames.Cut, GUIView.current).SendEvent); - } - pm.AddItem(EditorGUIUtility.TrTextContent("Copy"), false, new PopupMenuEvent(EventCommandNames.Copy, GUIView.current).SendEvent); + else + pm.AddDisabledItem(EditorGUIUtility.TrTextContent("Cut")); } + + // Copy -- when GUI is disabled, allow Copy even with no selection (will copy everything) + if ((s_RecycledEditor.hasSelection || !enabled) && !s_RecycledEditor.isPasswordField) + pm.AddItem(EditorGUIUtility.TrTextContent("Copy"), false, new PopupMenuEvent(EventCommandNames.Copy, GUIView.current).SendEvent); else - { - if (RecycledTextEditor.s_AllowContextCutOrPaste) - { - pm.AddDisabledItem(EditorGUIUtility.TrTextContent("Cut")); - } pm.AddDisabledItem(EditorGUIUtility.TrTextContent("Copy")); - } - if (s_RecycledEditor.CanPaste() && RecycledTextEditor.s_AllowContextCutOrPaste) + // Paste + if (s_RecycledEditor.CanPaste() && RecycledTextEditor.s_AllowContextCutOrPaste && enabled) { pm.AddItem(EditorGUIUtility.TrTextContent("Paste"), false, new PopupMenuEvent(EventCommandNames.Paste, GUIView.current).SendEvent); } @@ -742,6 +744,34 @@ static bool MightBePrintableKey(Event evt) } } + static EventType GetEventTypeForControlAllowDisabledContextMenuPaste(Event evt, int id) + { + // UI is enabled: regular code path + var wasEnabled = GUI.enabled; + if (wasEnabled) + return evt.GetTypeForControl(id); + + // UI is disabled: get type as if it was enabled + GUI.enabled = true; + var type = evt.GetTypeForControl(id); + GUI.enabled = false; + + // these events are always processed, no matter the enabled/disabled state (IMGUI::GetEventType) + if (type == EventType.Repaint || type == EventType.Layout || type == EventType.Used) + return type; + + // allow context / right click, and "Copy" commands + if (type == EventType.ContextClick) + return type; + if (type == EventType.MouseDown && evt.button == 1) + return type; + if ((type == EventType.ValidateCommand || type == EventType.ExecuteCommand) && evt.commandName == EventCommandNames.Copy) + return type; + + // ignore all other events for disabled controls + return EventType.Ignore; + } + // Should we select all text from the current field when the mouse goes up? // (We need to keep track of this to support both SwipeSelection & initial click selects all) internal static string DoTextField(RecycledTextEditor editor, int id, Rect position, string text, GUIStyle style, string allowedletters, out bool changed, bool reset, bool multiline, bool passwordField) @@ -806,7 +836,9 @@ internal static string DoTextField(RecycledTextEditor editor, int id, Rect posit bool mayHaveChanged = false; string textBeforeKey = editor.text; - switch (evt.GetTypeForControl(id)) + var wasEnabled = GUI.enabled; + + switch (GetEventTypeForControlAllowDisabledContextMenuPaste(evt, id)) { case EventType.ValidateCommand: if (GUIUtility.keyboardControl == id) @@ -853,7 +885,10 @@ internal static string DoTextField(RecycledTextEditor editor, int id, Rect posit mayHaveChanged = true; break; case EventCommandNames.Copy: - editor.Copy(); + if (wasEnabled) + editor.Copy(); + else if (!passwordField) + GUIUtility.systemCopyBuffer = text; evt.Use(); break; case EventCommandNames.Paste: @@ -1007,8 +1042,11 @@ internal static string DoTextField(RecycledTextEditor editor, int id, Rect posit if (!editor.IsEditingControl(id)) { // First click: focus before showing popup GUIUtility.keyboardControl = id; - editor.BeginEditing(id, text, position, style, multiline, passwordField); - editor.MoveCursorToPosition(Event.current.mousePosition); + if (wasEnabled) + { + editor.BeginEditing(id, text, position, style, multiline, passwordField); + editor.MoveCursorToPosition(Event.current.mousePosition); + } } ShowTextEditorPopupMenu(); Event.current.Use(); @@ -1344,7 +1382,7 @@ internal static Rect GetInspectorTitleBarObjectFoldoutRenderRect(Rect rect) internal static Rect GetInspectorTitleBarObjectFoldoutRenderRect(Rect rect, GUIStyle baseStyle) { - return new Rect(rect.x + EditorStyles.foldout.margin.left + 1f, rect.y + (rect.height - kInspTitlebarFoldoutIconWidth) / 2 + (baseStyle != null ? baseStyle.padding.top : 0), kInspTitlebarFoldoutIconWidth, kInspTitlebarFoldoutIconWidth); + return new Rect(rect.x + EditorStyles.titlebarFoldout.margin.left + 1f, rect.y + (rect.height - kInspTitlebarFoldoutIconWidth) / 2 + (baseStyle != null ? baseStyle.padding.top : 0), kInspTitlebarFoldoutIconWidth, kInspTitlebarFoldoutIconWidth); } [SuppressMessage("ReSharper", "RedundantCast.0")] @@ -1513,7 +1551,7 @@ private static void DoObjectFoldoutInternal(bool foldout, Rect renderRect, int i { case EventType.Repaint: bool isPressed = GUIUtility.hotControl == id; - EditorStyles.foldout.Draw(renderRect, isPressed, isPressed, foldout, false); + EditorStyles.titlebarFoldout.Draw(renderRect, isPressed, isPressed, foldout, false); break; } @@ -1616,6 +1654,13 @@ internal static string TextFieldInternal(Rect position, GUIContent label, string return text; } + internal static string TextFieldInternal(int id, Rect position, GUIContent label, string text, GUIStyle style) + { + bool dummy; + text = DoTextField(s_RecycledEditor, id, PrefixLabel(position, id, label), text, style, null, out dummy, false, false, false); + return text; + } + internal static string ToolbarSearchField(Rect position, string text, bool showWithPopupArrow) { int id = GUIUtility.GetControlID(s_SearchFieldHash, FocusType.Keyboard, position); @@ -1637,9 +1682,7 @@ internal static string ToolbarSearchField(int id, Rect position, string text, bo if (Event.current.type == EventType.MouseUp && buttonRect.Contains(Event.current.mousePosition)) { s_RecycledEditor.text = text = ""; - GUIUtility.keyboardControl = 0; GUI.changed = true; - Event.current.Use(); } text = DoTextField(s_RecycledEditor, id, textRect, text, showWithPopupArrow ? EditorStyles.toolbarSearchFieldPopup : EditorStyles.toolbarSearchField, null, out dummy, false, false, false); GUI.Button(buttonRect, GUIContent.none, @@ -1682,10 +1725,13 @@ internal static string ToolbarSearchField(int id, Rect position, string[] search { using (new DisabledScope(true)) { - var placeHolderTextRect = position; + var placeHolderTextRect = EditorStyles.toolbarSearchFieldPopup.padding.Remove(new Rect(position.x, position.y, position.width + , EditorStyles.toolbarSearchFieldPopup.fixedHeight > 0 ? EditorStyles.toolbarSearchFieldPopup.fixedHeight : position.height)); + var oldFontSize = EditorStyles.label.fontSize; - placeHolderTextRect.xMin += EditorStyles.toolbarSearchFieldPopup.padding.right; + EditorStyles.label.fontSize = EditorStyles.toolbarSearchFieldPopup.fontSize; EditorStyles.label.Draw(placeHolderTextRect, EditorGUIUtility.TempContent(searchModes[searchMode]), false, false, false, false); + EditorStyles.label.fontSize = oldFontSize; } } @@ -2034,7 +2080,7 @@ static bool HasKeyboardFocus(int controlID) // Every EditorWindow has its own keyboardControl state so we also need to // check if the current OS view has focus to determine if the control has actual key focus (gets the input) // and not just being a focused control in an unfocused window. - return (GUIUtility.keyboardControl == controlID && GUIView.current.hasFocus); + return (GUIUtility.keyboardControl == controlID && EditorGUIUtility.HasCurrentWindowKeyFocus()); } internal static void DoNumberField(RecycledTextEditor editor, Rect position, Rect dragHotZone, int id, bool isDouble, ref double doubleVal, ref long longVal, string formatString, GUIStyle style, bool draggable, double dragSensitivity) @@ -2492,18 +2538,30 @@ internal static GenericMenu FillPropertyContextMenu(SerializedProperty property, if (Event.current.shift) { if (pm.GetItemCount() > 0) - { pm.AddSeparator(""); - } pm.AddItem(EditorGUIUtility.TrTextContent("Print Property Path"), false, e => Debug.Log(((SerializedProperty)e).propertyPath), propertyWithPath); } + // If property is a reference and we're using VCS, add item to check it out + if (propertyWithPath.propertyType == SerializedPropertyType.ObjectReference && Provider.isActive) + { + var obj = propertyWithPath.objectReferenceValue; + if (obj != null && !AssetDatabase.IsOpenForEdit(obj)) + { + if (pm.GetItemCount() > 0) + pm.AddSeparator(""); + pm.AddItem( + new GUIContent(L10n.Tr("Check Out") + " '" + obj.name + "'"), + false, + o => AssetDatabase.MakeEditable(AssetDatabase.GetAssetOrScenePath((Object)o)), + obj); + } + } + if (EditorApplication.contextualPropertyMenu != null) { if (pm.GetItemCount() > 0) - { pm.AddSeparator(""); - } EditorApplication.contextualPropertyMenu(pm, property); } @@ -2885,10 +2943,11 @@ internal static int Popup(Rect position, GUIContent label, int selectedIndex, st } // Called from PropertyField + private static void Popup(Rect position, SerializedProperty property, GUIContent label) { BeginChangeCheck(); - int idx = Popup(position, label, property.hasMultipleDifferentValues ? -1 : property.enumValueIndex, EditorGUIUtility.TempContent(property.enumLocalizedDisplayNames)); + int idx = Popup(position, label, property.hasMultipleDifferentValues ? -1 : property.enumValueIndex, EnumNamesCache.GetEnumLocalizedGUIContents(property)); if (EndChangeCheck()) { property.enumValueIndex = idx; @@ -2931,11 +2990,8 @@ private static Enum EnumPopupInternal(Rect position, GUIContent label, Enum sele var enumData = EnumDataUtility.GetCachedEnumData(enumType, !includeObsolete); var i = Array.IndexOf(enumData.values, selected); - GUIContent[] options; - using (new UnityEditor.Localization.Editor.LocalizationGroup(enumType)) - { - options = EditorGUIUtility.TrTempContent(enumData.displayNames, enumData.tooltip); - } + var options = EnumNamesCache.GetEnumTypeLocalizedGUIContents(enumType, enumData); + s_CurrentCheckEnumEnabled = checkEnabled; s_CurrentEnumData = enumData; i = PopupInternal(position, label, i, options, checkEnabled == null ? (Func)null : CheckCurrentEnumTypeEnabled, style); @@ -2952,11 +3008,8 @@ private static int EnumPopupInternal(Rect position, GUIContent label, int flagVa var enumData = EnumDataUtility.GetCachedEnumData(enumType, !includeObsolete); var i = Array.IndexOf(enumData.flagValues, flagValue); - GUIContent[] options; - using (new UnityEditor.Localization.Editor.LocalizationGroup(enumType)) - { - options = EditorGUIUtility.TrTempContent(enumData.displayNames, enumData.tooltip); - } + var options = EnumNamesCache.GetEnumTypeLocalizedGUIContents(enumType, enumData); + s_CurrentCheckEnumEnabled = checkEnabled; s_CurrentEnumData = enumData; i = PopupInternal(position, label, i, options, checkEnabled == null ? (Func)null : CheckCurrentEnumTypeEnabled, style); @@ -3919,7 +3972,10 @@ private static Rect RectFieldNoIndent(Rect position, Rect value) s_Vector2Floats[0] = value.x; s_Vector2Floats[1] = value.y; BeginChangeCheck(); - MultiFloatField(position, s_XYLabels, s_Vector2Floats); + // Right align the text + var oldAlignment = EditorStyles.label.alignment; + EditorStyles.label.alignment = TextAnchor.MiddleRight; + MultiFloatFieldInternal(position, s_XYLabels, s_Vector2Floats, kMiniLabelW); if (EndChangeCheck()) { value.x = s_Vector2Floats[0]; @@ -3929,12 +3985,13 @@ private static Rect RectFieldNoIndent(Rect position, Rect value) s_Vector2Floats[0] = value.width; s_Vector2Floats[1] = value.height; BeginChangeCheck(); - MultiFloatField(position, s_WHLabels, s_Vector2Floats); + MultiFloatFieldInternal(position, s_WHLabels, s_Vector2Floats, kMiniLabelW); if (EndChangeCheck()) { value.width = s_Vector2Floats[0]; value.height = s_Vector2Floats[1]; } + EditorStyles.label.alignment = oldAlignment; return value; } @@ -4170,7 +4227,7 @@ public static void MultiFloatField(Rect position, GUIContent[] subLabels, float[ MultiFloatFieldInternal(position, subLabels, values); } - internal static void MultiFloatFieldInternal(Rect position, GUIContent[] subLabels, float[] values) + internal static void MultiFloatFieldInternal(Rect position, GUIContent[] subLabels, float[] values, float prefixLabelWidth = -1) { int eCount = values.Length; float w = (position.width - (eCount - 1) * kSpacingSubLabel) / eCount; @@ -4180,7 +4237,7 @@ internal static void MultiFloatFieldInternal(Rect position, GUIContent[] subLabe indentLevel = 0; for (int i = 0; i < values.Length; i++) { - EditorGUIUtility.labelWidth = EditorGUI.CalcPrefixLabelWidth(subLabels[i]); + EditorGUIUtility.labelWidth = prefixLabelWidth > 0 ? prefixLabelWidth : EditorGUI.CalcPrefixLabelWidth(subLabels[i]); values[i] = FloatField(nr, subLabels[i], values[i]); nr.x += w + kSpacingSubLabel; } @@ -4361,7 +4418,10 @@ private static Color DoColorField(Rect position, int id, Color value, bool showE bool hovered = position.Contains(evt.mousePosition); bool hoveredEyedropper = new Rect(position.x + position.width - kEyedropperSize, position.y, kEyedropperSize, position.height).Contains(evt.mousePosition); - switch (evt.GetTypeForControl(id)) + var wasEnabled = GUI.enabled; + var eventType = GetEventTypeForControlAllowDisabledContextMenuPaste(evt, id); + + switch (eventType) { case EventType.MouseDown: if (showEyedropper) @@ -4387,7 +4447,7 @@ private static Color DoColorField(Rect position, int id, Color value, bool showE GUIUtility.keyboardControl = id; var names = new[] { L10n.Tr("Copy"), L10n.Tr("Paste") }; - var enabled = new[] {true, ColorClipboard.HasColor()}; + var enabled = new[] {true, wasEnabled && ColorClipboard.HasColor()}; var currentView = GUIView.current; EditorUtility.DisplayCustomMenu( @@ -4415,7 +4475,7 @@ private static Color DoColorField(Rect position, int id, Color value, bool showE if (showEyedropper) { - if (hoveredEyedropper) + if (hoveredEyedropper && wasEnabled) { GUIUtility.keyboardControl = id; EyeDropper.Start(GUIView.current); @@ -4867,12 +4927,19 @@ internal static void DoInspectorTitlebar(Rect position, int id, bool foldout, Ob { GUIStyle textStyle = EditorStyles.inspectorTitlebarText; GUIStyle iconButtonStyle = EditorStyles.iconButton; + Event evt = Event.current; + bool pressed = GUIUtility.hotControl == id; + bool hasFocus = GUIUtility.HasKeyFocus(id); + bool hovered = position.Contains(evt.mousePosition); Rect iconRect = GetIconRect(position, baseStyle); Rect settingsRect = GetSettingsRect(position, baseStyle, iconButtonStyle); Rect textRect = GetTextRect(position, iconRect, settingsRect, baseStyle, textStyle); - Event evt = Event.current; + if (evt.type == EventType.Repaint) + { + baseStyle.Draw(position, GUIContent.none, hovered, pressed, foldout, hasFocus); + } bool isAddedComponentAndEventIsRepaint = false; Component comp = targetObjs[0] as Component; @@ -4999,8 +5066,7 @@ internal static void DoInspectorTitlebar(Rect position, int id, bool foldout, Ob } break; case EventType.Repaint: - baseStyle.Draw(position, GUIContent.none, id, foldout, position.Contains(Event.current.mousePosition)); - textStyle.Draw(textRect, EditorGUIUtility.TempContent(ObjectNames.GetInspectorTitle(targetObjs[0])), id, foldout, textRect.Contains(Event.current.mousePosition)); + textStyle.Draw(textRect, EditorGUIUtility.TempContent(ObjectNames.GetInspectorTitle(targetObjs[0])), hovered, pressed, foldout, hasFocus); if (EditorGUIUtility.comparisonViewMode == EditorGUIUtility.ComparisonViewMode.None) { EditorStyles.optionsButtonStyle.Draw(settingsRect, GUIContent.none, id, foldout, settingsRect.Contains(Event.current.mousePosition)); @@ -5127,7 +5193,7 @@ internal static bool ToggleTitlebar(Rect position, GUIContent label, bool foldou GUIStyle baseStyle = EditorStyles.inspectorTitlebar; GUIStyle textStyle = EditorStyles.inspectorTitlebarText; - GUIStyle foldoutStyle = EditorStyles.foldout; + GUIStyle foldoutStyle = EditorStyles.titlebarFoldout; Rect toggleRect = new Rect(position.x + baseStyle.padding.left, position.y + baseStyle.padding.top, kInspTitlebarIconWidth, kInspTitlebarIconWidth); Rect textRect = new Rect(toggleRect.xMax + kInspTitlebarSpacing, toggleRect.y, 200, kInspTitlebarIconWidth); @@ -5158,15 +5224,38 @@ internal static bool FoldoutTitlebar(Rect position, GUIContent label, bool foldo // Important to get controlId for the foldout first, so it gets keyboard focus before the toggle does. int id = GUIUtility.GetControlID(s_TitlebarHash, FocusType.Keyboard, position); - if (Event.current.type == EventType.Repaint) + switch (Event.current.type) { - GUIStyle foldoutStyle = EditorStyles.foldout; - Rect textRect = new Rect(position.x + baseStyle.padding.left + (skipIconSpacing ? 0 : (kInspTitlebarIconWidth + kInspTitlebarSpacing)), position.y + baseStyle.padding.top, EditorGUIUtility.labelWidth, kInspTitlebarIconWidth); + case EventType.KeyDown: + if (GUIUtility.keyboardControl == id) + { + KeyCode kc = Event.current.keyCode; + if (kc == KeyCode.LeftArrow && foldout || (kc == KeyCode.RightArrow && foldout == false)) + { + foldout = !foldout; + GUI.changed = true; + Event.current.Use(); + return foldout; + } + } - baseStyle.Draw(position, GUIContent.none, id, foldout); - foldoutStyle.Draw(GetInspectorTitleBarObjectFoldoutRenderRect(position, baseStyle), GUIContent.none, id, foldout); - position = baseStyle.padding.Remove(position); - textStyle.Draw(textRect, EditorGUIUtility.TempContent(label.text), id, foldout); + break; + case EventType.Repaint: + GUIStyle foldoutStyle = EditorStyles.titlebarFoldout; + Rect textRect = + new Rect( + position.x + baseStyle.padding.left + + (skipIconSpacing ? 0 : (kInspTitlebarIconWidth + kInspTitlebarSpacing)), + position.y + baseStyle.padding.top, EditorGUIUtility.labelWidth, kInspTitlebarIconWidth); + bool hovered = position.Contains(Event.current.mousePosition); + baseStyle.Draw(position, GUIContent.none, id, foldout, hovered); + foldoutStyle.Draw(GetInspectorTitleBarObjectFoldoutRenderRect(position, baseStyle), GUIContent.none, + id, foldout, hovered); + position = baseStyle.padding.Remove(position); + textStyle.Draw(textRect, EditorGUIUtility.TempContent(label.text), id, foldout, hovered); + break; + default: + break; } return EditorGUIInternal.DoToggleForward(IndentedRect(position), id, foldout, GUIContent.none, GUIStyle.none); @@ -5177,43 +5266,44 @@ internal static bool HelpIconButton(Rect position, Object[] objs) { var obj = objs[0]; bool isDevBuild = Unsupported.IsSourceBuild(); + // For efficiency, only check in development builds if this script is a user script. bool monoBehaviourFallback = !isDevBuild; if (!monoBehaviourFallback) { - EditorCompilation.TargetAssemblyInfo[] allTargetAssemblies = EditorCompilationInterface.GetTargetAssemblies(); + monoBehaviourFallback = HelpButtonCache.IsObjectPartOfTargetAssemblies(obj); + } + + bool hasHelp = HelpButtonCache.HasHelpForObject(obj, monoBehaviourFallback); + if (hasHelp || isDevBuild) + { + // Help button should not be disabled at any time. For example VCS system disables + // inspectors and it wouldn't make sense to prevent users from getting help because + // he didn't check out the file yet. + bool wasEditorDisabled = GUI.enabled; + GUI.enabled = true; - string assemblyName = obj.GetType().Assembly.ToString(); - for (int i = 0; i < allTargetAssemblies.Length; ++i) + GUIContent content = GUIContent.Temp(GUIContents.helpIcon.image); + + // Only build the tooltip string if the cursor is over our help button rect + if (position.Contains(Event.current.mousePosition)) { - if (assemblyName == allTargetAssemblies[i].Name) + string helpTopic = Help.GetNiceHelpNameForObject(obj, monoBehaviourFallback); + if (isDevBuild && !hasHelp) { - monoBehaviourFallback = true; - break; + bool isScript = obj is MonoBehaviour; + string pageName = (isScript ? "script-" : "sealed partial class-") + helpTopic; + content.tooltip = string.Format(@"Could not find Reference page for {0} ({1}).\nDocs for this object is missing or all docs are missing.\nThis warning only shows up in development builds.", helpTopic, pageName); + } + else + { + content.tooltip = string.Format("Open Reference for {0}.", helpTopic); } } - } - bool hasHelp = Help.HasHelpForObject(obj, monoBehaviourFallback); - if (hasHelp || isDevBuild) - { + Color oldColor = GUI.color; - GUIContent content = new GUIContent(GUIContents.helpIcon); - string helpTopic = Help.GetNiceHelpNameForObject(obj, monoBehaviourFallback); if (isDevBuild && !hasHelp) - { GUI.color = Color.yellow; - bool isScript = obj is MonoBehaviour; - string pageName = (isScript ? "script-" : "sealed partial class-") + helpTopic; - - content.tooltip = string.Format( -@"Could not find Reference page for {0} ({1}). -Docs for this object is missing or all docs are missing. -This warning only shows up in development builds.", helpTopic, pageName); - } - else - { - content.tooltip = string.Format("Open Reference for {0}.", helpTopic); - } GUIStyle helpIconStyle = EditorStyles.iconButton; if (GUI.Button(position, content, helpIconStyle)) @@ -5222,6 +5312,8 @@ internal static bool HelpIconButton(Rect position, Object[] objs) } GUI.color = oldColor; + GUI.enabled = wasEditorDisabled; + return true; } return false; @@ -5246,19 +5338,11 @@ internal static bool FoldoutInternal(Rect position, bool foldout, GUIContent con eventType = Event.current.rawType; } - Rect clickRect = position; - if (!toggleOnLabelClick) - { - clickRect = origPosition; - clickRect.width += style.padding.right; - clickRect.x += indent; - } - switch (eventType) { case EventType.MouseDown: // If the mouse is inside the button, we say that we're the hot control - if (clickRect.Contains(Event.current.mousePosition) && Event.current.button == 0) + if (position.Contains(Event.current.mousePosition) && Event.current.button == 0) { GUIUtility.keyboardControl = GUIUtility.hotControl = id; Event.current.Use(); @@ -5274,6 +5358,13 @@ internal static bool FoldoutInternal(Rect position, bool foldout, GUIContent con Event.current.Use(); // toggle the passed-in value if the mouse was over the button & return true + Rect clickRect = position; + if (!toggleOnLabelClick) + { + clickRect.width = style.padding.left; + clickRect.x += indent; + } + if (clickRect.Contains(Event.current.mousePosition)) { GUI.changed = true; @@ -5972,10 +6063,7 @@ internal static Texture2D transparentCheckerTexture private static Material GetPreviewMaterial(ref Material m, string shaderPath) { if (m == null) - { m = new Material(EditorGUIUtility.LoadRequired(shaderPath) as Shader); - m.hideFlags = HideFlags.HideAndDontSave; - } return m; } @@ -7135,7 +7223,7 @@ private static void EnumPopup(Rect position, SerializedProperty property, GUICon if (type != null && type.IsEnum) { BeginChangeCheck(); - int value = type.GetCustomAttributes(typeof(FlagsAttribute), false).Length > 0 + int value = EnumNamesCache.IsEnumTypeUsingFlagsAttribute(type) ? EnumFlagsField(position, label, property.intValue, type, false, EditorStyles.popup) : EnumPopupInternal(position, label, property.intValue, type, null, false, EditorStyles.popup); if (EndChangeCheck()) @@ -7146,7 +7234,7 @@ private static void EnumPopup(Rect position, SerializedProperty property, GUICon else { BeginChangeCheck(); - int idx = Popup(position, label, property.hasMultipleDifferentValues ? -1 : property.enumValueIndex, EditorGUIUtility.TempContent(property.enumLocalizedDisplayNames)); + int idx = Popup(position, label, property.hasMultipleDifferentValues ? -1 : property.enumValueIndex, EnumNamesCache.GetEnumLocalizedGUIContents(property)); if (EndChangeCheck()) { property.enumValueIndex = idx; @@ -7419,6 +7507,117 @@ public static bool PropertyField(Rect position, SerializedProperty property, GUI { return PropertyFieldInternal(position, property, label, includeChildren); } + + static class EnumNamesCache + { + static Dictionary s_EnumTypeLocalizedGUIContents = new Dictionary(); + static Dictionary s_SerializedPropertyEnumLocalizedGUIContents = new Dictionary(); + static Dictionary s_IsEnumTypeUsingFlagsAttribute = new Dictionary(); + + internal static GUIContent[] GetEnumTypeLocalizedGUIContents(Type enumType, EnumData enumData) + { + GUIContent[] result; + if (s_EnumTypeLocalizedGUIContents.TryGetValue(enumType, out result)) + { + return result; + } + else + { + // Build localized data and add to cache + using (new Localization.Editor.LocalizationGroup(enumType)) + { + result = EditorGUIUtility.TrTempContent(enumData.displayNames, enumData.tooltip); + s_EnumTypeLocalizedGUIContents[enumType] = result; + return result; + } + } + } + + internal static GUIContent[] GetEnumLocalizedGUIContents(SerializedProperty property) + { + var propertyHash = property.hashCodeForPropertyPathWithoutArrayIndex; + var typeHash = property.serializedObject.targetObject.GetType().GetHashCode(); + var hashCode = typeHash ^ propertyHash; + GUIContent[] result; + if (s_SerializedPropertyEnumLocalizedGUIContents.TryGetValue(hashCode, out result)) + { + return result; + } + + result = EditorGUIUtility.TempContent(property.enumLocalizedDisplayNames); + s_SerializedPropertyEnumLocalizedGUIContents[hashCode] = result; + return result; + } + + internal static bool IsEnumTypeUsingFlagsAttribute(Type type) + { + bool result; + if (s_IsEnumTypeUsingFlagsAttribute.TryGetValue(type, out result)) + return result; + + // GetCustomAttributes allocates a new List on every call so we cache the result + result = type.GetCustomAttributes(typeof(FlagsAttribute), false).Length > 0; + s_IsEnumTypeUsingFlagsAttribute[type] = result; + return result; + } + } + + static class HelpButtonCache + { + static Dictionary s_TypeIsPartOfTargetAssembliesMap = new Dictionary(); + static Dictionary s_ObjectHasHelp = new Dictionary(); + + internal static bool HasHelpForObject(Object obj, bool monoBehaviourFallback) + { + if (obj == null) + return false; + + bool result; + if (s_ObjectHasHelp.TryGetValue(obj.GetType(), out result)) + { + return result; + } + else + { + // Calc result and cache it + result = Help.HasHelpForObject(obj, monoBehaviourFallback); + s_ObjectHasHelp[obj.GetType()] = result; + return result; + } + } + + internal static bool IsObjectPartOfTargetAssemblies(Object obj) + { + if (obj == null) + return false; + + var type = obj.GetType(); + bool result; + if (s_TypeIsPartOfTargetAssembliesMap.TryGetValue(type, out result)) + { + return result; + } + else + { + // Calc result and cache it + result = false; + EditorCompilation.TargetAssemblyInfo[] allTargetAssemblies = EditorCompilationInterface.GetTargetAssemblies(); + + string assemblyName = obj.GetType().Assembly.ManifestModule.Name; + for (int i = 0; i < allTargetAssemblies.Length; ++i) + { + if (assemblyName == allTargetAssemblies[i].Name) + { + result = true; + break; + } + } + + s_TypeIsPartOfTargetAssembliesMap[type] = result; + return result; + } + } + } } // Auto-layouted version of [[EditorGUI]] diff --git a/Editor/Mono/EditorGUIUtility.bindings.cs b/Editor/Mono/EditorGUIUtility.bindings.cs index feccfbbfd6..fc3bb6a891 100644 --- a/Editor/Mono/EditorGUIUtility.bindings.cs +++ b/Editor/Mono/EditorGUIUtility.bindings.cs @@ -150,7 +150,7 @@ public static void RenderGameViewCameras(RenderTexture target, int targetDisplay internal static extern Texture2D GetIconForObject(Object obj); // Render all ingame cameras bound to a specific Display. - internal static extern void RenderPreviewCamerasInternal(RenderTexture target, int targetDisplay, Vector2 mousePosition, bool gizmos, bool renderIMGUI); + internal static extern void RenderPlayModeViewCamerasInternal(RenderTexture target, int targetDisplay, Vector2 mousePosition, bool gizmos, bool renderIMGUI); internal static extern void SetupWindowSpaceAndVSyncInternal(Rect screenRect); private static extern Texture2D FindTextureByName(string name); diff --git a/Editor/Mono/EditorGUIUtility.cs b/Editor/Mono/EditorGUIUtility.cs index 0cac3e680f..1e8d3b816b 100644 --- a/Editor/Mono/EditorGUIUtility.cs +++ b/Editor/Mono/EditorGUIUtility.cs @@ -49,6 +49,7 @@ internal static Material GUITextureBlit2SRGBMaterial { Shader shader = LoadRequired("SceneView/GUITextureBlit2SRGB.shader") as Shader; s_GUITextureBlit2SRGBMaterial = new Material(shader); + s_GUITextureBlit2SRGBMaterial.hideFlags |= HideFlags.DontSaveInEditor; } s_GUITextureBlit2SRGBMaterial.SetFloat("_ManualTex2SRGB", QualitySettings.activeColorSpace == ColorSpace.Linear ? 1.0f : 0.0f); return s_GUITextureBlit2SRGBMaterial; @@ -64,6 +65,7 @@ internal static Material GUITextureBlitSceneGUIMaterial { Shader shader = LoadRequired("SceneView/GUITextureBlitSceneGUI.shader") as Shader; s_GUITextureBlitSceneGUI = new Material(shader); + s_GUITextureBlitSceneGUI.hideFlags |= HideFlags.DontSaveInEditor; } return s_GUITextureBlitSceneGUI; } @@ -73,9 +75,9 @@ internal static Material GUITextureBlitSceneGUIMaterial internal static int s_LastControlID = 0; private static float s_LabelWidth = 0f; - private static Texture2D s_InfoIcon; - private static Texture2D s_WarningIcon; - private static Texture2D s_ErrorIcon; + private static ScalableGUIContent s_InfoIcon; + private static ScalableGUIContent s_WarningIcon; + private static ScalableGUIContent s_ErrorIcon; private static GUIStyle s_WhiteTextureStyle; private static GUIStyle s_BasicTextureStyle; @@ -98,6 +100,7 @@ internal static Material GUITextureBlitSceneGUIMaterial static EditorGUIUtility() { GUISkin.m_SkinChanged += SkinChanged; + s_HasCurrentWindowKeyFocusFunc = HasCurrentWindowKeyFocus; } internal static void RepaintCurrentWindow() @@ -843,9 +846,34 @@ public static Vector2 GetIconSize() return Internal_GetIconSize(); } - internal static Texture2D infoIcon => s_InfoIcon ?? (s_InfoIcon = LoadIcon("console.infoicon")); - internal static Texture2D warningIcon => s_WarningIcon ?? (s_WarningIcon = LoadIcon("console.warnicon")); - internal static Texture2D errorIcon => s_ErrorIcon ?? (s_ErrorIcon = LoadIcon("console.erroricon")); + internal static Texture2D infoIcon + { + get + { + if (s_InfoIcon == null) + s_InfoIcon = new ScalableGUIContent("console.infoicon"); + return s_InfoIcon.image as Texture2D; + } + } + internal static Texture2D warningIcon + { + get + { + if (s_WarningIcon == null) + s_WarningIcon = new ScalableGUIContent("console.warnicon"); + return s_WarningIcon.image as Texture2D; + } + } + + internal static Texture2D errorIcon + { + get + { + if (s_ErrorIcon == null) + s_ErrorIcon = new ScalableGUIContent("console.erroricon"); + return s_ErrorIcon.image as Texture2D; + } + } internal static Texture2D GetHelpIcon(MessageType type) { @@ -926,7 +954,10 @@ private static UnityObject Load(string filename, Type type) asset = bundle.LoadAsset(filename, type); if (asset != null) + { + asset.hideFlags |= HideFlags.HideAndDontSave; return asset; + } return AssetDatabase.LoadAssetAtPath(filename, type); } @@ -1464,7 +1495,7 @@ public static void ShowObjectPicker(UnityObject obj, bool allowSceneObjects, { Type objType = typeof(T); //case 1113046: Delay the show method when it is called while other object picker is closing - if (Event.current.commandName == "ObjectSelectorClosed") + if (Event.current?.commandName == "ObjectSelectorClosed") EditorApplication.delayCall += () => SetupObjectSelector(obj, objType, allowSceneObjects, searchFilter, controlID); else SetupObjectSelector(obj, objType, allowSceneObjects, searchFilter, controlID); diff --git a/Editor/Mono/EditorHandles/Disc.cs b/Editor/Mono/EditorHandles/Disc.cs index 46c91133a9..b3d172098b 100644 --- a/Editor/Mono/EditorHandles/Disc.cs +++ b/Editor/Mono/EditorHandles/Disc.cs @@ -163,7 +163,7 @@ public static Quaternion Do(int id, Quaternion rotation, Vector3 position, Vecto Handles.DrawSolidArc(position, axis, from, d, size); // Draw snap markers - if (EditorSnapSettings.active && snap > 0) + if (EditorSnapSettings.incrementalSnapActive && snap > 0) { DrawRotationUnitSnapMarkers(position, axis, size, k_RotationUnitSnapMarkerSize, snap, @from); DrawRotationUnitSnapMarkers(position, axis, size, k_RotationUnitSnapMajorMarkerSize, k_RotationUnitSnapMajorMarkerStep, @from); diff --git a/Editor/Mono/EditorHandles/FreeMove.cs b/Editor/Mono/EditorHandles/FreeMove.cs index caaf88d949..d982a65462 100644 --- a/Editor/Mono/EditorHandles/FreeMove.cs +++ b/Editor/Mono/EditorHandles/FreeMove.cs @@ -231,7 +231,7 @@ public static Vector3 Do(int id, Vector3 position, Quaternion rotation, float si } } - if (EditorSnapSettings.active && !evt.shift) + if (EditorSnapSettings.incrementalSnapActive && !evt.shift) { Vector3 delta = position - s_StartPosition; delta = Handles.SnapValue(delta, snap); diff --git a/Editor/Mono/EditorHandles/ScaleHandle.cs b/Editor/Mono/EditorHandles/ScaleHandle.cs index 20b917db01..b50f7f8d19 100644 --- a/Editor/Mono/EditorHandles/ScaleHandle.cs +++ b/Editor/Mono/EditorHandles/ScaleHandle.cs @@ -125,6 +125,7 @@ public ScaleHandleParam(Handle handles, Vector3 axisOffset, Vector3 axisSize, Ve } static Vector3 s_DoScaleHandle_AxisHandlesOctant = Vector3.one; + static Vector3 s_InitialScale; public static Vector3 DoScaleHandle(Vector3 scale, Vector3 position, Quaternion rotation, float size) { @@ -156,6 +157,13 @@ internal static Vector3 DoScaleHandle(ScaleHandleIds ids, Vector3 scale, Vector3 var isCenterIsHot = ids.xyz == GUIUtility.hotControl; + switch (Event.current.type) + { + case EventType.MouseDown: + s_InitialScale = scale; + break; + } + for (var i = 0; i < 3; ++i) { if (!param.ShouldShow(i)) @@ -217,12 +225,9 @@ internal static Vector3 DoScaleHandle(ScaleHandleIds ids, Vector3 scale, Vector3 color = ToActiveColorSpace(centerColor); EditorGUI.BeginChangeCheck(); var s = ScaleValueHandle(ids.xyz, scale.x, position, rotation, handleSize * param.xyzSize, CubeHandleCap, EditorSnapSettings.scale); - if (EditorGUI.EndChangeCheck() && !Mathf.Approximately(scale.x, 0)) + if (EditorGUI.EndChangeCheck()) { - var dif = s / scale.x; - scale.x *= dif; - scale.y *= dif; - scale.z *= dif; + scale = s_InitialScale * s; } } diff --git a/Editor/Mono/EditorHandles/Slider1D.cs b/Editor/Mono/EditorHandles/Slider1D.cs index 85a6e54c83..d2a5476543 100644 --- a/Editor/Mono/EditorHandles/Slider1D.cs +++ b/Editor/Mono/EditorHandles/Slider1D.cs @@ -150,8 +150,8 @@ internal static Vector3 Do(int id, Vector3 position, Vector3 offset, Vector3 han Vector3 worldDirection = Handles.matrix.MultiplyVector(slideDirection); Vector3 worldPosition = Handles.matrix.MultiplyPoint(s_StartPosition) + worldDirection * dist; - if (EditorSnapSettings.active && EditorSnapSettings.preferGrid && Snapping.IsCardinalDirection(worldDirection)) - worldPosition = Handles.SnapValue(worldPosition, new SnapAxisFilter(worldDirection) * snap); + if (EditorSnapSettings.gridSnapActive) + worldPosition = Snapping.Snap(worldPosition, GridSettings.size, (SnapAxis) new SnapAxisFilter(worldDirection)); position = Handles.inverseMatrix.MultiplyPoint(worldPosition); diff --git a/Editor/Mono/EditorHandles/Slider2D.cs b/Editor/Mono/EditorHandles/Slider2D.cs index 99ba7b064c..c644ac4271 100644 --- a/Editor/Mono/EditorHandles/Slider2D.cs +++ b/Editor/Mono/EditorHandles/Slider2D.cs @@ -76,14 +76,14 @@ public static Vector3 Do( { handlePos = s_StartPosition + slideDir1 * delta.x + slideDir2 * delta.y; - if (EditorSnapSettings.active && EditorSnapSettings.preferGrid) + if (EditorSnapSettings.gridSnapActive) { var normal = Vector3.Cross(slideDir1, slideDir2); if (Snapping.IsCardinalDirection(normal)) { var worldSpace = Handles.matrix.MultiplyPoint(handlePos); - worldSpace = Handles.SnapValue(worldSpace, (~new SnapAxisFilter(normal)) * snap); + worldSpace = Snapping.Snap(worldSpace, GridSettings.size, (SnapAxis) ~new SnapAxisFilter(normal)); handlePos = Handles.inverseMatrix.MultiplyPoint(worldSpace); } } @@ -145,14 +145,14 @@ public static Vector3 Do( { handlePos = s_StartPosition + slideDir1 * delta.x + slideDir2 * delta.y; - if (EditorSnapSettings.active && EditorSnapSettings.preferGrid) + if (EditorSnapSettings.gridSnapActive) { var normal = Vector3.Cross(slideDir1, slideDir2); if (Snapping.IsCardinalDirection(normal)) { var worldSpace = Handles.matrix.MultiplyPoint(handlePos); - worldSpace = Handles.SnapValue(worldSpace, (~new SnapAxisFilter(normal)) * snap); + worldSpace = Snapping.Snap(worldSpace, GridSettings.size, (SnapAxis) ~new SnapAxisFilter(normal)); handlePos = Handles.inverseMatrix.MultiplyPoint(worldSpace); } } diff --git a/Editor/Mono/EditorHeaderItemAttribute.cs b/Editor/Mono/EditorHeaderItemAttribute.cs index 8a6fafc42b..50d7984572 100644 --- a/Editor/Mono/EditorHeaderItemAttribute.cs +++ b/Editor/Mono/EditorHeaderItemAttribute.cs @@ -21,6 +21,6 @@ public EditorHeaderItemAttribute(Type targetType, int priority = 1) public Type TargetType; [RequiredSignature] - static extern bool SignatureBool(Rect rectangle, UnityEngine.Object[] targetObjets); + static bool SignatureBool(Rect rectangle, UnityEngine.Object[] targetObjets) { throw new InvalidOperationException(); } } } diff --git a/Editor/Mono/EditorMode/ModeService.cs b/Editor/Mono/EditorMode/ModeService.cs index b875b82a9f..05c975ce8d 100644 --- a/Editor/Mono/EditorMode/ModeService.cs +++ b/Editor/Mono/EditorMode/ModeService.cs @@ -69,7 +69,7 @@ public struct ModeChangedArgs } internal const string k_DefaultModeId = "default"; - internal const string k_ModeIndexKeyName = "mode-index"; + internal const string k_ModeCurrentIdKeyName = "mode-current-id"; internal const string k_CapabilitiesSectionName = "capabilities"; internal const string k_ExecuteHandlersSectionName = "execute_handlers"; internal const string k_LayoutsSectionName = "layouts"; @@ -83,6 +83,7 @@ public struct ModeChangedArgs public static string currentId => currentIndex == -1 ? k_DefaultModeId : modes[currentIndex].id; public static int currentIndex { get; private set; } private static ModeEntry[] modes { get; set; } = new ModeEntry[0]; + internal static bool hasSwitchableModes { get; private set; } public static event Action modeChanged; @@ -94,10 +95,16 @@ static ModeService() modeChanged += OnModeChangeLayouts; } - public static void ChangeModeById(string modeId) + internal static int GetModeIndexById(string modeId) { string lcModeId = modeId.ToLowerInvariant(); int modeIndex = Array.FindIndex(modes, m => m.id == lcModeId); + return modeIndex; + } + + public static void ChangeModeById(string modeId) + { + int modeIndex = GetModeIndexById(modeId); if (modeIndex != -1) ChangeModeByIndex(modeIndex); } @@ -278,87 +285,110 @@ private static void LoadModes(bool checkStartupMode = false) if (checkStartupMode && HasStartupMode()) { var requestEditorMode = Application.GetValueForARGV("editor-mode"); - var modeIndex = Array.FindIndex(modes, m => m.id == requestEditorMode); + var modeIndex = GetModeIndexById(requestEditorMode); if (modeIndex != -1) { currentModeIndex = modeIndex; - SaveProjectPrefModeIndex(currentModeIndex); + Console.WriteLine($"[MODES] Loading editor mode {modeNames[currentModeIndex]} ({currentModeIndex}) from command line."); } } SetModeIndex(currentModeIndex); - EditorApplication.delayCall += () => RaiseModeChanged(-1, currentIndex); + + EditorApplication.update -= DelayRaiseCurrentModeChanged; + EditorApplication.update += DelayRaiseCurrentModeChanged; + } + + private static void DelayRaiseCurrentModeChanged() + { + EditorApplication.update -= DelayRaiseCurrentModeChanged; + RaiseModeChanged(-1, currentIndex); + } + + private static void FillModeData(string path, Dictionary modesData) + { + try + { + var json = SJSON.Load(path); + foreach (var rawModeId in json.Keys) + { + var modeId = ((string)rawModeId).ToLower(); + if (IsValidModeId(modeId)) + { + object modeData = null; + if (modesData.TryGetValue(modeId, out modeData)) + modesData[modeId] = JsonUtils.DeepMerge(modeData as JSONObject, json[modeId] as JSONObject); + else + modesData[modeId] = json[modeId]; + } + else + { + Debug.LogWarning($"Invalid Mode Id: {modeId} contains non alphanumeric characters."); + } + } + } + catch (Exception ex) + { + Debug.LogError($"[ModeService] Error while parsing mode file {path}.\n{ex}"); + } } - private static void ScanModes() + internal static void ScanModes() { var modesData = new Dictionary { [k_DefaultModeId] = new Dictionary { [k_LabelSectionName] = "Default" } }; + + var builtinModeFile = Path.Combine(EditorApplication.applicationContentsPath, "Resources/default.mode"); + FillModeData(builtinModeFile, modesData); + var modeDescriptors = AssetDatabase.EnumerateAllAssets(new SearchFilter { searchArea = SearchFilter.SearchArea.InPackagesOnly, classNames = new[] { nameof(ModeDescriptor) }, - skipHidden = true, showAllHits = true }); + while (modeDescriptors.MoveNext()) { var md = modeDescriptors.Current.pptrValue as ModeDescriptor; if (md == null) continue; - try - { - var json = SJSON.Load(md.path); - foreach (var rawModeId in json.Keys) - { - var modeId = ((string)rawModeId).ToLower(); - if (IsValidModeId(modeId)) - { - if (modesData.ContainsKey(modeId)) - modesData[modeId] = JsonUtils.DeepMerge(modesData[modeId] as JSONObject, json[modeId] as JSONObject); - else - modesData[modeId] = json[modeId]; - } - else - { - Debug.LogWarning($"Invalid Mode Id: {modeId} contains non alphanumeric characters."); - } - } - } - catch (Exception ex) - { - Debug.LogError($"[ModeService] Error while parsing mode file {md.path}.\n{ex}"); - } + FillModeData(md.path, modesData); } modes = new ModeEntry[modesData.Keys.Count]; modes[0] = CreateEntry(k_DefaultModeId, (JSONObject)modesData[k_DefaultModeId]); var modeIndex = 1; + hasSwitchableModes = false; foreach (var modeId in modesData.Keys) { if (modeId == k_DefaultModeId) continue; - modes[modeIndex] = CreateEntry(modeId, (JSONObject)modesData[modeId]); + var modeFields = (JSONObject)modesData[modeId]; + modes[modeIndex] = CreateEntry(modeId, modeFields); + hasSwitchableModes |= !JsonUtils.JsonReadBoolean(modeFields, "builtin"); modeIndex++; } + + Array.Sort(modes, (m1, m2) => + { + if (m1.id == "default") + return -1; + if (m2.id == "default") + return 1; + return m1.id.CompareTo(m2.id); + }); } private static ModeEntry CreateEntry(string modeId, JSONObject data) { return new ModeEntry { - id = modeId, + id = modeId.ToLowerInvariant(), name = JsonUtils.JsonReadString(data, k_LabelSectionName, modeId), data = data }; } - private static bool IsEditorModeDescriptor(string path) - { - var pathLowerCased = path.ToLower(); - // Limit the authoring of editor modes to Unity packages for now. - return pathLowerCased.StartsWith("packages/com.unity") && pathLowerCased.EndsWith(".mode"); - } - private static void SetModeIndex(int modeIndex) { currentIndex = Math.Max(0, Math.Min(modeIndex, modeCount - 1)); @@ -366,12 +396,20 @@ private static void SetModeIndex(int modeIndex) private static int LoadProjectPrefModeIndex() { - return EditorPrefs.GetInt(GetProjectPrefKeyName(k_ModeIndexKeyName), 0); + var modePreyKeyName = GetProjectPrefKeyName(k_ModeCurrentIdKeyName); + var loadModeId = EditorPrefs.GetString(modePreyKeyName, "default"); + var loadModeIndex = GetModeIndexById(loadModeId); + if (loadModeIndex == -1) + return 0; // Fallback to default mode index + Console.WriteLine($"[MODES] Loading mode {modeNames[loadModeIndex]} ({loadModeIndex}) for {modePreyKeyName}"); + return loadModeIndex; } private static void SaveProjectPrefModeIndex(int modeIndex) { - EditorPrefs.SetInt(GetProjectPrefKeyName(k_ModeIndexKeyName), modeIndex); + var modePreyKeyName = GetProjectPrefKeyName(k_ModeCurrentIdKeyName); + Console.WriteLine($"[MODES] Saving user mode to {modeNames[modeIndex]} ({modeIndex}) for {modePreyKeyName}"); + EditorPrefs.SetString(modePreyKeyName, modes[modeIndex].id); } private static string GetProjectPrefKeyName(string prefix) @@ -522,7 +560,8 @@ private static void OnModeChangeLayouts(ModeChangedArgs args) } } - WindowLayout.ReloadWindowLayoutMenu(); + if (HasCapability(ModeCapability.LayoutWindowMenu, true)) + WindowLayout.ReloadWindowLayoutMenu(); } private static void OnModeChangeUpdate(ModeChangedArgs args) @@ -626,7 +665,7 @@ private static void DeepMergeArray(IList firstArray, IList secondArray) ? firstItemObject["id"] as string : (firstItemObject.Contains("name") ? firstItemObject["name"] as string : null); - if (firstItemId == secondItemId) + if (String.Equals(firstItemId, secondItemId, StringComparison.OrdinalIgnoreCase)) { DeepMergeInto(firstItemObject, secondItemObject); merged = true; diff --git a/Editor/Mono/EditorResources.bindings.cs b/Editor/Mono/EditorResources.bindings.cs index 4533e14a07..0b469adc9b 100644 --- a/Editor/Mono/EditorResources.bindings.cs +++ b/Editor/Mono/EditorResources.bindings.cs @@ -22,6 +22,7 @@ partial class EditorResources [NativeProperty("k_GeneratedIconsPath", true, TargetType.Field)] public static extern string generatedIconsPath { get; } [NativeProperty("k_FolderIconName", true, TargetType.Field)] public static extern string folderIconName { get; } [NativeProperty("k_EmptyFolderIconName", true, TargetType.Field)] public static extern string emptyFolderIconName { get; } + [NativeProperty("k_OpenedFolderIconName", true, TargetType.Field)] internal static extern string openedFolderIconName { get; } [NativeProperty("k_EditorDefaultResourcesPath", true, TargetType.Field)] public static extern string editorDefaultResourcesPath { get; } [NativeProperty("k_LibraryBundlePath", true, TargetType.Field)] public static extern string libraryBundlePath { get; } diff --git a/Editor/Mono/EditorResources.cs b/Editor/Mono/EditorResources.cs index aa403b2f2b..8b14f361e5 100644 --- a/Editor/Mono/EditorResources.cs +++ b/Editor/Mono/EditorResources.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using JetBrains.Annotations; +using UnityEditor.Profiling; using UnityEditor.StyleSheets; using UnityEngine; using UnityEngine.Internal; @@ -14,12 +15,113 @@ namespace UnityEditor.Experimental { + internal class FontDef + { + class FontData + { + private string m_File; + private Font m_LoadedFont; + private string[] m_FontNames; + + public FontData(string file, string[] fontNames) + { + m_File = file; + m_FontNames = fontNames; + } + + public Font font + { + get + { + if (m_LoadedFont == null) + { + m_LoadedFont = EditorGUIUtility.LoadRequired(m_File) as Font; + if (m_LoadedFont != null && m_FontNames != null) + { + m_LoadedFont.fontNames = m_FontNames; + } + } + + return m_LoadedFont; + } + } + } + + public const string k_Inter = "Inter"; + public const string k_LucidaGrande = "Lucida Grande"; + public const string k_Verdana = "Verdana"; + + private Dictionary m_Fonts = new Dictionary(); + + public enum Style + { + Normal = FontStyle.Normal, + Bold = FontStyle.Bold, + Italic = FontStyle.Italic, + BoldAndItalic = FontStyle.BoldAndItalic, + Small + } + + public string name { get; set; } + + public FontDef(string name) + { + this.name = name; + } + + public Font GetFont(Style style) + { + FontData data; + + if (m_Fonts.TryGetValue(style, out data)) + { + return data.font; + } + + if (style != Style.Normal) + return GetFont(Style.Normal); + return null; + } + + public void SetFont(Style style, string file, string[] fontNames = null) + { + m_Fonts[style] = new FontData(file, fontNames); + } + + public static FontDef CreateFromResources(string fontName, Dictionary fonts) + { + FontDef fontDef = new FontDef(fontName); + + foreach (var font in fonts) + { + fontDef.SetFont(font.Key, font.Value); + } + + return fontDef; + } + + public static FontDef CreateSystemFont(string fontName) + { + FontDef fontDef = new FontDef(fontName); + + fontDef.SetFont(Style.Small, "Fonts/System/System Small.ttf", new string[] {fontName}); + fontDef.SetFont(Style.Normal, "Fonts/System/System Normal.ttf", new string[] { fontName }); + fontDef.SetFont(Style.Bold, "Fonts/System/System Normal Bold.ttf", new string[] { fontName + " Bold"}); + return fontDef; + } + } + [ExcludeFromDocs] public partial class EditorResources { + private const string k_PrefsUserFontKey = "user_editor_font"; private static StyleCatalog s_StyleCatalog; private static bool s_RefreshGlobalStyleCatalog = false; - private static Dictionary s_BuiltInFonts = null; + + static class Constants + { + public static bool isDarkTheme => EditorGUIUtility.isProSkin; + } // Global editor styles internal static StyleCatalog styleCatalog @@ -43,175 +145,119 @@ private static bool CanEnableExtendedStyles() private static bool IsEditorStyleSheet(string path) { - var pathLowerCased = path.ToLower(); - return pathLowerCased.Contains("/stylesheets/extensions/") && (pathLowerCased.EndsWith("common.uss") || - pathLowerCased.EndsWith(EditorGUIUtility.isProSkin ? "dark.uss" : "light.uss")); + return path.IndexOf("/stylesheets/extensions/", StringComparison.OrdinalIgnoreCase) != -1 && + (path.EndsWith("common.uss", StringComparison.OrdinalIgnoreCase) || + path.EndsWith(Constants.isDarkTheme ? "dark.uss" : "light.uss", StringComparison.OrdinalIgnoreCase)); } internal static string GetDefaultFont() { - if (Application.platform == RuntimePlatform.WindowsEditor) - return "Verdana"; - else - return "Roboto"; - } - - internal static string GetCurrentFont() - { - var currentFont = EditorPrefs.GetString("user_editor_font", GetDefaultFont()); - - // If the current is not available then fallback to the default font - if (!GetSupportedFonts().Contains(currentFont)) + // In languages other than english, we use 'lucida grande' because the localization settings are currently mapped to 'Lucida Grande'. + // see Editor/Resources/Windows/fontsettings.text for instance + if (LocalizationDatabase.currentEditorLanguage == SystemLanguage.English) { - currentFont = GetDefaultFont(); - EditorPrefs.DeleteKey("user_editor_font"); + return FontDef.k_Inter; } - - return currentFont; - } - - private static Font s_SmallFont; - internal static Font GetSmallFont() - { - if (s_SmallFont == null) + else { - var currentFont = GetCurrentFont(); - - if (IsSystemFont(currentFont)) - { - s_SmallFont = EditorGUIUtility.LoadRequired("Fonts/System/System Small.ttf") as Font; - s_SmallFont.fontNames = new[] { currentFont }; - } - else - { - if (currentFont == "Roboto") - { - s_SmallFont = EditorGUIUtility.LoadRequired("Fonts/roboto/Roboto-Small.ttf") as Font; - } - else if (currentFont == "Lucida Grande") - { - s_SmallFont = EditorGUIUtility.LoadRequired("Fonts/Lucida Grande small.ttf") as Font; - } - else - { - s_SmallFont = GetNormalFont(); - } - } + return FontDef.k_LucidaGrande; } - - return s_SmallFont; } - private static Font s_NormalFont; - internal static Font GetNormalFont() - { - if (s_NormalFont == null) - { - var currentFont = GetCurrentFont(); + internal static IEnumerable supportedFontNames => EditorResources.supportedFonts.Keys; - if (IsSystemFont(currentFont)) - { - s_NormalFont = EditorGUIUtility.LoadRequired("Fonts/System/System Normal.ttf") as Font; - s_NormalFont.fontNames = new[] { currentFont }; - } - else - { - s_NormalFont = EditorGUIUtility.LoadRequired(builtInFonts[currentFont]) as Font; - } - } + private static string s_CurrentFontName = null; - return s_NormalFont; - } - - private static Font s_BoldFont; - internal static Font GetBoldFont() + internal static string currentFontName { - if (s_BoldFont == null) + get { - var currentFont = GetCurrentFont(); - - if (IsSystemFont(currentFont)) + if (s_CurrentFontName == null) { - s_BoldFont = EditorGUIUtility.LoadRequired("Fonts/System/System Normal Bold.ttf") as Font; - s_BoldFont.fontNames = new[] { currentFont + " Bold" }; - } - else - { - if (currentFont == "Roboto") + if (LocalizationDatabase.currentEditorLanguage == SystemLanguage.English) { - s_BoldFont = EditorGUIUtility.LoadRequired("Fonts/roboto/Roboto-Bold.ttf") as Font; - } - else if (currentFont == "Lucida Grande") - { - s_BoldFont = EditorGUIUtility.LoadRequired("Fonts/Lucida Grande Bold.ttf") as Font; + s_CurrentFontName = EditorPrefs.GetString(k_PrefsUserFontKey, GetDefaultFont()); + + // If the current is not available then fallback to the default font + if (!supportedFontNames.Contains(s_CurrentFontName)) + { + s_CurrentFontName = GetDefaultFont(); + EditorPrefs.DeleteKey(k_PrefsUserFontKey); + } } else { - s_BoldFont = EditorGUIUtility.LoadRequired(builtInFonts[currentFont]) as Font; + s_CurrentFontName = GetDefaultFont(); } } - } - return s_BoldFont; + return s_CurrentFontName; + } } - private static List s_SupportedFonts = null; - - internal static List GetSupportedFonts() + internal static Font GetFont(FontDef.Style fontStyle) { - if (s_SupportedFonts == null) - { - s_SupportedFonts = new List(); + var currentFontDef = supportedFonts[currentFontName]; - if (Application.platform == RuntimePlatform.WindowsEditor) - { - s_SupportedFonts.Add("Segoe UI"); - } + return currentFontDef.GetFont(fontStyle); + } - foreach (var builtinFont in EditorResources.builtInFonts.Keys) + static void AddLucidaGrande() + { + s_SupportedFonts[FontDef.k_LucidaGrande] = FontDef.CreateFromResources(FontDef.k_LucidaGrande, + new Dictionary { - s_SupportedFonts.Add(builtinFont); - } - - if (!s_SupportedFonts.Contains(EditorResources.GetDefaultFont())) - s_SupportedFonts.Add(EditorResources.GetDefaultFont()); - } - - return s_SupportedFonts; + {FontDef.Style.Small, "Fonts/Lucida Grande small.ttf"}, + {FontDef.Style.Normal, "Fonts/Lucida Grande.ttf"}, + {FontDef.Style.Bold, "Fonts/Lucida Grande Bold.ttf"} + }); } - internal static Dictionary builtInFonts + private static Dictionary s_SupportedFonts = null; + internal static Dictionary supportedFonts { get { - if (s_BuiltInFonts == null) + if (s_SupportedFonts == null) { - s_BuiltInFonts = new Dictionary - { - ["Roboto"] = "Fonts/roboto/Roboto-Regular.ttf" - }; + s_SupportedFonts = new Dictionary(); - if (Application.platform != RuntimePlatform.WindowsEditor) + // In languages other than english, we use 'lucida grande' because the localization settings are currently are mapped to 'lucida grande'. + // see Editor/Resources/Windows/fontsettings.text for instance + if (LocalizationDatabase.currentEditorLanguage != SystemLanguage.English) { - s_BuiltInFonts["Lucida Grande"] = "Fonts/Lucida Grande.ttf"; + AddLucidaGrande(); + } + else + { + s_SupportedFonts[FontDef.k_Inter] = FontDef.CreateFromResources(FontDef.k_Inter, + new Dictionary + { + {FontDef.Style.Small, "Fonts/Inter/Inter-Small.ttf"}, + {FontDef.Style.Normal, "Fonts/Inter/Inter-Regular.ttf"}, + {FontDef.Style.Bold, "Fonts/Inter/Inter-SemiBold.ttf"}, + {FontDef.Style.Italic, "Fonts/Inter/Inter-Italic.ttf"}, + {FontDef.Style.BoldAndItalic, "Fonts/Inter/Inter-SemiBoldItalic.ttf"} + }); + + if (Application.platform == RuntimePlatform.WindowsEditor) + { + s_SupportedFonts[FontDef.k_Verdana] = FontDef.CreateSystemFont(FontDef.k_Verdana); + } + else + { + AddLucidaGrande(); + } } } - return s_BuiltInFonts; + return s_SupportedFonts; } } - internal static bool IsSystemFont(string font) - { - // TODO: Can be better - if (builtInFonts.ContainsKey(font)) - return false; - return true; - } - private static List GetDefaultStyleCatalogPaths() { - bool useDarkTheme = EditorGUIUtility.isProSkin; + bool useDarkTheme = Constants.isDarkTheme; var catalogFiles = new List { "StyleSheets/Extensions/base/common.uss", @@ -220,7 +266,7 @@ private static List GetDefaultStyleCatalogPaths() if (LocalizationDatabase.currentEditorLanguage == SystemLanguage.English) { - string currentFontStyleSheet = "StyleSheets/Extensions/fonts/" + GetCurrentFont().ToLower() + ".uss"; + string currentFontStyleSheet = "StyleSheets/Extensions/fonts/" + currentFontName.ToLower() + ".uss"; catalogFiles.Add(currentFontStyleSheet); } @@ -230,36 +276,87 @@ private static List GetDefaultStyleCatalogPaths() return catalogFiles; } - [UsedImplicitly, RequiredByNativeCode] + static string ComputeCatalogHash(List paths) + { + var hash = $"__StyleCatalog_Hash_{Application.unityVersion}_"; + foreach (var path in paths) + { + hash += path.GetHashCode().ToString("X2"); + var fi = new FileInfo(path); + if (fi.Exists) + hash += fi.Length.ToString("X2"); + } + return hash; + } + + static void SaveCatalogToDisk(StyleCatalog catalog, string hash, string savePath) + { + using (var stream = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None)) + using (BinaryWriter writer = new BinaryWriter(stream)) + { + writer.Write(hash); + catalog.Save(writer); + } + } + internal static void BuildCatalog() { - s_StyleCatalog = new StyleCatalog(); - s_RefreshGlobalStyleCatalog = false; + using (new EditorPerformanceTracker(nameof(BuildCatalog))) + { + s_StyleCatalog = new StyleCatalog(); - var paths = GetDefaultStyleCatalogPaths(); - foreach (var editorUssPath in AssetDatabase.FindAssets("t:StyleSheet").Select(AssetDatabase.GUIDToAssetPath).Where(IsEditorStyleSheet)) - paths.Add(editorUssPath); + var paths = GetDefaultStyleCatalogPaths(); + foreach (var editorUssPath in AssetDatabase.FindAssets("t:StyleSheet").Select(AssetDatabase.GUIDToAssetPath).Where(IsEditorStyleSheet)) + paths.Add(editorUssPath); - Console.WriteLine($"Building style catalogs ({paths.Count})\r\n\t{String.Join("\r\n\t", paths.ToArray())}"); - styleCatalog.Load(paths); + var forceRebuild = s_RefreshGlobalStyleCatalog; + s_RefreshGlobalStyleCatalog = false; + + bool rebuildCatalog = true; + string catalogHash = ComputeCatalogHash(paths); + const string k_GlobalStyleCatalogCacheFilePath = "Library/Style.catalog"; + if (!forceRebuild && File.Exists(k_GlobalStyleCatalogCacheFilePath)) + { + using (var cacheCatalogStream = new FileStream(k_GlobalStyleCatalogCacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + using (var ccReader = new BinaryReader(cacheCatalogStream)) + { + string cacheHash = ccReader.ReadString(); + if (cacheHash == catalogHash) + rebuildCatalog = !styleCatalog.Load(ccReader); + } + } + } + + if (rebuildCatalog) + { + Console.WriteLine($"Loading style catalogs ({paths.Count})\r\n\t{String.Join("\r\n\t", paths.ToArray())}"); + + styleCatalog.Load(paths); + SaveCatalogToDisk(styleCatalog, catalogHash, k_GlobalStyleCatalogCacheFilePath); + } + } } internal static void RefreshSkin() { - if (!CanEnableExtendedStyles()) - return; + using (new EditorPerformanceTracker(nameof(RefreshSkin))) + { + if (!CanEnableExtendedStyles()) + return; - GUIStyle.onDraw = StylePainter.DrawStyle; + GUIStyle.onDraw = StylePainter.DrawStyle; - // Update gui skin style layouts - var skin = GUIUtility.GetDefaultSkin(); - if (skin != null) - { - // TODO: Emit OnStyleCatalogLoaded - if (Path.GetFileName(Path.GetDirectoryName(Application.dataPath)) == "editor_resources") - ConverterUtils.ResetSkinToPristine(skin, EditorGUIUtility.isProSkin ? SkinTarget.Dark : SkinTarget.Light); - skin.font = GetNormalFont(); - UpdateGUIStyleProperties(skin); + // Update gui skin style layouts + var skin = GUIUtility.GetDefaultSkin(); + if (skin != null) + { + // TODO: Emit OnStyleCatalogLoaded + if (Path.GetFileName(Path.GetDirectoryName(Application.dataPath)) == "editor_resources") + ConverterUtils.ResetSkinToPristine(skin, Constants.isDarkTheme ? SkinTarget.Dark : SkinTarget.Light); + skin.font = GetFont(FontDef.Style.Normal); + UpdateGUIStyleProperties(skin); + } } } @@ -303,7 +400,7 @@ private static void ResetDeprecatedBackgroundImage(GUIStyleState state) internal static void RefreshStyles() { Unsupported.ClearSkinCache(); - InternalEditorUtility.RequestScriptReload(); + EditorUtility.RequestScriptReload(); InternalEditorUtility.RepaintAllViews(); Debug.Log($"Style refreshed {DateTime.Now}"); } diff --git a/Editor/Mono/EditorSettings.bindings.cs b/Editor/Mono/EditorSettings.bindings.cs index abf2395a5a..bbe4643a9a 100644 --- a/Editor/Mono/EditorSettings.bindings.cs +++ b/Editor/Mono/EditorSettings.bindings.cs @@ -40,6 +40,19 @@ public enum LineEndingsMode Windows = 2 } + public enum AssetPipelineMode + { + Version1 = 0, + Version2 = 1 + } + + public enum CacheServerMode + { + AsPreferences = 0, + Enabled = 1, + Disabled = 2 + } + [Flags] public enum EnterPlayModeOptions { @@ -237,5 +250,23 @@ internal static extern string Internal_ProjectGenerationUserExtensions [StaticAccessor("GetEditorSettings()", StaticAccessorType.Dot)] public static extern EnterPlayModeOptions enterPlayModeOptions { get; set; } + + [StaticAccessor("GetEditorSettings()", StaticAccessorType.Dot)] + public static extern AssetPipelineMode assetPipelineMode { get; set; } + + [StaticAccessor("GetEditorSettings()", StaticAccessorType.Dot)] + public static extern CacheServerMode cacheServerMode { get; set; } + + [StaticAccessor("GetEditorSettings()", StaticAccessorType.Dot)] + public static extern string cacheServerEndpoint { get; set; } + + [StaticAccessor("GetEditorSettings()", StaticAccessorType.Dot)] + public static extern string cacheServerNamespacePrefix { get; set; } + + [StaticAccessor("GetEditorSettings()", StaticAccessorType.Dot)] + public static extern bool cacheServerEnableDownload { get; set; } + + [StaticAccessor("GetEditorSettings()", StaticAccessorType.Dot)] + public static extern bool cacheServerEnableUpload { get; set; } } } diff --git a/Editor/Mono/EditorUserBuildSettings.bindings.cs b/Editor/Mono/EditorUserBuildSettings.bindings.cs index 334e63a8fd..076c9c41f6 100644 --- a/Editor/Mono/EditorUserBuildSettings.bindings.cs +++ b/Editor/Mono/EditorUserBuildSettings.bindings.cs @@ -413,6 +413,8 @@ public static extern AndroidETC2Fallback androidETC2Fallback [Obsolete("androidUseLegacySdkTools has been deprecated. It does not have any effect.")] public static extern bool androidUseLegacySdkTools { get; set; } + public static extern bool androidCreateSymbolsZip { get; set; } + // *undocumented* // NOTE: This setting should probably not be a part of the public API as is. Atm it is used by playmode tests // and applied during build post-processing. We will however move towards separating building and launching diff --git a/Editor/Mono/EditorUtility.bindings.cs b/Editor/Mono/EditorUtility.bindings.cs index 74f27581c8..67df7c12e5 100644 --- a/Editor/Mono/EditorUtility.bindings.cs +++ b/Editor/Mono/EditorUtility.bindings.cs @@ -60,6 +60,8 @@ public static bool DisplayDialog(string title, string message, string ok) [FreeFunction("EditorUtility::SetDirtyObjectOrScene")] public static extern void SetDirty([NotNull] Object target); + public static extern void ClearDirty([NotNull] Object target); + [FreeFunction("InvokeDiffTool")] public static extern string InvokeDiffTool(string leftTitle, string leftFile, string rightTitle, string rightFile, string ancestorTitle, string ancestorFile); diff --git a/Editor/Mono/EditorWindow.bindings.cs b/Editor/Mono/EditorWindow.bindings.cs index 9a05ab5e86..7e3d56999f 100644 --- a/Editor/Mono/EditorWindow.bindings.cs +++ b/Editor/Mono/EditorWindow.bindings.cs @@ -10,7 +10,7 @@ namespace UnityEditor public partial class EditorWindow { [FreeFunction("ContainerWindowBindings::MakeModal")] - internal static extern void MakeModal(ContainerWindow win); + internal static extern void Internal_MakeModal(ContainerWindow win); } } diff --git a/Editor/Mono/EditorWindow.cs b/Editor/Mono/EditorWindow.cs index 2d0c07136e..33ef67bce6 100644 --- a/Editor/Mono/EditorWindow.cs +++ b/Editor/Mono/EditorWindow.cs @@ -136,7 +136,6 @@ internal void ClearPersistentViewData() Vector2 m_GameViewTargetSize; bool m_IsPlayModeView; - bool m_DontClearBackground; EventInterests m_EventInterests = new EventInterests(); bool m_DisableInputEvents; @@ -311,21 +310,6 @@ internal void DrawNotification() EditorGUI.DoDropShadowLabel(r, m_Notification, scaledNotificationText, .3f); } - internal bool dontClearBackground - { - get - { - return m_DontClearBackground; - } - set - { - m_DontClearBackground = value; - if (m_Parent && m_Parent.actualView == this) - m_Parent.backgroundValid = false; - } - } - - // Does the window automatically repaint whenever the scene has changed? public bool autoRepaintOnSceneChange { @@ -467,8 +451,7 @@ internal void MakeParentsSettingsMatchMe() m_Parent.disableInputEvents = m_DisableInputEvents; Vector2 parentBorderSizes = new Vector2(m_Parent.borderSize.left + m_Parent.borderSize.right, m_Parent.borderSize.top + m_Parent.borderSize.bottom); m_Parent.SetMinMaxSizes(minSize + parentBorderSizes, maxSize + parentBorderSizes); - if (m_IsPlayModeView) - m_Parent.SetPlayModeView(); + m_Parent.SetPlayModeView(m_IsPlayModeView); if (parentChanged) m_Parent.RecreateContext(); } @@ -489,9 +472,30 @@ public void ShowPopup() ShowPopupWithMode(ShowMode.PopupMenu, true); } + void MakeModal() + { + try + { + ContainerWindow.s_Modal = true; + + SavedGUIState guiState = SavedGUIState.Create(); + m_Parent.visualTree.panel.dispatcher?.PushDispatcherContext(); + + Internal_MakeModal(m_Parent.window); + + m_Parent.visualTree.panel.dispatcher?.PopDispatcherContext(); + guiState.ApplyAndForget(); + } + finally + { + ContainerWindow.s_Modal = false; + } + } + public void ShowModalUtility() { ShowWithMode(ShowMode.ModalUtility); + MakeModal(); } // Used for popup style windows. @@ -626,17 +630,10 @@ public void ShowAuxWindow() } // Show modal editor window. Other windows will not be accessible until this one is closed. - internal void ShowModal() + public void ShowModal() { ShowWithMode(ShowMode.AuxWindow); - SavedGUIState guiState = SavedGUIState.Create(); - - m_Parent.visualTree.panel.dispatcher?.PushDispatcherContext(); - MakeModal(m_Parent.window); - - m_Parent.visualTree.panel.dispatcher?.PopDispatcherContext(); - - guiState.ApplyAndForget(); + MakeModal(); } // Returns the first EditorWindow of type /t/ which is currently on the screen. @@ -1022,9 +1019,15 @@ internal void SetPlayModeViewSize(Vector2 targetSize) m_Parent.SetInternalGameViewDimensions(m_GameViewRect, m_GameViewClippedRect, m_GameViewTargetSize); } - internal void SetPlayModeView() + internal void SetMainPlayModeViewSize(Vector2 targetSize) + { + if (m_Parent != null) + m_Parent.SetMainPlayModeViewSize(targetSize); + } + + internal void SetPlayModeView(bool value) { - m_IsPlayModeView = true; + m_IsPlayModeView = value; } [Obsolete("AA is not supported on EditorWindows", false)] diff --git a/Editor/Mono/EnumDataUtility.cs b/Editor/Mono/EnumDataUtility.cs index bdb2a51647..59c907fdbf 100644 --- a/Editor/Mono/EnumDataUtility.cs +++ b/Editor/Mono/EnumDataUtility.cs @@ -34,24 +34,45 @@ internal static EnumData GetCachedEnumData(Type enumType, bool excludeObsolete = return enumData; if (!excludeObsolete && s_EnumData.TryGetValue(enumType, out enumData)) return enumData; - enumData = new EnumData {underlyingType = Enum.GetUnderlyingType(enumType)}; + enumData = new EnumData { underlyingType = Enum.GetUnderlyingType(enumType) }; enumData.unsigned = enumData.underlyingType == typeof(byte) || enumData.underlyingType == typeof(ushort) || enumData.underlyingType == typeof(uint) || enumData.underlyingType == typeof(ulong); - var enumFields = enumType.GetFields(BindingFlags.Static | BindingFlags.Public) - .Where(f => CheckObsoleteAddition(f, excludeObsolete)) - .OrderBy(f => f.MetadataToken).ToList(); - enumData.displayNames = enumFields.Select(f => EnumNameFromEnumField(f)).ToArray(); + var enumFields = enumType.GetFields(BindingFlags.Static | BindingFlags.Public); + List enumfieldlist = new List(); + int enumFieldslen = enumFields.Length; + for (int j = 0; j < enumFieldslen; j++) + { + if (CheckObsoleteAddition(enumFields[j], excludeObsolete)) + enumfieldlist.Add(enumFields[j]); + } + + // For Empty List Scenario + if (!enumfieldlist.Any()) + { + string[] defaultstr = { "" }; + Enum[] defaultenum = {}; + int[] defaultarr = { 0 }; + enumData.values = defaultenum; + enumData.flagValues = defaultarr; + enumData.displayNames = defaultstr; + enumData.tooltip = defaultstr; + enumData.flags = true; + enumData.serializable = true; + return enumData; + } + enumfieldlist.OrderBy(f => f.MetadataToken); + enumData.displayNames = enumfieldlist.Select(f => EnumNameFromEnumField(f)).ToArray(); if (enumData.displayNames.Distinct().Count() != enumData.displayNames.Length) { Debug.LogWarning( $"Enum {enumType.Name} has multiple entries with the same display name, this prevents selection in EnumPopup."); } - enumData.tooltip = enumFields.Select(f => EnumTooltipFromEnumField(f)).ToArray(); - enumData.values = enumFields.Select(f => (Enum)f.GetValue(null)).ToArray(); + enumData.tooltip = enumfieldlist.Select(f => EnumTooltipFromEnumField(f)).ToArray(); + enumData.values = enumfieldlist.Select(f => (Enum)f.GetValue(null)).ToArray(); enumData.flagValues = enumData.unsigned ? enumData.values.Select(v => unchecked((int)Convert.ToUInt64(v))).ToArray() : enumData.values.Select(v => unchecked((int)Convert.ToInt64(v))).ToArray(); diff --git a/Editor/Mono/FileUtil.cs b/Editor/Mono/FileUtil.cs index 7f8d124371..84d445591c 100644 --- a/Editor/Mono/FileUtil.cs +++ b/Editor/Mono/FileUtil.cs @@ -332,5 +332,30 @@ internal static bool HasReadOnly(IEnumerable assets) return true; return false; } + + internal static bool MakeWritable(string path) + { + string absolutePath = FileUtil.PathToAbsolutePath(path); + + if (Directory.Exists(absolutePath)) + { + foreach (var file in GetAllFilesRecursive(absolutePath)) + { + var fileInfo = new FileInfo(file); + fileInfo.IsReadOnly = false; + } + return true; + } + + if (File.Exists(absolutePath)) + { + var fileInfo = new FileInfo(absolutePath); + fileInfo.IsReadOnly = false; + + return true; + } + + return false; + } } } diff --git a/Editor/Mono/GUI/AboutWindow.cs b/Editor/Mono/GUI/AboutWindow.cs index 945f7962c9..c518de93ab 100644 --- a/Editor/Mono/GUI/AboutWindow.cs +++ b/Editor/Mono/GUI/AboutWindow.cs @@ -6,6 +6,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using UnityEditor; using UnityEditor.VisualStudioIntegration; using UnityEditorInternal; @@ -16,8 +17,8 @@ internal class AboutWindow : EditorWindow { static void ShowAboutWindow() { - AboutWindow w = EditorWindow.GetWindowWithRect(new Rect(100, 100, 570, 340), true, "About Unity"); - w.position = new Rect(100, 100, 570, 340); + AboutWindow w = EditorWindow.GetWindowWithRect(new Rect(100, 100, 570, 360), true, "About Unity"); + w.position = new Rect(100, 100, 570, 360); w.m_Parent.window.m_DontSaveToLayout = true; } @@ -25,6 +26,13 @@ static void ShowAboutWindow() private const string kSpecialThanksNames = "Thanks to Forest 'Yoggy' Johnson, Graham McAllister, David Janik-Jones, Raimund Schumacher, Alan J. Dickins and Emil 'Humus' Persson"; + static class Styles + { + public static GUIContent thanksContent = new GUIContent("Special thanks to our beta users"); + public static GUIStyle thanksStyle = EditorStyles.FromUSS("About-Thanks-Label"); + public static Uri thanksUri = new Uri("https://unity.com/releases/2019-3/thanks"); + } + private static void LoadLogos() { if (s_MonoLogo != null) @@ -142,7 +150,7 @@ public void OnGUI() GUILayout.Label(s_MonoLogo); GUILayout.Label("Scripting powered by The Mono Project.\n\n(c) 2011 Novell, Inc.", "MiniLabel", GUILayout.Width(200)); GUILayout.Label(s_AgeiaLogo); - GUILayout.Label("Physics powered by PhysX.\n\n(c) 2011 NVIDIA Corporation.", "MiniLabel", GUILayout.Width(200)); + GUILayout.Label("Physics powered by PhysX.\n\n(c) 2019 NVIDIA Corporation.", "MiniLabel", GUILayout.Width(200)); GUILayout.EndHorizontal(); GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal(); @@ -150,6 +158,14 @@ public void OnGUI() GUILayout.BeginVertical(); GUILayout.FlexibleSpace(); + var specialThanksRect = GUILayoutUtility.GetRect(Styles.thanksContent, Styles.thanksStyle); + if (GUI.Button(specialThanksRect, Styles.thanksContent, Styles.thanksStyle)) + { + Process.Start(Styles.thanksUri.AbsoluteUri); + } + + EditorGUIUtility.AddCursorRect(specialThanksRect, MouseCursor.Link); + var VSTUlabel = UnityVSSupport.GetAboutWindowLabel(); if (VSTUlabel.Length > 0) GUILayout.Label(VSTUlabel, "MiniLabel"); @@ -218,7 +234,7 @@ private void ListenForSecretCodes() bool enabled = !EditorPrefs.GetBool("DeveloperMode", false); EditorPrefs.SetBool("DeveloperMode", enabled); ShowNotification(new GUIContent(string.Format(L10n.Tr("Developer Mode {0}"), (enabled ? L10n.Tr("On") : L10n.Tr("Off"))))); - InternalEditorUtility.RequestScriptReload(); + EditorUtility.RequestScriptReload(); // Repaint all views to show/hide debug repaint indicator InternalEditorUtility.RepaintAllViews(); diff --git a/Editor/Mono/GUI/AppStatusBar.cs b/Editor/Mono/GUI/AppStatusBar.cs index 1995ed7344..9cc38375e8 100644 --- a/Editor/Mono/GUI/AppStatusBar.cs +++ b/Editor/Mono/GUI/AppStatusBar.cs @@ -172,7 +172,7 @@ protected override void OldOnGUI() } else if (Unsupported.IsDeveloperMode()) { - GUI.Label(new Rect(progressBarHorizontalPosition - 200 - spacing, 0, 200, barHeight), m_MiniMemoryOverview, EditorStyles.progressBarText); + GUI.Label(new Rect(progressBarHorizontalPosition - 300 - spacing, 0, 300, barHeight), m_MiniMemoryOverview, EditorStyles.progressBarText); EditorGUIUtility.CleanCache(m_MiniMemoryOverview); } } diff --git a/Editor/Mono/GUI/ColorPicker.cs b/Editor/Mono/GUI/ColorPicker.cs index 98f0afe1bc..7180807ddf 100644 --- a/Editor/Mono/GUI/ColorPicker.cs +++ b/Editor/Mono/GUI/ColorPicker.cs @@ -1284,7 +1284,7 @@ static void Start(GUIView viewToUpdate, Action colorPickedCallback) win.title = "EyeDropper"; win.hideFlags = HideFlags.DontSave; win.rootView = instance; - win.Show(ShowMode.PopupMenu, loadPosition: true, displayImmediately: false, setFocus: true); + win.Show(ShowMode.PopupMenu, loadPosition: true, displayImmediately: true, setFocus: true); instance.AddToAuxWindowList(); win.SetInvisible(); instance.SetMinMaxSizes(new Vector2(0, 0), new Vector2(kDummyWindowSize, kDummyWindowSize)); @@ -1296,7 +1296,10 @@ static void Start(GUIView viewToUpdate, Action colorPickedCallback) public static void End() { if (s_Instance != null) + { s_Instance.window.Close(); + s_Instance = null; + } } static EyeDropper instance diff --git a/Editor/Mono/GUI/DockArea.cs b/Editor/Mono/GUI/DockArea.cs index 8668e6d428..8054c13407 100644 --- a/Editor/Mono/GUI/DockArea.cs +++ b/Editor/Mono/GUI/DockArea.cs @@ -20,25 +20,15 @@ internal class DockArea : HostView, IDropArea private static class Styles { private static readonly StyleBlock tab = EditorResources.GetStyle("tab"); + public static readonly GUIStyle background = "dockarea"; public static readonly float tabMinWidth = tab.GetFloat(StyleCatalogKeyword.minWidth, 50.0f); public static readonly float tabMaxWidth = tab.GetFloat(StyleCatalogKeyword.maxWidth, 150.0f); public static readonly float tabWidthPadding = tab.GetFloat(StyleCatalogKeyword.paddingRight); public static readonly float tabDragWidth = EditorResources.GetStyle("tab-drag").GetFloat(StyleCatalogKeyword.width, 100.0f); - private static StyleBlock tabScrollButton = EditorResources.GetStyle("tab-scroll-button"); - private static StyleBlock tabScrollButtonHover = EditorResources.GetStyle("tab-scroll-button", StyleState.focus); - public static readonly Color tabScrollButtonBackgroundColor = tabScrollButton.GetColor(StyleCatalogKeyword.backgroundColor); - public static readonly Color tabScrollButtonBlendColor = tabScrollButton.GetColor(StyleCatalogKeyword.color, Color.white); - public static readonly Color tabScrollButtonHoverBlendColor = tabScrollButtonHover.GetColor(StyleCatalogKeyword.color, Color.white); - public static readonly Texture2D tabScrollPrevButtonImg = EditorGUIUtility.LoadIconRequired("tab_prev"); - public static readonly Texture2D tabScrollNextButtonImg = EditorGUIUtility.LoadIconRequired("tab_next"); - - public static SVC tabConnectionLineColor = new SVC("--theme-tab-background-color", Color.red); - public static SVC tabConnectionLineHeight = new SVC("--tab-connection-line-height"); - public static SVC tabConnectionLineHeightFloating = new SVC("--tab-connection-line-height-floating"); - public static SVC tabConnectionLineBottomOffset = new SVC("--tab-connection-line-bottom-offset"); - public static SVC tabConnectionLineBottomOffsetFloating = new SVC("--tab-connection-line-bottom-offset-floating"); + public static readonly GUIStyle tabScrollerPrevButton = new GUIStyle("dragtab scroller prev"); + public static readonly GUIStyle tabScrollerNextButton = new GUIStyle("dragtab scroller next"); public static SVC genericMenuLeftOffset = new SVC("--window-generic-menu-left-offset", 20f); public static SVC genericMenuTopOffset = new SVC("--window-generic-menu-top-offset", 20f); @@ -46,6 +36,9 @@ private static class Styles public static SVC genericMenuFloatingTopOffset = new SVC("--window-floating-generic-menu-top-offset", 20f); public static readonly GUIStyle tabLabel = new GUIStyle("dragtab") { name = "dragtab-label" }; + public static readonly GUIStyle dragTab = new GUIStyle("dragtab"); + public static readonly GUIStyle dragTabFirst = new GUIStyle("dragtab first"); + public static readonly GUIStyle dockTitleBarStyle = new GUIStyle("dockHeader"); } internal const int kFloatingWindowTopBorderWidth = 2; @@ -76,8 +69,6 @@ private static class Styles [SerializeField] internal int m_LastSelected; [NonSerialized] internal GUIStyle tabStyle = null; - [NonSerialized] internal GUIStyle firstTabStyle = null; - [NonSerialized] internal GUIStyle dockTitleBarStyle = null; private bool m_IsBeingDestroyed; private Rect m_ScrollLeftRect; @@ -86,6 +77,7 @@ private static class Styles private float m_ScrollOffset; private float m_HoldScrollOffset; private double m_HoldScrollTimestamp; + private Rect m_TabAreaRect = Rect.zero; public int selected { @@ -162,6 +154,7 @@ protected override void OnDestroy() base.OnDestroy(); } + private VisualElement m_SplitterGUIRoot; protected override void OnEnable() { if (m_Panes != null) @@ -183,9 +176,55 @@ protected override void OnEnable() imguiContainer.name = VisualElementUtils.GetUniqueName("Dockarea"); imguiContainer.tabIndex = -1; imguiContainer.focusOnlyIfHasFocusableControls = false; + + panel.rootIMGUIContainer = imguiContainer; + + var root = panel.visualTree; + RegisterSplitterEvents(root); + m_SplitterGUIRoot = root; } } + protected override void OnDisable() + { + var root = m_SplitterGUIRoot; + if (root != null) + UnregisterSplitterEvents(root); + m_SplitterGUIRoot = null; + + if (panel != null) + panel.rootIMGUIContainer = null; + + base.OnDisable(); + } + + const TrickleDown k_TricklePhase = TrickleDown.TrickleDown; + + private void RegisterSplitterEvents(VisualElement root) + { + root.RegisterCallback(SendEventToSplitterGUI, k_TricklePhase); + root.RegisterCallback(SendEventToSplitterGUI, k_TricklePhase); + root.RegisterCallback(SendEventToSplitterGUI, k_TricklePhase); + } + + private void UnregisterSplitterEvents(VisualElement root) + { + root.UnregisterCallback(SendEventToSplitterGUI, k_TricklePhase); + root.UnregisterCallback(SendEventToSplitterGUI, k_TricklePhase); + root.UnregisterCallback(SendEventToSplitterGUI, k_TricklePhase); + } + + private void SendEventToSplitterGUI(EventBase ev) + { + if (ev.imguiEvent == null || ev.imguiEvent.rawType == EventType.Used) + return; + + imguiContainer.HandleIMGUIEvent(ev.imguiEvent, HandleSplitView, false); + + if (ev.imguiEvent.rawType == EventType.Used) + ev.StopPropagation(); + } + public void AddTab(EditorWindow pane, bool sendPaneEvents = true) { AddTab(m_Panes.Count, pane, sendPaneEvents); @@ -269,9 +308,7 @@ public DropInfo DragOver(EditorWindow window, Vector2 mouseScreenPosition) r.height = kDockHeight; if (r.Contains(mouseScreenPosition)) { - if (background == null) - background = "hostview"; - Rect scr = background.margin.Remove(screenPosition); + Rect scr = Styles.background.margin.Remove(screenPosition); Vector2 pos = mouseScreenPosition - new Vector2(scr.x, scr.y); Rect tr = new Rect(0, 0, m_TotalTabWidth, kTabHeight); @@ -320,7 +357,7 @@ protected bool floatingWindow protected override void OldOnGUI() { - ClearBackground(); + var oldLabelWidth = EditorGUIUtility.labelWidth; EditorGUIUtility.ResetGUIState(); // Exit if the window was destroyed after entering play mode or on domain-reload. @@ -329,8 +366,6 @@ protected override void OldOnGUI() var borderSize = GetBorderSize(); - background = "dockarea"; - Rect dockAreaRect = new Rect(0, 0, position.width, position.height); Rect wPos = windowPosition; Rect containerWindowPosition = window.position; @@ -341,10 +376,11 @@ protected override void OldOnGUI() var viewRect = UpdateViewRect(dockAreaRect); var titleBarRect = new Rect(viewRect.x, dockAreaRect.y, viewRect.width, borderSize.top); - var tabAreaRect = new Rect(titleBarRect.x, viewRect.y - kTabHeight, titleBarRect.width - GetExtraButtonsWidth(), kTabHeight); + m_TabAreaRect = new Rect(titleBarRect.x, viewRect.y - kTabHeight, titleBarRect.width - GetExtraButtonsWidth(), kTabHeight); DrawDockTitleBarBackground(titleBarRect); - HandleTabScrolling(tabAreaRect); + HandleTabScrolling(m_TabAreaRect); + float genericMenuLeftOffset = Styles.genericMenuLeftOffset; float genericMenuTopOffset = Styles.genericMenuTopOffset; @@ -353,16 +389,20 @@ protected override void OldOnGUI() genericMenuLeftOffset = ContainerWindow.buttonStackWidth + Styles.genericMenuFloatingLeftOffset; genericMenuTopOffset = Styles.genericMenuFloatingTopOffset; } - ShowGenericMenu(position.width - genericMenuLeftOffset, tabAreaRect.y + genericMenuTopOffset); + if (!ContainerWindow.s_Modal) + { + ShowGenericMenu(position.width - genericMenuLeftOffset, m_TabAreaRect.y + genericMenuTopOffset); + } - DrawTabs(tabAreaRect); + DrawTabs(m_TabAreaRect); HandleSplitView(); //fogbugz 1169963: in order to easily use the splitter in the gameView, it must be prioritized over DrawView(). Side effect for touch is that splitter picking zones might overlap other controls but the tabs still have higher priority so the user can undock the window in that case DrawView(viewRect, dockAreaRect); - DrawTabScrollers(tabAreaRect); + DrawTabScrollers(m_TabAreaRect); EditorGUI.ShowRepaints(); Highlighter.ControlHighlightGUI(this); + EditorGUIUtility.labelWidth = oldLabelWidth; } private void DrawView(Rect viewRect, Rect dockAreaRect) @@ -378,18 +418,19 @@ private void DrawTabs(Rect tabAreaRect) if (floatingWindow && isTop) clipRect.yMin = 0; + // If the left scroll button is visible then clip the tabs a bit more because of the top left border radius of the scroll left button + if (m_ScrollOffset > 0f) + clipRect.xMin += 3; + using (new GUI.ClipScope(clipRect, new Vector2(-m_ScrollOffset - 1f, 0))) { if (tabStyle == null) - tabStyle = "dragtab"; + tabStyle = Styles.dragTab; - if (firstTabStyle == null) - firstTabStyle = EditorStyles.s_Current.GetStyle("dragtab first"); - - var totalTabWidth = DragTab(tabAreaRect, m_ScrollOffset, tabStyle, firstTabStyle); + var totalTabWidth = DragTab(tabAreaRect, m_ScrollOffset, tabStyle, Styles.dragTabFirst); if (totalTabWidth > 0f) m_TotalTabWidth = totalTabWidth; - tabStyle = "dragtab"; + tabStyle = Styles.dragTab; } } @@ -415,18 +456,14 @@ private void DrawDockAreaBackground(Rect dockAreaRect) { var backgroundRect = dockAreaRect; backgroundRect.y = 0; - background.Draw(backgroundRect, GUIContent.none, 0); + Styles.background.Draw(backgroundRect, GUIContent.none, 0); } } private void DrawDockTitleBarBackground(Rect titleBarRect) { if (Event.current.type == EventType.Repaint) - { - if (dockTitleBarStyle == null) - dockTitleBarStyle = "dockHeader"; - dockTitleBarStyle.Draw(titleBarRect, GUIContent.none, 0); - } + Styles.dockTitleBarStyle.Draw(titleBarRect, GUIContent.none, 0); } private void SetupHoldScrollerUpdate(float clickOffset) @@ -450,7 +487,7 @@ private void HandleTabScrolling(Rect tabAreaRect) else if (m_ScrollRightRect.Contains(Event.current.mousePosition)) { SetupHoldScrollerUpdate(2f); - m_ScrollOffset = Mathf.Min(m_ScrollOffset + scrollOffsetShift, m_TotalTabWidth - tabAreaRect.xMax); + m_ScrollOffset = Mathf.Min(m_ScrollOffset + scrollOffsetShift, m_TotalTabWidth - tabAreaRect.width); Event.current.Use(); } } @@ -461,20 +498,14 @@ private void HandleTabScrolling(Rect tabAreaRect) } } - private void DrawTabScroller(Rect scrollRect, Texture2D tabScrollButtonImg) + private void DrawTabScroller(Rect scrollRect, GUIStyle tabScroller) { - using (new GUI.ColorScope(Styles.tabScrollButtonBackgroundColor)) - GUI.DrawTexture(scrollRect, EditorGUIUtility.whiteTexture); - bool hoverRightScroller = scrollRect.Contains(Event.current.mousePosition); - Color hoverBlendColor = hoverRightScroller ? Styles.tabScrollButtonHoverBlendColor : Styles.tabScrollButtonBlendColor; - using (new GUI.ColorScope(hoverBlendColor)) - GUI.DrawTexture(scrollRect, tabScrollButtonImg, ScaleMode.ScaleToFit); - MarkHotRegion(scrollRect); + tabScroller.Draw(scrollRect, scrollRect.Contains(Event.current.mousePosition), false, false, false); } private float GetExtraButtonsWidth() { - return (HasExtraDockAreaButton() ? 44f : 26f) + (floatingWindow && isTopRightPane ? ContainerWindow.buttonStackWidth : 0f); + return (HasExtraDockAreaButton() ? 42f : 20f) + (floatingWindow && isTopRightPane ? ContainerWindow.buttonStackWidth : 0f); } private void DrawTabScrollers(Rect tabAreaRect) @@ -485,19 +516,19 @@ private void DrawTabScrollers(Rect tabAreaRect) if (m_TotalTabWidth <= tabAreaRect.xMax) m_ScrollOffset = 0; - float scrollerButtonHeight = tabAreaRect.height - (floatingWindow ? 2 : 3); + float scrollerButtonHeight = tabAreaRect.height; if (m_ScrollOffset > 0f) { - m_ScrollLeftRect = new Rect(tabAreaRect.xMin, tabAreaRect.yMin, 16f, scrollerButtonHeight); - DrawTabScroller(m_ScrollLeftRect, Styles.tabScrollPrevButtonImg); + m_ScrollLeftRect = new Rect(tabAreaRect.xMin, tabAreaRect.yMin , 16f, scrollerButtonHeight); + DrawTabScroller(m_ScrollLeftRect, Styles.tabScrollerPrevButton); } else m_ScrollLeftRect.width = 0; - if (m_TotalTabWidth > tabAreaRect.xMax && m_ScrollOffset < (m_TotalTabWidth - tabAreaRect.xMax)) + if (m_TotalTabWidth > tabAreaRect.width && m_ScrollOffset < (m_TotalTabWidth - tabAreaRect.width)) { - m_ScrollRightRect = new Rect(tabAreaRect.xMax - 11f, tabAreaRect.yMin, 16f, scrollerButtonHeight); - DrawTabScroller(m_ScrollRightRect, Styles.tabScrollNextButtonImg); + m_ScrollRightRect = new Rect(tabAreaRect.xMax - 16f, tabAreaRect.yMin, 16f, scrollerButtonHeight); + DrawTabScroller(m_ScrollRightRect, Styles.tabScrollerNextButton); } else m_ScrollRightRect.width = 0; @@ -564,7 +595,7 @@ private void OnMouseDownHoldScroller() double timeSinceStartup = EditorApplication.timeSinceStartup; float dt = (float)(timeSinceStartup - m_HoldScrollTimestamp); - float maxScrollOffset = m_TotalTabWidth - position.width + GetExtraButtonsWidth(); + float maxScrollOffset = m_TotalTabWidth - m_TabAreaRect.width; m_HoldScrollTimestamp = timeSinceStartup; m_ScrollOffset = Mathf.Max(0f, Mathf.Min(m_ScrollOffset + m_HoldScrollOffset * dt * 250.0f, maxScrollOffset)); Repaint(); @@ -800,7 +831,7 @@ private float DragTab(Rect tabAreaRect, float scrollOffset, GUIStyle tabStyle, G if (GUIUtility.hotControl == 0) { int sel = GetTabAtMousePos(tabStyle, evt.mousePosition, scrollOffset, tabAreaRect); - if (sel != -1 && sel < m_Panes.Count) + if (sel != -1 && sel < m_Panes.Count && !ContainerWindow.s_Modal) PopupGenericMenu(m_Panes[sel], new Rect(evt.mousePosition.x, evt.mousePosition.y, 0, 0)); } break; @@ -813,6 +844,9 @@ private float DragTab(Rect tabAreaRect, float scrollOffset, GUIStyle tabStyle, G // if we're not tab dragging yet, check to see if we should start + // If modal window exists, disable all tab behavior + if (ContainerWindow.s_Modal) break; + // check if we're allowed to drag tab bool dragAllowed = (window.showMode != ShowMode.MainWindow || AllowTabAction()); @@ -1073,11 +1107,12 @@ static class Styles protected override void OldOnGUI() { - ClearBackground(); // Call reset GUI state as first thing so GUI.color is correct when drawing window decoration. EditorGUIUtility.ResetGUIState(); - Rect maximizedViewRect = position; + Rect maximizedViewRect = Rect.zero; + + maximizedViewRect.size = position.size; maximizedViewRect = Styles.background.margin.Remove(maximizedViewRect); Rect backRect = new Rect(maximizedViewRect.x + 1, maximizedViewRect.y, maximizedViewRect.width - 2, DockArea.kTabHeight); @@ -1088,7 +1123,7 @@ protected override void OldOnGUI() GUI.Label(backRect, actualView.titleContent, Styles.titleLabel); } - if (Event.current.type == EventType.ContextClick && backRect.Contains(Event.current.mousePosition)) + if (Event.current.type == EventType.ContextClick && backRect.Contains(Event.current.mousePosition) && !ContainerWindow.s_Modal) PopupGenericMenu(actualView, new Rect(Event.current.mousePosition.x, Event.current.mousePosition.y, 0, 0)); ShowGenericMenu(position.width - Styles.genericMenuLeftOffset, backRect.yMin + Styles.genericMenuTopOffset); diff --git a/Editor/Mono/GUI/EditorApplicationLayout.cs b/Editor/Mono/GUI/EditorApplicationLayout.cs index af33df0b54..79d79e85c0 100644 --- a/Editor/Mono/GUI/EditorApplicationLayout.cs +++ b/Editor/Mono/GUI/EditorApplicationLayout.cs @@ -23,13 +23,13 @@ namespace UnityEditor { internal class EditorApplicationLayout { - static private PreviewEditorWindow m_PreviewWindow = null; + static private PlayModeView m_PlayModeView = null; static private bool m_MaximizePending = false; static internal bool IsInitializingPlaymodeLayout() { - return m_PreviewWindow != null; + return m_PlayModeView != null; } static internal void SetPlaymodeLayout() @@ -52,34 +52,34 @@ static internal void SetPausemodeLayout() static internal void InitPlaymodeLayout() { - m_PreviewWindow = WindowLayout.ShowAppropriateViewOnEnterExitPlaymode(true) as PreviewEditorWindow; - if (m_PreviewWindow == null) + m_PlayModeView = WindowLayout.ShowAppropriateViewOnEnterExitPlaymode(true) as PlayModeView; + if (m_PlayModeView == null) return; - if (m_PreviewWindow.maximizeOnPlay) + if (m_PlayModeView.maximizeOnPlay) { - DockArea da = m_PreviewWindow.m_Parent as DockArea; + DockArea da = m_PlayModeView.m_Parent as DockArea; if (da != null) m_MaximizePending = WindowLayout.MaximizePrepare(da.actualView); } - // Mark this preview window as the start preview so the backend - // can set size and mouseoffset properly for this preview - m_PreviewWindow.m_Parent.SetAsStartView(); - m_PreviewWindow.m_Parent.SetAsLastPlayModeView(); + // Mark this PlayModeView window as the start view so the backend + // can set size and mouseoffset properly for this view + m_PlayModeView.m_Parent.SetAsStartView(); + m_PlayModeView.m_Parent.SetAsLastPlayModeView(); Toolbar.RepaintToolbar(); } static internal void FinalizePlaymodeLayout() { - if (m_PreviewWindow != null) + if (m_PlayModeView != null) { if (m_MaximizePending) - WindowLayout.MaximizePresent(m_PreviewWindow); + WindowLayout.MaximizePresent(m_PlayModeView); - m_PreviewWindow.m_Parent.ClearStartView(); + m_PlayModeView.m_Parent.ClearStartView(); } Clear(); @@ -88,7 +88,7 @@ static internal void FinalizePlaymodeLayout() static private void Clear() { m_MaximizePending = false; - m_PreviewWindow = null; + m_PlayModeView = null; } } } // namespace diff --git a/Editor/Mono/GUI/EditorStyles.cs b/Editor/Mono/GUI/EditorStyles.cs index 6f760d6f53..b703b672b8 100644 --- a/Editor/Mono/GUI/EditorStyles.cs +++ b/Editor/Mono/GUI/EditorStyles.cs @@ -46,7 +46,7 @@ public sealed class EditorStyles private GUIStyle m_WordWrappedLabel; // Style for link label. - internal static GUIStyle linkLabel { get { return s_Current.m_LinkLabel; } } + public static GUIStyle linkLabel { get { return s_Current.m_LinkLabel; } } private GUIStyle m_LinkLabel; // Style for white label. @@ -150,6 +150,9 @@ public sealed class EditorStyles public static GUIStyle foldout { get { return s_Current.m_Foldout; } } private GUIStyle m_Foldout; + internal static GUIStyle titlebarFoldout { get { return s_Current.m_TitlebarFoldout; } } + private GUIStyle m_TitlebarFoldout; + // Style used for headings for EditorGUI::ref::Foldout. public static GUIStyle foldoutPreDrop { get { return s_Current.m_FoldoutPreDrop; } } private GUIStyle m_FoldoutPreDrop; @@ -178,20 +181,16 @@ public sealed class EditorStyles // Standard font. - public static Font standardFont { get { return s_Current.m_StandardFont; } } - internal Font m_StandardFont; + public static Font standardFont => EditorResources.GetFont(FontDef.Style.Normal); // Bold font. - public static Font boldFont { get { return s_Current.m_BoldFont; } } - internal Font m_BoldFont; + public static Font boldFont => EditorResources.GetFont(FontDef.Style.Bold); // Mini font. - public static Font miniFont { get { return s_Current.m_MiniFont; } } - internal Font m_MiniFont; + public static Font miniFont => EditorResources.GetFont(FontDef.Style.Small); // Mini Bold font. - public static Font miniBoldFont { get { return s_Current.m_MiniBoldFont; } } - internal Font m_MiniBoldFont; + public static Font miniBoldFont => EditorResources.GetFont(FontDef.Style.Bold); // Toolbar background from top of windows. public static GUIStyle toolbar { get { return s_Current.m_Toolbar; } } @@ -202,6 +201,8 @@ public sealed class EditorStyles // Style for Button and Toggles in toolbars. public static GUIStyle toolbarButton { get { return s_Current.m_ToolbarButton; } } private GUIStyle m_ToolbarButton; + internal static GUIStyle toolbarButtonLeft { get { return s_Current.m_ToolbarButtonLeft; } } + private GUIStyle m_ToolbarButtonLeft; internal static GUIStyle toolbarButtonRight { get { return s_Current.m_ToolbarButtonRight; } } private GUIStyle m_ToolbarButtonRight; @@ -216,7 +217,9 @@ public sealed class EditorStyles internal static GUIStyle toolbarPopupRight { get { return s_Current.m_ToolbarPopupRight; } } private GUIStyle m_ToolbarPopupRight; - // Toolbar Dropdown + internal static GUIStyle toolbarDropDownLeft { get { return s_Current.m_ToolbarDropDownLeft; } } + private GUIStyle m_ToolbarDropDownLeft; + public static GUIStyle toolbarDropDown { get { return s_Current.m_ToolbarDropDown; } } private GUIStyle m_ToolbarDropDown; @@ -249,6 +252,9 @@ public sealed class EditorStyles public static GUIStyle inspectorFullWidthMargins { get { return s_Current.m_InspectorFullWidthMargins; } } private GUIStyle m_InspectorFullWidthMargins; + internal static GUIStyle defaultContentMargins { get { return s_Current.m_DefaultContentMargins; } } + private GUIStyle m_DefaultContentMargins; + internal static GUIStyle frameBox => s_Current.m_FrameBox; private GUIStyle m_FrameBox; @@ -415,11 +421,13 @@ private void InitSharedStyles() m_Toolbar = GetStyle("toolbar"); m_ContentToolbar = GetStyle("contentToolbar"); m_ToolbarButton = GetStyle("toolbarbutton"); + m_ToolbarButtonLeft = GetStyle("toolbarbuttonLeft"); m_ToolbarButtonRight = GetStyle("toolbarbuttonRight"); m_ToolbarPopup = GetStyle("toolbarPopup"); m_ToolbarPopupLeft = GetStyle("toolbarPopupLeft"); m_ToolbarPopupRight = GetStyle("toolbarPopupRight"); m_ToolbarDropDown = GetStyle("toolbarDropDown"); + m_ToolbarDropDownLeft = GetStyle("toolbarDropDownLeft"); m_ToolbarDropDownRight = GetStyle("toolbarDropDownRight"); m_ToolbarDropDownToggle = GetStyle("toolbarDropDownToggle"); m_ToolbarDropDownToggleRight = GetStyle("toolbarDropDownToggleRight"); @@ -443,10 +451,6 @@ private void InitSharedStyles() m_MinMaxHorizontalSliderThumb = GetStyle("MinMaxHorizontalSliderThumb"); m_DropDownList = GetStyle("DropDownButton"); m_MinMaxStateDropdown = GetStyle("IN MinMaxStateDropdown"); - m_BoldFont = EditorResources.GetBoldFont(); - m_StandardFont = EditorResources.GetNormalFont(); - m_MiniFont = EditorResources.GetSmallFont(); - m_MiniBoldFont = EditorResources.GetBoldFont(); m_ProgressBarBack = GetStyle("ProgressBarBack"); m_ProgressBarBar = GetStyle("ProgressBarBar"); m_ProgressBarText = GetStyle("ProgressBarText"); @@ -476,6 +480,7 @@ private void InitSharedStyles() m_ToggleMixed = GetStyle("ToggleMixed"); m_ColorField = GetStyle("ColorField"); m_Foldout = GetStyle("Foldout"); + m_TitlebarFoldout = GetStyle("Titlebar Foldout"); m_FoldoutSelected = GUIStyle.none; m_IconButton = GetStyle("IconButton"); m_TextFieldDropDown = GetStyle("TextFieldDropDown"); @@ -492,7 +497,7 @@ private void InitSharedStyles() { padding = new RectOffset( InspectorWindow.kInspectorPaddingLeft, - InspectorWindow.kInspectorPaddingRight, 0, 0) + InspectorWindow.kInspectorPaddingRight, InspectorWindow.kInspectorPaddingTop, 0) }; // For the full width margins, use padding from right side in both sides, @@ -504,6 +509,11 @@ private void InitSharedStyles() InspectorWindow.kInspectorPaddingRight, 0, 0) }; + m_DefaultContentMargins = new GUIStyle + { + padding = new RectOffset(4, 4, 4, 4) + }; + // Derive centered grey mini label from base minilabel m_CenteredGreyMiniLabel = new GUIStyle(m_MiniLabel) { diff --git a/Editor/Mono/GUI/FoldoutHeader.cs b/Editor/Mono/GUI/FoldoutHeader.cs index 564f1793b9..7b9bb0d12c 100644 --- a/Editor/Mono/GUI/FoldoutHeader.cs +++ b/Editor/Mono/GUI/FoldoutHeader.cs @@ -33,6 +33,7 @@ public static void EndFoldoutHeaderGroup() public sealed partial class EditorGUI { static bool s_FoldoutHeaderGroupActive; + private static readonly int s_FoldoutHeaderHash = "FoldoutHeader".GetHashCode(); public static bool BeginFoldoutHeaderGroup(Rect position, bool foldout, string content, [DefaultValue("EditorStyles.foldoutHeader")] GUIStyle style = null, Action menuAction = null, GUIStyle menuIcon = null) @@ -46,6 +47,7 @@ public static bool BeginFoldoutHeaderGroup(Rect position, bool foldout, GUIConte if (EditorGUIUtility.hierarchyMode) { position.xMin -= EditorStyles.inspectorDefaultMargins.padding.left - EditorStyles.inspectorDefaultMargins.padding.right; + position.xMax += EditorStyles.inspectorDefaultMargins.padding.right; } if (style == null) @@ -72,8 +74,22 @@ public static bool BeginFoldoutHeaderGroup(Rect position, bool foldout, GUIConte menuAction.Invoke(menuRect); Event.current.Use(); } + int id = GUIUtility.GetControlID(s_FoldoutHeaderHash, FocusType.Keyboard, position); - foldout = GUI.Toggle(position, foldout, content, style); + if (Event.current.type == EventType.KeyDown && GUIUtility.keyboardControl == id) + { + KeyCode kc = Event.current.keyCode; + if (kc == KeyCode.LeftArrow && foldout || (kc == KeyCode.RightArrow && foldout == false)) + { + foldout = !foldout; + GUI.changed = true; + Event.current.Use(); + } + } + else + { + foldout = EditorGUIInternal.DoToggleForward(position, id, foldout, content, style); + } // Menu icon if (menuAction != null && Event.current.type == EventType.Repaint) diff --git a/Editor/Mono/GUI/MainView.cs b/Editor/Mono/GUI/MainView.cs index 1f41368a2a..cf3be9b500 100644 --- a/Editor/Mono/GUI/MainView.cs +++ b/Editor/Mono/GUI/MainView.cs @@ -10,7 +10,7 @@ internal class MainView : View, ICleanuppable { const float kStatusbarHeight = 20; - private static readonly Vector2 kMinSize = new Vector2(950, 300); + private static readonly Vector2 kMinSize = new Vector2(875, 300); private static readonly Vector2 kMaxSize = new Vector2(10000, 10000); void OnEnable() @@ -23,22 +23,36 @@ protected override void SetPosition(Rect newPos) base.SetPosition(newPos); if (children.Length == 0) return; - Toolbar t = (Toolbar)children[0]; - children[0].position = new Rect(0, 0, newPos.width, t.CalcHeight()); if (children.Length > 2) { + // toolbar - dock area view - status bar + Toolbar t = (Toolbar)children[0]; + children[0].position = new Rect(0, 0, newPos.width, t.CalcHeight()); children[1].position = new Rect(0, t.CalcHeight(), newPos.width, newPos.height - t.CalcHeight() - children[2].position.height); children[2].position = new Rect(0, newPos.height - children[2].position.height, newPos.width, children[2].position.height); } + else + { + // dock area view - status bar + children[0].position = new Rect(0, 0, newPos.width, newPos.height - children[1].position.height); + children[1].position = new Rect(0, newPos.height - children[1].position.height, newPos.width, children[1].position.height); + } } protected override void ChildrenMinMaxChanged() { - if (children.Length == 3) + if (children.Length == 3 && children[0] is Toolbar) { + // toolbar - dock area view - status bar Toolbar t = (Toolbar)children[0]; - var min = new Vector2(kMinSize.x, Mathf.Max(kMinSize.y, t.CalcHeight() + kStatusbarHeight + children[1].minSize.y)); - SetMinMaxSizes(min, kMaxSize); + var min = new Vector2(minSize.x, Mathf.Max(minSize.y, t.CalcHeight() + kStatusbarHeight + children[1].minSize.y)); + SetMinMaxSizes(min, maxSize); + } + else if (children.Length == 2) + { + // dock area view - status bar + var min = new Vector2(minSize.x, Mathf.Max(minSize.y, kStatusbarHeight + children[1].minSize.y)); + SetMinMaxSizes(min, maxSize); } base.ChildrenMinMaxChanged(); } @@ -65,7 +79,7 @@ public void Cleanup() // If we only have one child left, this means all views have been dragged out. // So we resize the window to be just the toolbar // On windows, this might need some special handling for the main menu - if (children[1].children.Length == 0) + if (children.Length == 3 && children[1].children.Length == 0) { Rect r = window.position; Toolbar t = (Toolbar)children[0]; diff --git a/Editor/Mono/GUI/ObjectField.cs b/Editor/Mono/GUI/ObjectField.cs index 8d543d952a..a70ee0e96a 100644 --- a/Editor/Mono/GUI/ObjectField.cs +++ b/Editor/Mono/GUI/ObjectField.cs @@ -448,12 +448,12 @@ internal class ObjectPreviewPopup : PopupWindowContent { readonly Editor m_Editor; readonly GUIContent m_ObjectName; - const float kToolbarHeight = 17f; + const float kToolbarHeight = 22f; internal class Styles { public readonly GUIStyle toolbar = "preToolbar"; - public readonly GUIStyle toolbarText = "preToolbar2"; + public readonly GUIStyle toolbarText = "ToolbarBoldLabel"; public GUIStyle background = "preBackground"; } Styles s_Styles; @@ -486,7 +486,6 @@ public override void OnGUI(Rect rect) if (s_Styles == null) s_Styles = new Styles(); - // Toolbar GUILayout.BeginArea(new Rect(rect.x, rect.y, rect.width, kToolbarHeight), s_Styles.toolbar); EditorGUILayout.BeginHorizontal(); @@ -495,7 +494,7 @@ public override void OnGUI(Rect rect) EditorGUILayout.EndHorizontal(); GUILayout.EndArea(); - const float kMaxSettingsWidth = 140f; + const float kMaxSettingsWidth = 240f; GUI.Label(new Rect(rect.x + 5f, rect.y, rect.width - kMaxSettingsWidth, kToolbarHeight), m_ObjectName, s_Styles.toolbarText); // Object preview @@ -505,7 +504,7 @@ public override void OnGUI(Rect rect) public override Vector2 GetWindowSize() { - return new Vector2(300f, 300f + kToolbarHeight); + return new Vector2(400f, 300f + kToolbarHeight); } } } diff --git a/Editor/Mono/GUI/PackageExport.cs b/Editor/Mono/GUI/PackageExport.cs index 86a10a925c..c7f9c9f081 100644 --- a/Editor/Mono/GUI/PackageExport.cs +++ b/Editor/Mono/GUI/PackageExport.cs @@ -54,13 +54,13 @@ internal static IEnumerable GetAssetItemsForExport(ICollectio guids = new HashSet(AssetDatabase.CollectAllChildren(AssetDatabase.assetFolderGUID, temp)); } - ExportPackageItem[] assets = PackageUtility.BuildExportPackageItemsList(guids.ToArray(), includeDependencies); + ExportPackageItem[] assets = PackageUtility.BuildExportPackageItemsListWithPackageManagerWarning(guids.ToArray(), includeDependencies, true); // If any scripts are included, add all scripts with dependencies if (includeDependencies && assets.Any(asset => UnityEditorInternal.InternalEditorUtility.IsScriptOrAssembly(asset.assetPath))) { - assets = PackageUtility.BuildExportPackageItemsList( - guids.Union(UnityEditorInternal.InternalEditorUtility.GetAllScriptGUIDs()).ToArray(), includeDependencies); + assets = PackageUtility.BuildExportPackageItemsListWithPackageManagerWarning( + guids.Union(UnityEditorInternal.InternalEditorUtility.GetAllScriptGUIDs()).ToArray(), includeDependencies, true); } // If the user exports the root Assets folder, we need to remove it from the list diff --git a/Editor/Mono/GUI/PackageImportTreeView.cs b/Editor/Mono/GUI/PackageImportTreeView.cs index 633a8438d1..5989474f65 100644 --- a/Editor/Mono/GUI/PackageImportTreeView.cs +++ b/Editor/Mono/GUI/PackageImportTreeView.cs @@ -369,7 +369,7 @@ override public void OnRowGUI(Rect rowRect, TreeViewItem tvItem, int row, bool s { Rect labelRect = new Rect(rowRect.xMax - 58, rowRect.y, rowRect.height, rowRect.height); EditorGUIUtility.SetIconSize(new Vector2(rowRect.height, rowRect.height)); - GUI.Label(labelRect, Constants.badgeWarn); + GUI.Label(labelRect, Constants.badgeWarn, Constants.paddinglessStyle); EditorGUIUtility.SetIconSize(Vector2.zero); } @@ -383,15 +383,6 @@ override public void OnRowGUI(Rect rowRect, TreeViewItem tvItem, int row, bool s GUI.Label(labelRect, Constants.badgeNew, Constants.paddinglessStyle); } - // 5. Optional badge ("Delete") - if (repainting && projectAsset) - { - // FIXME: Need to enable tooltips here. - Texture badge = Constants.badgeDelete.image; - Rect labelRect = new Rect(rowRect.xMax - badge.width - 6, rowRect.y + (rowRect.height - badge.height) / 2, badge.width, badge.height); - GUI.Label(labelRect, Constants.badgeDelete, Constants.paddinglessStyle); - } - // 7. Show what stuff has changed if (repainting && validItem && (exists || pathConflict) && assetChanged) { diff --git a/Editor/Mono/GUI/PreviewResizer.cs b/Editor/Mono/GUI/PreviewResizer.cs index fe6d922fd0..9165c973c5 100644 --- a/Editor/Mono/GUI/PreviewResizer.cs +++ b/Editor/Mono/GUI/PreviewResizer.cs @@ -127,6 +127,56 @@ public float ResizeHandle(Rect windowPosition, float minSize, float minRemaining return previewSize; } + public float SetExpanded(Rect windowPosition, float minSize, float minRemainingSize, float resizerHeight, Rect dragRect, bool isExpanded) + { + // Sanity check the cached value. It can be positive or negative, but never smaller than the minSize + if (Mathf.Abs(m_CachedPref) < minSize) + m_CachedPref = minSize * Mathf.Sign(m_CachedPref); + + float maxPreviewSize = windowPosition.height - minRemainingSize; + + float previewSize = Mathf.Max(0, m_CachedPref); + bool expanded = (m_CachedPref > 0); + float lastSize = Mathf.Abs(m_CachedPref); + + Rect resizerRect = new Rect(0, windowPosition.height - previewSize - resizerHeight, windowPosition.width, resizerHeight); + if (dragRect.width != 0) + { + resizerRect.x = dragRect.x; + resizerRect.width = dragRect.width; + } + + bool expandedBefore = expanded; + expanded = isExpanded; + previewSize = -previewSize; + previewSize = Mathf.Min(previewSize, maxPreviewSize); + + // First snap size between 0 and minimum size + if (previewSize < minSize) + previewSize = (previewSize < minSize * 0.5f ? 0 : minSize); + + // If user clicked area, adjust size + if (expanded != expandedBefore) + { + previewSize = (expanded ? lastSize : 0); + } + + // Determine new expanded state + expanded = (previewSize >= minSize / 2); + if (previewSize > 0) + lastSize = previewSize; + float newPref = lastSize * (expanded ? 1 : -1); + if (newPref != m_CachedPref) + { + // Save the value to prefs + m_CachedPref = newPref; + EditorPrefs.SetFloat(m_PrefName, m_CachedPref); + } + + s_CachedPreviewSizeWhileDragging = previewSize; + return previewSize; + } + // This value will change in realtime while dragging public bool GetExpanded() { @@ -152,6 +202,8 @@ public bool GetExpandedBeforeDragging() public void SetExpanded(bool expanded) { + if (GetExpanded() == expanded) + return; // Set the sign based on whether it's collapsed or not, then save to prefs m_CachedPref = Mathf.Abs(m_CachedPref) * (expanded ? 1 : -1); EditorPrefs.SetFloat(m_PrefName, m_CachedPref); diff --git a/Editor/Mono/GUI/ReorderableList.cs b/Editor/Mono/GUI/ReorderableList.cs index 300ef3d086..edf5624302 100644 --- a/Editor/Mono/GUI/ReorderableList.cs +++ b/Editor/Mono/GUI/ReorderableList.cs @@ -316,7 +316,7 @@ public int index // header height accessor public float headerHeight = 20; // footer height accessor - public float footerHeight = 13; + public float footerHeight = 20; // show default background public bool showDefaultBackground = true; private float listElementTopPadding => headerHeight > 5 ? 4 : 1; // headerHeight is usually set to 3px when there is no header content. Therefore, we add a 1px top margin to match the 4px bottom margin @@ -329,7 +329,6 @@ public bool draggable set { m_Draggable = value; } } - private Rect GetContentRect(Rect rect) { Rect r = rect; @@ -716,6 +715,11 @@ private void DoDraggingAndSelection(Rect listRect) m_NonDragTargetIndices = new List(); } GrabKeyboardFocus(); + + // Prevent consuming the right mouse event in order to enable the context menu + if (Event.current.button == 1) + break; + evt.Use(); clicked = true; break; @@ -875,7 +879,7 @@ public void ReleaseKeyboardFocus() public bool HasKeyboardControl() { - return GUIUtility.keyboardControl == id; + return GUIUtility.keyboardControl == id && EditorGUIUtility.HasCurrentWindowKeyFocus(); } } } diff --git a/Editor/Mono/GUI/ScalableGUIContent.cs b/Editor/Mono/GUI/ScalableGUIContent.cs index 57d81d510e..0b74160a25 100644 --- a/Editor/Mono/GUI/ScalableGUIContent.cs +++ b/Editor/Mono/GUI/ScalableGUIContent.cs @@ -28,6 +28,10 @@ struct TextureResource [SerializeField] private GUIContent m_GuiContent; + public string text => asGUIContent()?.text; + public Texture image => asGUIContent()?.image; + public string tooltip => asGUIContent()?.tooltip; + public ScalableGUIContent(string resourceName) : this(string.Empty, string.Empty, resourceName) { } @@ -44,39 +48,45 @@ public ScalableGUIContent(string text, string tooltip, string resourceName) } public static implicit operator GUIContent(ScalableGUIContent gc) + { + return gc.asGUIContent(); + } + + private GUIContent asGUIContent() { var dpi = EditorGUIUtility.pixelsPerPoint; - var resourcePath = gc.m_CurrentResourcePath; + var resourcePath = m_CurrentResourcePath; var resourceDpi = 1.0f; - var normalResourcePath = gc.m_TextureResources[0].resourcePath; + var normalResourcePath = m_TextureResources[0].resourcePath; - for (int i = 0, count = gc.m_TextureResources.Count; i < count; ++i) + for (int i = 0, count = m_TextureResources.Count; i < count; ++i) { - var currentResource = gc.m_TextureResources[i]; + var currentResource = m_TextureResources[i]; resourcePath = currentResource.resourcePath; resourceDpi = currentResource.pixelsPerPoint; if (resourceDpi >= dpi) break; } - if (resourcePath != gc.m_CurrentResourcePath) + + if (resourcePath != m_CurrentResourcePath) { Texture2D loadedResource = EditorGUIUtility.LoadIconRequired(normalResourcePath); loadedResource.pixelsPerPoint = resourceDpi; - gc.m_GuiContent.image = loadedResource; - gc.m_CurrentResourcePath = resourcePath; + m_GuiContent.image = loadedResource; + m_CurrentResourcePath = resourcePath; } if (resourceDpi != GUIUtility.pixelsPerPoint) { - Texture2D image = gc.m_GuiContent.image as Texture2D; + Texture2D image = m_GuiContent.image as Texture2D; if (image != null) { image.filterMode = FilterMode.Bilinear; } } - return gc.m_GuiContent; + return m_GuiContent; } } } diff --git a/Editor/Mono/GUI/Splitter.cs b/Editor/Mono/GUI/Splitter.cs index 9a9a077bc1..75094d6f18 100644 --- a/Editor/Mono/GUI/Splitter.cs +++ b/Editor/Mono/GUI/Splitter.cs @@ -58,6 +58,13 @@ public SplitterState(float[] relativeSizes, int[] minSizes, int[] maxSizes, int Init(relativeSizes, minSizes, maxSizes, splitSize); } + public bool IsValid() + { + return realSizes != null && minSizes != null && maxSizes != null && relativeSizes != null && + realSizes.Length > 0 && minSizes.Length > 0 && maxSizes.Length > 0 && relativeSizes.Length > 0 && + realSizes.Length == minSizes.Length && minSizes.Length == maxSizes.Length && maxSizes.Length == relativeSizes.Length; + } + private void Init(float[] relativeSizes, int[] minSizes, int[] maxSizes, int splitSize) { this.relativeSizes = relativeSizes; diff --git a/Editor/Mono/GUI/Toolbar.cs b/Editor/Mono/GUI/Toolbar.cs index afcfa91635..e8d67bb004 100644 --- a/Editor/Mono/GUI/Toolbar.cs +++ b/Editor/Mono/GUI/Toolbar.cs @@ -92,6 +92,12 @@ void InitializeToolIcons() s_CloudIcon = EditorGUIUtility.IconContent("CloudConnect"); s_AccountContent = EditorGUIUtility.TrTextContent("Account"); + + s_SnapToGridIcons = new GUIContent[] + { + EditorGUIUtility.TrIconContent("SceneViewSnap-Off", "Toggle Grid Snapping on and off. Available when you set tool handle rotation to Global."), + EditorGUIUtility.TrIconContent("SceneViewSnap-On", "Toggle Grid Snapping on and off. Available when you set tool handle rotation to Global.") + }; } static GUIContent[] s_ToolIcons; @@ -100,11 +106,11 @@ void InitializeToolIcons() static GUIContent s_LayerContent; static GUIContent[] s_PlayIcons; static GUIContent s_CustomToolIcon; + static GUIContent[] s_SnapToGridIcons; static int s_ViewToolOnOffset; + internal static event Action toolSettingsGui = delegate(Rect rect) {}; private static GUIContent s_AccountContent; static GUIContent s_CloudIcon; - internal static event Action toolSettingsGui; - static class Styles { public static readonly GUIStyle dropdown = "Dropdown"; @@ -200,8 +206,7 @@ void ReserveWidthRight(float width, ref Rect pos) protected override void OldOnGUI() { - const float space = 10; - const float largeSpace = 20; + const float space = 8; const float standardButtonWidth = 32; const float dropdownWidth = 80; const float playPauseStopWidth = 140; @@ -222,14 +227,20 @@ protected override void OldOnGUI() ReserveWidthRight(standardButtonWidth * s_ShownToolIcons.Length, ref pos); DoToolButtons(EditorToolGUI.GetThickArea(pos)); - ReserveWidthRight(largeSpace, ref pos); + ReserveWidthRight(space, ref pos); int playModeControlsStart = Mathf.RoundToInt((position.width - playPauseStopWidth) / 2); pos.x += pos.width; - pos.width = (playModeControlsStart - pos.x) - largeSpace; + const float pivotButtonsWidth = 128; + pos.width = pivotButtonsWidth; DoToolSettings(EditorToolGUI.GetThickArea(pos)); + ReserveWidthRight(standardButtonWidth, ref pos); + DoSnapButtons(EditorToolGUI.GetThickArea(pos)); + + DoAdditionalSettingsCallback(EditorToolGUI.GetThickArea(pos)); + // Position centered controls. pos = new Rect(playModeControlsStart, 0, 240, 0); @@ -286,7 +297,7 @@ protected override void OldOnGUI() subToolbar.OnGUI(EditorToolGUI.GetThinArea(pos)); } - if (ModeService.modeCount > 1 && Unsupported.IsDeveloperBuild()) + if (Unsupported.IsDeveloperBuild() && ModeService.hasSwitchableModes) { EditorGUI.BeginChangeCheck(); ReserveWidthLeft(space, ref pos); @@ -394,16 +405,26 @@ void DoToolButtons(Rect rect) void DoToolSettings(Rect rect) { - if (toolSettingsGui != null) + EditorToolGUI.DoBuiltinToolSettings(rect, Styles.buttonLeft, Styles.buttonRight); + } + + void DoSnapButtons(Rect rect) + { + using (new EditorGUI.DisabledScope(!EditorSnapSettings.activeToolSupportsGridSnap)) { - toolSettingsGui(rect); - return; + var snap = EditorSnapSettings.gridSnapEnabled; + var icon = snap ? s_SnapToGridIcons[1] : s_SnapToGridIcons[0]; + rect = EditorToolGUI.GetThickArea(rect); + EditorSnapSettings.gridSnapEnabled = GUI.Toggle(rect, snap, icon, Styles.command); } + } - const float pivotButtonsWidth = 128; - rect.width = pivotButtonsWidth; - rect = EditorToolGUI.GetThinArea(rect); - EditorToolGUI.DoBuiltinToolSettings(rect, Styles.buttonLeft, Styles.buttonRight); + void DoAdditionalSettingsCallback(Rect rect) + { + rect.x += rect.width + 8; + float playButtonsStart = position.width / 2 - (Styles.commandLeft.fixedWidth + Styles.commandMid.fixedWidth + Styles.commandRight.fixedWidth) * .5f; + rect.width = (playButtonsStart - rect.x) - 28; + toolSettingsGui(rect); } void DoPlayButtons(bool isOrWillEnterPlaymode) diff --git a/Editor/Mono/GUI/Tools/BuiltinTools.cs b/Editor/Mono/GUI/Tools/BuiltinTools.cs index f539e46acf..b51273c960 100644 --- a/Editor/Mono/GUI/Tools/BuiltinTools.cs +++ b/Editor/Mono/GUI/Tools/BuiltinTools.cs @@ -5,7 +5,6 @@ using UnityEngine; using UnityEditor.EditorTools; using UnityEditor.SceneManagement; -using UnityEditor.Snap; namespace UnityEditor { @@ -60,9 +59,7 @@ public override void OnToolGUI(EditorWindow window) using (new EditorGUI.DisabledScope(isStatic)) { Vector3 handlePosition = Tools.handlePosition; - ToolGUI(view, handlePosition, isStatic); - Handles.ShowStaticLabelIfNeeded(handlePosition); } } @@ -152,6 +149,9 @@ public override GUIContent toolbarIcon protected override void ToolGUI(SceneView view, Vector3 handlePosition, bool isStatic) { + if (view.camera.transform.position == handlePosition) + return; + var ids = Handles.TransformHandleIds.Default; TransformManipulator.BeginManipulationHandling(false); @@ -229,6 +229,9 @@ public override GUIContent toolbarIcon protected override void ToolGUI(SceneView view, Vector3 handlePosition, bool isStatic) { + if (view.camera.transform.position == handlePosition) + return; + TransformManipulator.BeginManipulationHandling(false); EditorGUI.BeginChangeCheck(); diff --git a/Editor/Mono/GUI/Tools/EditorToolContext.cs b/Editor/Mono/GUI/Tools/EditorToolContext.cs index 70964bca29..6168c8ae3a 100644 --- a/Editor/Mono/GUI/Tools/EditorToolContext.cs +++ b/Editor/Mono/GUI/Tools/EditorToolContext.cs @@ -418,12 +418,40 @@ void RebuildAvailableCustomEditorTools() // If the shared tracker is locked, use our own tracker instance so that the current selection is always // represented. Addresses case where a single locked inspector is open. var shared = ActiveEditorTracker.sharedTracker; - preservedActiveTool |= CollectCustomEditorToolsFromTracker(shared.isLocked ? tracker : shared, m_CustomEditorTools); + m_CustomEditorTools.Clear(); + m_LockedCustomEditorTools.Clear(); + + // Collect editor tools for the shared tracker first + EditorToolUtility.GetEditorToolsForTracker(shared.isLocked ? tracker : shared, s_CustomEditorTools); + + foreach (var customEditorTool in s_CustomEditorTools) + { + if (m_CustomEditorTools.Any(x => x.GetType() == customEditorTool.editorToolType && x.target == customEditorTool.owner.target)) + continue; + EditorTool tool; + preservedActiveTool |= CreateOrRestoreTool(customEditorTool, out tool); + m_CustomEditorTools.Add(tool); + } + + // Next, collect tools from locked inspectors foreach (var inspector in inspectors) { if (inspector.isLocked) - preservedActiveTool |= CollectCustomEditorToolsFromTracker(inspector.tracker, m_LockedCustomEditorTools); + { + EditorToolUtility.GetEditorToolsForTracker(inspector.tracker, s_CustomEditorTools); + + foreach (var customEditorTool in s_CustomEditorTools) + { + // Don't add duplicate tools to either another locked inspector with the same target, or a shared tracker + if (m_CustomEditorTools.Any(x => x.GetType() == customEditorTool.editorToolType && x.target == customEditorTool.owner.target) + || m_LockedCustomEditorTools.Any(x => x.GetType() == customEditorTool.editorToolType && x.target == customEditorTool.owner.target)) + continue; + EditorTool tool; + preservedActiveTool |= CreateOrRestoreTool(customEditorTool, out tool); + m_LockedCustomEditorTools.Add(tool); + } + } } if (IsCustomEditorTool(m_ActiveTool) && !preservedActiveTool) @@ -435,54 +463,45 @@ void RebuildAvailableCustomEditorTools() } } - bool CollectCustomEditorToolsFromTracker(ActiveEditorTracker tracker, List list) + bool CreateOrRestoreTool(CustomEditorTool customEditorTool, out EditorTool customEditorToolInstance) { - list.Clear(); + var toolType = customEditorTool.editorToolType; + var toolOwner = customEditorTool.owner; + var targets = customEditorTool.targets; + var target = customEditorTool.owner.target; var activeIsCustomEditorTool = IsCustomEditorTool(m_ActiveTool); - var preservedActiveTool = false; - - EditorToolUtility.GetEditorToolsForTracker(tracker, s_CustomEditorTools); + bool preservedActiveTool = false; - foreach (var customEditorTool in s_CustomEditorTools) + // The only case where a custom editor tool is serialized is when it is the active tool. All other + // instances are discarded and rebuilt on any tracker rebuild. + if (activeIsCustomEditorTool && CustomEditorToolIsMatch(toolOwner, toolType, m_ActiveTool)) { - var toolType = customEditorTool.editorToolType; - var toolOwner = customEditorTool.owner; - - EditorTool customEditorToolInstance; - - // The only case where a custom editor tool is serialized is when it is the active tool. All other - // instances are discarded and rebuilt on any tracker rebuild. - if (activeIsCustomEditorTool && CustomEditorToolIsMatch(toolOwner, toolType, m_ActiveTool)) - { - preservedActiveTool = true; + preservedActiveTool = true; - m_ActiveTool.m_Targets = toolOwner.targets; - m_ActiveTool.m_Target = toolOwner.target; + m_ActiveTool.m_Targets = targets; + m_ActiveTool.m_Target = target; - // domain reload - the owning editor was destroyed and therefore we need to reset the EditMode active - if (m_ActiveTool is EditModeTool && toolOwner.GetInstanceID() != UnityEditorInternal.EditMode.ownerID) - UnityEditorInternal.EditMode.EditModeToolStateChanged(toolOwner, ((EditModeTool)m_ActiveTool).editMode); + // domain reload - the owning editor was destroyed and therefore we need to reset the EditMode active + if (m_ActiveTool is EditModeTool && toolOwner.GetInstanceID() != UnityEditorInternal.EditMode.ownerID) + UnityEditorInternal.EditMode.EditModeToolStateChanged(toolOwner, ((EditModeTool)m_ActiveTool).editMode); - customEditorToolInstance = m_ActiveTool; - } - else + customEditorToolInstance = m_ActiveTool; + } + else + { + customEditorToolInstance = (EditorTool)CreateInstance(toolType, x => { - customEditorToolInstance = (EditorTool)CreateInstance(toolType, x => - { - ((EditorTool)x).m_Targets = toolOwner.targets; - ((EditorTool)x).m_Target = toolOwner.target; - }); - - customEditorToolInstance.hideFlags = HideFlags.DontSave; - } + ((EditorTool)x).m_Targets = targets; + ((EditorTool)x).m_Target = target; + }); - list.Add(customEditorToolInstance); + customEditorToolInstance.hideFlags = HideFlags.DontSave; + } - var editModeTool = customEditorToolInstance as EditModeTool; + var editModeTool = customEditorToolInstance as EditModeTool; - if (editModeTool != null) - editModeTool.owner = toolOwner; - } + if (editModeTool != null) + editModeTool.owner = toolOwner; return preservedActiveTool; } @@ -558,7 +577,7 @@ internal static void GetCustomEditorToolsForTarget(UnityObject target, List(IList tools) where T : EditorTool + { + EditorTool selected; + + if (EditorToolbar(EditorToolContext.activeTool, tools, out selected)) + { + if (selected == EditorToolContext.activeTool) + EditorToolContext.RestorePreviousTool(); + else + EditorToolContext.activeTool = selected; + } + } + + internal static bool EditorToolbar(EditorTool selected, IList tools, out EditorTool clicked) where T : EditorTool { int toolsLength = tools.Count; - int selected = -1; + int index = -1; var buttons = s_ButtonArrays.Get(toolsLength); var enabled = s_BoolArrays.Get(toolsLength); + clicked = selected; for (int i = 0; i < toolsLength; i++) { @@ -64,26 +78,23 @@ public static void EditorToolbar(IList tools) where T : EditorTool continue; } - if (tools[i] == EditorToolContext.activeTool) - selected = i; + if (tools[i] == selected) + index = i; enabled[i] = tools[i].IsAvailable(); buttons[i] = tools[i].toolbarIcon ?? GUIContent.none; } - int previous = selected; - EditorGUI.BeginChangeCheck(); - selected = GUILayout.Toolbar(selected, buttons, enabled, "Command"); + index = GUILayout.Toolbar(index, buttons, enabled, "Command"); if (EditorGUI.EndChangeCheck()) { - if (selected == previous) - EditorToolContext.RestorePreviousTool(); - else - EditorToolContext.activeTool = tools[selected]; + clicked = tools[index]; + return true; } + return false; } } diff --git a/Editor/Mono/GUI/Tools/EditorToolUtility.cs b/Editor/Mono/GUI/Tools/EditorToolUtility.cs index a9156071d0..505a801f03 100644 --- a/Editor/Mono/GUI/Tools/EditorToolUtility.cs +++ b/Editor/Mono/GUI/Tools/EditorToolUtility.cs @@ -14,9 +14,10 @@ namespace UnityEditor.EditorTools /// /// An Editor instance and EditorTool type. /// - struct CustomEditorTool + class CustomEditorTool { public Editor owner; + public List additionalEditors; public Type editorToolType; public CustomEditorTool(Editor owner, Type tool) @@ -24,6 +25,19 @@ public CustomEditorTool(Editor owner, Type tool) this.owner = owner; this.editorToolType = tool; } + + public UObject[] targets + { + get + { + if (additionalEditors == null) + return owner.targets; + List objects = new List(owner.targets); + foreach (var editor in additionalEditors) + objects.AddRange(editor.targets); + return objects.ToArray(); + } + } } static class EditorToolUtility @@ -40,6 +54,7 @@ struct CustomEditorToolAssociation static CustomEditorToolAssociation[] s_CustomEditorTools; static Dictionary> s_CustomEditorToolsTypeAssociations = new Dictionary>(); static Dictionary s_EditorToolDefaulToolbarIcons = new Dictionary(); + static HashSet s_TrackerSelectedTypes = new HashSet(); static CustomEditorToolAssociation[] customEditorTools { @@ -171,7 +186,7 @@ internal static Type GetCustomEditorToolTargetType(EditorTool tool) internal static void GetEditorToolsForTracker(ActiveEditorTracker tracker, List tools) { tools.Clear(); - + s_TrackerSelectedTypes.Clear(); var editors = tracker.activeEditors; for (int i = 0, c = editors.Length; i < c; i++) @@ -182,13 +197,31 @@ internal static void GetEditorToolsForTracker(ActiveEditorTracker tracker, List< continue; var targetType = editor.target.GetType(); - var eligibleToolTypes = GetCustomEditorToolsForType(targetType); - foreach (var type in eligibleToolTypes) + // Some components can be added to a GameObject multiple times. Prevent them from creating multiple tools. + if (s_TrackerSelectedTypes.Add(targetType)) { - tools.Add(new CustomEditorTool(editor, type)); + var eligibleToolTypes = GetCustomEditorToolsForType(targetType); + + foreach (var type in eligibleToolTypes) + tools.Add(new CustomEditorTool(editor, type)); + } + else + { + foreach (var tool in tools) + { + if (tool.owner.target.GetType() == targetType) + { + if (tool.additionalEditors == null) + tool.additionalEditors = new List() { editor }; + else + tool.additionalEditors.Add(editor); + } + } } } + + s_TrackerSelectedTypes.Clear(); } internal static EditorTool GetEditorToolWithEnum(Tool type) diff --git a/Editor/Mono/GUI/Tools/Tools.cs b/Editor/Mono/GUI/Tools/Tools.cs index 2667ddaf56..a61717f448 100644 --- a/Editor/Mono/GUI/Tools/Tools.cs +++ b/Editor/Mono/GUI/Tools/Tools.cs @@ -374,35 +374,48 @@ public static bool hidden static int s_LockHandleRectAxis; static bool s_LockHandleRectAxisActive = false; + struct LayerSettings + { + public int visibleLayersValue; + public int lockedLayersValue; + public LayerSettings(int visible, int locked) + { + visibleLayersValue = visible; + lockedLayersValue = locked; + } + } + + LayerSettings m_LayerSettings = new LayerSettings(-1, -1); + + static StateCache s_LayersStateCache = new StateCache("Library/StateCache/LayerSettings/"); + public static int visibleLayers { - get { return get.m_VisibleLayers; } + get { return get.m_LayerSettings.visibleLayersValue; } set { - if (get.m_VisibleLayers != value) + if (get.m_LayerSettings.visibleLayersValue != value) { - get.m_VisibleLayers = value; + get.m_LayerSettings.visibleLayersValue = value; EditorGUIUtility.SetVisibleLayers(value); - EditorPrefs.SetInt("VisibleLayers", visibleLayers); + s_LayersStateCache.SetState("LayerSettings", get.m_LayerSettings); } } } - int m_VisibleLayers = -1; public static int lockedLayers { - get { return get.m_LockedLayers; } + get { return get.m_LayerSettings.lockedLayersValue; } set { - if (get.m_LockedLayers != value) + if (get.m_LayerSettings.lockedLayersValue != value) { - get.m_LockedLayers = value; + get.m_LayerSettings.lockedLayersValue = value; EditorGUIUtility.SetLockedLayers(value); - EditorPrefs.SetInt("LockedLayers", lockedLayers); + s_LayersStateCache.SetState("LayerSettings", get.m_LayerSettings); } } } - int m_LockedLayers = -1; private void OnEnable() { @@ -410,8 +423,9 @@ private void OnEnable() pivotMode = (PivotMode)EditorPrefs.GetInt("PivotMode", 0); rectBlueprintMode = EditorPrefs.GetBool("RectBlueprintMode", false); pivotRotation = (PivotRotation)EditorPrefs.GetInt("PivotRotation", 0); - visibleLayers = EditorPrefs.GetInt("VisibleLayers", -1); - lockedLayers = EditorPrefs.GetInt("LockedLayers", 0); + var layerSettings = s_LayersStateCache.GetState("LayerSettings", new LayerSettings(-1, 0)); + visibleLayers = layerSettings.visibleLayersValue; + lockedLayers = layerSettings.lockedLayersValue; EditorToolContext.activeToolChanged += (previous, active) => { diff --git a/Editor/Mono/GUI/Tools/TransformManipulator.cs b/Editor/Mono/GUI/Tools/TransformManipulator.cs index 5cf99e3847..01794d28af 100644 --- a/Editor/Mono/GUI/Tools/TransformManipulator.cs +++ b/Editor/Mono/GUI/Tools/TransformManipulator.cs @@ -103,13 +103,18 @@ public void SetScaleDelta(Vector3 scaleDelta, Vector3 scalePivot, Quaternion sca scaleDelta = refAlignment * scaleDelta; scaleDelta = Vector3.Scale(scaleDelta, refAlignment * Vector3.one); + var applySmartRounding = Tools.current != Tool.Rect; + if (preferRectResize) { if (rectTransform != null) { Vector2 newSizeDelta = sizeDelta + Vector2.Scale(rect.size, scaleDelta) - rect.size; - newSizeDelta.x = MathUtils.RoundBasedOnMinimumDifference(newSizeDelta.x, minDifference.x); - newSizeDelta.y = MathUtils.RoundBasedOnMinimumDifference(newSizeDelta.y, minDifference.y); + if (applySmartRounding) + { + newSizeDelta.x = MathUtils.RoundBasedOnMinimumDifference(newSizeDelta.x, minDifference.x); + newSizeDelta.y = MathUtils.RoundBasedOnMinimumDifference(newSizeDelta.y, minDifference.y); + } rectTransform.sizeDelta = newSizeDelta; if (rectTransform.drivenByObject != null) RectTransform.SendReapplyDrivenProperties(rectTransform); @@ -126,9 +131,12 @@ public void SetScaleDelta(Vector3 scaleDelta, Vector3 scalePivot, Quaternion sca } } - scaleDelta.x = MathUtils.RoundBasedOnMinimumDifference(scaleDelta.x, minDifference.x); - scaleDelta.y = MathUtils.RoundBasedOnMinimumDifference(scaleDelta.y, minDifference.y); - scaleDelta.z = MathUtils.RoundBasedOnMinimumDifference(scaleDelta.z, minDifference.z); + if (applySmartRounding) + { + scaleDelta.x = MathUtils.RoundBasedOnMinimumDifference(scaleDelta.x, minDifference.x); + scaleDelta.y = MathUtils.RoundBasedOnMinimumDifference(scaleDelta.y, minDifference.y); + scaleDelta.z = MathUtils.RoundBasedOnMinimumDifference(scaleDelta.z, minDifference.z); + } SetScaleValue(Vector3.Scale(scale, scaleDelta)); } diff --git a/Editor/Mono/GUI/TreeView/AssetOrGameObjectTreeViewDragging.cs b/Editor/Mono/GUI/TreeView/AssetOrGameObjectTreeViewDragging.cs index 42c9a1a9ac..1a1450c441 100644 --- a/Editor/Mono/GUI/TreeView/AssetOrGameObjectTreeViewDragging.cs +++ b/Editor/Mono/GUI/TreeView/AssetOrGameObjectTreeViewDragging.cs @@ -84,7 +84,7 @@ public override void StartDrag(TreeViewItem draggedItem, List draggedItemID { DragAndDrop.PrepareStartDrag(); - if (Event.current.control || Event.current.command) + if ((Event.current.control || Event.current.command) && !draggedItemIDs.Contains(draggedItem.id)) { draggedItemIDs.Add(draggedItem.id); } diff --git a/Editor/Mono/GUI/TreeView/AssetsTreeViewDataSource.cs b/Editor/Mono/GUI/TreeView/AssetsTreeViewDataSource.cs index b262019563..3900cc18eb 100644 --- a/Editor/Mono/GUI/TreeView/AssetsTreeViewDataSource.cs +++ b/Editor/Mono/GUI/TreeView/AssetsTreeViewDataSource.cs @@ -11,6 +11,7 @@ using UnityEngine; using UnityEditorInternal; using UnityEditor.Experimental; +using AssetReference = UnityEditorInternal.InternalEditorUtility.AssetReference; namespace UnityEditor { @@ -61,6 +62,50 @@ public AssetsTreeViewDataSource(TreeViewController treeView, int rootInstanceID) m_rootInstanceID = rootInstanceID; } + public override List GetNewSelection(TreeViewItem clickedItem, TreeViewSelectState selectState) + { + var assetTreeClickedItem = clickedItem as IAssetTreeViewItem; + var clickedEntry = new AssetReference() { instanceID = clickedItem.id }; + if (clickedItem.id == 0 && assetTreeClickedItem != null) + clickedEntry.guid = assetTreeClickedItem.Guid; + + // Get ids from items + var visibleRows = GetRows(); + var allIDs = new List(visibleRows.Count); + var allGuids = new List(visibleRows.Count); + + for (int i = 0; i < visibleRows.Count; ++i) + { + int instanceID = visibleRows[i].id; + string guid = null; + if (instanceID == 0) + { + var assetTreeViewItem = visibleRows[i] as IAssetTreeViewItem; + if (assetTreeViewItem != null) + guid = assetTreeViewItem.Guid; + } + + allGuids.Add(guid); + allIDs.Add(instanceID); + } + + List selectedIDs = selectState.selectedIDs; + int lastClickedID = selectState.lastClickedID; + bool allowMultiselection = CanBeMultiSelected(clickedItem); + + var result = InternalEditorUtility.GetNewSelection(ref clickedEntry, allIDs, allGuids, selectedIDs, lastClickedID, selectState.keepMultiSelection, selectState.useShiftAsActionKey, allowMultiselection); + + clickedItem.id = clickedEntry.instanceID; + + for (int i = 0; i < allIDs.Count; ++i) + { + if (allGuids[i] != null && allIDs[i] != 0) + visibleRows[i].id = allIDs[i]; + } + + return result; + } + static string CreateDisplayName(int instanceID) { return Path.GetFileNameWithoutExtension(AssetDatabase.GetAssetPath(instanceID)); @@ -119,7 +164,6 @@ public override void FetchData() } m_Rows = new List(m_Roots.Count * 256); - Texture2D emptyFolderIcon = EditorGUIUtility.FindTexture(EditorResources.emptyFolderIconName); Texture2D folderIcon = EditorGUIUtility.FindTexture(EditorResources.folderIconName); var assetsInstanceIDs = AssetDatabase.GetMainAssetOrInProgressProxyInstanceID("Assets"); var projectPath = Path.GetFileName(Directory.GetCurrentDirectory()); @@ -131,6 +175,7 @@ public override void FetchData() var rootInstanceID = root.instanceID; var displayName = root.displayName ?? CreateDisplayName(rootInstanceID); var rootPath = root.path ?? AssetDatabase.GetAssetPath(rootInstanceID); + var rootGuid = AssetDatabase.AssetPathToGUID(rootPath); var property = new HierarchyProperty(rootPath); if (!root.skipValidation && !property.Find(rootInstanceID, null)) @@ -165,7 +210,7 @@ public override void FetchData() // Create root item TreeView item if (subDepth > 0) - rootItem = new FolderTreeItem(rootInstanceID, rootDepth, parentItem, displayName); + rootItem = new FolderTreeItem(rootGuid, rootInstanceID, rootDepth, parentItem, displayName); else rootItem = new RootTreeItem(rootInstanceID, rootDepth, parentItem, displayName); rootItem.icon = folderIcon; @@ -195,14 +240,11 @@ public override void FetchData() depth = property.depth - minDepth; TreeViewItem item; if (property.isFolder) - item = new FolderTreeItem(property.instanceID, depth + subDepth, null, property.name); + item = new FolderTreeItem(property.guid, property.instanceID, depth + subDepth, null, property.name); else - item = new NonFolderTreeItem(property.instanceID, depth + subDepth, null, property.name); + item = new NonFolderTreeItem(property.guid, property.GetInstanceIDIfImported(), depth + subDepth, null, property.name); - if (property.isFolder && !property.hasChildren) - item.icon = emptyFolderIcon; - else - item.icon = property.icon; + item.icon = property.icon; if (property.hasChildren) { @@ -473,25 +515,53 @@ public int Compare(TreeViewItem x, TreeViewItem y) } // Classes used for type checking - internal class RootTreeItem : TreeViewItem + internal interface IAssetTreeViewItem + { + string Guid { get; } + } + + internal abstract class FolderTreeItemBase : TreeViewItem + { + protected FolderTreeItemBase(int id, int depth, TreeViewItem parent, string displayName) + : base(id, depth, parent, displayName) + { + } + } + + internal class RootTreeItem : FolderTreeItemBase { public RootTreeItem(int id, int depth, TreeViewItem parent, string displayName) : base(id, depth, parent, displayName) { } } - class FolderTreeItem : TreeViewItem + + internal class PackageTreeItem : FolderTreeItemBase { - public FolderTreeItem(int id, int depth, TreeViewItem parent, string displayName) + public PackageTreeItem(int id, int depth, TreeViewItem parent, string displayName) : base(id, depth, parent, displayName) { } } - class NonFolderTreeItem : TreeViewItem + + internal class FolderTreeItem : FolderTreeItemBase, IAssetTreeViewItem + { + public string Guid { get; } + + public FolderTreeItem(string guid, int id, int depth, TreeViewItem parent, string displayName) + : base(id, depth, parent, displayName) + { + Guid = guid; + } + } + + class NonFolderTreeItem : TreeViewItem, IAssetTreeViewItem { - public NonFolderTreeItem(int id, int depth, TreeViewItem parent, string displayName) + public string Guid { get; } + public NonFolderTreeItem(string guid, int id, int depth, TreeViewItem parent, string displayName) : base(id, depth, parent, displayName) { + Guid = guid; } } } diff --git a/Editor/Mono/GUI/TreeView/AssetsTreeViewGUI.cs b/Editor/Mono/GUI/TreeView/AssetsTreeViewGUI.cs index a2e7afe610..84577e4451 100644 --- a/Editor/Mono/GUI/TreeView/AssetsTreeViewGUI.cs +++ b/Editor/Mono/GUI/TreeView/AssetsTreeViewGUI.cs @@ -8,6 +8,7 @@ using UnityEditorInternal.VersionControl; using UnityEditor.VersionControl; using System.Collections.Generic; +using UnityEditor.Experimental; using UnityEngine.Assertions; namespace UnityEditor @@ -17,6 +18,17 @@ internal class AssetsTreeViewGUI : TreeViewGUI static bool s_VCEnabled; const float k_IconOverlayPadding = 7f; + internal static ScalableGUIContent s_OpenFolderIcon = new ScalableGUIContent(null, null, EditorResources.openedFolderIconName); + + internal static Texture2D openFolderTexture + { + get + { + GUIContent folderContent = s_OpenFolderIcon; + return folderContent.image as Texture2D; + } + } + internal delegate void OnAssetIconDrawDelegate(Rect iconRect, string guid); internal static event OnAssetIconDrawDelegate postAssetIconDrawCallback = null; @@ -134,6 +146,14 @@ protected override Texture GetIconForItem(TreeViewItem item) string path = AssetDatabase.GetAssetPath(item.id); icon = AssetDatabase.GetCachedIcon(path); } + + AssetsTreeViewDataSource.FolderTreeItemBase folderItem = item as AssetsTreeViewDataSource.FolderTreeItemBase; + + if (folderItem != null && m_TreeView.data.IsExpanded(folderItem)) + { + icon = openFolderTexture; + } + return icon; } diff --git a/Editor/Mono/GUI/TreeView/GameObjectTreeViewGUI.cs b/Editor/Mono/GUI/TreeView/GameObjectTreeViewGUI.cs index f72ec0863b..c52e1e5252 100644 --- a/Editor/Mono/GUI/TreeView/GameObjectTreeViewGUI.cs +++ b/Editor/Mono/GUI/TreeView/GameObjectTreeViewGUI.cs @@ -308,7 +308,9 @@ override protected void DrawItemBackground(Rect rect, int row, TreeViewItem item { var gameObject = (GameObject)goItem.objectPPTR; if (gameObject != null && SubSceneGUI.IsSubSceneHeader(gameObject)) - SubSceneGUI.DrawSubSceneHeaderBackground(rect, gameObject); + { + SubSceneGUI.DrawSubSceneHeaderBackground(rect, k_BaseIndent, k_IndentWidth, gameObject); + } } } @@ -359,7 +361,9 @@ protected override void OnAdditionalGUI(Rect rect, int row, TreeViewItem item, b { m_ContentRectRight = PrefabModeButton(goItem, rect); if (SubSceneGUI.IsUsingSubScenes() && !showingSearchResults) - SubSceneGUI.DrawVerticalLine(rect, (GameObject)goItem.objectPPTR); + { + SubSceneGUI.DrawVerticalLine(rect, k_BaseIndent, k_IndentWidth, (GameObject)goItem.objectPPTR); + } } if (SceneHierarchy.s_Debug) diff --git a/Editor/Mono/GUI/TreeView/ITreeViewDataSource.cs b/Editor/Mono/GUI/TreeView/ITreeViewDataSource.cs index c64838a2dc..d8486cfc8b 100644 --- a/Editor/Mono/GUI/TreeView/ITreeViewDataSource.cs +++ b/Editor/Mono/GUI/TreeView/ITreeViewDataSource.cs @@ -64,6 +64,7 @@ internal interface ITreeViewDataSource // Selection bool CanBeMultiSelected(TreeViewItem item); bool CanBeParent(TreeViewItem item); + List GetNewSelection(TreeViewItem clickedItem, TreeViewSelectState selectState); // Renaming bool IsRenamingItemAllowed(TreeViewItem item); diff --git a/Editor/Mono/GUI/TreeView/LazyTreeViewDataSource.cs b/Editor/Mono/GUI/TreeView/LazyTreeViewDataSource.cs index 30bbdd31d0..738e569928 100644 --- a/Editor/Mono/GUI/TreeView/LazyTreeViewDataSource.cs +++ b/Editor/Mono/GUI/TreeView/LazyTreeViewDataSource.cs @@ -18,6 +18,8 @@ namespace UnityEditor.IMGUI.Controls internal abstract class LazyTreeViewDataSource : TreeViewDataSource { + static readonly List s_ChildListForCollapsedParent = new List(); + public LazyTreeViewDataSource(TreeViewController treeView) : base(treeView) { @@ -27,7 +29,13 @@ public static List CreateChildListForCollapsedParent() { // To mark a collapsed parent we use a list with one element that is null. // The null element in the children list ensures we show the collapse arrow. - return new List() { null }; + // Reuse read-only list to prevent allocations. + if (s_ChildListForCollapsedParent.Count != 1 || s_ChildListForCollapsedParent[0] != null) + { + s_ChildListForCollapsedParent.Clear(); + s_ChildListForCollapsedParent.Add(null); + } + return s_ChildListForCollapsedParent; } public static bool IsChildListForACollapsedParent(IList childList) diff --git a/Editor/Mono/GUI/TreeView/SubSceneGUI.cs b/Editor/Mono/GUI/TreeView/SubSceneGUI.cs index a56f206c1c..78ff30c65d 100644 --- a/Editor/Mono/GUI/TreeView/SubSceneGUI.cs +++ b/Editor/Mono/GUI/TreeView/SubSceneGUI.cs @@ -14,6 +14,8 @@ static class SubSceneGUI static Dictionary m_SceneToSubSceneMap = new Dictionary(); static Dictionary m_SceneAssetToSubSceneMap = new Dictionary(); const int kMaxSubSceneIterations = 100; + static float s_HalfFoldoutWidth = 6f; + static float s_SubSceneHeaderIndentAdjustment = -2f; internal static void FetchSubSceneInfo() { @@ -215,9 +217,9 @@ internal static Scene GetSubScene(GameObject gameObject) return default(Scene); } - internal static void DrawSubSceneHeaderBackground(Rect rect, GameObject gameObject) + internal static void DrawSubSceneHeaderBackground(Rect rect, float baseIndent, float indentWidth, GameObject gameObject) { - float indent = CalcIndentOfSubSceneHeader(gameObject); + float indent = CalcIndentOfSubSceneHeader(gameObject, baseIndent, indentWidth); if (indent < 0) { Debug.LogError("Only call DrawSubSceneHeaderBackground if IsSubSceneHeader() is true"); @@ -232,7 +234,7 @@ internal static void DrawSubSceneHeaderBackground(Rect rect, GameObject gameObje GUI.color = oldColor; } - static float CalcIndentOfSubSceneHeader(GameObject gameObject) + static float CalcIndentOfSubSceneHeader(GameObject gameObject, float baseIndent, float indentWidth) { SceneHierarchyHooks.SubSceneInfo subSceneInfo; if (gameObject == null || !m_SubSceneHeadersMap.TryGetValue(gameObject, out subSceneInfo)) @@ -241,9 +243,7 @@ static float CalcIndentOfSubSceneHeader(GameObject gameObject) int hierarchyDepth = CalculateHierarchyDepthOfSubScene(subSceneInfo); if (hierarchyDepth > 0) { - float indentWidth = 14f; - float indent = hierarchyDepth * indentWidth; - return indent; + return baseIndent + s_SubSceneHeaderIndentAdjustment + (hierarchyDepth * indentWidth); } return -1f; } @@ -252,7 +252,7 @@ static float CalcIndentOfSubSceneHeader(GameObject gameObject) static SceneHierarchyHooks.SubSceneInfo s_LastSubSceneInfo; static Rect s_LastRectCalculated; - internal static Rect GetRectForVerticalLine(Rect rowRect, Scene scene) + internal static Rect GetRectForVerticalLine(Rect rowRect, float baseIndent, float indentWidth, Scene scene) { // Fast path: reuse last rect if same scene if (s_LastSubSceneInfo.isValid && s_LastSubSceneInfo.scene == scene) @@ -269,38 +269,40 @@ internal static Rect GetRectForVerticalLine(Rect rowRect, Scene scene) if (s_LastSubSceneInfo.color.a == 0) return new Rect(); - float indent = CalcIndentOfVerticalLine(s_LastSubSceneInfo); + float indent = CalcIndentOfVerticalLine(s_LastSubSceneInfo, baseIndent, indentWidth); if (indent < 0) return new Rect(); s_LastRectCalculated = rowRect; - s_LastRectCalculated.x += indent; + s_LastRectCalculated.x += indent; s_LastRectCalculated.width = 1; return s_LastRectCalculated; } - internal static void DrawVerticalLine(Rect rowRect, GameObject gameObject) + internal static void DrawVerticalLine(Rect rowRect, float baseIndent, float indentWidth, GameObject gameObject) { if (gameObject == null) return; if (Event.current.type == EventType.Repaint) { - Rect lineRect = GetRectForVerticalLine(rowRect, gameObject.scene); + Scene scene = gameObject.scene; + Rect lineRect = GetRectForVerticalLine(rowRect, baseIndent, indentWidth, scene); if (lineRect.width > 0f) - EditorGUI.DrawRect(lineRect, GetColorForSubScene(gameObject.scene)); + { + Color color = GetColorForSubScene(scene); + GUI.DrawTexture(lineRect, EditorGUIUtility.whiteTexture, ScaleMode.StretchToFill, true, 1, color, color, color, color, Vector4.zero, Vector4.zero, false); + } } } - static float CalcIndentOfVerticalLine(SceneHierarchyHooks.SubSceneInfo subSceneInfo) + static float CalcIndentOfVerticalLine(SceneHierarchyHooks.SubSceneInfo subSceneInfo, float baseIndent, float indentWidth) { int hierarchyDepth = CalculateHierarchyDepthOfSubScene(subSceneInfo); if (hierarchyDepth > 0) { - float indentWidth = 14f; - float indent = hierarchyDepth * indentWidth; - return indent; + return baseIndent + hierarchyDepth * indentWidth + s_HalfFoldoutWidth; } return -1f; } diff --git a/Editor/Mono/GUI/TreeView/TreeViewControl/TreeViewControl.cs b/Editor/Mono/GUI/TreeView/TreeViewControl/TreeViewControl.cs index 94199e0b84..6de2bd5e14 100644 --- a/Editor/Mono/GUI/TreeView/TreeViewControl/TreeViewControl.cs +++ b/Editor/Mono/GUI/TreeView/TreeViewControl/TreeViewControl.cs @@ -29,6 +29,15 @@ protected GetNewSelectionFunction getNewSelectionOverride set { m_TreeView.getNewSelectionOverride = (x, y, z) => value(x, y, z); } } + internal bool deselectOnUnhandledMouseDown + { + set + { + if (m_TreeView != null) + m_TreeView.deselectOnUnhandledMouseDown = value; + } + } + TreeViewController m_TreeView; TreeViewControlDataSource m_DataSource; TreeViewControlGUI m_GUI; diff --git a/Editor/Mono/GUI/TreeView/TreeViewController.cs b/Editor/Mono/GUI/TreeView/TreeViewController.cs index 8132a7119b..c9b86eb771 100644 --- a/Editor/Mono/GUI/TreeView/TreeViewController.cs +++ b/Editor/Mono/GUI/TreeView/TreeViewController.cs @@ -9,7 +9,6 @@ using UnityEngine; using UnityEditorInternal; - namespace UnityEditor.IMGUI.Controls { /* @@ -59,6 +58,14 @@ internal virtual void OnAwake() } } + internal struct TreeViewSelectState + { + public List selectedIDs; + public int lastClickedID; + public bool keepMultiSelection; + public bool useShiftAsActionKey; + } + internal class TreeViewController { public System.Action selectionChangedCallback { get; set; } // ids @@ -110,6 +117,7 @@ internal class TreeViewController bool m_HadFocusLastEvent; // Cached from last event for keyboard focus changed event int m_KeyboardControlID; + const double kSlowSelectTimeout = 0.2; const float kSpaceForScrollBar = 16f; public TreeViewItem hoveredItem { get; set; } @@ -333,13 +341,16 @@ public void HandleUnusedMouseEventsForItem(Rect rect, TreeViewItem item, int row } else { - var dragSelection = GetNewSelection(item, true, false); - bool canStartDrag = dragging != null && dragSelection.Count != 0 && dragging.CanStartDrag(item, dragSelection, Event.current.mousePosition); + double selectStartTime = Time.realtimeSinceStartup; + var dragSelection = GetNewSelection(item, true, false); + bool dragAbortedBySlowSelect = (Time.realtimeSinceStartup - selectStartTime) > kSlowSelectTimeout; + + bool canStartDrag = !dragAbortedBySlowSelect && dragging != null && dragSelection.Count != 0 && dragging.CanStartDrag(item, dragSelection, Event.current.mousePosition); if (canStartDrag) { // Prepare drag and drop delay (we start the drag after a couple of pixels mouse drag: See the case MouseDrag below) m_DragSelection = dragSelection; - DragAndDropDelay delay = (DragAndDropDelay)GUIUtility.GetStateObject(typeof(DragAndDropDelay), itemControlID); + DragAndDropDelay delay = (DragAndDropDelay)GUIUtility.GetStateObject(typeof(DragAndDropDelay), GetItemControlID(item)); delay.mouseDownPosition = Event.current.mousePosition; } else @@ -357,7 +368,7 @@ public void HandleUnusedMouseEventsForItem(Rect rect, TreeViewItem item, int row itemSingleClickedCallback(item.id); } - GUIUtility.hotControl = itemControlID; + GUIUtility.hotControl = GetItemControlID(item); } evt.Use(); } @@ -1135,17 +1146,15 @@ List GetNewSelection(TreeViewItem clickedItem, bool keepMultiSelection, boo { if (getNewSelectionOverride != null) return getNewSelectionOverride(clickedItem, keepMultiSelection, useShiftAsActionKey); - // Get ids from items - var visibleRows = data.GetRows(); - List allIDs = new List(visibleRows.Count); - for (int i = 0; i < visibleRows.Count; ++i) - allIDs.Add(visibleRows[i].id); - List selectedIDs = state.selectedIDs; - int lastClickedID = state.lastClickedID; - bool allowMultiselection = data.CanBeMultiSelected(clickedItem); + var selectState = new TreeViewSelectState() { + selectedIDs = state.selectedIDs, + lastClickedID = state.lastClickedID, + keepMultiSelection = keepMultiSelection, + useShiftAsActionKey = useShiftAsActionKey + }; - return InternalEditorUtility.GetNewSelection(clickedItem.id, allIDs, selectedIDs, lastClickedID, keepMultiSelection, useShiftAsActionKey, allowMultiselection); + return data.GetNewSelection(clickedItem, selectState); } void SelectionByKey(TreeViewItem itemSelected) diff --git a/Editor/Mono/GUI/TreeView/TreeViewDataSource.cs b/Editor/Mono/GUI/TreeView/TreeViewDataSource.cs index e7932ba3b2..4c0c9d6b46 100644 --- a/Editor/Mono/GUI/TreeView/TreeViewDataSource.cs +++ b/Editor/Mono/GUI/TreeView/TreeViewDataSource.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using UnityEngine; +using UnityEditorInternal; namespace UnityEditor.IMGUI.Controls { @@ -349,6 +350,19 @@ virtual public bool CanBeParent(TreeViewItem item) return true; } + virtual public List GetNewSelection(TreeViewItem clickedItem, TreeViewSelectState selectState) + { + // Get ids from items + var visibleRows = GetRows(); + List allIDs = new List(visibleRows.Count); + for (int i = 0; i < visibleRows.Count; ++i) + allIDs.Add(visibleRows[i].id); + + bool allowMultiselection = CanBeMultiSelected(clickedItem); + + return InternalEditorUtility.GetNewSelection(clickedItem.id, allIDs, selectState.selectedIDs, selectState.lastClickedID, selectState.keepMultiSelection, selectState.useShiftAsActionKey, allowMultiselection); + } + virtual public void OnExpandedStateChanged() { if (m_TreeView.expandedStateChanged != null) diff --git a/Editor/Mono/GUI/TreeView/TreeViewGUI.cs b/Editor/Mono/GUI/TreeView/TreeViewGUI.cs index f498ca247e..9a770bacb2 100644 --- a/Editor/Mono/GUI/TreeView/TreeViewGUI.cs +++ b/Editor/Mono/GUI/TreeView/TreeViewGUI.cs @@ -33,8 +33,9 @@ public float k_LineHeight get { if (m_LineHeight < 0) - return new SVC("--treeview-line-height", 16f); - else return m_LineHeight; + m_LineHeight = new SVC("--treeview-line-height", 16f); + + return m_LineHeight; } set { m_LineHeight = value; } } @@ -409,7 +410,7 @@ float GetFoldoutYPosition(float rectY) protected virtual Rect DoFoldout(Rect rect, TreeViewItem item, int row) { float indent = GetFoldoutIndent(item); - Rect foldoutRect = new Rect(rect.x + indent, GetFoldoutYPosition(rect.y), foldoutStyleWidth, EditorGUIUtility.singleLineHeight); + Rect foldoutRect = new Rect(rect.x + indent, GetFoldoutYPosition(rect.y), foldoutStyleWidth, k_LineHeight); FoldoutButton(foldoutRect, item, row, foldoutStyle); return foldoutRect; } diff --git a/Editor/Mono/GUI/WindowLayout.cs b/Editor/Mono/GUI/WindowLayout.cs index a299c80203..b3e236a17b 100644 --- a/Editor/Mono/GUI/WindowLayout.cs +++ b/Editor/Mono/GUI/WindowLayout.cs @@ -172,11 +172,18 @@ private static void InitializeLayoutPreferencesFolder() static WindowLayout() { - EditorApplication.delayCall += () => + EditorApplication.update -= DelayReloadWindowLayoutMenu; + EditorApplication.update += DelayReloadWindowLayoutMenu; + } + + internal static void DelayReloadWindowLayoutMenu() + { + EditorApplication.update -= DelayReloadWindowLayoutMenu; + if (ModeService.HasCapability(ModeCapability.LayoutWindowMenu, true)) { ReloadWindowLayoutMenu(); EditorUtility.Internal_UpdateAllMenus(); - }; + } } internal static void ReloadWindowLayoutMenu() @@ -250,14 +257,14 @@ internal static EditorWindow TryGetLastFocusedWindowInSameDock() if (windowTypeName != "") type = Type.GetType(windowTypeName); - // Also get the Preview Window - var previewWindow = PreviewEditorWindow.GetMainPreviewWindow(); - if (type != null && previewWindow && previewWindow.m_Parent != null && previewWindow.m_Parent is DockArea) + // Also get the PlayModeView Window + var playModeView = PlayModeView.GetMainPlayModeView(); + if (type != null && playModeView && playModeView != null && playModeView.m_Parent is DockArea) { // Get all windows of that type object[] potentials = Resources.FindObjectsOfTypeAll(type); - DockArea dock = previewWindow.m_Parent as DockArea; + DockArea dock = playModeView.m_Parent as DockArea; // Find the one that is actually docked together with the GameView for (int i = 0; i < potentials.Length; i++) @@ -295,14 +302,14 @@ internal static EditorWindow TryFocusAppropriateWindow(bool enteringPlaymode) { if (enteringPlaymode) { - var previewWindow = PreviewEditorWindow.GetMainPreviewWindow(); - if (previewWindow) + var playModeView = PlayModeView.GetMainPlayModeView(); + if (playModeView) { - SaveCurrentFocusedWindowInSameDock(previewWindow); - previewWindow.Focus(); + SaveCurrentFocusedWindowInSameDock(playModeView); + playModeView.Focus(); } - return previewWindow; + return playModeView; } else { @@ -633,8 +640,11 @@ public static bool MaximizePrepare(EditorWindow win) maximizedHostView.actualView = win; maximizedHostView.position = p; // Must be set after actualView so that value is propagated - UnityObject.DestroyImmediate(rootSplit, true); + var gv = win as GameView; + if (gv != null) + maximizedHostView.EnableVSync(gv.vSyncEnabled); + UnityObject.DestroyImmediate(rootSplit, true); return true; } @@ -820,10 +830,10 @@ public static bool LoadWindowLayout(string path, bool newProjectLayoutWasCreated containerWindow.Show(containerWindow.showMode, loadPosition: false, displayImmediately: true, setFocus: true); } - // Unmaximize maximized Preview window if maximize on play is enabled - PreviewEditorWindow preview = GetMaximizedWindow() as PreviewEditorWindow; - if (preview != null && preview.maximizeOnPlay) - Unmaximize(preview); + // Unmaximize maximized PlayModeView window if maximize on play is enabled + PlayModeView playModeView = GetMaximizedWindow() as PlayModeView; + if (playModeView != null && playModeView.maximizeOnPlay) + Unmaximize(playModeView); } catch (Exception ex) { diff --git a/Editor/Mono/GUID.bindings.cs b/Editor/Mono/GUID.bindings.cs index 4315e44684..c4837ead38 100644 --- a/Editor/Mono/GUID.bindings.cs +++ b/Editor/Mono/GUID.bindings.cs @@ -134,5 +134,8 @@ public override string ToString() [NativeMethod(Name = "GenerateGUID", IsFreeFunction = true)] extern private static GUID GenerateGUIDInternal(); + + [NativeMethod(Name = "CreateGUIDFromSInt64", IsFreeFunction = true)] + extern internal static GUID CreateGUIDFromSInt64(long value); } } diff --git a/Editor/Mono/GUIView.bindings.cs b/Editor/Mono/GUIView.bindings.cs index a7ec1a711a..22de92b2a9 100644 --- a/Editor/Mono/GUIView.bindings.cs +++ b/Editor/Mono/GUIView.bindings.cs @@ -37,9 +37,10 @@ internal partial class GUIView internal extern void AddToAuxWindowList(); internal extern void RemoveFromAuxWindowList(); internal extern void SetInternalGameViewDimensions(Rect rect, Rect clippedRect, Vector2 targetSize); + internal extern void SetMainPlayModeViewSize(Vector2 targetSize); internal extern void SetAsStartView(); internal extern void SetAsLastPlayModeView(); - internal extern void SetPlayModeView(); + internal extern void SetPlayModeView(bool value); internal extern void ClearStartView(); internal extern void MakeVistaDWMHappyDance(); internal extern void StealMouseCapture(); diff --git a/Editor/Mono/GUIView.cs b/Editor/Mono/GUIView.cs index eadd6e726c..d3c3d620bd 100644 --- a/Editor/Mono/GUIView.cs +++ b/Editor/Mono/GUIView.cs @@ -17,6 +17,14 @@ namespace UnityEditor [StructLayout(LayoutKind.Sequential)] internal partial class GUIView : View { + // Case 1183719 - The delegate getEditorShader is being reset upon domain reload and InitializeOnLoad is not rerun + // Hence a static constructor to Initialize the Delegate. EditorShaderLoader is still needed for Batch mode where GUIView may not be created + static GUIView() + { + // TODO: Remove this once case 1148851 has been fixed. + UnityEngine.UIElements.UIR.UIRenderDevice.getEditorShader = () => EditorShader; + } + [InitializeOnLoad] static class EditorShaderLoader { @@ -78,7 +86,6 @@ protected Panel panel int m_AntiAliasing = 1; EventInterests m_EventInterests; bool m_AutoRepaintOnSceneChange = false; - private bool m_BackgroundValid = false; internal bool SendEvent(Event e) { @@ -107,14 +114,11 @@ internal override void SetWindow(ContainerWindow win) Internal_SetWantsMouseEnterLeaveWindow(m_EventInterests.wantsMouseMove); panel.visualTree.SetSize(windowPosition.size); - - m_BackgroundValid = false; } internal void RecreateContext() { Internal_Recreate(m_DepthBufferBits, m_AntiAliasing); - m_BackgroundValid = false; } public EventInterests eventInterests @@ -154,12 +158,6 @@ public bool wantsMouseEnterLeaveWindow } } - internal bool backgroundValid - { - get { return m_BackgroundValid; } - set { m_BackgroundValid = value; } - } - public bool autoRepaintOnSceneChange { get { return m_AutoRepaintOnSceneChange; } @@ -270,8 +268,6 @@ protected override void SetPosition(Rect newPos) Internal_SetPosition(windowPosition); - m_BackgroundValid = false; - panel.visualTree.SetSize(windowPosition.size); positionChanged?.Invoke(this); diff --git a/Editor/Mono/GameView/GameView.cs b/Editor/Mono/GameView/GameView.cs index 8e081d8a98..f65482dadc 100644 --- a/Editor/Mono/GameView/GameView.cs +++ b/Editor/Mono/GameView/GameView.cs @@ -9,7 +9,10 @@ using System.Globalization; using UnityEngine.Rendering; using System.Linq; +using System.Collections.Generic; using JetBrains.Annotations; +using UnityEngine.XR; +using UnityEditorInternal.VR; /* The main GameView can be in the following states when entering playmode. @@ -30,12 +33,12 @@ Floating GameView in separate window namespace UnityEditor { [EditorWindowTitle(title = "Game", useTypeNameAsIconName = true)] - internal class GameView : PreviewEditorWindow, IHasCustomMenu, IGameViewSizeMenuUser + internal class GameView : PlayModeView, IHasCustomMenu, IGameViewSizeMenuUser { const int kScaleSliderMinWidth = 30; const int kScaleSliderMaxWidth = 150; const int kScaleSliderSnapThreshold = 4; - const int kScaleLabelWidth = 35; + const int kScaleLabelWidth = 40; readonly Vector2 kWarningSize = new Vector2(400f, 140f); readonly Color kClearBlack = new Color(0, 0 , 0, 0); const float kMinScale = 1f; @@ -78,6 +81,8 @@ float maxScale int m_SizeChangeID = int.MinValue; + List m_DisplaySubsystems = new List(); + internal static class Styles { public static GUIContent gizmosContent = EditorGUIUtility.TrTextContent("Gizmos"); @@ -110,7 +115,7 @@ public GameView() { autoRepaintOnSceneChange = true; InitializeZoomArea(); - previewName = "GameView"; + playModeViewName = "GameView"; clearColor = kClearBlack; showGizmos = m_Gizmos; targetDisplay = 0; @@ -272,11 +277,12 @@ public void OnEnable() prevSizeGroupType = (int)currentSizeGroupType; titleContent = GetLocalizedTitleContent(); UpdateZoomAreaAndParent(); - dontClearBackground = true; showToolbar = ModeService.HasCapability(ModeCapability.GameViewToolbar, true); ModeService.modeChanged += OnEditorModeChanged; EditorApplication.playModeStateChanged += OnPlayModeStateChanged; + + targetSize = targetRenderSize; } public void OnDisable() @@ -292,7 +298,7 @@ public void OnDisable() [UsedImplicitly] // This is here because NGUI uses it via reflection (noted in https://confluence.hq.unity3d.com/display/DEV/Game+View+Bucket) internal static Vector2 GetSizeOfMainGameView() { - return GetMainPreviewTargetSize(); + return GetMainPlayModeViewTargetSize(); } private void UpdateZoomAreaAndParent() @@ -324,6 +330,7 @@ private void OnFocus() { AllowCursorLockAndHide(true); SetFocus(true); + targetSize = targetRenderSize; } private void OnLostFocus() @@ -339,6 +346,11 @@ private void OnLostFocus() SetFocus(false); } + internal override void OnResized() + { + targetSize = targetRenderSize; + } + // Call when number of available aspects can have changed (after deserialization or gui change) private void EnsureSelectedSizeAreValid() { @@ -379,8 +391,7 @@ private void LoadRenderDoc() { if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) { - RenderDoc.Load(); - ShaderUtil.RecreateGfxDevice(); + ShaderUtil.RequestLoadRenderDoc(); } } @@ -409,8 +420,9 @@ public void SizeSelectionCallback(int indexClicked, object objectSelected) if (indexClicked != selectedSizeIndex) { selectedSizeIndex = indexClicked; - dontClearBackground = true; // will cause re-clear UpdateZoomAreaAndParent(); + targetSize = targetRenderSize; + SceneView.RepaintAll(); } } @@ -471,6 +483,9 @@ void DoZoomSlider() private void DoToolbarGUI() { + if (Event.current.isKey || Event.current.type == EventType.Used) + return; + GameViewSizes.instance.RefreshStandaloneAndRemoteDefaultSizes(); GUILayout.BeginHorizontal(EditorStyles.toolbar); @@ -492,7 +507,7 @@ private void DoToolbarGUI() if (ModuleManager.ShouldShowMultiDisplayOption()) { - int display = EditorGUILayout.Popup(targetDisplay, DisplayUtility.GetDisplayNames(), EditorStyles.toolbarPopup, GUILayout.Width(80)); + int display = EditorGUILayout.Popup(targetDisplay, DisplayUtility.GetDisplayNames(), EditorStyles.toolbarPopupLeft, GUILayout.Width(80)); if (display != targetDisplay) { targetDisplay = display; @@ -539,11 +554,34 @@ private void DoToolbarGUI() } } + SubsystemManager.GetInstances(m_DisplaySubsystems); // Allow the user to select how the XR device will be rendered during "Play In Editor" - if (PlayerSettings.virtualRealitySupported) + if (VREditor.GetVREnabledOnTargetGroup(BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget)) || (m_DisplaySubsystems.Count != 0 && !m_DisplaySubsystems[0].disableLegacyRenderer)) + { + EditorGUI.BeginChangeCheck(); + GameViewRenderMode currentGameViewRenderMode = UnityEngine.XR.XRSettings.gameViewRenderMode; + int selectedRenderMode = EditorGUILayout.Popup(Mathf.Clamp(((int)currentGameViewRenderMode) - 1, 0, Styles.xrRenderingModes.Length - 1), Styles.xrRenderingModes, EditorStyles.toolbarPopup, GUILayout.Width(80)); + if (EditorGUI.EndChangeCheck() && currentGameViewRenderMode != GameViewRenderMode.None) + { + SetXRRenderMode(selectedRenderMode); + } + } + // Handles the case where XRSDK is being used without the shim layer + else if (m_DisplaySubsystems.Count != 0 && m_DisplaySubsystems[0].disableLegacyRenderer) { - int selectedRenderMode = EditorGUILayout.Popup(m_XRRenderMode, Styles.xrRenderingModes, EditorStyles.toolbarPopup, GUILayout.Width(80)); - SetXRRenderMode(selectedRenderMode); + EditorGUI.BeginChangeCheck(); + int currentMirrorViewBlitMode = m_DisplaySubsystems[0].GetPreferredMirrorBlitMode(); + int currentRenderMode = XRTranslateMirrorViewBlitModeToRenderMode(currentMirrorViewBlitMode); + int selectedRenderMode = EditorGUILayout.Popup(Mathf.Clamp(currentRenderMode , 0, Styles.xrRenderingModes.Length - 1), Styles.xrRenderingModes, EditorStyles.toolbarPopup, GUILayout.Width(80)); + int selectedMirrorViewBlitMode = XRTranslateRenderModeToMirrorViewBlitMode(selectedRenderMode); + if (EditorGUI.EndChangeCheck() || currentMirrorViewBlitMode == 0) + { + m_DisplaySubsystems[0].SetPreferredMirrorBlitMode(selectedMirrorViewBlitMode); + if (selectedMirrorViewBlitMode != m_XRRenderMode) + ClearTargetTexture(); + + m_XRRenderMode = selectedMirrorViewBlitMode; + } } maximizeOnPlay = GUILayout.Toggle(maximizeOnPlay, Styles.maximizeOnPlayContent, EditorStyles.toolbarButton); @@ -564,6 +602,73 @@ private void DoToolbarGUI() GUILayout.EndHorizontal(); } + private int XRTranslateMirrorViewBlitModeToRenderMode(int mirrorViewBlitMode) + { + switch (mirrorViewBlitMode) + { + default: + return 0; + case XRMirrorViewBlitMode.RightEye: + return 1; + case XRMirrorViewBlitMode.SideBySide: + return 2; + case XRMirrorViewBlitMode.SideBySideOcclusionMesh: + return 3; + } + } + + private int XRTranslateRenderModeToMirrorViewBlitMode(int renderMode) + { + switch (renderMode) + { + default: // or 0 + return XRMirrorViewBlitMode.LeftEye; + case 1: + return XRMirrorViewBlitMode.RightEye; + case 2: + return XRMirrorViewBlitMode.SideBySide; + case 3: + return XRMirrorViewBlitMode.SideBySideOcclusionMesh; + } + } + + private GUIContent[] GetSupportedBlitModeContent(XRDisplaySubsystem display) + { + List supportedBlitModesGUIContent = new List(); + //at least add left eye as it is the default if the xr subsystem does not support main screen blitting + supportedBlitModesGUIContent.Add(Styles.xrRenderingModes[(int)UnityEngine.XR.GameViewRenderMode.LeftEye - 1]); + + XRMirrorViewBlitModeDesc blitModeDesc; + for (int i = 0; i < display.SubsystemDescriptor.GetAvailableMirrorBlitModeCount(); i++) + { + display.SubsystemDescriptor.GetMirrorBlitModeByIndex(i, out blitModeDesc); + switch (blitModeDesc.blitMode) + { + case XRMirrorViewBlitMode.RightEye: + supportedBlitModesGUIContent.Add(Styles.xrRenderingModes[(int)UnityEngine.XR.GameViewRenderMode.RightEye - 1]); + break; + case XRMirrorViewBlitMode.SideBySide: + supportedBlitModesGUIContent.Add(Styles.xrRenderingModes[(int)UnityEngine.XR.GameViewRenderMode.BothEyes - 1]); + break; + case XRMirrorViewBlitMode.SideBySideOcclusionMesh: + supportedBlitModesGUIContent.Add(Styles.xrRenderingModes[(int)UnityEngine.XR.GameViewRenderMode.OcclusionMesh - 1]); + break; + } + } + return supportedBlitModesGUIContent.ToArray(); + } + + private XRDisplaySubsystem GetActiveXRDisplay(List displays) + { + for (int i = 0; i < displays.Count; i++) + { + if (displays[i].running) + return displays[i]; + } + + return null; + } + private void SetXRRenderMode(int mode) { switch (mode) @@ -590,7 +695,7 @@ private void SetXRRenderMode(int mode) private void ClearTargetTexture() { - if (m_RenderTexture.IsCreated()) + if (m_RenderTexture && m_RenderTexture.IsCreated()) { var previousTarget = RenderTexture.active; RenderTexture.active = m_RenderTexture; @@ -666,7 +771,7 @@ public void RenderToHMDOnly() showGizmos = false; renderIMGUI = false; - m_RenderTexture = RenderPreview(mousePos, clearTexture: false); + m_RenderTexture = RenderView(mousePos, clearTexture: false); } private void OnPlayModeStateChanged(PlayModeStateChange state) @@ -736,6 +841,10 @@ private void OnGUI() m_ZoomArea.BeginViewGUI(); + // Window size might change on Layout event + if (type == EventType.Layout) + targetSize = targetRenderSize; + // Setup game view dimensions, so that player loop can use it for input var gameViewTarget = GUIClip.UnclipToWindow(m_ZoomArea.drawRect); if (m_Parent) @@ -772,13 +881,13 @@ private void OnGUI() clearColor = kClearBlack; renderIMGUI = true; - if (!EditorApplication.isPlaying || (EditorApplication.isPlaying && Time.frameCount % OnDemandRendering.GetRenderFrameInterval() == 0)) - m_RenderTexture = RenderPreview(gameMousePosition, clearTexture); + if (!EditorApplication.isPlaying || (EditorApplication.isPlaying && Time.frameCount % OnDemandRendering.renderFrameInterval == 0)) + m_RenderTexture = RenderView(gameMousePosition, clearTexture); if (m_TargetClamped) Debug.LogWarningFormat("GameView reduced to a reasonable size for this system ({0}x{1})", targetSize.x, targetSize.y); EditorGUIUtility.SetupWindowSpaceAndVSyncInternal(GUIClip.Unclip(viewInWindow)); - if (m_RenderTexture.IsCreated()) + if (m_RenderTexture != null && m_RenderTexture.IsCreated()) { oldState.ApplyAndForget(); GUIUtility.s_EditorScreenPointOffset = oldOffset; diff --git a/Editor/Mono/GenerateIconsWithMipLevels.cs b/Editor/Mono/GenerateIconsWithMipLevels.cs index edd4c55b2c..90bbd94bc5 100644 --- a/Editor/Mono/GenerateIconsWithMipLevels.cs +++ b/Editor/Mono/GenerateIconsWithMipLevels.cs @@ -8,6 +8,7 @@ using UnityEditor.Experimental; using System.IO; using System.Collections.Generic; +using UnityEngine.Experimental.Rendering; /* Some notes on icon caches and loading * @@ -267,7 +268,7 @@ private static Texture2D CreateIconWithMipLevels(InputData inputData, string bas return null; // Create our icon - Texture2D iconWithMips = new Texture2D(maxResolution, maxResolution, TextureFormat.RGBA32, true, false); + Texture2D iconWithMips = new Texture2D(maxResolution, maxResolution, GraphicsFormat.R8G8B8A8_SRGB, -1, TextureCreationFlags.MipChain); // Add max mip if (BlitMip(iconWithMips, allMips, 0)) diff --git a/Editor/Mono/Grids/EditorSnap.cs b/Editor/Mono/Grids/EditorSnap.cs index 5b5061c093..9378af40ed 100644 --- a/Editor/Mono/Grids/EditorSnap.cs +++ b/Editor/Mono/Grids/EditorSnap.cs @@ -38,6 +38,10 @@ internal void Save() } } + // Most of EditorSnapSettings is private for now because we are just hard-coding grid snap and incremental snap. + // in the near future, we will be extending support for multiple snapping modes and this API will change. + // ex, snap modes will likely be implemented as objects that can be registered to this class, making user-implemented + // snapping behaviours possible. public static class EditorSnapSettings { static EditorSnapSettingsData instance @@ -45,34 +49,35 @@ static EditorSnapSettingsData instance get { return EditorSnapSettingsData.instance; } } - // Is snapping toggled as `on` in the grid toolbar - public static bool enabled - { - get { return instance.snapEnabled; } - set { instance.snapEnabled = value; } - } - - // Is snapping active (either through shortcut key or enabled) - public static bool active + internal static bool activeToolSupportsGridSnap { get { - return Event.current == null - ? instance.snapEnabled - : hotkeyActive ? !instance.snapEnabled : instance.snapEnabled; + return (EditorTools.EditorTools.activeToolType == typeof(MoveTool) + || EditorTools.EditorTools.activeToolType == typeof(TransformTool)) + && Tools.pivotRotation == PivotRotation.Global; } } + internal static bool gridSnapActive + { + get { return !incrementalSnapActive && activeToolSupportsGridSnap && instance.snapEnabled; } + } + + public static bool gridSnapEnabled + { + get { return instance.snapEnabled; } + set { instance.snapEnabled = value; } + } + internal static bool hotkeyActive { get { return EditorGUI.actionKey; } } - - public static bool preferGrid + internal static bool incrementalSnapActive { - get { return instance.snapSettings.preferGrid; } - set { instance.snapSettings.preferGrid = value; } + get { return Event.current != null && EditorGUI.actionKey; } } public static Vector3 move diff --git a/Editor/Mono/Grids/GridSettingsWindow.cs b/Editor/Mono/Grids/GridSettingsWindow.cs index 97d6001aea..3d397a8ad9 100644 --- a/Editor/Mono/Grids/GridSettingsWindow.cs +++ b/Editor/Mono/Grids/GridSettingsWindow.cs @@ -6,7 +6,7 @@ namespace UnityEditor.Snap { - internal sealed class GridSettingsWindow : PopupWindowContent + sealed class GridSettingsWindow : PopupWindowContent { static class Contents { @@ -17,17 +17,37 @@ static class Contents public static readonly GUIContent settingsHeader = EditorGUIUtility.TrTextContent("Grid Settings"); public static readonly GUIContent opacitySlider = EditorGUIUtility.TrTextContent("Opacity"); + + public static readonly GUIContent reset = EditorGUIUtility.TrTextContent("Reset"); + public static readonly GUIContent editGridAndSnapSettings = EditorGUIUtility.TrTextContent("Edit Grid and Snap Settings..."); } static class Styles { + static bool s_Initialized; public static readonly GUIStyle menuItem = "MenuItem"; public static readonly GUIStyle header = EditorStyles.boldLabel; public static readonly GUIStyle separator = "sv_iconselector_sep"; + public static GUIStyle settingsArea; + public static readonly GUIStyle options = "PaneOptions"; + + internal static void Init() + { + if (s_Initialized) + return; + + s_Initialized = true; + + settingsArea = new GUIStyle() + { + padding = new RectOffset(k_SettingsAreaBorder, k_SettingsAreaBorder, k_SettingsAreaBorder, k_SettingsAreaBorder) + }; + } } - const float k_WindowWidth = 190; - const float k_WindowHeight = 120; + const float k_WindowWidth = 210; + const int k_SettingsAreaBorder = 4; + const float k_WindowHeight = EditorGUI.kSingleLineHeight * 6 + k_SettingsAreaBorder * 2; readonly SceneView m_SceneView; public GridSettingsWindow(SceneView sceneView) @@ -58,19 +78,34 @@ public override void OnGUI(Rect rect) void Draw() { + Styles.Init(); + GUILayout.BeginVertical(Styles.settingsArea); EditorGUI.BeginChangeCheck(); DoGridAxes(); DoSeparator(); DoGridSettings(); if (EditorGUI.EndChangeCheck()) - { SceneView.RepaintAll(); - } + GUILayout.EndVertical(); } void DoGridAxes() { + GUILayout.BeginHorizontal(); GUILayout.Label(Contents.axisHeader, Styles.header); + GUILayout.FlexibleSpace(); + if (GUILayout.Button(GUIContent.none, Styles.options)) + { + var menu = new GenericMenu(); + menu.AddItem(Contents.reset, false, Reset); + menu.AddSeparator(""); + menu.AddItem(Contents.editGridAndSnapSettings, false, () => + { + EditorWindow.GetWindow(false, SnapSettingsWindow.k_WindowTitle, true); + }); + menu.ShowAsContext(); + } + GUILayout.EndHorizontal(); var axis = m_SceneView.sceneViewGrids.gridAxis; @@ -105,5 +140,11 @@ static bool DrawListElement(GUIContent content, bool selected) GUILayout.Toggle(selected, content, Styles.menuItem); return EditorGUI.EndChangeCheck(); } + + void Reset() + { + m_SceneView.sceneViewGrids.Reset(); + m_SceneView.sceneViewGrids.ResetPivot(SceneViewGrid.GridRenderAxis.All); + } } } diff --git a/Editor/Mono/Grids/GridShortcuts.cs b/Editor/Mono/Grids/GridShortcuts.cs index 0e90989232..1c9d38ffaf 100644 --- a/Editor/Mono/Grids/GridShortcuts.cs +++ b/Editor/Mono/Grids/GridShortcuts.cs @@ -7,36 +7,37 @@ namespace UnityEditor.Snap { - internal static class Shortcuts + static class Shortcuts { - [Shortcut("Snap/Toggle Snap", typeof(SceneView), KeyCode.Backslash)] - internal static void ToggleSnap() + [Shortcut("Snap/Toggle Grid Snap", typeof(SceneView), KeyCode.Backslash)] + internal static void ToggleGridSnap() { - EditorSnapSettings.enabled = !EditorSnapSettings.enabled; + EditorSnapSettings.gridSnapEnabled = !EditorSnapSettings.gridSnapEnabled; } [Shortcut("Grid/Increase Grid Size", typeof(SceneView), KeyCode.RightBracket, ShortcutModifiers.Action)] internal static void IncreaseGridSize() { - EditorSnapSettingsData.instance.snapSettings.IncrementSnapMultiplier(); + GridSettings.sizeMultiplier++; + SceneView.RepaintAll(); + SnapSettingsWindow.RepaintAll(); } [Shortcut("Grid/Decrease Grid Size", typeof(SceneView), KeyCode.LeftBracket, ShortcutModifiers.Action)] internal static void DecreaseGridSize() { - EditorSnapSettingsData.instance.snapSettings.DecrementSnapMultiplier(); + GridSettings.sizeMultiplier--; + SceneView.RepaintAll(); + SnapSettingsWindow.RepaintAll(); } [Shortcut("Grid/Reset Grid", typeof(SceneView))] internal static void ResetGrid() { MenuNudgePerspectiveReset(); - ResetGridSize(); - } - - internal static void ResetGridSize() - { - EditorSnapSettings.ResetMultiplier(); + GridSettings.ResetGridSettings(); + SceneView.RepaintAll(); + SnapSettingsWindow.RepaintAll(); } [Shortcut("Grid/Nudge Grid Backward", typeof(SceneView), KeyCode.LeftBracket, ShortcutModifiers.Shift)] @@ -46,16 +47,18 @@ internal static void MenuNudgePerspectiveBackward() SceneViewGrid.Grid grid = sv.sceneViewGrids.activeGrid; SceneViewGrid.GridRenderAxis axis = sv.sceneViewGrids.gridAxis; Vector3 v = sv.sceneViewGrids.GetPivot(axis); + Vector3 gridSize = GridSettings.size; + switch (axis) { case SceneViewGrid.GridRenderAxis.X: - v -= Vector3.right * EditorSnapSettings.move.x; + v -= Vector3.right * gridSize.x; break; case SceneViewGrid.GridRenderAxis.Y: - v -= Vector3.up * EditorSnapSettings.move.y; + v -= Vector3.up * gridSize.y; break; case SceneViewGrid.GridRenderAxis.Z: - v -= Vector3.forward * EditorSnapSettings.move.z; + v -= Vector3.forward * gridSize.z; break; } @@ -70,16 +73,18 @@ internal static void MenuNudgePerspectiveForward() SceneViewGrid.Grid grid = sv.sceneViewGrids.activeGrid; SceneViewGrid.GridRenderAxis axis = sv.sceneViewGrids.gridAxis; Vector3 v = sv.sceneViewGrids.GetPivot(axis); + Vector3 gridSize = GridSettings.size; + switch (axis) { case SceneViewGrid.GridRenderAxis.X: - v += Vector3.right * EditorSnapSettings.move.x; + v += Vector3.right * gridSize.x; break; case SceneViewGrid.GridRenderAxis.Y: - v += Vector3.up * EditorSnapSettings.move.y; + v += Vector3.up * gridSize.y; break; case SceneViewGrid.GridRenderAxis.Z: - v += Vector3.forward * EditorSnapSettings.move.z; + v += Vector3.forward * gridSize.z; break; } @@ -90,7 +95,7 @@ internal static void MenuNudgePerspectiveForward() internal static void MenuNudgePerspectiveReset() { SceneView sv = SceneView.lastActiveSceneView; - sv.ResetGrid(); + sv.ResetGridPivot(); sv.Repaint(); } diff --git a/Editor/Mono/Grids/SnapSettings.cs b/Editor/Mono/Grids/SnapSettings.cs index 0ed023c51f..03633a72e2 100644 --- a/Editor/Mono/Grids/SnapSettings.cs +++ b/Editor/Mono/Grids/SnapSettings.cs @@ -11,13 +11,10 @@ namespace UnityEditor class SnapSettings { const int k_DefaultSnapMultiplier = 2048; - const float k_DefaultSnapValue = 1f; + const float k_DefaultSnapValue = .25f; const float k_DefaultRotation = 15f; const float k_DefaultScale = 1f; - - // If handle movement is aligned with grid coordinates, snap to grid instead of incremental from handle origin - [SerializeField] - bool m_PreferGrid = true; + const float k_MinSnapValue = 0f; [SerializeField] Vector3 m_SnapValue = new Vector3(k_DefaultSnapValue, k_DefaultSnapValue, k_DefaultSnapValue); @@ -34,15 +31,13 @@ class SnapSettings internal Vector3 snapValue { get { return SnapValueInUnityUnits(); } - set { m_SnapValue = value; snapMultiplier = new Vector3Int(k_DefaultSnapMultiplier, k_DefaultSnapMultiplier, k_DefaultSnapMultiplier); } - } - - // When moving a handle along a cardinal direction, handles will snap to the nearest grid point instead of - // increments from the handle origin. - internal bool preferGrid - { - get { return m_PreferGrid; } - set { m_PreferGrid = value; } + set + { + m_SnapValue.x = Mathf.Max(value.x, k_MinSnapValue); + m_SnapValue.y = Mathf.Max(value.y, k_MinSnapValue); + m_SnapValue.z = Mathf.Max(value.z, k_MinSnapValue); + snapMultiplier = new Vector3Int(k_DefaultSnapMultiplier, k_DefaultSnapMultiplier, k_DefaultSnapMultiplier); + } } internal Vector3Int snapMultiplier diff --git a/Editor/Mono/Grids/SnapSettingsWindow.cs b/Editor/Mono/Grids/SnapSettingsWindow.cs index 8d8991f202..5504a8f252 100644 --- a/Editor/Mono/Grids/SnapSettingsWindow.cs +++ b/Editor/Mono/Grids/SnapSettingsWindow.cs @@ -2,229 +2,210 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; using UnityEngine; namespace UnityEditor { - sealed class SnapSettingsWindow : PopupWindowContent + sealed class SnapSettingsWindow : EditorWindow { + internal const string k_WindowTitle = "Grid and Snap"; + const float k_LabelWidth = 80f; + + [SerializeField] + bool m_GridVisualFoldout = true; + + [SerializeField] + bool m_IncrementSnapFoldout = true; + + [SerializeField] + bool m_AlignToGridFoldout = true; + + [SerializeField] + Vector2 m_Scroll = Vector2.zero; + + [MenuItem("Edit/" + k_WindowTitle + " Settings...")] + static void Init() + { + GetWindow(false, k_WindowTitle, true); + } + static class Contents { - public static readonly GUIContent snapSettings = EditorGUIUtility.TrTextContent("Snap Settings"); - public static readonly GUIContent moveValue = EditorGUIUtility.TrTextContent("Move", "Snap value for the Move tool"); - public static readonly GUIContent moveX = EditorGUIUtility.TrTextContent("X", "X snap value"); - public static readonly GUIContent moveY = EditorGUIUtility.TrTextContent("Y", "Y snap value"); - public static readonly GUIContent moveZ = EditorGUIUtility.TrTextContent("Z", "Z snap value"); public static readonly GUIContent rotateValue = EditorGUIUtility.TrTextContent("Rotate", "Snap value for the Rotate tool"); public static readonly GUIContent scaleValue = EditorGUIUtility.TrTextContent("Scale", "Snap value for the Scale tool"); - public static readonly GUIContent pushToGrid = EditorGUIUtility.TrIconContent("SceneViewPushToGrid", "Snaps selected object to the grid"); - public static readonly GUIContent pushX = EditorGUIUtility.TrIconContent("SceneViewPushToGrid", "Snaps selected object to the grid on the X axis"); - public static readonly GUIContent pushY = EditorGUIUtility.TrIconContent("SceneViewPushToGrid", "Snaps selected object to the grid on the Y axis"); - public static readonly GUIContent pushZ = EditorGUIUtility.TrIconContent("SceneViewPushToGrid", "Snaps selected object to the grid on the Z axis"); public static readonly GUIContent reset = EditorGUIUtility.TrTextContent("Reset"); - public static readonly GUIContent preferGrid = EditorGUIUtility.TrTextContent("Prefer Grid", "When moving a handle along a cardinal direction, handles will snap to the nearest grid point instead of increments from the handle origin."); - } + public static readonly GUIContent gridVisuals = EditorGUIUtility.TrTextContent("Grid Visuals", "The step size between lines on each axis of the Scene view grid."); + public static readonly GUIContent incrementSnap = EditorGUIUtility.TrTextContent("Increment Snap", "Snap values relative to the origin of movement."); + public static readonly GUIContent alignSelectionToGridHeader = EditorGUIUtility.TrTextContent("Align Selection to Grid", "Snap selected objects to the nearest grid position."); - static class Styles - { - public static readonly GUIStyle separator = "sv_iconselector_sep"; - public static readonly GUIStyle header = EditorStyles.boldLabel; - public static readonly GUIStyle button = new GUIStyle(GUI.skin.button) + public static GUIContent[] moveContent = new GUIContent[] { - padding = new RectOffset(2, 2, 2, 2), + EditorGUIUtility.TrTextContent("Move", "Snap value for the Move tool."), + EditorGUIUtility.TrTextContent("Axis", "Snap value for the Move tool per-axis.") }; - } - const float k_LabelWidth = 80; - const float k_WindowWidth = 200 + k_LabelWidth; - readonly float k_WindowHeight = EditorGUIUtility.singleLineHeight * 10 + 5; - readonly float k_PushToGridIconWidth = EditorGUIUtility.singleLineHeight; - readonly float k_SettingsIconSize = EditorGUIUtility.singleLineHeight; + public static GUIContent[] gridSize = new GUIContent[] + { + EditorGUIUtility.TrTextContent("Grid Size", "The amount of space between X, Y, and Z grid lines."), + EditorGUIUtility.TrTextContent("Axis", "The amount of space between grid lines.") + }; - static bool snapValueLinked - { - get { return EditorPrefs.GetBool("SnapSettingsWindow.snapValueLinked", true); } - set { EditorPrefs.SetBool("SnapSettingsWindow.snapValueLinked", value); } + public static GUIContent[] pushToGrid = new GUIContent[] + { + EditorGUIUtility.TrTextContent("All Axes", "Snaps selected objects to the nearest grid point."), + EditorGUIUtility.TrTextContent("X", "Snaps selected object to the grid on the X axis."), + EditorGUIUtility.TrTextContent("Y", "Snaps selected object to the grid on the Y axis."), + EditorGUIUtility.TrTextContent("Z", "Snaps selected object to the grid on the Z axis.") + }; } - public override Vector2 GetWindowSize() + static bool gridValueLinked { - return new Vector2(k_WindowWidth, k_WindowHeight); + get { return EditorPrefs.GetBool("SnapSettingsWindow.gridValueLinked", true); } + set { EditorPrefs.SetBool("SnapSettingsWindow.gridValueLinked", value); } } - public override void OnGUI(Rect rect) + static bool snapValueLinked { - DrawTitleSettingsButton(rect); - Draw(); - - // Use mouse move so we get hover state correctly in the menu item rows - if (Event.current.type == EventType.MouseMove) - Event.current.Use(); - - // Escape closes the window - if (Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Escape) - { - editorWindow.Close(); - GUIUtility.ExitGUI(); - } + get { return EditorPrefs.GetBool("SnapSettingsWindow.snapValueLinked", true); } + set { EditorPrefs.SetBool("SnapSettingsWindow.snapValueLinked", value); } } - void Draw() + public void OnGUI() { - EditorGUIUtility.labelWidth = k_LabelWidth; + m_Scroll = EditorGUILayout.BeginScrollView(m_Scroll); - GUILayout.Label(Contents.snapSettings, Styles.header); + m_GridVisualFoldout = DoTitlebar(m_GridVisualFoldout, Contents.gridVisuals, GridSettings.ResetGridSettings); - EditorGUI.BeginChangeCheck(); + if (m_GridVisualFoldout) + DoGridVisualSettings(); - DrawMoveValuesFields(); + m_IncrementSnapFoldout = DoTitlebar(m_IncrementSnapFoldout, Contents.incrementSnap, EditorSnapSettings.ResetSnapSettings); - DoSeparator(); + if (m_IncrementSnapFoldout) + DoIncrementSnapSettings(); - EditorSnapSettings.rotate = EditorGUILayout.FloatField(Contents.rotateValue, EditorSnapSettings.rotate); + m_AlignToGridFoldout = DoTitlebar(m_AlignToGridFoldout, Contents.alignSelectionToGridHeader, null); - EditorSnapSettings.scale = EditorGUILayout.FloatField(Contents.scaleValue, EditorSnapSettings.scale); + if (m_AlignToGridFoldout) + DoSnapActions(); - if (EditorGUI.EndChangeCheck()) - EditorSnapSettings.Save(); - - EditorGUIUtility.labelWidth = 0; + EditorGUILayout.EndScrollView(); } - void DoSeparator() + static bool DoTitlebar(bool isOpen, GUIContent title, GenericMenu.MenuFunction reset) { - GUILayout.Space(EditorGUIUtility.standardVerticalSpacing); - GUILayout.Label(GUIContent.none, Styles.separator); - GUILayout.Space(EditorGUIUtility.standardVerticalSpacing); + EditorGUILayout.BeginHorizontal(); + isOpen = EditorGUILayout.FoldoutTitlebar(isOpen, title, true); + GUILayout.FlexibleSpace(); + if (reset != null && GUILayout.Button(EditorGUI.GUIContents.titleSettingsIcon, EditorStyles.iconButton)) + { + var menu = new GenericMenu(); + menu.AddItem(Contents.reset, false, () => + { + reset(); + SceneView.RepaintAll(); + }); + menu.ShowAsContext(); + } + + EditorGUILayout.EndHorizontal(); + return isOpen; } - void DrawMoveValuesFields() + void DoGridVisualSettings() { - EditorSnapSettings.preferGrid = EditorGUILayout.Toggle(Contents.preferGrid, EditorSnapSettings.preferGrid); + EditorGUIUtility.labelWidth = k_LabelWidth; - var v = EditorSnapSettings.move; + Vector3 grid = GridSettings.size; + bool linked = gridValueLinked; - using (new EditorGUILayout.HorizontalScope()) + EditorGUI.BeginChangeCheck(); + grid = DoLinkedVector3Field(Contents.gridSize, grid, ref linked); + if (EditorGUI.EndChangeCheck()) { - EditorGUI.BeginChangeCheck(); - var linked = snapValueLinked; - var newValue = DoFloatFieldWithLink(Contents.moveValue, v.x, ref linked); - if (EditorGUI.EndChangeCheck()) - { - snapValueLinked = linked; - EditorSnapSettings.move = new Vector3(newValue, newValue, newValue); - } - - if (GUILayout.Button(Contents.pushToGrid, Styles.button, GUILayout.Width(k_PushToGridIconWidth))) - { - SnapSelectionToGrid(); - } + gridValueLinked = linked; + GridSettings.size = grid; + SceneView.RepaintAll(); } + EditorGUIUtility.labelWidth = 0; + } - ++EditorGUI.indentLevel; - - using (new EditorGUILayout.HorizontalScope()) - { - using (new EditorGUI.DisabledScope(snapValueLinked)) - { - var value = EditorSnapSettings.move; - EditorGUI.BeginChangeCheck(); - using (new EditorGUI.DisabledScope(snapValueLinked)) - { - var newValue = EditorGUILayout.FloatField(Contents.moveX, value.x); - - if (EditorGUI.EndChangeCheck()) - { - EditorSnapSettings.move = new Vector3(newValue, value.y, value.z); - } - } - } + void DoIncrementSnapSettings() + { + EditorGUIUtility.labelWidth = k_LabelWidth; - if (GUILayout.Button(Contents.pushX, Styles.button, GUILayout.Width(k_PushToGridIconWidth))) - { - SnapSelectionToGrid(SnapAxis.X); - } - } + EditorGUI.BeginChangeCheck(); + var linked = snapValueLinked; + EditorSnapSettings.move = DoLinkedVector3Field(Contents.moveContent, EditorSnapSettings.move, ref linked); + snapValueLinked = linked; + EditorSnapSettings.rotate = EditorGUILayout.FloatField(Contents.rotateValue, EditorSnapSettings.rotate); + EditorSnapSettings.scale = EditorGUILayout.FloatField(Contents.scaleValue, EditorSnapSettings.scale); - using (new EditorGUILayout.HorizontalScope()) - { - using (new EditorGUI.DisabledScope(snapValueLinked)) - { - var value = EditorSnapSettings.move; - EditorGUI.BeginChangeCheck(); - var newValue = EditorGUILayout.FloatField(Contents.moveY, value.y); - if (EditorGUI.EndChangeCheck()) - { - EditorSnapSettings.move = new Vector3(value.x, newValue, value.z); - } - } + if (EditorGUI.EndChangeCheck()) + EditorSnapSettings.Save(); - if (GUILayout.Button(Contents.pushY, Styles.button, GUILayout.Width(k_PushToGridIconWidth))) - SnapSelectionToGrid(SnapAxis.Y); - } + EditorGUIUtility.labelWidth = 0f; + } - using (new EditorGUILayout.HorizontalScope()) + static void DoSnapActions() + { + int selected = -1; + selected = GUILayout.Toolbar(selected, Contents.pushToGrid); + if (selected > -1 && Selection.count > 0) { - using (new EditorGUI.DisabledScope(snapValueLinked)) + switch (selected) { - var value = EditorSnapSettings.move; - EditorGUI.BeginChangeCheck(); - var newValue = EditorGUILayout.FloatField(Contents.moveZ, value.z); - if (EditorGUI.EndChangeCheck()) - { - EditorSnapSettings.move = new Vector3(value.x, value.y, newValue); - } + case 0: + SnapSelectionToGrid(); + break; + case 1: + SnapSelectionToGrid(SnapAxis.X); + break; + case 2: + SnapSelectionToGrid(SnapAxis.Y); + break; + case 3: + SnapSelectionToGrid(SnapAxis.Z); + break; } - - if (GUILayout.Button(Contents.pushZ, Styles.button, GUILayout.Width(k_PushToGridIconWidth))) - SnapSelectionToGrid(SnapAxis.Z); } - --EditorGUI.indentLevel; } - public static bool IsMoveSnapValueMixed() + public static bool IsVector3FieldMixed(Vector3 v) { - if (snapValueLinked) - return false; - - return EditorSnapSettings.move.x != EditorSnapSettings.move.y || EditorSnapSettings.move.y != EditorSnapSettings.move.z; + return v.x != v.y || v.y != v.z; } - float DoFloatFieldWithLink(GUIContent content, float value, ref bool linkToggle) + static Vector3 DoLinkedVector3Field(GUIContent[] content, Vector3 value, ref bool linked) { EditorGUILayout.BeginHorizontal(); - EditorGUILayout.PrefixLabel(content); - float result; - using (new EditorGUI.DisabledScope(!linkToggle)) + using (new EditorGUI.DisabledScope(!linked)) { - EditorGUI.showMixedValue = IsMoveSnapValueMixed(); - result = EditorGUILayout.FloatField(value); + EditorGUI.showMixedValue = !linked && IsVector3FieldMixed(value); + EditorGUI.BeginChangeCheck(); + var result = EditorGUILayout.FloatField(content[0], value.x); + if (EditorGUI.EndChangeCheck()) + value = new Vector3(result, result, result); EditorGUI.showMixedValue = false; } - linkToggle = EditorGUILayout.Toggle(linkToggle, GUILayout.Width(15)); + linked = EditorGUILayout.Toggle(linked, GUILayout.Width(15)); EditorGUILayout.EndHorizontal(); - return result; - } - - void DrawTitleSettingsButton(Rect rect) - { - var settingsRect = rect; - settingsRect.x = settingsRect.xMax - k_SettingsIconSize; - settingsRect.y = 0; - settingsRect.width = settingsRect.height = k_SettingsIconSize; - - if (GUI.Button(settingsRect, EditorGUI.GUIContents.titleSettingsIcon, EditorStyles.iconButton)) - ShowContextMenu(); - } + using (new EditorGUI.DisabledScope(linked)) + { + ++EditorGUI.indentLevel; + var wide = EditorGUIUtility.wideMode; + EditorGUIUtility.wideMode = true; + value = EditorGUILayout.Vector3Field(content[1], value); + EditorGUIUtility.wideMode = wide; + --EditorGUI.indentLevel; + } - void ShowContextMenu() - { - GenericMenu menu = new GenericMenu(); - menu.AddItem(Contents.reset, false, EditorSnapSettings.ResetSnapSettings); - menu.ShowAsContext(); + return value; } internal static void SnapSelectionToGrid(SnapAxis axis = SnapAxis.All) @@ -236,5 +217,15 @@ internal static void SnapSelectionToGrid(SnapAxis axis = SnapAxis.All) Handles.SnapToGrid(selections, axis); } } + + internal static void RepaintAll() + { + EditorWindow[] openWindows = Resources.FindObjectsOfTypeAll(); + if (openWindows != null) + { + foreach (var item in openWindows) + item.Repaint(); + } + } } } diff --git a/Editor/Mono/HandleUtility.cs b/Editor/Mono/HandleUtility.cs index b04f6926b0..9788c8d7e9 100644 --- a/Editor/Mono/HandleUtility.cs +++ b/Editor/Mono/HandleUtility.cs @@ -334,7 +334,7 @@ public static Vector3 ClosestPointToDisc(Vector3 center, Vector3 normal, float r public static float DistanceToArc(Vector3 center, Vector3 normal, Vector3 from, float angle, float radius) { Handles.SetDiscSectionPoints(m_ArcPointsBuffer, center, normal, from, angle, radius); - return DistanceToPolyLineOnPlane(m_ArcPointsBuffer, center, normal); + return DistanceToPolyLine(m_ArcPointsBuffer); } // Get the nearest 3D point. @@ -347,64 +347,25 @@ public static Vector3 ClosestPointToArc(Vector3 center, Vector3 normal, Vector3 // Pixel distance from mouse pointer to a polyline. public static float DistanceToPolyLine(params Vector3[] points) { - Camera cam = Camera.current; - Matrix4x4 handlesMatrix = Handles.matrix; - float screenHeight = Screen.height; + Matrix4x4 handleMatrix = Handles.matrix; + CameraProjectionCache cam = new CameraProjectionCache(Camera.current, Screen.height); + Vector2 mouse = Event.current.mousePosition; - Vector2 point = Event.current.mousePosition; - Vector3 p1 = WorldToGUIPointWithDepth(points[0], cam, handlesMatrix, screenHeight); - Vector3 p2 = WorldToGUIPointWithDepth(points[1], cam, handlesMatrix, screenHeight); - float dist = DistanceToLineInternal(point, p1, p2); + Vector2 p1 = cam.WorldToGUIPoint(handleMatrix.MultiplyPoint3x4(points[0])); + Vector2 p2 = cam.WorldToGUIPoint(handleMatrix.MultiplyPoint3x4(points[1])); + float dist = DistanceToLineInternal(mouse, p1, p2); for (int i = 2; i < points.Length; i++) { p1 = p2; - p2 = WorldToGUIPointWithDepth(points[i], cam, handlesMatrix, screenHeight); - - float d = DistanceToLineInternal(point, p1, p2); + p2 = cam.WorldToGUIPoint(handleMatrix.MultiplyPoint3x4(points[i])); + float d = DistanceToLineInternal(mouse, p1, p2); if (d < dist) dist = d; } return dist; } - // Pixel distance from mouse pointer to a polyline on a 2D plane. - internal static float DistanceToPolyLineOnPlane(Vector3[] points, Vector3 center, Vector3 normal) - { - Plane p = new Plane(normal, center); - - Vector2 point = Event.current.mousePosition; - Ray r = GUIPointToWorldRay(point); - - float enter; - if (!p.Raycast(r, out enter)) - return -1; - - Vector3 intersect = r.GetPoint(enter); - - Vector3 p1 = points[0]; - Vector3 p2 = points[1]; - float dist = DistanceToLineInternal(intersect, p1, p2); - - Vector3 s1 = Vector3.zero, s2 = Vector3.zero; - - for (int i = 2; i < points.Length; i++) - { - p1 = p2; - p2 = points[i]; - - float d = DistanceToLineInternal(intersect, p1, p2); - if (d < dist) - { - dist = d; - s1 = p1; - s2 = p2; - } - } - - return DistanceToLineInternal(point, WorldToGUIPoint(s1), WorldToGUIPoint(s2)); - } - // Get the nearest 3D point. public static Vector3 ClosestPointToPolyLine(params Vector3[] vertices) { diff --git a/Editor/Mono/Handles.cs b/Editor/Mono/Handles.cs index 2afcfafcf2..012b06f960 100644 --- a/Editor/Mono/Handles.cs +++ b/Editor/Mono/Handles.cs @@ -332,11 +332,11 @@ public static void DrawWireCube(Vector3 center, Vector3 size) public static bool ShouldRenderGizmos() { - var preview = PreviewEditorWindow.GetRenderingPreview(); + var playModeView = PlayModeView.GetRenderingView(); SceneView sv = SceneView.currentDrawingSceneView; - if (preview != null) - return preview.IsShowingGizmos(); + if (playModeView != null) + return playModeView.IsShowingGizmos(); if (sv != null) return sv.drawGizmos; @@ -838,7 +838,7 @@ internal static void SetupIgnoreRaySnapObjects() // If snapping is active, return a new value rounded to the nearest increment of snap. public static float SnapValue(float value, float snap) { - if (EditorSnapSettings.active) + if (EditorSnapSettings.incrementalSnapActive) return Snapping.Snap(value, snap); return value; } @@ -846,7 +846,7 @@ public static float SnapValue(float value, float snap) // If snapping is active, return a new value rounded to the nearest increment of snap. public static Vector2 SnapValue(Vector2 value, Vector2 snap) { - if (EditorSnapSettings.active) + if (EditorSnapSettings.incrementalSnapActive) return Snapping.Snap(value, snap); return value; } @@ -854,7 +854,7 @@ public static Vector2 SnapValue(Vector2 value, Vector2 snap) // If snapping is active, return a new value rounded to the nearest increment of snap. public static Vector3 SnapValue(Vector3 value, Vector3 snap) { - if (EditorSnapSettings.active) + if (EditorSnapSettings.incrementalSnapActive) return Snapping.Snap(value, snap); return value; } @@ -867,7 +867,7 @@ public static void SnapToGrid(Transform[] transforms, SnapAxis axis = SnapAxis.A foreach (var t in transforms) { if (t != null) - t.position = Snapping.Snap(t.position, Vector3.Scale(EditorSnapSettings.move, new SnapAxisFilter(axis))); + t.position = Snapping.Snap(t.position, Vector3.Scale(GridSettings.size, new SnapAxisFilter(axis))); } } } @@ -1311,10 +1311,10 @@ internal static Rect GetCameraRect(Rect position) return cameraRect; } - // Get the size of the main preview window + // Get the size of the main playModeView window public static Vector2 GetMainGameViewSize() { - return PreviewEditorWindow.GetMainPreviewTargetSize(); + return PlayModeView.GetMainPlayModeViewTargetSize(); } // Clears the camera. diff --git a/Editor/Mono/HostView.cs b/Editor/Mono/HostView.cs index a8bb09f15b..d831c967e8 100644 --- a/Editor/Mono/HostView.cs +++ b/Editor/Mono/HostView.cs @@ -9,17 +9,32 @@ using UnityEditor.UIElements.Debugger; using UnityEngine.UIElements; using System.Linq; +using UnityEditor.Profiling; using UnityEditor.ShortcutManagement; namespace UnityEditor { internal class HostView : GUIView { + static class Styles + { + public static readonly GUIStyle background = new GUIStyle("hostview"); + public static readonly GUIStyle overlay = "dockareaoverlay"; + public static readonly GUIStyle paneOptions = "PaneOptions"; + public static readonly GUIStyle tabWindowBackground = "TabWindowBackground"; + + static Styles() + { + // Fix annoying GUILayout issue: When using GUILayout in Utility windows there + // was always padded 10 px at the top! Todo: Fix this in EditorResources + background.padding.top = 0; + } + } + static string kPlayModeDarkenKey = "Playmode tint"; internal static PrefColor kPlayModeDarken = new PrefColor(kPlayModeDarkenKey, .8f, .8f, .8f, 1); internal static event Action actualViewChanged; - internal GUIStyle background; [SerializeField] private EditorWindow m_ActualView; [System.NonSerialized] protected readonly RectOffset m_BorderSize = new RectOffset(); @@ -28,6 +43,7 @@ internal class HostView : GUIView Color m_PlayModeDarkenColor; private IMGUIContainer m_NotificationContainer; + private bool m_HasExtraDockAreaButton = false; internal EditorWindow actualView { @@ -41,10 +57,14 @@ internal void SetActualViewInternal(EditorWindow value, bool sendEvents) return; DeregisterSelectedPane(clearActualView: true, sendEvents: true); m_ActualView = value; + m_ActualViewName = null; + name = GetViewName(); SetActualViewName(name); RegisterSelectedPane(sendEvents); actualViewChanged?.Invoke(this); + + m_HasExtraDockAreaButton = GetPaneMethod("ShowButton", m_ActualView) != null; } internal void ResetActiveView() @@ -105,7 +125,6 @@ protected override void OnEnable() EditorApplication.playModeStateChanged += PlayModeStateChangedCallback; EditorPrefs.onValueWasUpdated += PlayModeTintColorChangedCallback; base.OnEnable(); - background = null; m_NotificationContainer = new IMGUIContainer(); m_NotificationContainer.StretchToParentSize(); m_NotificationContainer.pickingMode = PickingMode.Ignore; @@ -122,19 +141,11 @@ protected override void OnDisable() protected override void OldOnGUI() { - ClearBackground(); - // Call reset GUI state as first thing so GUI.color is correct when drawing window decoration. EditorGUIUtility.ResetGUIState(); DoWindowDecorationStart(); - if (background == null) - { - background = "hostview"; - // Fix annoying GUILayout issue: When using guilayout in Utility windows there was always padded 10 px at the top! Todo: Fix this in EditorResources - background.padding.top = 0; - } - using (new GUILayout.VerticalScope(background)) + using (new GUILayout.VerticalScope(Styles.background)) { if (actualView) actualView.m_Pos = screenPosition; @@ -306,35 +317,36 @@ static public void BeginOffsetArea(Rect screenRect, GUIContent content, GUIStyle { case EventType.Layout: g.resetCoords = false; - g.minWidth = g.maxWidth = screenRect.width + 1; - g.minHeight = g.maxHeight = screenRect.height + 2; - g.rect = Rect.MinMaxRect(-1, 0, g.rect.xMax, g.rect.yMax - 10); + g.minWidth = g.maxWidth = screenRect.width; + g.minHeight = g.maxHeight = screenRect.height; + g.rect = Rect.MinMaxRect(0, 0, g.rect.xMax, g.rect.yMax); break; } GUI.BeginGroup(screenRect, content, style); } - static class HostViewStyles + private string m_ActualViewName; + private string GetActualViewName() { - public static readonly GUIStyle overlay = "dockareaoverlay"; + if (m_ActualViewName != null) + return m_ActualViewName; + m_ActualViewName = actualView != null ? actualView.GetType().Name : GetType().Name; + return m_ActualViewName; } public void InvokeOnGUI(Rect onGUIPosition, Rect viewRect) { DoWindowDecorationStart(); - BeginOffsetArea(viewRect, GUIContent.none, "TabWindowBackground"); + BeginOffsetArea(viewRect, GUIContent.none, Styles.tabWindowBackground); EditorGUIUtility.ResetGUIState(); bool isExitGUIException = false; try { - var viewName = actualView != null ? actualView.GetType().Name : GetType().Name; - using (new PerformanceTracker(viewName + ".OnGUI." + Event.current.type)) - { + using (new EditorPerformanceTracker($"{GetActualViewName()}.OnGUI.{Event.current.type}")) Invoke("OnGUI"); - } } catch (TargetInvocationException e) { @@ -356,7 +368,7 @@ public void InvokeOnGUI(Rect onGUIPosition, Rect viewRect) DoWindowDecorationEnd(); if (Event.current != null && Event.current.type == EventType.Repaint) - HostViewStyles.overlay.Draw(onGUIPosition, GUIContent.none, 0); + Styles.overlay.Draw(onGUIPosition, GUIContent.none, 0); } } } @@ -386,15 +398,22 @@ protected void RegisterSelectedPane(bool sendEvents) panel.name = m_ActualView.GetType().Name; if (GetPaneMethod("Update") != null) + { + EditorApplication.update -= SendUpdate; EditorApplication.update += SendUpdate; + } if (GetPaneMethod("ModifierKeysChanged") != null) + { + EditorApplication.modifierKeysChanged -= SendModKeysChanged; EditorApplication.modifierKeysChanged += SendModKeysChanged; + } m_ActualView.MakeParentsSettingsMatchMe(); if (m_ActualView.m_FadeoutTime != 0) { + EditorApplication.update -= m_ActualView.CheckForWindowRepaint; EditorApplication.update += m_ActualView.CheckForWindowRepaint; } @@ -490,15 +509,13 @@ void SendModKeysChanged() protected bool HasExtraDockAreaButton() { - MethodInfo mi = GetPaneMethod("ShowButton", m_ActualView); - return mi != null; + return m_HasExtraDockAreaButton; } protected void ShowGenericMenu(float leftOffset, float topOffset) { - GUIStyle gs = "PaneOptions"; - Rect paneMenu = new Rect(leftOffset, topOffset, gs.fixedWidth, gs.fixedHeight); - if (EditorGUI.DropdownButton(paneMenu, GUIContent.none, FocusType.Passive, "PaneOptions")) + Rect paneMenu = new Rect(leftOffset, topOffset, Styles.paneOptions.fixedWidth, Styles.paneOptions.fixedHeight); + if (EditorGUI.DropdownButton(paneMenu, GUIContent.none, FocusType.Passive, Styles.paneOptions)) PopupGenericMenu(m_ActualView, paneMenu); // Give panes an option of showing a small button next to the generic menu (used for inspector lock icon @@ -513,7 +530,7 @@ protected void ShowGenericMenu(float leftOffset, float topOffset) // Developer-mode render doc button to enable capturing any HostView content/panels if (Unsupported.IsDeveloperMode() && UnityEditorInternal.RenderDoc.IsLoaded() && UnityEditorInternal.RenderDoc.IsSupported()) { - Rect renderDocRect = new Rect(leftOffset - (mi == null ? 16 : 32), Mathf.Floor(background.margin.top + 4), 17, 16); + Rect renderDocRect = new Rect(leftOffset - (mi == null ? 16 : 32), Mathf.Floor(Styles.background.margin.top + 4), 17, 16); RenderDocCaptureButton(renderDocRect); } } @@ -653,17 +670,6 @@ static void PropagateDirtyRepaint(VisualElement ve) } } - protected void ClearBackground() - { - if (Event.current.type != EventType.Repaint) - return; - EditorWindow view = actualView; - GL.Clear(true, true, EditorApplication.isPlayingOrWillChangePlaymode && !view.dontClearBackground ? - EditorGUIUtility.kViewBackgroundColor * kPlayModeDarken.Color : - EditorGUIUtility.kViewBackgroundColor); - backgroundValid = true; - } - protected void AddUIElementsDebuggerToMenu(GenericMenu menu) { var itemContent = UIElementsDebugger.WindowName; diff --git a/Editor/Mono/ImportSettings/AnimationClipInfoProperties.cs b/Editor/Mono/ImportSettings/AnimationClipInfoProperties.cs index 887621b790..7c93e6f62b 100644 --- a/Editor/Mono/ImportSettings/AnimationClipInfoProperties.cs +++ b/Editor/Mono/ImportSettings/AnimationClipInfoProperties.cs @@ -2,14 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License -using System; -using System.IO; -using UnityEditor.Animations; using UnityEngine; -using UnityEditor; -using UnityEditorInternal; -using System.Collections.Generic; -using Object = UnityEngine.Object; namespace UnityEditor { @@ -17,50 +10,102 @@ internal class AnimationClipInfoProperties { SerializedProperty m_Property; - Dictionary m_CachedProp = new Dictionary(); - SerializedProperty Get(string property) + public AnimationClipInfoProperties(SerializedProperty prop) { - SerializedProperty prop; - if (!m_CachedProp.TryGetValue(property, out prop)) - { - prop = m_Property.FindPropertyRelative(property); - m_CachedProp.Add(property, prop); - } - return prop; + m_Property = prop; + Editor.AssignCachedProperties(this, prop); } - public AnimationClipInfoProperties(SerializedProperty prop) { m_Property = prop; } - - public string name { get { return Get("name").stringValue; } set { Get("name").stringValue = value; } } - public string takeName { get { return Get("takeName").stringValue; } set { Get("takeName").stringValue = value; } } - public long internalID { get { return Get("internalID").longValue; } set { Get("internalID").longValue = value; } } - public float firstFrame { get { return Get("firstFrame").floatValue; } set { Get("firstFrame").floatValue = value; } } - public float lastFrame { get { return Get("lastFrame").floatValue; } set { Get("lastFrame").floatValue = value; } } - public int wrapMode { get { return Get("wrapMode").intValue; } set { Get("wrapMode").intValue = value; } } - public bool loop { get { return Get("loop").boolValue; } set { Get("loop").boolValue = value; } } +#pragma warning disable 0649 + [CacheProperty("name")] + SerializedProperty m_Name; + [CacheProperty("takeName")] + SerializedProperty m_TakeName; + [CacheProperty("internalID")] + SerializedProperty m_InternalId; + [CacheProperty("firstFrame")] + SerializedProperty m_FirstFrame; + [CacheProperty("lastFrame")] + SerializedProperty m_LastFrame; + [CacheProperty("wrapMode")] + SerializedProperty m_WrapMode; + [CacheProperty("loop")] + SerializedProperty m_Loop; + [CacheProperty("orientationOffsetY")] + SerializedProperty m_OrientationOffsetY; + [CacheProperty("level")] + SerializedProperty m_Level; + [CacheProperty("cycleOffset")] + SerializedProperty m_CycleOffset; + [CacheProperty("additiveReferencePoseFrame")] + SerializedProperty m_AdditiveReferencePoseFrame; + [CacheProperty("hasAdditiveReferencePose")] + SerializedProperty m_HasAdditiveReferencePose; + [CacheProperty("loopTime")] + SerializedProperty m_LoopTime; + [CacheProperty("loopBlend")] + SerializedProperty m_LoopBlend; + [CacheProperty("loopBlendOrientation")] + SerializedProperty m_LoopBlendOrientation; + [CacheProperty("loopBlendPositionY")] + SerializedProperty m_LoopBlendPositionY; + [CacheProperty("loopBlendPositionXZ")] + SerializedProperty m_LoopBlendPositionXZ; + [CacheProperty("keepOriginalOrientation")] + SerializedProperty m_KeepOriginalOrientation; + [CacheProperty("keepOriginalPositionY")] + SerializedProperty m_KeepOriginalPositionY; + [CacheProperty("keepOriginalPositionXZ")] + SerializedProperty m_KeepOriginalPositionXZ; + [CacheProperty("heightFromFeet")] + SerializedProperty m_HeightFromFeet; + [CacheProperty("mirror")] + SerializedProperty m_Mirror; + [CacheProperty("maskType")] + SerializedProperty m_MaskType; + [CacheProperty("maskSource")] + SerializedProperty m_MaskSource; + [CacheProperty("bodyMask")] + SerializedProperty m_BodyMask; + [CacheProperty("transformMask")] + SerializedProperty m_TransformMask; + [CacheProperty("curves")] + SerializedProperty m_Curves; + [CacheProperty("events")] + SerializedProperty m_Events; +#pragma warning restore 0649 + + public string name { get { return m_Name.stringValue; } set { m_Name.stringValue = value; } } + public string takeName { get { return m_TakeName.stringValue; } set { m_TakeName.stringValue = value; } } + public long internalID { get { return m_InternalId.longValue; } set { m_InternalId.longValue = value; } } + public float firstFrame { get { return m_FirstFrame.floatValue; } set { m_FirstFrame.floatValue = value; } } + public float lastFrame { get { return m_LastFrame.floatValue; } set { m_LastFrame.floatValue = value; } } + public int wrapMode { get { return m_WrapMode.intValue; } set { m_WrapMode.intValue = value; } } + public bool loop { get { return m_Loop.boolValue; } set { m_Loop.boolValue = value; } } // Mecanim animation properties - public float orientationOffsetY { get { return Get("orientationOffsetY").floatValue; } set { Get("orientationOffsetY").floatValue = value; } } - public float level { get { return Get("level").floatValue; } set { Get("level").floatValue = value; } } - public float cycleOffset { get { return Get("cycleOffset").floatValue; } set { Get("cycleOffset").floatValue = value; } } - public float additiveReferencePoseFrame { get { return Get("additiveReferencePoseFrame").floatValue; } set { Get("additiveReferencePoseFrame").floatValue = value; } } - public bool hasAdditiveReferencePose { get { return Get("hasAdditiveReferencePose").boolValue; } set { Get("hasAdditiveReferencePose").boolValue = value; } } - public bool loopTime { get { return Get("loopTime").boolValue; } set { Get("loopTime").boolValue = value; } } - public bool loopBlend { get { return Get("loopBlend").boolValue; } set { Get("loopBlend").boolValue = value; } } - public bool loopBlendOrientation { get { return Get("loopBlendOrientation").boolValue; } set { Get("loopBlendOrientation").boolValue = value; } } - public bool loopBlendPositionY { get { return Get("loopBlendPositionY").boolValue; } set { Get("loopBlendPositionY").boolValue = value; } } - public bool loopBlendPositionXZ { get { return Get("loopBlendPositionXZ").boolValue; } set { Get("loopBlendPositionXZ").boolValue = value; } } - public bool keepOriginalOrientation { get { return Get("keepOriginalOrientation").boolValue; } set { Get("keepOriginalOrientation").boolValue = value; } } - public bool keepOriginalPositionY { get { return Get("keepOriginalPositionY").boolValue; } set { Get("keepOriginalPositionY").boolValue = value; } } - public bool keepOriginalPositionXZ { get { return Get("keepOriginalPositionXZ").boolValue; } set { Get("keepOriginalPositionXZ").boolValue = value; } } - public bool heightFromFeet { get { return Get("heightFromFeet").boolValue; } set { Get("heightFromFeet").boolValue = value; } } - public bool mirror { get { return Get("mirror").boolValue; } set { Get("mirror").boolValue = value; } } - public ClipAnimationMaskType maskType { get { return (ClipAnimationMaskType)Get("maskType").intValue; } set { Get("maskType").intValue = (int)value; } } - public SerializedProperty maskTypeProperty { get { return Get("maskType"); } } - public AvatarMask maskSource { get { return Get("maskSource").objectReferenceValue as AvatarMask; } set { Get("maskSource").objectReferenceValue = value; } } - public SerializedProperty maskSourceProperty { get { return Get("maskSource"); } } - public SerializedProperty bodyMaskProperty { get { return Get("bodyMask"); } } - public SerializedProperty transformMaskProperty { get { return Get("transformMask"); } } + public float orientationOffsetY { get { return m_OrientationOffsetY.floatValue; } set { m_OrientationOffsetY.floatValue = value; } } + public float level { get { return m_Level.floatValue; } set { m_Level.floatValue = value; } } + public float cycleOffset { get { return m_CycleOffset.floatValue; } set { m_CycleOffset.floatValue = value; } } + public float additiveReferencePoseFrame { get { return m_AdditiveReferencePoseFrame.floatValue; } set { m_AdditiveReferencePoseFrame.floatValue = value; } } + public bool hasAdditiveReferencePose { get { return m_HasAdditiveReferencePose.boolValue; } set { m_HasAdditiveReferencePose.boolValue = value; } } + public bool loopTime { get { return m_LoopTime.boolValue; } set { m_LoopTime.boolValue = value; } } + public bool loopBlend { get { return m_LoopBlend.boolValue; } set { m_LoopBlend.boolValue = value; } } + public bool loopBlendOrientation { get { return m_LoopBlendOrientation.boolValue; } set { m_LoopBlendOrientation.boolValue = value; } } + public bool loopBlendPositionY { get { return m_LoopBlendPositionY.boolValue; } set { m_LoopBlendPositionY.boolValue = value; } } + public bool loopBlendPositionXZ { get { return m_LoopBlendPositionXZ.boolValue; } set { m_LoopBlendPositionXZ.boolValue = value; } } + public bool keepOriginalOrientation { get { return m_KeepOriginalOrientation.boolValue; } set { m_KeepOriginalOrientation.boolValue = value; } } + public bool keepOriginalPositionY { get { return m_KeepOriginalPositionY.boolValue; } set { m_KeepOriginalPositionY.boolValue = value; } } + public bool keepOriginalPositionXZ { get { return m_KeepOriginalPositionXZ.boolValue; } set { m_KeepOriginalPositionXZ.boolValue = value; } } + public bool heightFromFeet { get { return m_HeightFromFeet.boolValue; } set { m_HeightFromFeet.boolValue = value; } } + public bool mirror { get { return m_Mirror.boolValue; } set { m_Mirror.boolValue = value; } } + public ClipAnimationMaskType maskType { get { return (ClipAnimationMaskType)m_MaskType.intValue; } set { m_MaskType.intValue = (int)value; } } + public AvatarMask maskSource { get { return (AvatarMask)m_MaskSource.objectReferenceValue; } set { m_MaskSource.objectReferenceValue = value; } } + + public SerializedProperty maskTypeProperty => m_MaskType; + public SerializedProperty maskSourceProperty => m_MaskSource; + public SerializedProperty bodyMaskProperty => m_BodyMask; + public SerializedProperty transformMaskProperty => m_TransformMask; public void ApplyModifiedProperties() { @@ -87,7 +132,7 @@ public bool MaskNeedsUpdating() public void MaskFromClip(AvatarMask mask) { - SerializedProperty bodyMask = Get("bodyMask"); + SerializedProperty bodyMask = bodyMaskProperty; if (bodyMask != null && bodyMask.isArray) { @@ -123,7 +168,7 @@ public void MaskFromClip(AvatarMask mask) public void MaskToClip(AvatarMask mask) { - SerializedProperty bodyMask = Get("bodyMask"); + SerializedProperty bodyMask = bodyMaskProperty; if (bodyMask != null && bodyMask.isArray) { @@ -140,7 +185,7 @@ public void MaskToClip(AvatarMask mask) public void ClearCurves() { - SerializedProperty curves = Get("curves"); + SerializedProperty curves = m_Curves; if (curves != null && curves.isArray) { @@ -152,7 +197,7 @@ public int GetCurveCount() { int ret = 0; - SerializedProperty curves = Get("curves"); + SerializedProperty curves = m_Curves; if (curves != null && curves.isArray) { @@ -166,7 +211,7 @@ public SerializedProperty GetCurveProperty(int index) { SerializedProperty ret = null; - SerializedProperty curves = Get("curves"); + SerializedProperty curves = m_Curves; if (curves != null && curves.isArray) { @@ -180,7 +225,7 @@ public string GetCurveName(int index) { string ret = ""; - SerializedProperty curves = Get("curves"); + SerializedProperty curves = m_Curves; if (curves != null && curves.isArray) { @@ -192,7 +237,7 @@ public string GetCurveName(int index) public void SetCurveName(int index, string name) { - SerializedProperty curves = Get("curves"); + SerializedProperty curves = m_Curves; if (curves != null && curves.isArray) { @@ -226,7 +271,7 @@ public void SetCurve(int index, AnimationCurve curveValue) public void AddCurve() { - SerializedProperty curves = Get("curves"); + SerializedProperty curves = m_Curves; if (curves != null && curves.isArray) { @@ -241,7 +286,7 @@ public void AddCurve() public void RemoveCurve(int index) { - SerializedProperty curves = Get("curves"); + SerializedProperty curves = m_Curves; if (curves != null && curves.isArray) { @@ -252,7 +297,7 @@ public void RemoveCurve(int index) public AnimationEvent GetEvent(int index) { AnimationEvent evt = new AnimationEvent(); - SerializedProperty events = Get("events"); + SerializedProperty events = m_Events; if (events != null && events.isArray) { @@ -276,7 +321,7 @@ public AnimationEvent GetEvent(int index) public void SetEvent(int index, AnimationEvent animationEvent) { - SerializedProperty events = Get("events"); + SerializedProperty events = m_Events; if (events != null && events.isArray) { @@ -298,7 +343,7 @@ public void SetEvent(int index, AnimationEvent animationEvent) public void ClearEvents() { - SerializedProperty events = Get("events"); + SerializedProperty events = m_Events; if (events != null && events.isArray) { @@ -310,7 +355,7 @@ public int GetEventCount() { int ret = 0; - SerializedProperty curves = Get("events"); + SerializedProperty curves = m_Events; if (curves != null && curves.isArray) { @@ -322,7 +367,7 @@ public int GetEventCount() public void AddEvent(float time) { - SerializedProperty events = Get("events"); + SerializedProperty events = m_Events; if (events != null && events.isArray) { @@ -334,7 +379,7 @@ public void AddEvent(float time) public void RemoveEvent(int index) { - SerializedProperty events = Get("events"); + SerializedProperty events = m_Events; if (events != null && events.isArray) { @@ -344,7 +389,7 @@ public void RemoveEvent(int index) public void SetEvents(AnimationEvent[] newEvents) { - SerializedProperty events = Get("events"); + SerializedProperty events = m_Events; if (events != null && events.isArray) { @@ -361,7 +406,7 @@ public void SetEvents(AnimationEvent[] newEvents) public AnimationEvent[] GetEvents() { AnimationEvent[] ret = new AnimationEvent[GetEventCount()]; - SerializedProperty events = Get("events"); + SerializedProperty events = m_Events; if (events != null && events.isArray) { diff --git a/Editor/Mono/ImportSettings/DesktopPluginImporterExtension.cs b/Editor/Mono/ImportSettings/DesktopPluginImporterExtension.cs index b307c51f52..be1a4732b4 100644 --- a/Editor/Mono/ImportSettings/DesktopPluginImporterExtension.cs +++ b/Editor/Mono/ImportSettings/DesktopPluginImporterExtension.cs @@ -22,11 +22,6 @@ internal enum DesktopPluginCPUArchitecture internal class DesktopSingleCPUProperty : Property { - public DesktopSingleCPUProperty(GUIContent name, string platformName) - : this(name, platformName, DesktopPluginCPUArchitecture.AnyCPU) - { - } - public DesktopSingleCPUProperty(GUIContent name, string platformName, DesktopPluginCPUArchitecture architecture) : base(name, "CPU", architecture, platformName) { @@ -37,7 +32,11 @@ internal bool IsTargetEnabled(PluginImporterInspector inspector) PluginImporterInspector.Compatibility compatibililty = inspector.GetPlatformCompatibility(platformName); if (compatibililty == PluginImporterInspector.Compatibility.Mixed) throw new Exception("Unexpected mixed value for '" + inspector.importer.assetPath + "', platform: " + platformName); - return compatibililty == PluginImporterInspector.Compatibility.Compatible; + if (compatibililty != PluginImporterInspector.Compatibility.Compatible) + return false; + + var pluginCPU = value as DesktopPluginCPUArchitecture ? ?? DesktopPluginCPUArchitecture.None; + return pluginCPU == (DesktopPluginCPUArchitecture)defaultValue || pluginCPU == DesktopPluginCPUArchitecture.AnyCPU; } internal override void OnGUI(PluginImporterInspector inspector) @@ -49,7 +48,7 @@ internal override void OnGUI(PluginImporterInspector inspector) // This toggle controls two things: // * Is platform enabled/disabled? // * Platform CPU value - bool isTargetEnabled = EditorGUILayout.Toggle(name, IsTargetEnabled(inspector) && value.ToString() == defaultValue.ToString()); + bool isTargetEnabled = EditorGUILayout.Toggle(name, IsTargetEnabled(inspector)); if (EditorGUI.EndChangeCheck()) { value = isTargetEnabled ? defaultValue : DesktopPluginCPUArchitecture.None; @@ -76,12 +75,12 @@ public DesktopPluginImporterExtension() private Property[] GetProperties() { List properties = new List(); - m_WindowsX86 = new DesktopSingleCPUProperty(EditorGUIUtility.TrTextContent("x86"), BuildPipeline.GetBuildTargetName(BuildTarget.StandaloneWindows)); - m_WindowsX86_X64 = new DesktopSingleCPUProperty(EditorGUIUtility.TrTextContent("x86_x64"), BuildPipeline.GetBuildTargetName(BuildTarget.StandaloneWindows64)); + m_WindowsX86 = new DesktopSingleCPUProperty(EditorGUIUtility.TrTextContent("x86"), BuildPipeline.GetBuildTargetName(BuildTarget.StandaloneWindows), DesktopPluginCPUArchitecture.x86); + m_WindowsX86_X64 = new DesktopSingleCPUProperty(EditorGUIUtility.TrTextContent("x86_x64"), BuildPipeline.GetBuildTargetName(BuildTarget.StandaloneWindows64), DesktopPluginCPUArchitecture.x86_64); m_LinuxX86_X64 = new DesktopSingleCPUProperty(EditorGUIUtility.TrTextContent("x86_x64"), BuildPipeline.GetBuildTargetName(BuildTarget.StandaloneLinux64), DesktopPluginCPUArchitecture.x86_64); - m_OSX_X64 = new DesktopSingleCPUProperty(EditorGUIUtility.TrTextContent("x64"), BuildPipeline.GetBuildTargetName(BuildTarget.StandaloneOSX)); + m_OSX_X64 = new DesktopSingleCPUProperty(EditorGUIUtility.TrTextContent("x64"), BuildPipeline.GetBuildTargetName(BuildTarget.StandaloneOSX), DesktopPluginCPUArchitecture.x86_64); properties.Add(m_WindowsX86); properties.Add(m_WindowsX86_X64); @@ -172,10 +171,10 @@ public void ValidateSingleCPUTargets(PluginImporterInspector inspector) foreach (var target in singleCPUTargets) { - string value = target.IsTargetEnabled(inspector) ? target.defaultValue.ToString() : DesktopPluginCPUArchitecture.None.ToString(); + target.value = target.IsTargetEnabled(inspector) ? target.defaultValue : DesktopPluginCPUArchitecture.None; foreach (var importer in inspector.importers) { - importer.SetPlatformData(target.platformName, "CPU", value); + importer.SetPlatformData(target.platformName, "CPU", target.value.ToString()); } } @@ -224,9 +223,12 @@ public override string CalculateFinalPluginPath(string platformName, PluginImpor if (string.Compare(cpu, "None", true) == 0) return string.Empty; - if (!string.IsNullOrEmpty(cpu) && string.Compare(cpu, "AnyCPU", true) != 0) + if (pluginForWindows) { - return Path.Combine(cpu, Path.GetFileName(imp.assetPath)); + // Fix case 1185926: plugins for x86_64 are supposed to be copied to Plugins/x86_64 + // Plugins for x86 are supposed to be copied to Plugins/x86 + var cpuName = target == BuildTarget.StandaloneWindows ? nameof(DesktopPluginCPUArchitecture.x86) : nameof(DesktopPluginCPUArchitecture.x86_64); + return Path.Combine(cpuName, Path.GetFileName(imp.assetPath)); } // For files this will return filename, for directories, this will return last path component diff --git a/Editor/Mono/ImportSettings/SpeedTreeImporterInspector.cs b/Editor/Mono/ImportSettings/SpeedTreeImporterInspector.cs index bf2a469f15..17e19713fe 100644 --- a/Editor/Mono/ImportSettings/SpeedTreeImporterInspector.cs +++ b/Editor/Mono/ImportSettings/SpeedTreeImporterInspector.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; -using UnityEditor.VersionControl; using UnityEngine; namespace UnityEditor @@ -150,7 +149,7 @@ private void GenerateMaterials() bool doGenerate = true; if (paths.Count() > 0) - doGenerate = Provider.PromptAndCheckoutIfNeeded(paths.ToArray(), String.Format("Materials will be checked out in:\n{0}", String.Join("\n", matFolders.ToArray()))); + doGenerate = AssetDatabase.MakeEditable(paths.ToArray(), $"Materials will be checked out in:\n{string.Join("\n", matFolders.ToArray())}"); if (doGenerate) { diff --git a/Editor/Mono/ImportSettings/TextureImporterInspector.cs b/Editor/Mono/ImportSettings/TextureImporterInspector.cs index 277a7f420a..7192c18b0a 100644 --- a/Editor/Mono/ImportSettings/TextureImporterInspector.cs +++ b/Editor/Mono/ImportSettings/TextureImporterInspector.cs @@ -351,7 +351,7 @@ internal class Styles public readonly GUIContent showAdvanced = EditorGUIUtility.TrTextContent("Advanced", "Show advanced settings."); public readonly GUIContent psdRemoveMatte = EditorGUIUtility.TrTextContent("Remove Matte (PSD)", "Enable special processing for PSD that has transparency, as color pixels will be tweaked (blended with white color)."); - public readonly GUIContent psdRemoveMatteWarning = EditorGUIUtility.TrTextContent("If you have PSD with transparency, colors will be tweaked by blending them with white color. Matte removal refers to our attempts to undo that, and this is deprecated."); + public readonly GUIContent psdRemoveMatteInfo = EditorGUIUtility.TrTextContent("If you have PSD with transparency, colors will be tweaked by blending them with white color. Matte removal refers to our attempts to undo that."); public readonly GUIContent psdRemoveMatteURLButton = EditorGUIUtility.TrTextContent("How to handle PSD with alpha"); public readonly string psdRemoveMatteURL = "https://docs.unity3d.com/Manual/HOWTO-alphamaps.html"; @@ -383,12 +383,15 @@ public Styles() void ToggleFromInt(SerializedProperty property, GUIContent label) { + var content = EditorGUI.BeginProperty(EditorGUILayout.BeginHorizontal(), label, property); EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = property.hasMultipleDifferentValues; - int value = EditorGUILayout.Toggle(label, property.intValue > 0) ? 1 : 0; + int value = EditorGUILayout.Toggle(content, property.intValue > 0) ? 1 : 0; EditorGUI.showMixedValue = false; if (EditorGUI.EndChangeCheck()) property.intValue = value; + EditorGUILayout.EndHorizontal(); + EditorGUI.EndProperty(); } void EnumPopup(SerializedProperty property, System.Type type, GUIContent label) @@ -916,7 +919,7 @@ void AlphaHandlingGUI(TextureInspectorGUIElement guiElements) if (m_PSDRemoveMatte.boolValue) { GUILayout.BeginVertical(); - EditorGUILayout.HelpBox(s_Styles.psdRemoveMatteWarning.text, MessageType.Warning, true); + EditorGUILayout.HelpBox(s_Styles.psdRemoveMatteInfo.text, MessageType.Info, true); if (EditorGUILayout.LinkLabel(s_Styles.psdRemoveMatteURLButton)) Application.OpenURL(s_Styles.psdRemoveMatteURL); GUILayout.EndVertical(); @@ -1459,20 +1462,6 @@ public override bool HasModified() return false; } - public static void SelectMainAssets(Object[] targets) - { - ArrayList newSelection = new ArrayList(); - foreach (AssetImporter importer in targets) - { - Texture tex = AssetDatabase.LoadMainAssetAtPath(importer.assetPath) as Texture; - if (tex) - newSelection.Add(tex); - } - // The selection can be empty if for some reason the asset import failed. In this case, we don't want to cancel out the original selection so that user can correct its settings. - if (newSelection.Count > 0) - Selection.objects = newSelection.ToArray(typeof(Object)) as Object[]; - } - protected override void ResetValues() { base.ResetValues(); @@ -1482,11 +1471,6 @@ protected override void ResetValues() BuildTargetList(); System.Diagnostics.Debug.Assert(!HasModified(), "TextureImporter settings are marked as modified after calling Reset."); ApplySettingsToTexture(); - - // since some texture types (like Cubemaps) might add/remove new assets during import - // and main asset of these textures might change, - // update selection to include main assets (case 561340) - SelectMainAssets(targets); } protected override void Apply() diff --git a/Editor/Mono/ImportSettings/TrueTypeFontImporterInspector.cs b/Editor/Mono/ImportSettings/TrueTypeFontImporterInspector.cs index 6cc78a84db..dff8a5f1ba 100644 --- a/Editor/Mono/ImportSettings/TrueTypeFontImporterInspector.cs +++ b/Editor/Mono/ImportSettings/TrueTypeFontImporterInspector.cs @@ -69,13 +69,16 @@ protected override bool needsApplyRevert protected override void Apply() { - m_FallbackFontReferencesArraySize.intValue = m_FallbackFontReferences.Length; - SerializedProperty fontReferenceProp = m_FallbackFontReferencesArraySize.Copy(); - - for (int i = 0; i < m_FallbackFontReferences.Length; i++) + if (targets.Length == 1) { - fontReferenceProp.Next(false); - fontReferenceProp.objectReferenceValue = m_FallbackFontReferences[i]; + m_FallbackFontReferencesArraySize.intValue = m_FallbackFontReferences.Length; + SerializedProperty fontReferenceProp = m_FallbackFontReferencesArraySize.Copy(); + + for (int i = 0; i < m_FallbackFontReferences.Length; i++) + { + fontReferenceProp.Next(false); + fontReferenceProp.objectReferenceValue = m_FallbackFontReferences[i]; + } } base.Apply(); diff --git a/Editor/Mono/Inspector/AddComponent/AddComponentGUI.cs b/Editor/Mono/Inspector/AddComponent/AddComponentGUI.cs index 440a03a23f..5124044c3b 100644 --- a/Editor/Mono/Inspector/AddComponent/AddComponentGUI.cs +++ b/Editor/Mono/Inspector/AddComponent/AddComponentGUI.cs @@ -64,17 +64,5 @@ internal override void DrawItem(AdvancedDropdownItem item, string name, Texture2 EditorGUILayout.Space(); } - - internal override string DrawSearchFieldControl(string searchString) - { - float padding = 8f; - m_SearchRect = GUILayoutUtility.GetRect(0, 0); - m_SearchRect.x += padding; - m_SearchRect.y = 7; - m_SearchRect.width -= padding * 2; - m_SearchRect.height = 30; - var newSearch = EditorGUI.SearchField(m_SearchRect, searchString); - return newSearch; - } } } diff --git a/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownGUI.cs b/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownGUI.cs index 443f86572d..8677536051 100644 --- a/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownGUI.cs +++ b/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownGUI.cs @@ -4,6 +4,7 @@ using System; using System.Linq; +using UnityEditor.StyleSheets; using UnityEngine; using Event = UnityEngine.Event; @@ -19,6 +20,11 @@ private static class Styles public static GUIStyle lineSeparator = "DefaultLineSeparator"; public static GUIStyle rightArrow = "ArrowNavigationRight"; public static GUIStyle leftArrow = "ArrowNavigationLeft"; + public static GUIStyle searchFieldStyle = new GUIStyle(EditorStyles.toolbarSearchField) + { + margin = new RectOffset(5, 4, 4, 5) + }; + public static SVC searchBackgroundColor = new SVC("--theme-toolbar-background-color", Color.black); public static GUIContent checkMarkContent = new GUIContent("✔"); } @@ -104,9 +110,10 @@ internal void DrawHeader(AdvancedDropdownItem group, Action backButtonPressed, b { var content = GUIContent.Temp(group.displayName, group.icon); m_HeaderRect = GUILayoutUtility.GetRect(content, Styles.header, GUILayout.ExpandWidth(true)); + bool hovered = m_HeaderRect.Contains(Event.current.mousePosition); if (Event.current.type == EventType.Repaint) - Styles.header.Draw(m_HeaderRect, content, false, false, false, false); + Styles.header.Draw(m_HeaderRect, content, hovered, false, false, false); // Back button if (hasParent) @@ -119,7 +126,7 @@ internal void DrawHeader(AdvancedDropdownItem group, Action backButtonPressed, b Styles.leftArrow.fixedHeight); if (Event.current.type == EventType.Repaint) Styles.leftArrow.Draw(arrowRect, false, false, false, false); - if (Event.current.type == EventType.MouseDown && m_HeaderRect.Contains(Event.current.mousePosition)) + if (Event.current.type == EventType.MouseDown && hovered) { backButtonPressed(); Event.current.Use(); @@ -149,16 +156,18 @@ internal void DrawSearchField(bool isSearchFieldDisabled, string searchString, A internal virtual string DrawSearchFieldControl(string searchString) { - var paddingX = 8f; - var paddingY = 2f; - var rect = GUILayoutUtility.GetRect(0, 0, EditorStyles.toolbarSearchField); - rect.x += paddingX; - rect.y += paddingY + 1; // Add one for the border - rect.height += EditorStyles.toolbarSearchField.fixedHeight + paddingY * 3; - rect.width -= paddingX * 2; - m_SearchRect = rect; - searchString = EditorGUI.ToolbarSearchField(m_SearchRect, searchString, false); - return searchString; + const float kBorderWidth = 1f; + var controlRect = GUILayoutUtility.GetRect(0, 0, Styles.searchFieldStyle); + + controlRect.height = Styles.searchFieldStyle.fixedHeight; + controlRect.xMin += kBorderWidth; + controlRect.xMax -= kBorderWidth; + controlRect.yMin += kBorderWidth; + m_SearchRect = Styles.searchFieldStyle.margin.Add(controlRect); + EditorGUI.DrawRect(m_SearchRect, Styles.searchBackgroundColor); + var newSearch = EditorGUI.ToolbarSearchField(controlRect, searchString, false); + + return newSearch; } internal Rect GetAnimRect(Rect position, float anim) diff --git a/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownWindow.cs b/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownWindow.cs index abaefaffa2..4620a7c250 100644 --- a/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownWindow.cs +++ b/Editor/Mono/Inspector/AdvancedDropdown/AdvancedDropdownWindow.cs @@ -15,7 +15,7 @@ internal class AdvancedDropdownWindow : EditorWindow { private static class Styles { - public static GUIStyle background = "box"; + public static GUIStyle background = "DD Background"; public static GUIStyle previewHeader = new GUIStyle(EditorStyles.label); public static GUIStyle previewText = new GUIStyle(EditorStyles.wordWrappedLabel); diff --git a/Editor/Mono/Inspector/AnimationClipEditor.cs b/Editor/Mono/Inspector/AnimationClipEditor.cs index 083203825a..be58e798ce 100644 --- a/Editor/Mono/Inspector/AnimationClipEditor.cs +++ b/Editor/Mono/Inspector/AnimationClipEditor.cs @@ -33,7 +33,7 @@ internal static void EditWithImporter(AnimationClip clip) clipIndex = i; } - EditorPrefs.SetInt("ModelImporterClipEditor.ActiveClipIndex", clipIndex); + EditorPrefs.SetInt(ModelImporterClipEditor.ActiveClipIndex, clipIndex); } } @@ -744,10 +744,10 @@ public override void OnInspectorGUI() { using (new EditorGUI.DisabledScope(true)) { - GUILayout.Label(Styles.Length, EditorStyles.miniLabel, GUILayout.Width(50 - 4)); - GUILayout.Label(GetClipLength().ToString("0.000", CultureInfo.InvariantCulture.NumberFormat), EditorStyles.miniLabel); + GUILayout.Label(Styles.Length, EditorStyles.label, GUILayout.Width(50 - 4)); + GUILayout.Label(GetClipLength().ToString("0.000", CultureInfo.InvariantCulture.NumberFormat), EditorStyles.label); GUILayout.FlexibleSpace(); - GUILayout.Label(m_Clip.frameRate + " FPS", EditorStyles.miniLabel); + GUILayout.Label(m_Clip.frameRate + " FPS", EditorStyles.label); } } EditorGUILayout.EndHorizontal(); diff --git a/Editor/Mono/Inspector/AnimatorInspector.cs b/Editor/Mono/Inspector/AnimatorInspector.cs index bc2b6c0c0f..bbf04afd51 100644 --- a/Editor/Mono/Inspector/AnimatorInspector.cs +++ b/Editor/Mono/Inspector/AnimatorInspector.cs @@ -106,15 +106,6 @@ public override void OnInspectorGUI() controllers.Add(animator.runtimeAnimatorController); } serializedObject.ApplyModifiedProperties(); - - //Force rebind if the controller has changed. - for (int i = 0; i < targets.Length; i++) - { - if (controllers[i] != controller) - { - (targets[i] as Animator).Rebind(); - } - } AnimationWindowUtility.ControllerChanged(); } diff --git a/Editor/Mono/Inspector/AssemblyDefinitionImporterInspector.cs b/Editor/Mono/Inspector/AssemblyDefinitionImporterInspector.cs index f963fd1cf1..b0417ff8c6 100644 --- a/Editor/Mono/Inspector/AssemblyDefinitionImporterInspector.cs +++ b/Editor/Mono/Inspector/AssemblyDefinitionImporterInspector.cs @@ -2,15 +2,15 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License -using UnityEngine; +using System; using System.Collections.Generic; +using System.IO; using System.Linq; -using UnityEditorInternal; -using UnityEditor.Scripting.ScriptCompilation; -using UnityEditor.Experimental.AssetImporters; using UnityEditor.Compilation; -using System; -using System.IO; +using UnityEditor.Experimental.AssetImporters; +using UnityEditor.Scripting.ScriptCompilation; +using UnityEditorInternal; +using UnityEngine; using AssemblyFlags = UnityEditor.Scripting.ScriptCompilation.AssemblyFlags; using Object = UnityEngine.Object; @@ -41,6 +41,41 @@ internal class Styles public static readonly GUIContent loadError = EditorGUIUtility.TrTextContent("Load error"); public static readonly GUIContent expressionOutcome = EditorGUIUtility.TrTextContent("Expression outcome", "Shows the mathematical equation that your Expression represents."); public static readonly GUIContent noEngineReferences = EditorGUIUtility.TrTextContent("No Engine References", "When enabled, references to UnityEngine/UnityEditor will not be added when compiling this assembly."); + + public const int kValidityIconHeight = 16; + public const int kValidityIconWidth = 16; + static readonly Texture2D kValidDefineConstraint = EditorGUIUtility.FindTexture("Valid"); + static readonly Texture2D kValidDefineConstraintHighDpi = EditorGUIUtility.FindTexture("Valid@2x"); + static readonly Texture2D kInvalidDefineConstraint = EditorGUIUtility.FindTexture("Invalid"); + static readonly Texture2D kInvalidDefineConstraintHighDpi = EditorGUIUtility.FindTexture("Invalid@2x"); + + public static Texture2D validDefineConstraint => EditorGUIUtility.pixelsPerPoint > 1 ? kValidDefineConstraintHighDpi : kValidDefineConstraint; + public static Texture2D invalidDefineConstraint => EditorGUIUtility.pixelsPerPoint > 1 ? kInvalidDefineConstraintHighDpi : kInvalidDefineConstraint; + + static string kCompatibleTextTitle = L10n.Tr("Define constraints are compatible."); + static string kIncompatibleTextTitle = L10n.Tr("One or more define constraints are invalid or incompatible."); + + public static string GetTitleTooltipFromDefineConstraintCompatibility(bool compatible) + { + return compatible ? kCompatibleTextTitle : kIncompatibleTextTitle; + } + + static string kCompatibleTextIndividual = L10n.Tr("Define constraint is compatible."); + static string kIncompatibleTextIndividual = L10n.Tr("Define constraint is incompatible."); + static string kInvalidTextIndividual = L10n.Tr("Define constraint is invalid."); + + public static string GetIndividualTooltipFromDefineConstraintStatus(DefineConstraintsHelper.DefineConstraintStatus status) + { + switch (status) + { + case DefineConstraintsHelper.DefineConstraintStatus.Compatible: + return Styles.kCompatibleTextIndividual; + case DefineConstraintsHelper.DefineConstraintStatus.Incompatible: + return Styles.kIncompatibleTextIndividual; + default: + return Styles.kInvalidTextIndividual; + } + } } GUIStyle m_TextStyle; @@ -51,14 +86,6 @@ internal class DefineConstraint public string name; } - [Serializable] - internal class VersionDefine - { - public string name; - public string expression; - public string define; - } - [Serializable] internal class AssemblyDefinitionReference { @@ -92,10 +119,7 @@ internal class PrecompiledReference class AssemblyDefinitionState : ScriptableObject { - public string path - { - get { return AssetDatabase.GetAssetPath(asset); } - } + public string path => AssetDatabase.GetAssetPath(asset); public string assemblyName; public AssemblyDefinitionAsset asset; @@ -113,6 +137,7 @@ public string path } SemVersionRangesFactory m_SemVersionRanges; + ReorderableList m_ReferencesList; ReorderableList m_PrecompiledReferencesList; ReorderableList m_VersionDefineList; @@ -127,17 +152,14 @@ public string path SerializedProperty m_PlatformCompatibility; SerializedProperty m_NoEngineReferences; - string[] m_Defines; Exception initializeException; - public override bool showImportedObject { get { return false; } } + public override bool showImportedObject => false; public override void OnEnable() { base.OnEnable(); m_AssemblyName = extraDataSerializedObject.FindProperty("assemblyName"); - m_Defines = CompilationPipeline.GetDefinesFromAssemblyName(m_AssemblyName.stringValue); - InitializeReorderableLists(); m_SemVersionRanges = new SemVersionRangesFactory(); m_AllowUnsafeCode = extraDataSerializedObject.FindProperty("allowUnsafeCode"); @@ -147,6 +169,21 @@ public override void OnEnable() m_CompatibleWithAnyPlatform = extraDataSerializedObject.FindProperty("compatibleWithAnyPlatform"); m_PlatformCompatibility = extraDataSerializedObject.FindProperty("platformCompatibility"); m_NoEngineReferences = extraDataSerializedObject.FindProperty("noEngineReferences"); + + AssemblyReloadEvents.afterAssemblyReload += AfterAssemblyReload; + } + + public override void OnDisable() + { + base.OnDisable(); + AssemblyReloadEvents.afterAssemblyReload -= AfterAssemblyReload; + } + + void AfterAssemblyReload() + { + var selector = (ObjectSelector)WindowLayout.FindEditorWindowOfType(typeof(ObjectSelector)); + if (selector != null && selector.hasFocus) + selector.Close(); } public override void OnInspectorGUI() @@ -160,7 +197,7 @@ public override void OnInspectorGUI() extraDataSerializedObject.Update(); - var platforms = Compilation.CompilationPipeline.GetAssemblyDefinitionPlatforms(); + var platforms = CompilationPipeline.GetAssemblyDefinitionPlatforms(); using (new EditorGUI.DisabledScope(false)) { if (targets.Length > 1) @@ -186,7 +223,41 @@ public override void OnInspectorGUI() EditorGUILayout.EndVertical(); GUILayout.Space(10f); + EditorGUILayout.BeginHorizontal(); GUILayout.Label(Styles.defineConstraints, EditorStyles.boldLabel); + GUILayout.FlexibleSpace(); + EditorGUILayout.EndHorizontal(); + + if (m_DefineConstraints.serializedProperty.arraySize > 0) + { + var defineConstraintsCompatible = true; + + var defines = CompilationPipeline.GetDefinesFromAssemblyName(m_AssemblyName.stringValue); + + if (defines != null) + { + for (var i = 0; i < m_DefineConstraints.serializedProperty.arraySize && defineConstraintsCompatible; ++i) + { + var defineConstraint = m_DefineConstraints.serializedProperty.GetArrayElementAtIndex(i).FindPropertyRelative("name").stringValue; + + if (DefineConstraintsHelper.GetDefineConstraintCompatibility(defines, defineConstraint) != DefineConstraintsHelper.DefineConstraintStatus.Compatible) + { + defineConstraintsCompatible = false; + } + } + + var constraintValidityRect = new Rect(GUILayoutUtility.GetLastRect()); + constraintValidityRect.x += constraintValidityRect.width - 23; + var image = defineConstraintsCompatible ? Styles.validDefineConstraint : Styles.invalidDefineConstraint; + var tooltip = Styles.GetTitleTooltipFromDefineConstraintCompatibility(defineConstraintsCompatible); + var content = new GUIContent(image, tooltip); + + constraintValidityRect.width = Styles.kValidityIconWidth; + constraintValidityRect.height = Styles.kValidityIconHeight; + EditorGUI.LabelField(constraintValidityRect, content); + } + } + m_DefineConstraints.DoLayoutList(); GUILayout.Label(Styles.references, EditorStyles.boldLabel); @@ -215,7 +286,6 @@ public override void OnInspectorGUI() } } - GUILayout.Label(Styles.platforms, EditorStyles.boldLabel); EditorGUILayout.BeginVertical(GUI.skin.box); @@ -233,6 +303,7 @@ public override void OnInspectorGUI() { InversePlatformCompatibility(state); } + extraDataSerializedObject.Update(); } } @@ -254,6 +325,7 @@ public override void OnInspectorGUI() { property = m_PlatformCompatibility.GetArrayElementAtIndex(i); } + EditorGUILayout.PropertyField(property, new GUIContent(platforms[i].DisplayName)); } @@ -304,6 +376,7 @@ public override void OnInspectorGUI() protected override void Apply() { base.Apply(); + // Do not write back to the asset if no asset can be found. if (assetTarget != null) SaveAndUpdateAssemblyDefinitionStates(extraDataTargets.Cast().ToArray()); @@ -311,7 +384,7 @@ protected override void Apply() static void InversePlatformCompatibility(AssemblyDefinitionState state) { - var platforms = Compilation.CompilationPipeline.GetAssemblyDefinitionPlatforms(); + var platforms = CompilationPipeline.GetAssemblyDefinitionPlatforms(); for (int i = 0; i < platforms.Length; ++i) state.platformCompatibility[i] = !state.platformCompatibility[i]; } @@ -332,7 +405,7 @@ protected override void InitializeExtraDataInstance(Object extraTarget, int targ { try { - LoadAssemblyDefintionState((AssemblyDefinitionState)extraTarget, ((AssetImporter)targets[targetIndex]).assetPath); + LoadAssemblyDefinitionState((AssemblyDefinitionState)extraTarget, ((AssetImporter)targets[targetIndex]).assetPath); initializeException = null; } catch (Exception e) @@ -378,9 +451,7 @@ private void DrawDefineConstraintListElement(Rect rect, int index, bool isactive rect.height -= EditorGUIUtility.standardVerticalSpacing; - var textFieldRect = new Rect(rect.x, rect.y + 1, rect.width - ReorderableList.Defaults.dragHandleWidth, rect.height); - - var validRect = new Rect(rect.width + ReorderableList.Defaults.dragHandleWidth + 1, rect.y + 1, ReorderableList.Defaults.dragHandleWidth, rect.height); + var textFieldRect = new Rect(rect.x, rect.y + 1, rect.width - ReorderableList.Defaults.dragHandleWidth + 1, rect.height); string noValue = L10n.Tr("(Missing)"); @@ -390,11 +461,17 @@ private void DrawDefineConstraintListElement(Rect rect, int index, bool isactive var textFieldValue = EditorGUI.TextField(textFieldRect, mixed ? L10n.Tr("(Multiple Values)") : label); EditorGUI.showMixedValue = false; - if (m_Defines != null) + var defines = CompilationPipeline.GetDefinesFromAssemblyName(m_AssemblyName.stringValue); + + if (defines != null) { - EditorGUI.BeginDisabled(true); - EditorGUI.Toggle(validRect, DefineConstraintsHelper.IsDefineConstraintValid(m_Defines, defineConstraint.stringValue)); - EditorGUI.EndDisabled(); + var status = DefineConstraintsHelper.GetDefineConstraintCompatibility(defines, defineConstraint.stringValue); + var image = status == DefineConstraintsHelper.DefineConstraintStatus.Compatible ? Styles.validDefineConstraint : Styles.invalidDefineConstraint; + + var content = new GUIContent(image, Styles.GetIndividualTooltipFromDefineConstraintStatus(status)); + + var constraintValidityRect = new Rect(rect.width + ReorderableList.Defaults.dragHandleWidth + ReorderableList.Defaults.dragHandleWidth / 2f - Styles.kValidityIconWidth / 2f + 1, rect.y, Styles.kValidityIconWidth, Styles.kValidityIconHeight); + EditorGUI.LabelField(constraintValidityRect, content); } if (!string.IsNullOrEmpty(textFieldValue) && textFieldValue != noValue) @@ -545,7 +622,7 @@ static void AddPrecompiledReferenceListElement(ReorderableList list) newProp.FindPropertyRelative("fileName").stringValue = string.Empty; } - static void LoadAssemblyDefintionState(AssemblyDefinitionState state, string path) + static void LoadAssemblyDefinitionState(AssemblyDefinitionState state, string path) { var asset = AssetDatabase.LoadAssetAtPath(path); if (asset == null) @@ -584,20 +661,7 @@ static void LoadAssemblyDefintionState(AssemblyDefinitionState state, string pat { foreach (var versionDefine in data.versionDefines) { - if (!SymbolNameRestrictions.IsValid(versionDefine.define)) - { - var exception = new AssemblyDefinitionException($"Invalid version define {versionDefine.define}", path); - Debug.LogException(exception, asset); - } - else - { - state.versionDefines.Add(new VersionDefine - { - name = versionDefine.name, - expression = versionDefine.expression, - define = versionDefine.define, - }); - } + state.versionDefines.Add(versionDefine); } } @@ -605,19 +669,10 @@ static void LoadAssemblyDefintionState(AssemblyDefinitionState state, string pat { foreach (var defineConstraint in data.defineConstraints) { - var symbolName = defineConstraint.StartsWith(DefineConstraintsHelper.Not) ? defineConstraint.Substring(1) : defineConstraint; - if (!SymbolNameRestrictions.IsValid(symbolName)) - { - var exception = new AssemblyDefinitionException($"Invalid define constraint {symbolName}", path); - Debug.LogException(exception, asset); - } - else + state.defineConstraints.Add(new DefineConstraint { - state.defineConstraints.Add(new DefineConstraint - { - name = defineConstraint, - }); - } + name = defineConstraint, + }); } } @@ -645,7 +700,7 @@ static void LoadAssemblyDefintionState(AssemblyDefinitionState state, string pat } catch (AssemblyDefinitionException e) { - UnityEngine.Debug.LogException(e, asset); + Debug.LogException(e, asset); state.references.Add(new AssemblyDefinitionReference()); } } @@ -669,6 +724,7 @@ static void LoadAssemblyDefintionState(AssemblyDefinitionState state, string pat precompiledReference.path = assembly.Path; precompiledReference.fileName = AssetPath.GetFileName(assembly.Path); } + state.precompiledReferences.Add(precompiledReference); } catch (AssemblyDefinitionException e) @@ -742,20 +798,13 @@ static void SaveAssemblyDefinitionState(AssemblyDefinitionState state) .Select(r => r.name) .ToArray(); - data.versionDefines = state.versionDefines.Select(x => new UnityEditor.Scripting.ScriptCompilation.VersionDefine - { - name = x.name, - expression = x.expression, - define = x.define, - }).ToArray(); - + data.versionDefines = state.versionDefines.ToArray(); data.autoReferenced = state.autoReferenced; data.overrideReferences = state.overrideReferences; data.precompiledReferences = state.precompiledReferences .Select(r => r.name).ToArray(); - data.allowUnsafeCode = state.allowUnsafeCode; data.noEngineReferences = state.noEngineReferences; diff --git a/Editor/Mono/Inspector/AssetBundleNameGUI.cs b/Editor/Mono/Inspector/AssetBundleNameGUI.cs index 76bef7fdea..daf3b35432 100644 --- a/Editor/Mono/Inspector/AssetBundleNameGUI.cs +++ b/Editor/Mono/Inspector/AssetBundleNameGUI.cs @@ -33,6 +33,7 @@ private static class Styles public void OnAssetBundleNameGUI(IEnumerable assets) { + float oldLabelWidth = EditorGUIUtility.labelWidth; EditorGUIUtility.labelWidth = 90f; Rect bundleRect = EditorGUILayout.GetControlRect(true, EditorGUI.kSingleLineHeight); @@ -56,6 +57,8 @@ public void OnAssetBundleNameGUI(IEnumerable assets) AssetBundleTextField(variantRect, id, assets, true); else AssetBundlePopup(variantRect, id, assets, true); + + EditorGUIUtility.labelWidth = oldLabelWidth; } private void ShowNewAssetBundleField(bool isVariant) diff --git a/Editor/Mono/Inspector/AudioClipInspector.cs b/Editor/Mono/Inspector/AudioClipInspector.cs index d41ad1cbdf..4027b1464c 100644 --- a/Editor/Mono/Inspector/AudioClipInspector.cs +++ b/Editor/Mono/Inspector/AudioClipInspector.cs @@ -24,9 +24,9 @@ internal class AudioClipInspector : Editor static bool s_PlayFirst; static AudioClipInspector s_PlayingInstance; - static GUIContent[] s_PlayIcons = {null, null}; - static GUIContent[] s_AutoPlayIcons = {null, null}; - static GUIContent[] s_LoopIcons = {null, null}; + static GUIContent s_PlayIcon; + static GUIContent s_AutoPlayIcon; + static GUIContent s_LoopIcon; static Texture2D s_DefaultIcon; @@ -48,12 +48,9 @@ static void Init() s_AutoPlay = EditorPrefs.GetBool("AutoPlayAudio", false); s_Loop = false; - s_AutoPlayIcons[0] = EditorGUIUtility.TrIconContent("preAudioAutoPlayOff", "Turn Auto Play on"); - s_AutoPlayIcons[1] = EditorGUIUtility.TrIconContent("preAudioAutoPlayOn", "Turn Auto Play off"); - s_PlayIcons[0] = EditorGUIUtility.TrIconContent("preAudioPlayOff", "Play"); - s_PlayIcons[1] = EditorGUIUtility.TrIconContent("preAudioPlayOn", "Stop"); - s_LoopIcons[0] = EditorGUIUtility.TrIconContent("preAudioLoopOff", "Loop on"); - s_LoopIcons[1] = EditorGUIUtility.TrIconContent("preAudioLoopOn", "Loop off"); + s_AutoPlayIcon = EditorGUIUtility.TrIconContent("preAudioAutoPlayOff", "Turn Auto Play on/off"); + s_PlayIcon = EditorGUIUtility.TrIconContent("PlayButton", "Play"); + s_LoopIcon = EditorGUIUtility.TrIconContent("preAudioLoopOff", "Loop on/off"); s_DefaultIcon = EditorGUIUtility.LoadIcon("Profiler.Audio"); } @@ -122,20 +119,9 @@ public override void OnPreviewSettings() m_MultiEditing = targets.Length > 1; { - using (new EditorGUI.DisabledScope(m_MultiEditing)) - { - s_AutoPlay = s_AutoPlay && !m_MultiEditing; - s_AutoPlay = PreviewGUI.CycleButton(s_AutoPlay ? 1 : 0, s_AutoPlayIcons) != 0; - } - - bool loop = s_Loop; - s_Loop = PreviewGUI.CycleButton(s_Loop ? 1 : 0, s_LoopIcons) != 0; - if ((loop != s_Loop) && playing) - AudioUtil.LoopClip(clip, s_Loop); - using (new EditorGUI.DisabledScope(m_MultiEditing && !playing)) { - bool newPlaying = PreviewGUI.CycleButton(playing ? 1 : 0, s_PlayIcons) != 0; + bool newPlaying = GUILayout.Toggle(playing, s_PlayIcon, EditorStyles.toolbarButton); if (newPlaying != playing) { @@ -148,6 +134,17 @@ public override void OnPreviewSettings() } } } + + using (new EditorGUI.DisabledScope(m_MultiEditing)) + { + s_AutoPlay = s_AutoPlay && !m_MultiEditing; + s_AutoPlay = GUILayout.Toggle(s_AutoPlay, s_AutoPlayIcon, EditorStyles.toolbarButton); + } + + bool loop = s_Loop; + s_Loop = GUILayout.Toggle(s_Loop, s_LoopIcon, EditorStyles.toolbarButton); + if ((loop != s_Loop) && playing) + AudioUtil.LoopClip(clip, s_Loop); } } diff --git a/Editor/Mono/Inspector/Avatar/AvatarEditor.cs b/Editor/Mono/Inspector/Avatar/AvatarEditor.cs index 8276b2f6bd..bb93ea01a3 100644 --- a/Editor/Mono/Inspector/Avatar/AvatarEditor.cs +++ b/Editor/Mono/Inspector/Avatar/AvatarEditor.cs @@ -281,6 +281,8 @@ void OnEnable() { StageNavigationManager.instance.stageChanging += OnStageChanging; EditorApplication.update += Update; + SceneView.duringSceneGui += OnSceneGUI; + m_SwitchToEditMode = false; if (m_EditMode == EditMode.Editing) { @@ -301,6 +303,8 @@ void OnEnable() void OnDisable() { + SceneView.duringSceneGui -= OnSceneGUI; + if (m_EditMode == EditMode.Editing) editor.Disable(); @@ -469,7 +473,7 @@ void EditingGUI() editor.OnInspectorGUI(); } - public void OnSceneGUI() + public void OnSceneGUI(SceneView view) { if (m_EditMode == EditMode.Editing) editor.OnSceneGUI(); diff --git a/Editor/Mono/Inspector/Avatar/AvatarMappingEditor.cs b/Editor/Mono/Inspector/Avatar/AvatarMappingEditor.cs index 0a401c9782..4aec2722a5 100644 --- a/Editor/Mono/Inspector/Avatar/AvatarMappingEditor.cs +++ b/Editor/Mono/Inspector/Avatar/AvatarMappingEditor.cs @@ -78,6 +78,7 @@ internal class Styles private SerializedProperty m_HumanBoneArray; private SerializedProperty m_Skeleton; + private SerializedProperty m_AutoGenerateAvatarMappingIfUnspecified; protected bool[] m_BodyPartToggle; protected bool[] m_BodyPartFoldout; @@ -163,6 +164,7 @@ protected void Init() m_HumanBoneArray = serializedObject.FindProperty("m_HumanDescription.m_Human"); m_Skeleton = serializedObject.FindProperty("m_HumanDescription.m_Skeleton"); + m_AutoGenerateAvatarMappingIfUnspecified = serializedObject.FindProperty("m_AutoGenerateAvatarMappingIfUnspecified"); m_IsBiped = AvatarBipedMapper.IsBiped(gameObject.transform, null); @@ -456,6 +458,7 @@ protected void AutoMapping() bone.bone = kvp.Value; bone.Serialize(m_HumanBoneArray); } + m_AutoGenerateAvatarMappingIfUnspecified.boolValue = false; } protected void BipedMapping() @@ -467,6 +470,7 @@ protected void BipedMapping() bone.bone = kvp.Value; bone.Serialize(m_HumanBoneArray); } + m_AutoGenerateAvatarMappingIfUnspecified.boolValue = false; } protected void ClearMapping() @@ -477,6 +481,7 @@ protected void ClearMapping() AvatarSetupTool.ClearHumanBoneArray(m_HumanBoneArray); ResetBones(); ValidateMapping(); + m_AutoGenerateAvatarMappingIfUnspecified.boolValue = false; SceneView.RepaintAll(); } } diff --git a/Editor/Mono/Inspector/AvatarPreview.cs b/Editor/Mono/Inspector/AvatarPreview.cs index 9dd3e3b381..9c01014b23 100644 --- a/Editor/Mono/Inspector/AvatarPreview.cs +++ b/Editor/Mono/Inspector/AvatarPreview.cs @@ -17,7 +17,7 @@ internal class AvatarPreview const string k2DPref = "Avatarpreview2D"; const string kReferencePref = "AvatarpreviewShowReference"; const string kSpeedPref = "AvatarpreviewSpeed"; - const float kTimeControlRectHeight = 21; + const float kTimeControlRectHeight = 20; public delegate void OnAvatarChange(); OnAvatarChange m_OnAvatarChangeFunc = null; @@ -191,10 +191,14 @@ private class Styles public GUIContent speedScale = EditorGUIUtility.TrIconContent("SpeedScale", "Changes animation preview speed"); public GUIContent pivot = EditorGUIUtility.TrIconContent("AvatarPivot", "Displays avatar's pivot and mass center"); public GUIContent ik = EditorGUIUtility.TrTextContent("IK", "Toggles feet IK preview"); - public GUIContent is2D = EditorGUIUtility.TrTextContent("2D", "Toggles 2D preview mode"); - public GUIContent avatarIcon = EditorGUIUtility.TrIconContent("Changes the model to use for previewing."); + public GUIContent is2D = EditorGUIUtility.TrIconContent("SceneView2D", "Toggles 2D preview mode"); + public GUIContent avatarIcon = EditorGUIUtility.TrIconContent("AvatarSelector", "Changes the model to use for previewing."); - public GUIStyle preButton = "preButton"; + public GUIStyle avatarDropdown = new GUIStyle(EditorStyles.toolbarButton) + { + stretchWidth = false + }; + public GUIStyle preButton = "toolbarbutton"; public GUIStyle preSlider = "preSlider"; public GUIStyle preSliderThumb = "preSliderThumb"; public GUIStyle preLabel = "preLabel"; @@ -499,9 +503,9 @@ public AvatarPreview(Animator previewObjectInScene, Motion objectOnSameAsset) InitInstance(previewObjectInScene, objectOnSameAsset); } - float PreviewSlider(float val, float snapThreshold) + float PreviewSlider(Rect rect, float val, float snapThreshold) { - val = GUILayout.HorizontalSlider(val, 0.1f, 2.0f, s_Styles.preSlider, s_Styles.preSliderThumb, GUILayout.MaxWidth(64)); + val = GUI.HorizontalSlider(rect, val, 0.1f, 2.0f, s_Styles.preSlider, s_Styles.preSliderThumb);//, GUILayout.MaxWidth(64)); if (val > 0.25f - snapThreshold && val < 0.25f + snapThreshold) val = 0.25f; else if (val > 0.5f - snapThreshold && val < 0.5f + snapThreshold) @@ -545,12 +549,14 @@ public void DoPreviewSettings() if (EditorGUI.EndChangeCheck()) EditorPrefs.SetBool(kReferencePref, m_ShowReference); - GUILayout.Box(s_Styles.speedScale, s_Styles.preLabel); - EditorGUI.BeginChangeCheck(); - timeControl.playbackSpeed = PreviewSlider(timeControl.playbackSpeed, 0.03f); - if (EditorGUI.EndChangeCheck()) - EditorPrefs.SetFloat(kSpeedPref, timeControl.playbackSpeed); - GUILayout.Label(timeControl.playbackSpeed.ToString("f2", CultureInfo.InvariantCulture.NumberFormat), s_Styles.preLabel); + if (EditorGUILayout.DropdownButton(s_Styles.avatarIcon, FocusType.Passive, EditorStyles.toolbarDropDownRight)) + { + GenericMenu menu = new GenericMenu(); + menu.AddItem(EditorGUIUtility.TrTextContent("Auto"), false, SetPreviewAvatarOption, PreviewPopupOptions.Auto); + menu.AddItem(EditorGUIUtility.TrTextContent("Unity Model"), false, SetPreviewAvatarOption, PreviewPopupOptions.DefaultModel); + menu.AddItem(EditorGUIUtility.TrTextContent("Other..."), false, SetPreviewAvatarOption, PreviewPopupOptions.Other); + menu.ShowAsContext(); + } } private RenderTexture RenderPreviewShadowmap(Light light, float scale, Vector3 center, Vector3 floorPos, out Matrix4x4 outShadowMatrix) @@ -834,13 +840,38 @@ private void PositionPreviewObjects(Quaternion pivotRot, Vector3 pivotPos, Quate public void AvatarTimeControlGUI(Rect rect) { + const float kSliderWidth = 150f; + const float kSpacing = 4f; Rect timeControlRect = rect; + + // background + GUI.Box(rect, GUIContent.none, EditorStyles.toolbar); + timeControlRect.height = kTimeControlRectHeight; + timeControlRect.xMax -= kSliderWidth; + + Rect sliderControlRect = rect; + sliderControlRect.height = kTimeControlRectHeight; + sliderControlRect.yMin += 1; + sliderControlRect.yMax -= 1; + sliderControlRect.xMin = sliderControlRect.xMax - kSliderWidth + kSpacing; timeControl.DoTimeControl(timeControlRect); + Rect labelRect = new Rect(new Vector2(rect.x, rect.y), EditorStyles.toolbarLabel.CalcSize(EditorGUIUtility.TrTempContent("xxxxxx")));; + labelRect.x = rect.xMax - labelRect.width; + labelRect.yMin = rect.yMin; + labelRect.yMax = rect.yMax; + + sliderControlRect.xMax = labelRect.xMin; + + EditorGUI.BeginChangeCheck(); + timeControl.playbackSpeed = PreviewSlider(sliderControlRect, timeControl.playbackSpeed, 0.03f); + if (EditorGUI.EndChangeCheck()) + EditorPrefs.SetFloat(kSpeedPref, timeControl.playbackSpeed); + GUI.Label(labelRect, timeControl.playbackSpeed.ToString("f2", CultureInfo.InvariantCulture.NumberFormat) + "x", EditorStyles.toolbarLabel); // Show current time in seconds:frame and in percentage - rect.y = rect.yMax - 20; + rect.y = rect.yMax - 24; float time = timeControl.currentTime - timeControl.startTime; EditorGUI.DropShadowLabel(new Rect(rect.x, rect.y, rect.width, 20), UnityString.Format("{0,2}:{1:00} ({2:000.0%}) Frame {3}", (int)time, Repeat(Mathf.FloorToInt(time * fps), fps), timeControl.normalizedTime, Mathf.FloorToInt(timeControl.currentTime * fps)) @@ -1060,7 +1091,6 @@ public void DoAvatarPreview(Rect rect, GUIStyle background) AvatarTimeControlGUI(rect); - GUI.DrawTexture(choserRect, s_Styles.avatarIcon.image); int previewSceneID = GUIUtility.GetControlID(m_PreviewSceneHint, FocusType.Passive); type = evt.GetTypeForControl(previewSceneID); diff --git a/Editor/Mono/Inspector/BlendTreeInspector.cs b/Editor/Mono/Inspector/BlendTreeInspector.cs index e0089c0b4a..4e515a19da 100644 --- a/Editor/Mono/Inspector/BlendTreeInspector.cs +++ b/Editor/Mono/Inspector/BlendTreeInspector.cs @@ -224,7 +224,7 @@ internal override void OnHeaderTitleGUI(Rect titleRect, string header) serializedObject.Update(); Rect textFieldRect = titleRect; - textFieldRect.height = 16f; + textFieldRect.height = EditorGUI.kSingleLineHeight; EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = m_Name.hasMultipleDifferentValues; @@ -1360,7 +1360,7 @@ public void DrawChild(Rect r, int index, bool isActive, bool isFocused) SerializedProperty motion = child.FindPropertyRelative("m_Motion"); r.y++; - r.height = 16; + r.height = EditorGUI.kSingleLineHeight; Rect[] rects = GetRowRects(r, m_BlendType.intValue); int col = 0; diff --git a/Editor/Mono/Inspector/CameraEditor.cs b/Editor/Mono/Inspector/CameraEditor.cs index 0d4e4d119e..5ded4bf60a 100644 --- a/Editor/Mono/Inspector/CameraEditor.cs +++ b/Editor/Mono/Inspector/CameraEditor.cs @@ -14,6 +14,7 @@ using UnityEngine.Scripting; using UnityEditor.Modules; using UnityEditor.SceneManagement; +using UnityEditorInternal.VR; using Object = UnityEngine.Object; namespace UnityEditor @@ -407,7 +408,7 @@ public void DrawDynamicResolution() public void DrawVR() { - if (PlayerSettings.virtualRealitySupported) + if (VREditor.GetVREnabledOnTargetGroup(BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget))) { EditorGUILayout.PropertyField(stereoSeparation); EditorGUILayout.PropertyField(stereoConvergence); @@ -440,7 +441,6 @@ public static void DrawCameraWarnings(Camera camera) readonly AnimatedBool m_ShowBGColorOptions = new AnimatedBool(); readonly AnimatedBool m_ShowOrthoOptions = new AnimatedBool(); - readonly AnimatedBool m_ShowTargetEyeOption = new AnimatedBool(); private Camera camera { get { return target as Camera; } } @@ -516,11 +516,9 @@ public void OnEnable() var c = (Camera)target; m_ShowBGColorOptions.value = !clearFlagsHasMultipleValues && (c.clearFlags == CameraClearFlags.SolidColor || c.clearFlags == CameraClearFlags.Skybox); m_ShowOrthoOptions.value = c.orthographic; - m_ShowTargetEyeOption.value = targetEyeValue != (int)StereoTargetEyeMask.Both || PlayerSettings.virtualRealitySupported; m_ShowBGColorOptions.valueChanged.AddListener(Repaint); m_ShowOrthoOptions.valueChanged.AddListener(Repaint); - m_ShowTargetEyeOption.valueChanged.AddListener(Repaint); SubsystemManager.GetSubsystemDescriptors(displayDescriptors); SubsystemManager.reloadSubsytemsCompleted += OnReloadSubsystemsComplete; @@ -530,7 +528,6 @@ internal void OnDisable() { m_ShowBGColorOptions.valueChanged.RemoveListener(Repaint); m_ShowOrthoOptions.valueChanged.RemoveListener(Repaint); - m_ShowTargetEyeOption.valueChanged.RemoveListener(Repaint); } public void OnDestroy() @@ -626,7 +623,7 @@ private void CommandBufferGUI() { cam.RemoveCommandBuffer(ce, cb); SceneView.RepaintAll(); - PreviewEditorWindow.RepaintAll(); + PlayModeView.RepaintAll(); GUIUtility.ExitGUI(); } } @@ -640,7 +637,7 @@ private void CommandBufferGUI() { cam.RemoveAllCommandBuffers(); SceneView.RepaintAll(); - PreviewEditorWindow.RepaintAll(); + PlayModeView.RepaintAll(); } } EditorGUI.indentLevel--; @@ -655,7 +652,6 @@ public override void OnInspectorGUI() m_ShowOrthoOptions.target = !orthographicHasMultipleValues && c.orthographic; bool displaySubsystemPresent = displayDescriptors.Count > 0; - m_ShowTargetEyeOption.target = targetEyeValue != (int)StereoTargetEyeMask.Both || PlayerSettings.virtualRealitySupported || displaySubsystemPresent; settings.DrawClearFlags(); @@ -698,9 +694,7 @@ public override void OnInspectorGUI() EditorGUILayout.Space(); settings.DrawMultiDisplay(); - if (EditorGUILayout.BeginFadeGroup(m_ShowTargetEyeOption.faded)) - settings.DrawTargetEye(); - EditorGUILayout.EndFadeGroup(); + settings.DrawTargetEye(); DepthTextureModeGUI(); CommandBufferGUI(); @@ -720,7 +714,7 @@ public virtual void OnOverlayGUI(Object target, SceneView sceneView) if (targetStage != sceneViewStage) return; - Vector2 previewSize = c.targetTexture ? new Vector2(c.targetTexture.width, c.targetTexture.height) : PreviewEditorWindow.GetMainPreviewTargetSize(); + Vector2 previewSize = c.targetTexture ? new Vector2(c.targetTexture.width, c.targetTexture.height) : PlayModeView.GetMainPlayModeViewTargetSize(); if (previewSize.x < 0f) { @@ -767,7 +761,6 @@ public virtual void OnOverlayGUI(Object target, SceneView sceneView) { // setup camera and render previewCamera.CopyFrom(c); - previewCamera.cameraType = CameraType.Preview; // make sure the preview camera is rendering the same stage as the SceneView is previewCamera.scene = sceneView.customScene; @@ -823,7 +816,7 @@ private RenderTexture GetPreviewTextureWithSize(int width, int height) [RequiredByNativeCode] internal static float GetGameViewAspectRatio() { - Vector2 gameViewSize = PreviewEditorWindow.GetMainPreviewTargetSize(); + Vector2 gameViewSize = PlayModeView.GetMainPlayModeViewTargetSize(); if (gameViewSize.x < 0f) { // Fallback to Scene View of not a valid game view size @@ -835,9 +828,9 @@ internal static float GetGameViewAspectRatio() } [RequiredByNativeCode] - internal static Vector2 GetMainPreviewSize() + internal static void GetMainPlayModeViewSize(out Vector2 size) { - return PreviewEditorWindow.GetMainPreviewTargetSize(); + size = PlayModeView.GetMainPlayModeViewTargetSize(); } // Called from C++ when we need to render a Camera's gizmo @@ -846,7 +839,7 @@ internal static void RenderGizmo(Camera camera) CameraEditorUtils.DrawFrustumGizmo(camera); } - private static Vector2 s_PreviousMainGameViewTargetSize; + private static Vector2 s_PreviousMainPlayModeViewTargetSize; public virtual void OnSceneGUI() { if (!target) @@ -856,12 +849,12 @@ public virtual void OnSceneGUI() if (!CameraEditorUtils.IsViewportRectValidToRender(c.rect)) return; - Vector2 currentMainGameViewTargetSize = PreviewEditorWindow.GetMainPreviewTargetSize(); - if (s_PreviousMainGameViewTargetSize != currentMainGameViewTargetSize) + Vector2 currentMainPlayModeViewTargetSize = PlayModeView.GetMainPlayModeViewTargetSize(); + if (s_PreviousMainPlayModeViewTargetSize != currentMainPlayModeViewTargetSize) { // a gameView size change can affect horizontal FOV, refresh the inspector when that happens. Repaint(); - s_PreviousMainGameViewTargetSize = currentMainGameViewTargetSize; + s_PreviousMainPlayModeViewTargetSize = currentMainPlayModeViewTargetSize; } SceneViewOverlay.Window(EditorGUIUtility.TrTextContent("Camera Preview"), OnOverlayGUI, (int)SceneViewOverlay.Ordering.Camera, target, SceneViewOverlay.WindowDisplayOption.OneWindowPerTarget); diff --git a/Editor/Mono/Inspector/CanvasEditor.cs b/Editor/Mono/Inspector/CanvasEditor.cs index ea1964fbe1..fa50c0c055 100644 --- a/Editor/Mono/Inspector/CanvasEditor.cs +++ b/Editor/Mono/Inspector/CanvasEditor.cs @@ -4,6 +4,7 @@ using UnityEngine; using UnityEditor.AnimatedValues; +using UnityEditorInternal.VR; namespace UnityEditor { @@ -124,7 +125,7 @@ void OnDisable() private void AllRootCanvases() { - if (PlayerSettings.virtualRealitySupported && (m_RenderMode.enumValueIndex == (int)RenderMode.ScreenSpaceOverlay)) + if (VREditor.GetVREnabledOnTargetGroup(BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget)) && (m_RenderMode.enumValueIndex == (int)RenderMode.ScreenSpaceOverlay)) { EditorGUILayout.HelpBox("Using a render mode of ScreenSpaceOverlay while VR is enabled will cause the Canvas to continue to incur a rendering cost, even though the Canvas will not be visible in VR.", MessageType.Warning); } diff --git a/Editor/Mono/Inspector/ClothInspector.cs b/Editor/Mono/Inspector/ClothInspector.cs index 5d46640c90..f3e465b362 100644 --- a/Editor/Mono/Inspector/ClothInspector.cs +++ b/Editor/Mono/Inspector/ClothInspector.cs @@ -94,8 +94,10 @@ public enum CollisionVisualizationMode { SelfCollision, InterCollision }; int m_NumSelection = 0; - SkinnedMeshRenderer m_SkinnedMeshRenderer; - + SkinnedMeshRenderer m_Smr; + Transform m_TransformOverride; + Matrix4x4 m_CachedTransformState; + WeakReference m_CachedMesh; private static class Styles { public static readonly GUIContent editConstraintsLabel = EditorGUIUtility.TrTextContent("Edit Constraints"); @@ -106,6 +108,7 @@ private static class Styles public static readonly GUIContent paintCollisionParticles = EditorGUIUtility.TrTextContent("Paint Collision Particles"); public static readonly GUIContent selectCollisionParticles = EditorGUIUtility.TrTextContent("Select Collision Particles"); public static readonly GUIContent brushRadiusString = EditorGUIUtility.TrTextContent("Brush Radius"); + public static readonly GUIContent constraintSizeString = EditorGUIUtility.TrTextContent("Constraint Size"); public static readonly GUIContent gradientStartString = EditorGUIUtility.TrTextContent("Gradient Start"); public static readonly GUIContent gradientEndString = EditorGUIUtility.TrTextContent("Gradient End"); public static readonly GUIContent setMaxDistanceString = EditorGUIUtility.TrTextContent("Max Distance"); @@ -250,8 +253,31 @@ public override void OnInspectorGUI() serializedObject.Update(); // Multi-editing in scene not supported - if (targets.Length <= 1) + if (targets.Length < 2) { + bool reinitInspector = false; + //sync transform override from smr + var actualRootBone = m_Smr.actualRootBone; + if (m_TransformOverride != actualRootBone) + { + reinitInspector = true; + m_TransformOverride = actualRootBone; + } + else if (actualRootBone.localToWorldMatrix != m_CachedTransformState) + { + reinitInspector = true; + m_CachedTransformState = actualRootBone.localToWorldMatrix; + } + + if (m_Smr.sharedMesh != m_CachedMesh.Target as Mesh) + { + m_CachedMesh.Target = m_Smr.sharedMesh; + reinitInspector = true; + } + + if (reinitInspector) + InitInspector(); + GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); EditMode.DoInspectorToolbar(Styles.sceneViewEditModes, Styles.toolContents, this); @@ -259,12 +285,6 @@ public override void OnInspectorGUI() GUILayout.EndHorizontal(); } - if (m_SkinnedMeshRenderer.transform.hasChanged) - { - InitClothParticlesInWorldSpace(); - m_SkinnedMeshRenderer.transform.hasChanged = false; - } - if (editingSelfAndInterCollisionParticles) { if ((state.SetSelfAndInterCollision) || ((state.CollToolMode == CollToolMode.Paint) || (state.CollToolMode == CollToolMode.Erase))) @@ -356,7 +376,6 @@ bool SelectionMeshDirty() { if (m_LastVertices != null) { - Transform t = m_SkinnedMeshRenderer.actualRootBone; int numLastVertices = m_LastVertices.Length; if (numLastVertices != m_NumVerts) return true; @@ -382,7 +401,6 @@ void GenerateSelectionMesh() m_ParticleRectSelection = new bool[m_NumVerts]; m_LastVertices = new Vector3[m_NumVerts]; - Transform t = m_SkinnedMeshRenderer.actualRootBone; for (int i = 0; i < m_NumVerts; i++) { m_LastVertices[i] = m_ClothParticlesInWorldSpace[i]; @@ -403,14 +421,9 @@ void InitSelfAndInterCollisionSelection() cloth.GetSelfAndInterCollisionIndices(selfAndInterCollisionIndices); int length = selfAndInterCollisionIndices.Count; - // if the number of self and inter collision indices are different then don't copy - // as this means the underlying meshes have changed so copying these values is meaningless in this situation - if (length == m_NumVerts) + for (int i = 0; i < length; i++) { - for (int i = 0; i < length; i++) - { - m_SelfAndInterCollisionSelection[selfAndInterCollisionIndices[i]] = true; - } + m_SelfAndInterCollisionSelection[selfAndInterCollisionIndices[i]] = true; } } @@ -419,9 +432,8 @@ void InitClothParticlesInWorldSpace() Vector3[] vertices = cloth.vertices; m_ClothParticlesInWorldSpace = new Vector3[m_NumVerts]; - Transform t = m_SkinnedMeshRenderer.actualRootBone; - Quaternion rotation = t.rotation; - Vector3 position = t.position; + Quaternion rotation = m_TransformOverride.rotation; + Vector3 position = m_TransformOverride.position; for (int i = 0; i < m_NumVerts; i++) { m_ClothParticlesInWorldSpace[i] = rotation * vertices[i] + position; @@ -434,9 +446,8 @@ void InitClothNormalsInWorldSpace() int length = normals.Length; m_ClothNormalsInWorldSpace = new Vector3[length]; - Transform t = m_SkinnedMeshRenderer.actualRootBone; - Quaternion rotation = t.rotation; - Vector3 position = t.position; + Quaternion rotation = m_TransformOverride.rotation; + Vector3 position = m_TransformOverride.position; for (int i = 0; i < length; i++) { m_ClothNormalsInWorldSpace[i] = rotation * normals[i] + position; @@ -445,8 +456,6 @@ void InitClothNormalsInWorldSpace() void DrawSelfAndInterCollisionParticles() { - Transform t = m_SkinnedMeshRenderer.actualRootBone; - int id = GUIUtility.GetControlID(FocusType.Passive); float size = state.SelfCollisionDistance; if (state.VisualizeSelfOrInterCollision == CollisionVisualizationMode.SelfCollision) @@ -465,11 +474,11 @@ void DrawSelfAndInterCollisionParticles() bool forwardFacing = Vector3.Dot(m_ClothNormalsInWorldSpace[i], Camera.current.transform.forward) <= 0; if (forwardFacing || state.ManipulateBackfaces) { - if ((m_SelfAndInterCollisionSelection[i] == true) && !(m_ParticleSelection[i] == true)) + if (m_SelfAndInterCollisionSelection[i] && !m_ParticleSelection[i]) { Handles.color = s_SelfAndInterCollisionParticleColor; } - else if (!(m_SelfAndInterCollisionSelection[i] == true) && !(m_ParticleSelection[i] == true)) + else if (!m_SelfAndInterCollisionSelection[i] && !m_ParticleSelection[i]) { Handles.color = s_UnselectedSelfAndInterCollisionParticleColor; } @@ -487,7 +496,7 @@ void DrawSelfAndInterCollisionParticles() } } - Handles.SphereHandleCap(id, m_ClothParticlesInWorldSpace[i], t.rotation, size, EventType.Repaint); + Handles.SphereHandleCap(id, m_ClothParticlesInWorldSpace[i], m_TransformOverride.rotation, size, EventType.Repaint); } } } @@ -506,8 +515,10 @@ void OnEnable() if (s_ColorTexture == null) s_ColorTexture = GenerateColorTexture(100); - m_SkinnedMeshRenderer = cloth.GetComponent(); - + m_Smr = cloth.GetComponent(); + m_TransformOverride = m_Smr.actualRootBone; + m_CachedTransformState = m_TransformOverride.localToWorldMatrix; + m_CachedMesh = new WeakReference(m_Smr.sharedMesh); InitInspector(); GenerateSelectionMesh(); @@ -772,6 +783,8 @@ void SelectionGUI() } cloth.coefficients = coefficients; } + + EditConstraintSize(); } void GradientToolGUI() @@ -975,6 +988,19 @@ void EditBrushSize() } } + void EditConstraintSize() + { + EditorGUI.BeginChangeCheck(); + float fieldValue = EditorGUILayout.FloatField(Styles.constraintSizeString, state.ConstraintSize); + bool changed = EditorGUI.EndChangeCheck(); + if (changed) + { + state.ConstraintSize = fieldValue; + if (state.ConstraintSize < 0.0f) + state.ConstraintSize = 0.0f; + } + } + void EditGradientStart() { EditorGUI.BeginChangeCheck(); @@ -1022,6 +1048,7 @@ void PaintGUI() } EditBrushSize(); + EditConstraintSize(); } int GetMouseVertex(Event e) @@ -1035,16 +1062,14 @@ int GetMouseVertex(Event e) return -1; } - ClothSkinningCoefficient[] coefficients = cloth.coefficients; Ray mouseRay = HandleUtility.GUIPointToWorldRay(e.mousePosition); float minDistance = 1000; int found = -1; - Quaternion rotation = m_SkinnedMeshRenderer.actualRootBone.rotation; for (int i = 0; i < m_NumVerts; i++) { Vector3 dir = m_LastVertices[i] - mouseRay.origin; float sqrDistance = Vector3.Cross(dir, mouseRay.direction).sqrMagnitude; - bool forwardFacing = Vector3.Dot(m_ClothNormalsInWorldSpace[i], Camera.current.transform.forward) <= 0; + bool forwardFacing = Vector3.Dot(m_ClothNormalsInWorldSpace[i], SceneView.GetLastActiveSceneViewCamera().transform.forward) <= 0; if ((forwardFacing || state.ManipulateBackfaces) && sqrDistance < minDistance && sqrDistance < 0.05f * 0.05f) { minDistance = sqrDistance; @@ -1059,7 +1084,6 @@ void DrawConstraints() if (SelectionMeshDirty()) GenerateSelectionMesh(); - Transform t = m_SkinnedMeshRenderer.actualRootBone; int id = GUIUtility.GetControlID(FocusType.Passive); ClothSkinningCoefficient[] coefficients = cloth.coefficients; int length = coefficients.Length; @@ -1081,7 +1105,7 @@ void DrawConstraints() for (int i = 0; i < length; i++) { - bool forwardFacing = Vector3.Dot(m_ClothNormalsInWorldSpace[i], Camera.current.transform.forward) <= 0; + bool forwardFacing = Vector3.Dot(m_ClothNormalsInWorldSpace[i], SceneView.GetLastActiveSceneViewCamera().transform.forward) <= 0; if (forwardFacing || state.ManipulateBackfaces) { float val = GetCoefficient(coefficients[i]); @@ -1113,7 +1137,7 @@ void DrawConstraints() } } - Handles.SphereHandleCap(id, m_ClothParticlesInWorldSpace[i], t.rotation, state.ConstraintSize, EventType.Repaint); + Handles.SphereHandleCap(id, m_ClothParticlesInWorldSpace[i], m_TransformOverride.rotation, state.ConstraintSize, EventType.Repaint); } } } @@ -1146,13 +1170,11 @@ bool UpdateRectParticleSelection() Plane left = new Plane(topLeft.origin + topLeft.direction, botLeft.origin + botLeft.direction, botLeft.origin); Plane right = new Plane(botRight.origin + botRight.direction, topRight.origin + topRight.direction, topRight.origin); - Quaternion rotation = m_SkinnedMeshRenderer.actualRootBone.rotation; - int length = coefficients.Length; for (int i = 0; i < length; i++) { Vector3 v = m_LastVertices[i]; - bool forwardFacing = Vector3.Dot(m_ClothNormalsInWorldSpace[i], Camera.current.transform.forward) <= 0; + bool forwardFacing = Vector3.Dot(m_ClothNormalsInWorldSpace[i], SceneView.GetLastActiveSceneViewCamera().transform.forward) <= 0; bool selected = top.GetSide(v) && bottom.GetSide(v) && left.GetSide(v) && right.GetSide(v); selected = selected && (state.ManipulateBackfaces || forwardFacing); if (m_ParticleRectSelection[i] != selected) @@ -1313,11 +1335,10 @@ void GetBrushedConstraints(Event e) } ClothSkinningCoefficient[] coefficients = cloth.coefficients; - Quaternion rotation = m_SkinnedMeshRenderer.actualRootBone.rotation; for (int i = 0; i < m_NumVerts; i++) { Vector3 distanceBetween = m_ClothParticlesInWorldSpace[i] - m_BrushPos; - bool forwardFacing = Vector3.Dot(m_ClothNormalsInWorldSpace[i], Camera.current.transform.forward) <= 0; + bool forwardFacing = Vector3.Dot(m_ClothNormalsInWorldSpace[i], SceneView.GetLastActiveSceneViewCamera().transform.forward) <= 0; if ((distanceBetween.magnitude < state.BrushRadius) && (forwardFacing || state.ManipulateBackfaces)) { bool changed = false; @@ -1348,7 +1369,7 @@ void GetBrushedParticles(Event e) return; } - Quaternion rotation = m_SkinnedMeshRenderer.actualRootBone.rotation; + Quaternion rotation = m_TransformOverride.rotation; for (int i = 0; i < m_NumVerts; i++) { Vector3 distanceBetween = m_ClothParticlesInWorldSpace[i] - m_BrushPos; @@ -1398,7 +1419,6 @@ void PaintPreSceneGUI(int id) EventType type = e.GetTypeForControl(id); if (type == EventType.MouseDown || type == EventType.MouseDrag) { - ClothSkinningCoefficient[] coefficients = cloth.coefficients; if (GUIUtility.hotControl != id && (e.alt || e.control || e.command || e.button != 0)) return; if (type == EventType.MouseDown) @@ -1614,7 +1634,6 @@ public void OnSceneGUI() if (editingSelfAndInterCollisionParticles) { OnSceneEditSelfAndInterCollisionParticlesGUI(); - return; } } @@ -1813,7 +1832,7 @@ void ConstraintEditing(UnityObject unused, SceneView sceneView) break; } - if (m_SkinnedMeshRenderer.sharedMesh == null) + if (m_CachedMesh.Target == null) { EditorGUILayout.HelpBox("No mesh has been selected to use with cloth, please select a mesh for the skinned mesh renderer.", MessageType.Info); } @@ -1924,7 +1943,7 @@ void SelfAndInterCollisionEditing(UnityObject unused, SceneView sceneView) cloth.SetSelfAndInterCollisionIndices(selfAndInterCollisionIndices); } - if (m_SkinnedMeshRenderer.sharedMesh == null) + if (m_CachedMesh.Target == null) { EditorGUILayout.HelpBox("No mesh has been selected to use with cloth, please select a mesh for the skinned mesh renderer.", MessageType.Info); } diff --git a/Editor/Mono/Inspector/Collider2DEditorBase.cs b/Editor/Mono/Inspector/Collider2DEditorBase.cs index 60a4691e3c..1306a5076c 100644 --- a/Editor/Mono/Inspector/Collider2DEditorBase.cs +++ b/Editor/Mono/Inspector/Collider2DEditorBase.cs @@ -163,7 +163,7 @@ bool ShouldShowDensity() void ShowContacts(Collider2D collider) { EditorGUI.indentLevel++; - m_ShowContacts.target = EditorGUILayout.Foldout(m_ShowContacts.target, "Contacts"); + m_ShowContacts.target = EditorGUILayout.Foldout(m_ShowContacts.target, "Contacts", true); if (EditorGUILayout.BeginFadeGroup(m_ShowContacts.faded)) { var contactCount = collider.GetContacts(m_Contacts); diff --git a/Editor/Mono/Inspector/ComputeShaderInspector.cs b/Editor/Mono/Inspector/ComputeShaderInspector.cs index 708f3442f2..77538125cd 100644 --- a/Editor/Mono/Inspector/ComputeShaderInspector.cs +++ b/Editor/Mono/Inspector/ComputeShaderInspector.cs @@ -97,12 +97,22 @@ private void ShowCompiledCodeSection(ComputeShader cs) } } + ShaderMessage[] m_ShaderMessages; private void ShowShaderErrors(ComputeShader s) { - int n = ShaderUtil.GetComputeShaderMessageCount(s); - if (n < 1) + if (Event.current.type == EventType.Layout) + { + int n = ShaderUtil.GetComputeShaderMessageCount(s); + m_ShaderMessages = null; + if (n >= 1) + { + m_ShaderMessages = ShaderUtil.GetComputeShaderMessages(s); + } + } + + if (m_ShaderMessages == null) return; - ShaderInspector.ShaderErrorListUI(s, ShaderUtil.GetComputeShaderMessages(s), ref m_ScrollPosition); + ShaderInspector.ShaderErrorListUI(s, m_ShaderMessages, ref m_ScrollPosition); } } } diff --git a/Editor/Mono/Inspector/DirectorEditor.cs b/Editor/Mono/Inspector/DirectorEditor.cs index 97df63b169..2b44ac936f 100644 --- a/Editor/Mono/Inspector/DirectorEditor.cs +++ b/Editor/Mono/Inspector/DirectorEditor.cs @@ -154,7 +154,7 @@ private void DoDirectorBindingInspector() if (!m_BindingPropertiesCache.Any()) return; - m_SceneBindings.isExpanded = EditorGUILayout.Foldout(m_SceneBindings.isExpanded, Styles.BindingsTitleContent); + m_SceneBindings.isExpanded = EditorGUILayout.Foldout(m_SceneBindings.isExpanded, Styles.BindingsTitleContent, true); if (m_SceneBindings.isExpanded) { EditorGUI.indentLevel++; diff --git a/Editor/Mono/Inspector/EdgeCollider2DEditor.cs b/Editor/Mono/Inspector/EdgeCollider2DEditor.cs index 0e0acf02b6..67d41a86a3 100644 --- a/Editor/Mono/Inspector/EdgeCollider2DEditor.cs +++ b/Editor/Mono/Inspector/EdgeCollider2DEditor.cs @@ -55,12 +55,14 @@ public void OnEnable() { EditorTools.EditorTools.activeToolChanged += OnActiveToolChanged; EditorTools.EditorTools.activeToolChanging += OnActiveToolChanging; + Selection.selectionChanged += OnSelectionChanged; } public void OnDisable() { EditorTools.EditorTools.activeToolChanged -= OnActiveToolChanged; EditorTools.EditorTools.activeToolChanging -= OnActiveToolChanging; + Selection.selectionChanged -= OnSelectionChanged; } public override void OnToolGUI(EditorWindow window) @@ -68,16 +70,34 @@ public override void OnToolGUI(EditorWindow window) m_PolyUtility.OnSceneGUI(); } - void OnActiveToolChanged() + void TryBeginEditing() { - if (EditorTools.EditorTools.IsActiveTool(this)) + var collider = target as EdgeCollider2D; + + if (EditorTools.EditorTools.IsActiveTool(this) && IsAvailable() && collider) m_PolyUtility.StartEditing(target as Collider2D); } - void OnActiveToolChanging() + void TryEndEditing() { if (EditorTools.EditorTools.IsActiveTool(this)) m_PolyUtility.StopEditing(); } + + void OnActiveToolChanged() + { + TryBeginEditing(); + } + + void OnActiveToolChanging() + { + TryEndEditing(); + } + + void OnSelectionChanged() + { + TryEndEditing(); + TryBeginEditing(); + } } } diff --git a/Editor/Mono/Inspector/Editor.cs b/Editor/Mono/Inspector/Editor.cs index 28081c2eeb..30dcff03fc 100644 --- a/Editor/Mono/Inspector/Editor.cs +++ b/Editor/Mono/Inspector/Editor.cs @@ -3,7 +3,9 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Collections.Generic; using System.Linq; +using System.Reflection; using UnityEditor.Experimental.AssetImporters; using UnityEngine; using UnityEngine.Internal; @@ -240,7 +242,7 @@ internal static void DrawPreview(IPreviewable defaultPreview, Rect previewArea, if (Event.current.type == EventType.Repaint && text != string.Empty) { var textHeight = Styles.dropShadowLabelStyle.CalcHeight(GUIContent.Temp(text), previewArea.width); - EditorGUI.LabelField(new Rect(previewArea.x, previewArea.yMax - textHeight - kPreviewLabelPadding, previewArea.width, textHeight), text, Styles.preOverlayLabel); + EditorGUI.DropShadowLabel(new Rect(previewArea.x, previewArea.yMax - textHeight - kPreviewLabelPadding, previewArea.width, textHeight), text); } } @@ -305,6 +307,22 @@ public CustomEditorForRenderPipelineAttribute(Type inspectedType, Type renderPip public sealed partial class CanEditMultipleObjects : System.Attribute {} + [AttributeUsage(AttributeTargets.Field)] + internal sealed class CachePropertyAttribute : System.Attribute + { + public string propertyPath { get; } + + public CachePropertyAttribute() + { + propertyPath = null; + } + + public CachePropertyAttribute(string propertyPath) + { + this.propertyPath = propertyPath; + } + } + // Base class to derive custom Editors from. Use this to create your own custom inspectors and editors for your objects. [ExcludeFromObjectFactory] public partial class Editor : ScriptableObject, IPreviewable, IToolModeOwner @@ -569,6 +587,7 @@ private void CreateSerializedObject() { m_SerializedObject = new SerializedObject(targets, m_Context); m_SerializedObject.inspectorMode = inspectorMode; + AssignCachedProperties(this, m_SerializedObject.GetIterator()); m_EnabledProperty = m_SerializedObject.FindProperty("m_Enabled"); } catch (ArgumentException e) @@ -579,6 +598,54 @@ private void CreateSerializedObject() } } + internal static void AssignCachedProperties(T self, SerializedProperty root) where T : class + { + var fields = ScriptAttributeUtility.GetAutoLoadProperties(typeof(T)); + if (fields.Count == 0) + return; + + var properties = new Dictionary(fields.Count); + var allParents = new HashSet(); + foreach (var fieldInfo in fields) + { + var attribute = (CachePropertyAttribute)fieldInfo.GetCustomAttributes(typeof(CachePropertyAttribute), false).First(); + var propertyName = string.IsNullOrEmpty(attribute.propertyPath) ? fieldInfo.Name : attribute.propertyPath; + properties.Add(propertyName, fieldInfo); + int dot = propertyName.LastIndexOf('.'); + while (dot != -1) + { + propertyName = propertyName.Substring(0, dot); + if (!allParents.Add(propertyName)) + break; + dot = propertyName.LastIndexOf('.'); + } + } + + var parentPath = root.propertyPath; + var parentPathLength = parentPath.Length > 0 ? parentPath.Length + 1 : 0; + var exitCount = properties.Count; + var iterator = root.Copy(); + bool enterChildren = true; + while (iterator.Next(enterChildren) && exitCount > 0) + { + FieldInfo fieldInfo; + var propertyPath = iterator.propertyPath.Substring(parentPathLength); + if (properties.TryGetValue(propertyPath, out fieldInfo)) + { + fieldInfo.SetValue(self, iterator.Copy()); + properties.Remove(propertyPath); + exitCount--; + } + + enterChildren = allParents.Contains(propertyPath); + } + iterator.Dispose(); + if (exitCount > 0) + { + Debug.LogWarning("The following properties registered with CacheProperty where not found during the inspector creation: " + string.Join(", ", properties.Keys.ToArray())); + } + } + internal virtual void InternalSetTargets(UnityObject[] t) { m_Targets = t; } internal void InternalSetHidden(bool hidden) { hideInspector = hidden; } internal void InternalSetContextObject(UnityObject context) { m_Context = context; } @@ -859,11 +926,18 @@ internal virtual Rect DrawHeaderHelpAndSettingsGUI(Rect r) float currentOffset = settingsSize.x; const int kTopMargin = 5; - // Settings + // Settings; process event even for disabled UI Rect settingsRect = new Rect(r.xMax - currentOffset, r.y + kTopMargin, settingsSize.x, settingsSize.y); - if (EditorGUI.DropdownButton(settingsRect, EditorGUI.GUIContents.titleSettingsIcon, FocusType.Passive, - EditorStyles.iconButton)) + var wasEnabled = GUI.enabled; + GUI.enabled = true; + var showMenu = EditorGUI.DropdownButton(settingsRect, EditorGUI.GUIContents.titleSettingsIcon, FocusType.Passive, + EditorStyles.iconButton); + GUI.enabled = wasEnabled; + if (showMenu) + { EditorUtility.DisplayObjectContextMenu(settingsRect, targets, 0); + } + currentOffset += settingsSize.x; // Show Editor Header Items. @@ -937,9 +1011,14 @@ internal static Rect DrawHeaderGUI(Editor editor, string header, float leftMargi else GUI.Label(titleRect, header, EditorStyles.largeLabel); - // Context Menu + // Context Menu; process event even for disabled UI + var wasEnabled = GUI.enabled; + GUI.enabled = true; Event evt = Event.current; - if (editor != null && evt.type == EventType.MouseDown && evt.button == 1 && r.Contains(evt.mousePosition)) + var showMenu = editor != null && evt.type == EventType.MouseDown && evt.button == 1 && r.Contains(evt.mousePosition); + GUI.enabled = wasEnabled; + + if (showMenu) { EditorUtility.DisplayObjectContextMenu(new Rect(evt.mousePosition.x, evt.mousePosition.y, 0, 0), editor.targets, 0); evt.Use(); @@ -1073,6 +1152,13 @@ internal bool CanBeExpandedViaAFoldout() m_SerializedObject.Update(); m_SerializedObject.inspectorMode = inspectorMode; + return CanBeExpandedViaAFoldoutWithoutUpdate(); + } + + internal bool CanBeExpandedViaAFoldoutWithoutUpdate() + { + if (m_SerializedObject == null) + CreateSerializedObject(); SerializedProperty property = m_SerializedObject.GetIterator(); bool analyzePropertyChildren = true; diff --git a/Editor/Mono/Inspector/EditorElement.cs b/Editor/Mono/Inspector/EditorElement.cs index ab614d245c..6f361e2ee0 100644 --- a/Editor/Mono/Inspector/EditorElement.cs +++ b/Editor/Mono/Inspector/EditorElement.cs @@ -15,9 +15,25 @@ namespace UnityEditor.UIElements internal class EditorElement : VisualElement { readonly InspectorWindow inspectorWindow; + Editor[] m_EditorCache; - Editor[] m_Editors => inspectorWindow.tracker.activeEditors; + private Editor[] PopulateCache() + { + m_EditorCache = inspectorWindow.tracker.activeEditors; + return m_EditorCache; + } + Editor[] m_Editors + { + get + { + if (m_EditorCache == null || m_EditorIndex >= m_EditorCache.Length || !m_EditorCache[m_EditorIndex]) + { + PopulateCache(); + } + return m_EditorCache; + } + } internal IEnumerable Editors => m_Editors.AsEnumerable(); int m_EditorIndex; @@ -37,7 +53,7 @@ private bool IsEditorValid() { if (m_EditorIndex < m_Editors.Length) { - return m_Editors[m_EditorIndex] != null; + return m_Editors[m_EditorIndex]; } return false; } @@ -51,6 +67,8 @@ private bool IsEditorValid() internal InspectorElement m_InspectorElement { get; private set; } IMGUIContainer m_Footer; + private bool m_WasVisible = false; + static class Styles { public static GUIStyle importedObjectsHeaderStyle = new GUIStyle("IN BigTitle"); @@ -67,6 +85,7 @@ internal EditorElement(int editorIndex, InspectorWindow iw) { m_EditorIndex = editorIndex; inspectorWindow = iw; + pickingMode = PickingMode.Ignore; Init(); @@ -83,6 +102,7 @@ internal EditorElement(int editorIndex, InspectorWindow iw) void Init() { + var editors = PopulateCache(); Object editorTarget = editor.targets[0]; string editorTitle = ObjectNames.GetInspectorTitle(editorTarget); @@ -102,7 +122,7 @@ void Init() m_InspectorElement.name = editorTitle + "Inspector"; m_InspectorElement.style.paddingBottom = InspectorWindow.kEditorElementPaddingBottom; - if (EditorNeedsVerticalOffset(editorTarget)) + if (EditorNeedsVerticalOffset(editors, editorTarget)) { m_InspectorElement.style.overflow = Overflow.Hidden; } @@ -112,6 +132,7 @@ void Init() internal void Reinit(int editorIndex) { + PopulateCache(); Object editorTarget = editor.targets[0]; string editorTitle = ObjectNames.GetInspectorTitle(editorTarget); @@ -119,7 +140,7 @@ internal void Reinit(int editorIndex) m_Header.onGUIHandler = HeaderOnGUI; m_Footer.onGUIHandler = FooterOnGUI; - m_InspectorElement.editor = editor; + m_InspectorElement.AssignExistingEditor(editor); name = editorTitle; m_InspectorElement.name = editorTitle + "Inspector"; @@ -131,13 +152,15 @@ internal void Reinit(int editorIndex) private void UpdateInspectorVisibility() { - if (!editor.CanBeExpandedViaAFoldout()) + if (editor.CanBeExpandedViaAFoldoutWithoutUpdate()) { - SetElementVisible(m_InspectorElement, false); + m_Footer.style.marginTop = m_WasVisible ? 0 : -kFooterDefaultHeight; + m_InspectorElement.style.paddingBottom = InspectorWindow.kEditorElementPaddingBottom; } else { - SetElementVisible(m_InspectorElement, true); + m_Footer.style.marginTop = -kFooterDefaultHeight; + m_InspectorElement.style.paddingBottom = 0; } } @@ -180,6 +203,7 @@ internal static void InvalidateIMGUILayouts(VisualElement element) void HeaderOnGUI() { + var editors = PopulateCache(); if (!IsEditorValid()) { SetElementVisible(m_InspectorElement, false); @@ -187,6 +211,8 @@ void HeaderOnGUI() } var target = editor.target; + if ((target.hideFlags & HideFlags.HideInInspector) == HideFlags.HideInInspector) + return; // Avoid drawing editor if native target object is not alive, unless it's a MonoBehaviour/ScriptableObject // We want to draw the generic editor with a warning about missing/invalid script @@ -199,7 +225,7 @@ void HeaderOnGUI() return; } - bool wasVisible = inspectorWindow.WasEditorVisible(m_Editors, m_EditorIndex, target); + m_WasVisible = inspectorWindow.WasEditorVisible(editors, m_EditorIndex, target); GUIUtility.GetControlID(target.GetInstanceID(), FocusType.Passive); EditorGUIUtility.ResetGUIState(); @@ -211,7 +237,7 @@ void HeaderOnGUI() ScriptAttributeUtility.propertyHandlerCache = editor.propertyHandlerCache; using (new InspectorWindowUtils.LayoutGroupChecker()) { - m_DragRect = DrawEditorHeader(target, ref wasVisible); + m_DragRect = DrawEditorHeader(editors, target, ref m_WasVisible); } if (GUI.changed) @@ -221,16 +247,16 @@ void HeaderOnGUI() InvalidateIMGUILayouts(this); } - wasVisible = wasVisible && editor.CanBeExpandedViaAFoldout(); - - if (wasVisible != IsElementVisible(m_InspectorElement)) + if (m_WasVisible != IsElementVisible(m_InspectorElement)) { - SetElementVisible(m_InspectorElement, wasVisible); + SetElementVisible(m_InspectorElement, m_WasVisible); } + UpdateInspectorVisibility(); + var multiEditingSupported = inspectorWindow.IsMultiEditingSupported(editor, target); - if (!multiEditingSupported && wasVisible) + if (!multiEditingSupported && m_WasVisible) { GUILayout.Label("Multi-object editing not supported.", EditorStyles.helpBox); return; @@ -263,25 +289,25 @@ void HeaderOnGUI() } } - Rect DrawEditorHeader(Object target, ref bool wasVisible) + Rect DrawEditorHeader(Editor[] editors, Object target, ref bool wasVisible) { - var largeHeader = DrawEditorLargeHeader(ref wasVisible); + var largeHeader = DrawEditorLargeHeader(editors, ref wasVisible); // Dragging handle used for editor reordering var dragRect = largeHeader ? new Rect() - : DrawEditorSmallHeader(target, wasVisible); + : DrawEditorSmallHeader(editors, target, wasVisible); return dragRect; } - bool DrawEditorLargeHeader(ref bool wasVisible) + bool DrawEditorLargeHeader(Editor[] editors, ref bool wasVisible) { if (!IsEditorValid()) { return true; } - bool largeHeader = InspectorWindow.EditorHasLargeHeader(m_EditorIndex, m_Editors); + bool largeHeader = InspectorWindow.EditorHasLargeHeader(m_EditorIndex, editors); // Draw large headers before we do the culling of unsupported editors below, // so the large header is always shown even when the editor can't be. @@ -296,7 +322,13 @@ bool DrawEditorLargeHeader(ref bool wasVisible) var importedObjectBarRect = GUILayoutUtility.GetRect(16, 20); importedObjectBarRect.height = 21; - var headerText = m_Editors[0] is PrefabImporterEditor ? "Root in Prefab Asset" : "Imported Object"; + var headerText = "Imported Object"; + if (editors.Length > 1) + { + if (editors[0] is PrefabImporterEditor && editors[1] is GameObjectInspector) + headerText = "Root in Prefab Asset"; + } + GUILayout.Label(headerText, Styles.importedObjectsHeaderStyle, GUILayout.ExpandWidth(true)); GUILayout.Space(-7f); // Ensures no spacing between this header and the next header } @@ -313,7 +345,7 @@ bool DrawEditorLargeHeader(ref bool wasVisible) // Draw small headers (the header above each component) after the culling above // so we don't draw a component header for all the components that can't be shown. - Rect DrawEditorSmallHeader(Object target, bool wasVisible) + Rect DrawEditorSmallHeader(Editor[] editors, Object target, bool wasVisible) { var currentEditor = editor; @@ -321,7 +353,7 @@ Rect DrawEditorSmallHeader(Object target, bool wasVisible) return GUILayoutUtility.GetLastRect(); // ensure first component's title bar is flush with the header - if (EditorNeedsVerticalOffset(target)) + if (EditorNeedsVerticalOffset(editors, target)) { // TODO: Check if we can fix this in the GameObjectInspector instead GUILayout.Space( @@ -333,13 +365,7 @@ Rect DrawEditorSmallHeader(Object target, bool wasVisible) using (new EditorGUI.DisabledScope(!currentEditor.IsEnabled())) { - // Woraround: Temporarily adjust the top padding to 2px in order to vertically center the content of the titlebar. - // This is because there is a 3px bottom margin used to be able to pick and drag a component section - var oldPadding = EditorStyles.inspectorTitlebar.padding.top; - EditorStyles.inspectorTitlebar.padding.top = 2; - bool isVisible = EditorGUILayout.InspectorTitlebar(wasVisible, currentEditor); - EditorStyles.inspectorTitlebar.padding.top = oldPadding; if (wasVisible != isVisible) { @@ -379,11 +405,14 @@ internal static void SetElementVisible(InspectorElement ve, bool visible) static void SetInspectorElementChildIMGUIContainerFocusable(InspectorElement ve, bool focusable) { - foreach (var child in ve.Children()) + var childCount = ve.childCount; + + for (int i = 0; i < childCount; ++i) { - var imguiContainer = child as IMGUIContainer; - if (imguiContainer != null) + var child = ve[i]; + if (child.isIMGUIContainer) { + var imguiContainer = (IMGUIContainer)child; imguiContainer.focusable = focusable; } } @@ -402,6 +431,7 @@ IMGUIContainer BuildFooterElement(string editorTitle) void FooterOnGUI() { + var editors = m_EditorCache; var ed = editor; if (ed == null) @@ -409,17 +439,20 @@ void FooterOnGUI() return; } + var target = ed.target; + if ((target.hideFlags & HideFlags.HideInInspector) == HideFlags.HideInInspector) + return; + m_ContentRect.y = -m_ContentRect.height; - inspectorWindow.editorDragging.HandleDraggingToEditor(m_Editors, m_EditorIndex, m_DragRect, m_ContentRect); + inspectorWindow.editorDragging.HandleDraggingToEditor(editors, m_EditorIndex, m_DragRect, m_ContentRect); HandleComponentScreenshot(m_ContentRect, ed); - var target = ed.target; var comp = target as Component; if (EditorGUI.ShouldDrawOverrideBackground(ed.targets, Event.current, comp)) { var rect = GUILayoutUtility.kDummyRect; - bool wasVisible = inspectorWindow.WasEditorVisible(m_Editors, m_EditorIndex, target); + bool wasVisible = inspectorWindow.WasEditorVisible(editors, m_EditorIndex, target); // if the inspector is currently visible then the override background drawn by the footer needs to be slightly larger than if the inspector is collapsed if (wasVisible) { @@ -453,9 +486,9 @@ void HandleComponentScreenshot(Rect content, Editor editor) #endregion Footer - internal bool EditorNeedsVerticalOffset(Object target) + internal bool EditorNeedsVerticalOffset(Editor[] editors, Object target) { - return m_EditorIndex > 0 && IsEditorValid() && m_Editors[m_EditorIndex - 1].target is GameObject && target is Component; + return m_EditorIndex > 0 && IsEditorValid() && editors[m_EditorIndex - 1].target is GameObject && target is Component; } } } diff --git a/Editor/Mono/Inspector/EditorSettingsInspector.cs b/Editor/Mono/Inspector/EditorSettingsInspector.cs index ba6633eb26..3c51f536a6 100644 --- a/Editor/Mono/Inspector/EditorSettingsInspector.cs +++ b/Editor/Mono/Inspector/EditorSettingsInspector.cs @@ -2,15 +2,15 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; using System.Collections.Generic; +using System.Linq; using UnityEditorInternal; using UnityEngine; using UnityEditor.Hardware; using UnityEditor.VersionControl; -using UnityEngine.SceneManagement; -using UnityEditor.SceneManagement; using UnityEditor.Collaboration; -using UnityEditor.Web; +using UnityEditor.Experimental; namespace UnityEditor { @@ -28,18 +28,23 @@ class Content public static GUIContent versionControl = EditorGUIUtility.TrTextContent("Version Control"); public static GUIContent mode = EditorGUIUtility.TrTextContent("Mode"); public static GUIContent logLevel = EditorGUIUtility.TrTextContent("Log Level"); - public static GUIContent status = EditorGUIUtility.TrTextContent("Status"); - public static GUIContent automaticAdd = EditorGUIUtility.TrTextContent("Automatic add"); + public static GUIContent automaticAdd = EditorGUIUtility.TrTextContent("Automatic Add", "Automatically add newly created assets to version control."); public static GUIContent smartMerge = EditorGUIUtility.TrTextContent("Smart merge"); - public static GUIContent workOffline = EditorGUIUtility.TrTextContent("Work Offline"); - public static GUIContent allowAsyncUpdate = EditorGUIUtility.TrTextContent("Allow Async Update"); - public static GUIContent showFailedCheckouts = EditorGUIUtility.TrTextContent("Show Failed Checkouts"); + public static GUIContent vcsConnect = EditorGUIUtility.TrTextContent("Connect"); + public static GUIContent vcsReconnect = EditorGUIUtility.TrTextContent("Reconnect"); + public static GUIContent workOffline = EditorGUIUtility.TrTextContent("Work Offline", "Enable asset modifications even when not connected to a version control server. Requires manual integration into VCS system afterwards."); + public static GUIContent allowAsyncUpdate = EditorGUIUtility.TrTextContent("Allow Async Update", "Enable asynchronous file status queries (use with slow server connections)."); + public static GUIContent showFailedCheckouts = EditorGUIUtility.TrTextContent("Show Failed Checkouts", "Show dialogs for failed 'Check Out' operations."); public static GUIContent overwriteFailedCheckoutAssets = EditorGUIUtility.TrTextContent("Overwrite Failed Checkout Assets", "When on, assets that can not be checked out will get saved anyway."); public static GUIContent overlayIcons = EditorGUIUtility.TrTextContent("Overlay Icons", "Should version control status icons be shown in project view."); - public static GUIContent assetPipeline = EditorGUIUtility.TrTextContent("Asset Pipeline (experimental)"); - public static GUIContent cacheServer = EditorGUIUtility.TrTextContent("Cache Server"); + public static GUIContent assetPipeline = EditorGUIUtility.TrTextContent("Asset Pipeline"); + public static GUIContent cacheServer = EditorGUIUtility.TrTextContent("Cache Server (project specific)"); + public static GUIContent cacheServerIPLabel = EditorGUIUtility.TrTextContent("IP address"); + public static GUIContent cacheServerNamespacePrefixLabel = EditorGUIUtility.TrTextContent("Namespace prefix"); + public static GUIContent cacheServerEnableDownloadLabel = EditorGUIUtility.TrTextContent("Download"); + public static GUIContent cacheServerEnableUploadLabel = EditorGUIUtility.TrTextContent("Upload"); public static GUIContent assetSerialization = EditorGUIUtility.TrTextContent("Asset Serialization"); public static GUIContent defaultBehaviorMode = EditorGUIUtility.TrTextContent("Default Behaviour Mode"); @@ -179,13 +184,13 @@ public PopupElement(string content, bool requiresTeamLicense) private PopupElement[] assetPipelineModePopupList = { - new PopupElement("Version 1"), - new PopupElement("Version 2 (experimental)"), + new PopupElement("Version 1 (deprecated)"), + new PopupElement("Version 2"), }; private PopupElement[] cacheServerModePopupList = { - new PopupElement("As preferences"), + new PopupElement("Use global settings (stored in preferences)"), new PopupElement("Enabled"), new PopupElement("Disabled"), }; @@ -223,30 +228,14 @@ public PopupElement(string content, bool requiresTeamLicense) SerializedProperty m_AsyncShaderCompilation; - - private enum AssetPipelineMode - { - Version1, - Version2, - } - - private enum CacheServerMode - { - AsPreferences, - Enabled, - Disabled, - } - - ReorderableList m_CacheServerList; - enum CacheServerConnectionState { Unknown, Success, Failure }; + enum CacheServerConnectionState { Unknown, Success, Failure } private CacheServerConnectionState m_CacheServerConnectionState; private static string s_ForcedAssetPipelineWarning; - const string kEditorUserSettingsPath = "Library/EditorUserSettings.asset"; - SerializedObject m_EditorUserSettings; - SerializedProperty m_AssetPipelineMode; - SerializedProperty m_CacheServerMode; - SerializedProperty m_CacheServers; + const int kVCFieldRecentCount = 10; + const string kVCFieldRecentPrefix = "vcs_ConfigField"; + Dictionary m_VCConfigFieldsRecentValues = new Dictionary(); + bool m_NeedToSaveValuesOnConnect; public void OnEnable() { @@ -269,14 +258,18 @@ public void OnEnable() m_AsyncShaderCompilation = serializedObject.FindProperty("m_AsyncShaderCompilation"); - LoadEditorUserSettings(); + m_CacheServerConnectionState = CacheServerConnectionState.Unknown; + s_ForcedAssetPipelineWarning = null; } public void OnDisable() { DevDeviceList.Changed -= OnDeviceListChanged; - if (m_EditorUserSettings.targetObject != null && EditorUtility.IsDirty(m_EditorUserSettings.targetObject)) - InternalEditorUtility.SaveToSerializedFileAndForget(new[] { m_EditorUserSettings.targetObject }, kEditorUserSettingsPath, true); + if (AssetDatabase.IsV2Enabled()) + { + AssetDatabaseExperimental.RefreshCacheServerNamespacePrefix(); + AssetDatabaseExperimental.RefreshConnectionToCacheServer(); + } } void OnDeviceListChanged() @@ -284,26 +277,6 @@ void OnDeviceListChanged() BuildRemoteDeviceList(); } - private void DrawCacheServerListElement(Rect rect, int index, bool selected, bool focused) - { - rect.height -= 2; // nicer looking with selected list row and a text field in it - - // De-indent by the drag handle width, so the text field lines up with others in the inspector. - // Will have space in front of label for more space between it and the drag handle. - rect.xMin -= ReorderableList.Defaults.dragHandleWidth; - - string oldName = m_CacheServers.GetArrayElementAtIndex(index).stringValue; - if (string.IsNullOrEmpty(oldName)) - oldName = ""; - string newName = EditorGUI.TextField(rect, " Server " + index, oldName); - - if (newName != oldName) - { - m_CacheServers.GetArrayElementAtIndex(index).stringValue = newName; - } - } - - void BuildRemoteDeviceList() { var devices = new List(); @@ -332,13 +305,59 @@ void BuildRemoteDeviceList() remoteDevicePopupList = popupList.ToArray(); } - public override void OnInspectorGUI() + string[] GetVCConfigFieldRecentValues(string fieldName) + { + if (m_VCConfigFieldsRecentValues.ContainsKey(fieldName)) + return m_VCConfigFieldsRecentValues[fieldName]; + + var res = new List(); + for (var i = 0; i < kVCFieldRecentCount; ++i) + { + var prefName = $"{kVCFieldRecentPrefix}{fieldName}{i}"; + var prefValue = EditorPrefs.GetString(prefName); + if (!string.IsNullOrEmpty(prefValue)) + res.Add(prefValue); + } + + var arr = res.ToArray(); + m_VCConfigFieldsRecentValues[fieldName] = arr; + return arr; + } + + void UpdateVCConfigFieldRecentValue(string fieldName, string value) + { + if (string.IsNullOrEmpty(value)) + return; + var arr = GetVCConfigFieldRecentValues(fieldName); + var newVal = new[] {value}; + // put newly used value in front + arr = newVal.Concat(arr.Except(newVal)).Take(kVCFieldRecentCount).ToArray(); + m_VCConfigFieldsRecentValues[fieldName] = arr; + + for (var i = 0; i < arr.Length; ++i) + { + var prefName = $"{kVCFieldRecentPrefix}{fieldName}{i}"; + EditorPrefs.SetString(prefName, arr[i]); + } + } + + void UpdateVCConfigFieldRecentValues(ConfigField[] fields) { - if (m_EditorUserSettings != null && !m_EditorUserSettings.targetObject) + if (fields == null) + return; + foreach (var field in fields) { - LoadEditorUserSettings(); + if (field.isPassword) + continue; + var val = EditorUserSettings.GetConfigValue(field.name); + if (string.IsNullOrEmpty(val)) + continue; + UpdateVCConfigFieldRecentValue(field.name, val); } + } + public override void OnInspectorGUI() + { serializedObject.Update(); // GUI.enabled hack because we don't want some controls to be disabled if the EditorSettings.asset is locked @@ -364,6 +383,8 @@ public override void OnInspectorGUI() EditorGUILayout.HelpBox("Version Control not available when using Collaboration feature.", MessageType.Warning); } + ConfigField[] configFields = null; + if (VersionControlSystemHasGUI()) { GUI.enabled = true; @@ -376,7 +397,7 @@ public override void OnInspectorGUI() } else { - ConfigField[] configFields = Provider.GetActiveConfigFields(); + configFields = Provider.GetActiveConfigFields(); hasRequiredFields = true; @@ -386,13 +407,14 @@ public override void OnInspectorGUI() string oldVal = EditorUserSettings.GetConfigValue(field.name); if (field.isPassword) { - newVal = EditorGUILayout.PasswordField(field.label, oldVal); + newVal = EditorGUILayout.PasswordField(GUIContent.Temp(field.label, field.description), oldVal); if (newVal != oldVal) EditorUserSettings.SetPrivateConfigValue(field.name, newVal); } else { - newVal = EditorGUILayout.TextField(field.label, oldVal); + var recentValues = GetVCConfigFieldRecentValues(field.name); + newVal = EditorGUILayout.TextFieldDropDown(GUIContent.Temp(field.label, field.description), oldVal, recentValues); if (newVal != oldVal) EditorUserSettings.SetConfigValue(field.name, newVal); } @@ -422,33 +444,45 @@ public override void OnInspectorGUI() EditorUserSettings.SetConfigValue("vcSharedLogLevel", logLevelPopupList[newIdx].ToLower()); } - GUI.enabled = editorEnabled; - - string osState = "Connected"; - if (Provider.onlineState == OnlineState.Updating) - osState = "Connecting..."; - else if (Provider.onlineState == OnlineState.Offline) - osState = "Disconnected"; + if (Provider.onlineState == OnlineState.Offline) + { + var text = "Not Connected. " + (Provider.offlineReason ?? ""); + EditorGUILayout.HelpBox(text, MessageType.Error); + } + else if (Provider.onlineState == OnlineState.Updating) + { + var text = "Connecting..."; + EditorGUILayout.HelpBox(text, MessageType.Info); + } else if (EditorUserSettings.WorkOffline) - osState = "Work Offline"; - - EditorGUILayout.LabelField(Content.status.text, osState); - - if (Provider.onlineState != OnlineState.Online && !string.IsNullOrEmpty(Provider.offlineReason)) { - GUI.enabled = false; - GUILayout.TextArea(Provider.offlineReason); - GUI.enabled = editorEnabled; + var text = "Working Offline. Manually integrate your changes using a version control client, and uncheck 'Work Offline' setting below to get back to regular state."; + EditorGUILayout.HelpBox(text, MessageType.Warning); + } + else if (Provider.onlineState == OnlineState.Online) + { + var text = "Connected"; + EditorGUILayout.HelpBox(text, MessageType.Info); } + GUI.enabled = editorEnabled; + GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); GUI.enabled = hasRequiredFields && Provider.onlineState != OnlineState.Updating; - if (GUILayout.Button("Connect", EditorStyles.miniButton)) + if (GUILayout.Button(Provider.onlineState != OnlineState.Offline ? Content.vcsReconnect : Content.vcsConnect, EditorStyles.miniButton)) + { + m_NeedToSaveValuesOnConnect = true; Provider.UpdateSettings(); + } GUILayout.EndHorizontal(); - EditorUserSettings.AutomaticAdd = EditorGUILayout.Toggle(Content.automaticAdd, EditorUserSettings.AutomaticAdd); + if (m_NeedToSaveValuesOnConnect && Provider.onlineState == OnlineState.Online) + { + // save connection field settings if we got online with them successfully + m_NeedToSaveValuesOnConnect = false; + UpdateVCConfigFieldRecentValues(configFields); + } if (Provider.requiresNetwork) { @@ -463,9 +497,12 @@ public override void OnInspectorGUI() EditorUserSettings.WorkOffline = workOfflineNew; EditorApplication.RequestRepaintAllViews(); } + } + + EditorUserSettings.AutomaticAdd = EditorGUILayout.Toggle(Content.automaticAdd, EditorUserSettings.AutomaticAdd); + if (Provider.requiresNetwork) EditorUserSettings.allowAsyncStatusUpdate = EditorGUILayout.Toggle(Content.allowAsyncUpdate, EditorUserSettings.allowAsyncStatusUpdate); - } if (Provider.hasCheckoutSupport) { @@ -473,19 +510,18 @@ public override void OnInspectorGUI() EditorUserSettings.overwriteFailedCheckoutAssets = EditorGUILayout.Toggle(Content.overwriteFailedCheckoutAssets, EditorUserSettings.overwriteFailedCheckoutAssets); } + GUI.enabled = editorEnabled; + + EditorUserSettings.semanticMergeMode = (SemanticMergeMode)EditorGUILayout.Popup(Content.smartMerge, (int)EditorUserSettings.semanticMergeMode, semanticMergePopupList); + var newOverlayIcons = EditorGUILayout.Toggle(Content.overlayIcons, EditorUserSettings.overlayIcons); if (newOverlayIcons != EditorUserSettings.overlayIcons) { EditorUserSettings.overlayIcons = newOverlayIcons; EditorApplication.RequestRepaintAllViews(); } - - GUI.enabled = editorEnabled; - - // Semantic merge popup - EditorUserSettings.semanticMergeMode = (SemanticMergeMode)EditorGUILayout.Popup(Content.smartMerge, (int)EditorUserSettings.semanticMergeMode, semanticMergePopupList); - - DrawOverlayDescriptions(); + if (newOverlayIcons) + DrawOverlayDescriptions(); } GUILayout.Space(10); @@ -520,7 +556,7 @@ public override void OnInspectorGUI() DoAssetPipelineSettings(); - if (m_AssetPipelineMode.intValue == (int)AssetPipelineMode.Version2) + if (EditorSettings.assetPipelineMode == AssetPipelineMode.Version2) DoCacheServerSettings(); GUI.enabled = wasEnabled; @@ -591,38 +627,6 @@ public override void OnInspectorGUI() DoEnterPlayModeSettings(); serializedObject.ApplyModifiedProperties(); - m_EditorUserSettings.ApplyModifiedProperties(); - } - - private void LoadEditorUserSettings() - { - var editorUserSettingsObjects = InternalEditorUtility.LoadSerializedFileAndForget(kEditorUserSettingsPath); - foreach (var o in editorUserSettingsObjects) - { - if (o.GetType() == typeof(EditorUserSettings)) - { - m_EditorUserSettings = new SerializedObject(o); - } - } - - m_AssetPipelineMode = m_EditorUserSettings.FindProperty("m_AssetPipelineMode2"); - m_CacheServerMode = m_EditorUserSettings.FindProperty("m_CacheServerMode"); - m_CacheServers = m_EditorUserSettings.FindProperty("m_CacheServers"); - - m_CacheServerConnectionState = CacheServerConnectionState.Unknown; - s_ForcedAssetPipelineWarning = null; - - if (m_CacheServerList == null) - { - m_CacheServerList = new ReorderableList(serializedObject, m_CacheServers, true, false, true, true); - m_CacheServerList.onReorderCallback = (ReorderableList list) => { serializedObject.ApplyModifiedProperties(); }; - m_CacheServerList.onAddCallback = (ReorderableList list) => { m_CacheServers.arraySize += 1; serializedObject.ApplyModifiedProperties(); }; - m_CacheServerList.onRemoveCallback = (ReorderableList list) => { ReorderableList.defaultBehaviours.DoRemoveButton(list); serializedObject.ApplyModifiedProperties(); }; - m_CacheServerList.onCanRemoveCallback = (ReorderableList list) => { return list.index < m_CacheServers.arraySize && list.index >= 0; }; - m_CacheServerList.drawElementCallback = DrawCacheServerListElement; - m_CacheServerList.elementHeight = EditorGUIUtility.singleLineHeight + 2; - m_CacheServerList.headerHeight = 3; - } } private void DoProjectGenerationSettings() @@ -677,7 +681,7 @@ private static string GetForcedAssetPipelineWarning() else if (CacheServerPreferences.GetMagicFileAssetPipelineOverride()) s_ForcedAssetPipelineWarning = "Asset pipeline mode was forced via via magic adb2.txt file in project root. The above setting is not in effect before restarting without the magic file."; else - s_ForcedAssetPipelineWarning = ""; + s_ForcedAssetPipelineWarning = string.Empty; } return s_ForcedAssetPipelineWarning; } @@ -689,17 +693,24 @@ private void DoAssetPipelineSettings() var assetPipelineWarning = GetForcedAssetPipelineWarning(); - int index = Mathf.Clamp((int)m_AssetPipelineMode.intValue, 0, assetPipelineModePopupList.Length - 1); + int index = Mathf.Clamp((int)EditorSettings.assetPipelineMode, 0, assetPipelineModePopupList.Length - 1); CreatePopupMenu(Content.mode.text, assetPipelineModePopupList, index, SetAssetPipelineMode); EditorGUILayout.LabelField(Content.activeAssetPipelineVersionLabel, Content.activeAssetPipelineVersion); - bool isAssetPipelineVersion1 = m_AssetPipelineMode.intValue == (int)AssetPipelineMode.Version1; + bool isAssetPipelineVersion1 = EditorSettings.assetPipelineMode == AssetPipelineMode.Version1; if (!string.IsNullOrEmpty(assetPipelineWarning)) EditorGUILayout.HelpBox(assetPipelineWarning, MessageType.Info, true); else if (isAssetPipelineVersion1 != AssetDatabase.IsV1Enabled()) - EditorGUILayout.HelpBox("Changes in asset pipeline version will take effect after saving and restarting the project.", MessageType.Info, true); + { + var message = "Changes in Asset Pipeline Version will take effect after saving and restarting the project."; + + if (isAssetPipelineVersion1) + message += "\nPlease note that Asset Pipeline Version 1 is now deprecated."; + + EditorGUILayout.HelpBox(message, MessageType.Info, true); + } } private void DoCacheServerSettings() @@ -713,7 +724,7 @@ private void DoCacheServerSettings() EditorGUILayout.HelpBox("Cache Server remote address forced via command line argument. To use the cache server address specified here please restart Unity without the -CacheServerIPAddress command line argument.", MessageType.Info, true); } - int index = Mathf.Clamp((int)m_CacheServerMode.intValue, 0, cacheServerModePopupList.Length - 1); + int index = Mathf.Clamp((int)EditorSettings.cacheServerMode, 0, cacheServerModePopupList.Length - 1); CreatePopupMenu(Content.mode.text, cacheServerModePopupList, index, SetCacheServerMode); if (index != (int)CacheServerMode.Disabled) @@ -737,16 +748,37 @@ private void DoCacheServerSettings() if (isCacheServerEnabled) { - m_CacheServerList.DoLayoutList(); + var oldEndpoint = EditorSettings.cacheServerEndpoint; + var newEndpoint = EditorGUILayout.TextField(Content.cacheServerIPLabel, oldEndpoint); + if (newEndpoint != oldEndpoint) + { + EditorSettings.cacheServerEndpoint = newEndpoint; + } EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("Check Connection", GUILayout.Width(150))) { - if (InternalEditorUtility.CanConnectToCacheServer()) - m_CacheServerConnectionState = CacheServerConnectionState.Success; + if (AssetDatabase.IsV2Enabled()) + { + var address = EditorSettings.cacheServerEndpoint.Split(':'); + var ip = address[0]; + UInt16 port = 0; // If 0, will use the default set port + if (address.Length == 2) + port = Convert.ToUInt16(address[1]); + + if (AssetDatabaseExperimental.CanConnectToCacheServer(ip, port)) + m_CacheServerConnectionState = CacheServerConnectionState.Success; + else + m_CacheServerConnectionState = CacheServerConnectionState.Failure; + } else - m_CacheServerConnectionState = CacheServerConnectionState.Failure; + { + if (InternalEditorUtility.CanConnectToCacheServer()) + m_CacheServerConnectionState = CacheServerConnectionState.Success; + else + m_CacheServerConnectionState = CacheServerConnectionState.Failure; + } } GUILayout.Space(25); @@ -767,6 +799,25 @@ private void DoCacheServerSettings() } EditorGUILayout.EndHorizontal(); + + var old = EditorSettings.cacheServerNamespacePrefix; + var newvalue = EditorGUILayout.TextField(Content.cacheServerNamespacePrefixLabel, old); + if (newvalue != old) + { + EditorSettings.cacheServerNamespacePrefix = newvalue; + } + + EditorGUI.BeginChangeCheck(); + bool enableDownload = EditorSettings.cacheServerEnableDownload; + enableDownload = EditorGUILayout.Toggle(Content.cacheServerEnableDownloadLabel, enableDownload); + if (EditorGUI.EndChangeCheck()) + EditorSettings.cacheServerEnableDownload = enableDownload; + + EditorGUI.BeginChangeCheck(); + bool enableUpload = EditorSettings.cacheServerEnableUpload; + enableUpload = EditorGUILayout.Toggle(Content.cacheServerEnableUploadLabel, enableUpload); + if (EditorGUI.EndChangeCheck()) + EditorSettings.cacheServerEnableUpload = enableUpload; } } } @@ -872,26 +923,26 @@ private void ShowUnityRemoteGUI(bool editorEnabled) DoPopup(popupRect, remoteJoystickSourceList, joystickSource, SetUnityRemoteJoystickSource); } - private void DrawOverlayDescriptions() + void DrawOverlayDescriptions() { Texture2D atlas = Provider.overlayAtlas; if (atlas == null) return; - GUILayout.Space(10); - GUILayout.Label("Overlay legends", EditorStyles.boldLabel); GUILayout.BeginHorizontal(); GUILayout.BeginVertical(); DrawOverlayDescription(Asset.States.Local); DrawOverlayDescription(Asset.States.OutOfSync); DrawOverlayDescription(Asset.States.CheckedOutLocal); DrawOverlayDescription(Asset.States.CheckedOutRemote); - DrawOverlayDescription(Asset.States.DeletedLocal); - DrawOverlayDescription(Asset.States.DeletedRemote); GUILayout.EndVertical(); GUILayout.BeginVertical(); + DrawOverlayDescription(Asset.States.DeletedLocal); + DrawOverlayDescription(Asset.States.DeletedRemote); DrawOverlayDescription(Asset.States.AddedLocal); DrawOverlayDescription(Asset.States.AddedRemote); + GUILayout.EndVertical(); + GUILayout.BeginVertical(); DrawOverlayDescription(Asset.States.Conflicted); DrawOverlayDescription(Asset.States.LockedLocal); DrawOverlayDescription(Asset.States.LockedRemote); @@ -1041,29 +1092,12 @@ private void SetSpritePackerPaddingPower(object data) private void SetAssetPipelineMode(object data) { - int newValue = (int)data; - - int oldValue = m_AssetPipelineMode.intValue; - if (oldValue == newValue) - return; - - m_AssetPipelineMode.intValue = newValue; - m_EditorUserSettings.ApplyModifiedProperties(); - m_EditorUserSettings.Update(); - EditorUtility.SetDirty(m_EditorUserSettings.targetObject); + EditorSettings.assetPipelineMode = (AssetPipelineMode)data; } private void SetCacheServerMode(object data) { - int newValue = (int)data; - - if (m_CacheServerMode.intValue == newValue) - return; - - m_CacheServerMode.intValue = newValue; - m_EditorUserSettings.ApplyModifiedProperties(); - m_EditorUserSettings.Update(); - EditorUtility.SetDirty(m_EditorUserSettings.targetObject); + EditorSettings.cacheServerMode = (CacheServerMode)data; } diff --git a/Editor/Mono/Inspector/GameObjectInspector.cs b/Editor/Mono/Inspector/GameObjectInspector.cs index ed38c9bd9c..c9010b413e 100644 --- a/Editor/Mono/Inspector/GameObjectInspector.cs +++ b/Editor/Mono/Inspector/GameObjectInspector.cs @@ -234,6 +234,11 @@ private static bool ShowMixedStaticEditorFlags(StaticEditorFlags mask) return countedBits > 0 && countedBits != numFlags; } + public override bool UseDefaultMargins() + { + return false; + } + protected override void OnHeaderGUI() { bool enabledTemp = GUI.enabled; @@ -475,7 +480,7 @@ private void DoLayerField(GameObject go) private void DoTagsField(GameObject go) { - string tagName = go.tag ?? "Undefined"; + string tagName = go.tag; EditorGUIUtility.labelWidth = Styles.tagFieldWidth; Rect tagRect = GUILayoutUtility.GetRect(GUIContent.none, Styles.tagPopup); EditorGUI.BeginProperty(tagRect, GUIContent.none, m_Tag); @@ -906,9 +911,26 @@ public void OnSceneDrag(SceneView sceneView) Scene destinationScene = sceneView.customScene.IsValid() ? sceneView.customScene : SceneManager.GetActiveScene(); if (dragObject == null) { - dragObject = (GameObject)PrefabUtility.InstantiatePrefab(prefabAssetRoot, destinationScene); + // While dragging the instantiated prefab we do not want to record undo for this object + // this will cause a remerge of the instance since changes are undone while dragging. + // The DrivenRectTransformTracker by default records Undo when used when driving + // UI components. This breaks our hideflag setup below due to a remerge of the dragged instance. + // StartRecordingUndo() is called on DragExited. Fixes case 1223793. + DrivenRectTransformTracker.StopRecordingUndo(); + + if (!EditorApplication.isPlaying || EditorSceneManager.IsPreviewScene(destinationScene)) + { + dragObject = (GameObject)PrefabUtility.InstantiatePrefab(prefabAssetRoot, destinationScene); + dragObject.name = go.name; + } + else + { + // Instatiate as regular GameObject in Play Mode so runtime logic + // won't run into restrictions on restructuring Prefab instances. + dragObject = Instantiate(prefabAssetRoot); + SceneManager.MoveGameObjectToScene(dragObject, destinationScene); + } dragObject.hideFlags = HideFlags.HideInHierarchy; - dragObject.name = go.name; } if (HandleUtility.ignoreRaySnapObjects == null) @@ -943,6 +965,10 @@ public void OnSceneDrag(SceneView sceneView) evt.Use(); break; case EventType.DragPerform: + // DragExited is always fired after DragPerform so we do no need to call StartRecordingUndo + // in DragPerform + DrivenRectTransformTracker.StartRecordingUndo(); + var stage = StageNavigationManager.instance.currentItem; if (stage.isPrefabStage) @@ -968,7 +994,8 @@ public void OnSceneDrag(SceneView sceneView) HandleUtility.ignoreRaySnapObjects = null; if (SceneView.mouseOverWindow != null) SceneView.mouseOverWindow.Focus(); - dragObject.name = uniqueName; + if (!Application.IsPlaying(dragObject)) + dragObject.name = uniqueName; dragObject = null; evt.Use(); break; diff --git a/Editor/Mono/Inspector/GenericInspector.cs b/Editor/Mono/Inspector/GenericInspector.cs index d44688db83..5172a33430 100644 --- a/Editor/Mono/Inspector/GenericInspector.cs +++ b/Editor/Mono/Inspector/GenericInspector.cs @@ -64,9 +64,9 @@ internal override bool GetOptimizedGUIBlock(bool isDirty, bool isVisible, out fl while (property.NextVisible(childrenAreExpanded)) { var handler = ScriptAttributeUtility.GetHandler(property); - height += handler.GetHeight(property, null, false) + EditorGUI.kControlVerticalSpacing; - - childrenAreExpanded = property.isExpanded && EditorGUI.HasVisibleChildFields(property); + var hasPropertyDrawer = handler.propertyDrawer != null; + height += handler.GetHeight(property, null, hasPropertyDrawer) + EditorGUI.kControlVerticalSpacing; + childrenAreExpanded = !hasPropertyDrawer && property.isExpanded && EditorGUI.HasVisibleChildFields(property); } m_LastHeight = height; @@ -99,16 +99,16 @@ internal override bool OnOptimizedInspectorGUI(Rect contentRect) while (property.NextVisible(childrenAreExpanded)) { var handler = ScriptAttributeUtility.GetHandler(property); - contentRect.height = handler.GetHeight(property, null, false); + var hasPropertyDrawer = handler.propertyDrawer != null; + childrenAreExpanded = !hasPropertyDrawer && property.isExpanded && EditorGUI.HasVisibleChildFields(property); + contentRect.height = handler.GetHeight(property, null, hasPropertyDrawer); if (contentRect.Overlaps(visibleRect)) { EditorGUI.indentLevel = property.depth; using (new EditorGUI.DisabledScope(isInspectorModeNormal && "m_Script" == property.propertyPath)) - childrenAreExpanded = handler.OnGUI(contentRect, property, null, false, visibleRect) && property.isExpanded; + childrenAreExpanded &= handler.OnGUI(contentRect, property, null, false, visibleRect); } - else - childrenAreExpanded = property.isExpanded && EditorGUI.HasVisibleChildFields(property); contentRect.y += contentRect.height + EditorGUI.kControlVerticalSpacing; } diff --git a/Editor/Mono/Inspector/GraphicsSettingsInspector.cs b/Editor/Mono/Inspector/GraphicsSettingsInspector.cs index 8d9ef53c1d..4d35110ea7 100644 --- a/Editor/Mono/Inspector/GraphicsSettingsInspector.cs +++ b/Editor/Mono/Inspector/GraphicsSettingsInspector.cs @@ -143,7 +143,6 @@ public override void OnInspectorGUI() bool usingSRP = GraphicsSettings.currentRenderPipeline != null; - if (usingSRP) EditorGUILayout.HelpBox("A Scriptable Render Pipeline is in use, some settings will not be used and are hidden", MessageType.Info); @@ -156,7 +155,17 @@ public override void OnInspectorGUI() EditorGUILayout.Space(); } - TierSettingsGUI(); + // Hide tier settings for SRPs and close tier settings window if open + if (usingSRP) + { + TierSettingsWindow window = TierSettingsWindow.GetInstance(); + if (window != null) + window.Close(); + } + else + { + TierSettingsGUI(); + } GUILayout.Label(Styles.builtinSettings, EditorStyles.boldLabel); if (!usingSRP) diff --git a/Editor/Mono/Inspector/InspectorElement.cs b/Editor/Mono/Inspector/InspectorElement.cs index afe24bd235..7163ae641e 100644 --- a/Editor/Mono/Inspector/InspectorElement.cs +++ b/Editor/Mono/Inspector/InspectorElement.cs @@ -4,6 +4,7 @@ using System; using System.Linq; +using UnityEditor.Profiling; using UnityEngine; using UnityEngine.UIElements; using Object = UnityEngine.Object; @@ -29,6 +30,14 @@ public class InspectorElement : BindableElement public new class UxmlFactory : UxmlFactory {} + public new class UxmlTraits : BindableElement.UxmlTraits + { + public UxmlTraits() + { + m_PickingMode.defaultValue = PickingMode.Ignore; + } + } + [Flags] internal enum Mode { @@ -55,18 +64,20 @@ internal enum Mode internal Editor editor { get { return m_Editor; } - set + private set { if (m_Editor != value) { DestroyOwnedEditor(); m_Editor = value; ownsEditor = false; - PartialReset(m_Editor.serializedObject); } } } + private string m_TrackerName; + internal string trackerName => m_TrackerName ?? (m_TrackerName = GetInspectorTrackerName(this)); + internal bool ownsEditor { get; private set; } = false; internal SerializedObject boundObject { get; private set; } @@ -83,6 +94,7 @@ internal InspectorElement(Object obj, Mode mode) { m_IgnoreOnInspectorGUIErrors = false; + pickingMode = PickingMode.Ignore; AddToClassList(ussClassName); this.mode = mode; @@ -101,6 +113,7 @@ public InspectorElement(SerializedObject obj) : this(obj, Mode.Normal) {} internal InspectorElement(SerializedObject obj, Mode mode) { + pickingMode = PickingMode.Ignore; AddToClassList(ussClassName); this.mode = mode; @@ -119,6 +132,7 @@ public InspectorElement(Editor editor) : this(editor, Mode.Normal) {} internal InspectorElement(Editor editor, Mode mode) { + pickingMode = PickingMode.Ignore; AddToClassList(ussClassName); this.mode = mode; @@ -147,6 +161,15 @@ void OnDetachFromPanel(DetachFromPanelEvent evt) DestroyOwnedEditor(); } + internal void AssignExistingEditor(Editor value) + { + if (m_Editor != value) + { + editor = value; + PartialReset(m_Editor.serializedObject); + } + } + void DestroyOwnedEditor() { if (ownsEditor && editor != null) @@ -238,7 +261,7 @@ private void PartialReset(SerializedObject bindObject) if (customInspector != null && customInspector != this) hierarchy.Add(customInspector); - this.Bind(boundObject); + customInspector?.Bind(boundObject); } protected override void ExecuteDefaultActionAtTarget(EventBase evt) @@ -400,6 +423,9 @@ private VisualElement CreateIMGUIInspectorFromEditor(SerializedObject serialized m_IgnoreOnInspectorGUIErrors = false; inspector.onGUIHandler = () => { + if (editor && editor.target && (editor.target.hideFlags & HideFlags.HideInInspector) == HideFlags.HideInInspector) + return; + // It's possible to run 2-3 frames after the tracker of this inspector window has // been recreated, and with it the Editor and its SerializedObject. One example of // when this happens is when the Preview window is detached from a *second* instance @@ -464,7 +490,7 @@ private VisualElement CreateIMGUIInspectorFromEditor(SerializedObject serialized var originalWideMode = SetWideModeForWidth(inspector); - GUIStyle editorWrapper = (editor.UseDefaultMargins() + GUIStyle editorWrapper = (editor.UseDefaultMargins() && editor.CanBeExpandedViaAFoldoutWithoutUpdate() ? EditorStyles.inspectorDefaultMargins : GUIStyle.none); try @@ -503,7 +529,8 @@ private VisualElement CreateIMGUIInspectorFromEditor(SerializedObject serialized else { InspectorWindowUtils.DrawAddedComponentBackground(contentRect, editor.targets); - editor.OnInspectorGUI(); + using (new EditorPerformanceTracker(trackerName)) + editor.OnInspectorGUI(); } } catch (Exception e) @@ -549,6 +576,15 @@ private VisualElement CreateIMGUIInspectorFromEditor(SerializedObject serialized return inspector; } + internal static string GetInspectorTrackerName(VisualElement el) + { + var editorElementParent = el.parent as EditorElement; + if (editorElementParent == null) + return $"Editor.Unknown.OnInspectorGUI"; + + return $"Editor.{editorElementParent.name}.OnInspectorGUI"; + } + private VisualElement CreateInspectorElementFromEditor(Editor editor, bool reuseIMGUIContainer = false) { var serializedObject = editor.serializedObject; diff --git a/Editor/Mono/Inspector/InspectorWindow.cs b/Editor/Mono/Inspector/InspectorWindow.cs index 25e7a32321..041c2d2be8 100644 --- a/Editor/Mono/Inspector/InspectorWindow.cs +++ b/Editor/Mono/Inspector/InspectorWindow.cs @@ -44,10 +44,11 @@ internal InspectorMode inspectorMode static readonly List m_AllInspectors = new List(); static bool s_AllOptimizedGUIBlocksNeedsRebuild; - protected const float kBottomToolbarHeight = 17f; + protected const float kBottomToolbarHeight = 21f; const float kAddComponentButtonHeight = 45f; internal const int kInspectorPaddingLeft = 8 + 10; internal const int kInspectorPaddingRight = 4; + internal const int kInspectorPaddingTop = 4; internal const float kEditorElementPaddingBottom = 2f; const float k_MinAreaAbovePreview = 130; @@ -55,6 +56,7 @@ internal InspectorMode inspectorMode const float k_InspectorPreviewMinTotalHeight = k_InspectorPreviewMinHeight + kBottomToolbarHeight; const int k_MinimumRootVisualHeight = 81; const int k_MinimumWindowWidth = 275; + const int k_AutoScrollZoneHeight = 24; private const long delayRepaintWhilePlayingAnimation = 150; // Delay between repaints in milliseconds while playing animation private long s_LastUpdateWhilePlayingAnimation = 0; @@ -110,6 +112,10 @@ internal InspectorMode inspectorMode VisualElement m_MultiEditLabel; + ScrollView m_ScrollView; + [SerializeField] int m_LastInspectedObjectInstanceID = -1; + [SerializeField] float m_LastVerticalScrollValue = 0; + VisualElement FindVisualElementInTreeByClassName(string elementClassName) { var element = rootVisualElement.Q(className: elementClassName); @@ -127,6 +133,7 @@ internal static class Styles { public static readonly GUIStyle preToolbar = "preToolbar"; public static readonly GUIStyle preToolbar2 = "preToolbar2"; + public static readonly GUIStyle preToolbarLabel = "ToolbarBoldLabel"; public static readonly GUIStyle preDropDown = "preDropDown"; public static readonly GUIStyle dragHandle = "RL DragHandle"; public static readonly GUIStyle lockButton = "IN LockButton"; @@ -135,20 +142,30 @@ internal static class Styles public static readonly GUIContent labelTitle = EditorGUIUtility.TrTextContent("Asset Labels"); public static readonly GUIContent addComponentLabel = EditorGUIUtility.TrTextContent("Add Component"); public static GUIStyle preBackground = "preBackground"; + public static GUIStyle footer = "IN Footer"; + public static GUIStyle preMargins = new GUIStyle() {margin = new RectOffset(0, 0, 0, 4)}; + public static GUIStyle preOptionsButton = new GUIStyle(EditorStyles.toolbarButtonRight) { padding = new RectOffset(), contentOffset = new Vector2(1, 0) }; public static GUIStyle addComponentArea = EditorStyles.inspectorTitlebar; public static GUIStyle addComponentButtonStyle = "AC Button"; + public static readonly GUIContent menuIcon = EditorGUIUtility.TrIconContent("_Menu"); public static GUIStyle previewMiniLabel = EditorStyles.whiteMiniLabel; public static GUIStyle typeSelection = "IN TypeSelection"; public static readonly GUIContent vcsCheckoutHint = EditorGUIUtility.TrTextContent("Under Version Control\nCheck out this asset in order to make changes.", EditorGUIUtility.GetHelpIcon(MessageType.Info)); public static readonly GUIContent vcsNotConnected = EditorGUIUtility.TrTextContent("VCS ({0}) is not connected"); public static readonly GUIContent vcsOffline = EditorGUIUtility.TrTextContent("Work Offline option is active"); + public static readonly GUIContent vcsSettings = EditorGUIUtility.TrTextContent("Settings"); public static readonly GUIContent vcsCheckout = EditorGUIUtility.TrTextContent("Check Out"); public static readonly GUIContent vcsAdd = EditorGUIUtility.TrTextContent("Add"); public static readonly GUIContent vcsLock = EditorGUIUtility.TrTextContent("Lock"); public static readonly GUIContent vcsUnlock = EditorGUIUtility.TrTextContent("Unlock"); public static readonly GUIContent vcsSubmit = EditorGUIUtility.TrTextContent("Submit"); - public static GUIStyle vcsButtonStyle = "preButton"; + public static readonly GUIContent vcsRevert = EditorGUIUtility.TrTextContent("Revert"); + public static readonly GUIContent vcsRevertUnchanged = EditorGUIUtility.TrTextContent("Revert Unchanged"); + public static readonly GUIStyle vcsButtonStyle = EditorStyles.miniButton; + public static GUIStyle vcsRevertStyle = new GUIStyle(EditorStyles.dropDownList); + public static readonly GUIStyle vcsBarStyleOneRow = EditorStyles.toolbar; + public static GUIStyle vcsBarStyleTwoRows = new GUIStyle(EditorStyles.toolbar); public static readonly string objectDisabledModuleWarningFormat = L10n.Tr( "The built-in package '{0}', which implements this component type, has been disabled in Package Manager. This object will be removed in play mode and from any builds you make." ); @@ -158,6 +175,12 @@ internal static class Styles public static SVC lineSeparatorOffset = new SVC("AC-Button", "--separator-line-top-offset"); public static SVC lineSeparatorColor = new SVC("--theme-line-separator-color", Color.red); + + static Styles() + { + vcsRevertStyle.padding.right = 15; + vcsBarStyleTwoRows.fixedHeight *= 2; + } } @@ -230,6 +253,7 @@ private void LoadVisualTreeFromUxml() var container = tpl.CloneTree(); container.AddToClassList(s_MainContainerClassName); rootVisualElement.hierarchy.Add(container); + m_ScrollView = container.Q(); var multiContainer = rootVisualElement.Q(className: s_MultiEditClassName); multiContainer.Query().ForEach((label) => label.text = L10n.Tr(label.text)); @@ -252,6 +276,7 @@ void OnGeometryChanged(GeometryChangedEvent e) m_PreviewResizer.SetExpanded(false); } } + RestoreVerticalScrollIfNeeded(); } private void OnProjectWasLoaded() @@ -280,6 +305,10 @@ private void OnProjectWasLoaded() protected virtual void OnDisable() { + // save vertical scroll position + m_LastInspectedObjectInstanceID = GetInspectedObject()?.GetInstanceID() ?? -1; + m_LastVerticalScrollValue = m_ScrollView?.verticalScroller.value ?? 0; + RemoveInspectorWindow(this); m_LockTracker?.lockStateChanged.RemoveListener(LockStateChanged); @@ -783,6 +812,21 @@ void DragOverBottomArea(DragUpdatedEvent dragUpdatedEvent) { if (editorsElement.ContainsPoint(editorsElement.WorldToLocal(dragUpdatedEvent.mousePosition))) { + if (m_ScrollView != null) + { + // implement auto-scroll for easier component drag'n'drop, + // we define a zone of height = k_AutoScrollZoneHeight + // at the top/bottom of the scrollView viewport, + // while dragging, when the mouse moves in these zones, + // we automatically scroll up/down + var localDragPosition = m_ScrollView.contentViewport.WorldToLocal(dragUpdatedEvent.mousePosition); + + if (localDragPosition.y < k_AutoScrollZoneHeight) + m_ScrollView.verticalScroller.ScrollPageUp(); + else if (localDragPosition.y > m_ScrollView.contentViewport.rect.height - k_AutoScrollZoneHeight) + m_ScrollView.verticalScroller.ScrollPageDown(); + } + return; } @@ -1037,7 +1081,7 @@ private void DrawPreviewAndLabels() } dragIconRect.x = dragRect.x + dragPadding; - dragIconRect.y = dragRect.y + (kBottomToolbarHeight - Styles.dragHandle.fixedHeight) / 2 + 1; + dragIconRect.y = dragRect.y + (kBottomToolbarHeight - Styles.dragHandle.fixedHeight) / 2; dragIconRect.width = dragRect.width - dragPadding * 2; dragIconRect.height = Styles.dragHandle.fixedHeight; @@ -1096,7 +1140,7 @@ private void DrawPreviewAndLabels() dragIconRect.xMin = labelRect.xMax + dragPadding; - GUI.Label(labelRect, title, Styles.preToolbar2); + GUI.Label(labelRect, title, Styles.preToolbarLabel); } if (m_HasPreview && Event.current.type == EventType.Repaint) @@ -1108,6 +1152,36 @@ private void DrawPreviewAndLabels() if (m_HasPreview && m_PreviewResizer.GetExpandedBeforeDragging()) previewEditor.OnPreviewSettings(); + + if (m_HasPreview || m_PreviewWindow != null) + { + if (EditorGUILayout.DropdownButton(Styles.menuIcon, FocusType.Passive, Styles.preOptionsButton)) + { + GenericMenu menu = new GenericMenu(); + menu.AddItem( + EditorGUIUtility.TrTextContent(m_PreviewWindow == null + ? "Convert to Floating Window" + : "Dock Preview to Inspector"), false, + () => + { + if (m_PreviewWindow == null) + DetachPreview(false); + else + m_PreviewWindow.Close(); + }); + menu.AddItem( + EditorGUIUtility.TrTextContent(m_PreviewResizer.GetExpanded() + ? "Minimize in Inspector" + : "Restore in Inspector"), false, + () => + { + m_PreviewResizer.SetExpanded(position, k_InspectorPreviewMinTotalHeight, + k_MinAreaAbovePreview, kBottomToolbarHeight, dragRect, + !m_PreviewResizer.GetExpanded()); + }); + menu.ShowAsContext(); + } + } } EditorGUILayout.EndHorizontal(); @@ -1156,6 +1230,7 @@ private void DrawPreviewAndLabels() } } + GUILayout.BeginVertical(Styles.footer); if (hasLabels) { using (new EditorGUI.DisabledScope(assets.Any(a => EditorUtility.IsPersistent(a) && !Editor.IsAppropriateFileOpenForEdit(a)))) @@ -1168,6 +1243,7 @@ private void DrawPreviewAndLabels() { m_AssetBundleNameGUI.OnAssetBundleNameGUI(assets); } + GUILayout.EndVertical(); } GUILayout.EndVertical(); @@ -1202,15 +1278,17 @@ private void OnPreviewSelected(object userData, string[] options, int selected) m_SelectedPreview = availablePreviews[selected]; } - private void DetachPreview() + private void DetachPreview(bool exitGUI = true) { - Event.current.Use(); + if (Event.current != null) + Event.current.Use(); m_PreviewWindow = CreateInstance(typeof(PreviewWindow)) as PreviewWindow; m_PreviewWindow.SetParentInspector(this); m_PreviewWindow.Show(); Repaint(); UIElementsUtility.MakeCurrentIMGUIContainerDirty(); - GUIUtility.ExitGUI(); + if (exitGUI) + GUIUtility.ExitGUI(); } internal static void VersionControlBar(Editor assetEditor) @@ -1257,12 +1335,23 @@ internal static void VersionControlBar(Editor assetEditor) var hasAssetState = !string.IsNullOrEmpty(currentState); var hasMetaState = !string.IsNullOrEmpty(currentMetaState); - GUILayout.Label(GUIContent.none, Styles.preToolbar, GUILayout.Height(kBottomToolbarHeight)); + var assetList = new AssetList(); + assetList.AddRange(assetEditor.targets.Select(o => Provider.GetAssetByPath(AssetDatabase.GetAssetPath(o)))); + + // Figure out how many VCS action buttons we'll need to show for the selected assets. + // Based on that and the current inspector width, we might want to layout the VCS + // bar in two rows to better fit status label & buttons. + var buttonPresence = VersionControlBarButtonPresence.Calculate(assetEditor, asset, assetList, connected); + var approxSpaceForButtons = buttonPresence.GetButtonCount() * 50; + var useTwoRows = GUIView.current != null && GUIView.current.position.width * 0.5f < approxSpaceForButtons; - var rect = GUILayoutUtility.GetLastRect(); + var lineHeight = Styles.vcsBarStyleOneRow.fixedHeight; + var barStyle = useTwoRows ? Styles.vcsBarStyleTwoRows : Styles.vcsBarStyleOneRow; + GUILayout.BeginHorizontal(GUIContent.none, barStyle); + var barRect = GUILayoutUtility.GetRect(GUIContent.none, barStyle, GUILayout.ExpandWidth(true)); var icon = AssetDatabase.GetCachedIcon(assetPath) as Texture2D; - var overlayRect = new Rect(rect.x, rect.y + 1, 28, 16); + var overlayRect = new Rect(barRect.x, barRect.y + 1, 28, 16); var iconRect = overlayRect; iconRect.x += 6; iconRect.width = 16; @@ -1280,10 +1369,14 @@ internal static void VersionControlBar(Editor assetEditor) currentState = currentMetaState + " (meta only)"; } - var buttonX = VersionControlBarButtons(assetEditor, rect, asset, connected); - var textRect = rect; + var buttonsRect = barRect; + buttonsRect.yMin = buttonsRect.yMax - lineHeight; + var buttonX = VersionControlBarButtons(buttonPresence, buttonsRect, assetList, connected); + var textRect = barRect; + textRect.height = lineHeight; textRect.xMin += 26; - textRect.xMax = buttonX; + if (!useTwoRows) + textRect.xMax = buttonX; var content = GUIContent.Temp(currentState); var fullState = Asset.AllStateToString(asset.state); @@ -1294,6 +1387,7 @@ internal static void VersionControlBar(Editor assetEditor) fullState = "State: " + fullState; content.tooltip = fullState; GUI.Label(textRect, content, EditorStyles.label); + GUILayout.EndHorizontal(); VersionControlCheckoutHint(assetEditor, connected); } @@ -1317,45 +1411,137 @@ static void VersionControlCheckoutHint(Editor assetEditor, bool connected) GUILayout.Space(4); } - static float VersionControlBarButtons(Editor assetEditor, Rect rect, Asset asset, bool connected) + static void DoRevertUnchanged(object o) + { + var al = (AssetList)o; + Provider.Revert(al, RevertMode.Unchanged); + } + + struct VersionControlBarButtonPresence + { + public bool settings; + public bool revert; + public bool revertUnchanged; + public bool checkout; + public bool add; + public bool submit; + public bool @lock; + public bool unlock; + + public int GetButtonCount() + { + var c = 0; + if (settings) ++c; + if (revert) ++c; // revertUnchanged is same button in a drop-down + if (checkout) ++c; + if (add) ++c; + if (submit) ++c; + if (@lock) ++c; + if (unlock) ++c; + return c; + } + + public static VersionControlBarButtonPresence Calculate(Editor assetEditor, Asset asset, AssetList assetList, bool connected) + { + var res = new VersionControlBarButtonPresence(); + if (!connected) + { + res.settings = true; + return res; + } + + var isFolder = asset.isFolder && !Provider.isVersioningFolders; + + res.revert = Provider.RevertIsValid(assetList, RevertMode.Normal); + res.revertUnchanged = Provider.RevertIsValid(assetList, RevertMode.Unchanged); + res.checkout = isFolder || Provider.CheckoutIsValid(assetList, CheckoutMode.Both); + res.add = !isFolder && Provider.AddIsValid(assetList); + res.submit = Provider.SubmitIsValid(null, assetList); + res.@lock = !isFolder && Provider.LockIsValid(assetList); + res.unlock = !isFolder && Provider.UnlockIsValid(assetList); + return res; + } + } + + static float VersionControlBarButtons(VersionControlBarButtonPresence presence, Rect rect, AssetList assetList, bool connected) { var buttonX = rect.xMax - 7; - if (!connected) - return buttonX; var buttonRect = rect; var buttonStyle = Styles.vcsButtonStyle; + buttonRect.y += 1; buttonRect.height = buttonStyle.CalcSize(Styles.vcsAdd).y; - var isFolder = asset.isFolder && !Provider.isVersioningFolders; + if (!connected) + { + if (presence.settings) + { + if (VersionControlActionButton(buttonRect, ref buttonX, Styles.vcsSettings)) + SettingsService.OpenProjectSettings("Project/Editor"); + } + return buttonX; + } + - var assetList = new AssetList(); - foreach (var o in assetEditor.targets) + if (presence.revert && !presence.revertUnchanged) + { + // just a simple revert button + if (VersionControlActionButton(buttonRect, ref buttonX, Styles.vcsRevert)) + { + WindowRevert.Open(assetList); + GUIUtility.ExitGUI(); + } + } + else if (presence.revert || presence.revertUnchanged) { - assetList.Add(Provider.GetAssetByPath(AssetDatabase.GetAssetPath(o))); + // revert + revert unchanged dropdown button + var dropdownStyle = Styles.vcsRevertStyle; + const float kDropDownButtonWidth = 20f; + buttonRect.width = dropdownStyle.CalcSize(Styles.vcsRevert).x + 6; + buttonRect.x = buttonX - buttonRect.width; + buttonX -= buttonRect.width; + + var dropDownRect = buttonRect; + dropDownRect.xMin = dropDownRect.xMax - kDropDownButtonWidth; + + if (Event.current.type == EventType.MouseDown && dropDownRect.Contains(Event.current.mousePosition)) + { + var menu = new GenericMenu(); + menu.AddItem(Styles.vcsRevertUnchanged, false, DoRevertUnchanged, assetList); + menu.DropDown(buttonRect); + Event.current.Use(); + } + else + { + if (GUI.Button(buttonRect, Styles.vcsRevert, dropdownStyle) && presence.revert) + { + WindowRevert.Open(assetList); + GUIUtility.ExitGUI(); + } + } } - if (!Editor.IsAppropriateFileOpenForEdit(assetEditor.target)) + if (presence.checkout) { if (VersionControlActionButton(buttonRect, ref buttonX, Styles.vcsCheckout)) Provider.Checkout(assetList, CheckoutMode.Both).Wait(); // TODO: Retrieve default CheckoutMode from VC settings (depends on asset type; native vs. imported) } - if (!isFolder && Provider.AddIsValid(assetList)) + if (presence.add) { if (VersionControlActionButton(buttonRect, ref buttonX, Styles.vcsAdd)) Provider.Add(assetList, false).Wait(); } - if (Provider.SubmitIsValid(null, assetList)) + if (presence.submit) { if (VersionControlActionButton(buttonRect, ref buttonX, Styles.vcsSubmit)) WindowChange.Open(assetList, true); } - if (!isFolder && Provider.LockIsValid(assetList)) + if (presence.@lock) { if (VersionControlActionButton(buttonRect, ref buttonX, Styles.vcsLock)) Provider.Lock(assetList, true).Wait(); } - if (!isFolder && Provider.UnlockIsValid(assetList)) + if (presence.unlock) { if (VersionControlActionButton(buttonRect, ref buttonX, Styles.vcsUnlock)) Provider.Lock(assetList, false).Wait(); @@ -1453,13 +1639,17 @@ void DrawEditors(Editor[] editors) var editor = editors[editorIndex]; Object editorTarget = editor.targets[0]; - string editorTitle = ObjectNames.GetInspectorTitle(editorTarget); EditorElement editorContainer; + if (editorTarget && (editorTarget?.hideFlags & HideFlags.HideInInspector) == HideFlags.HideInInspector) + continue; try { if (mapping == null || !mapping.TryGetValue(editors[editorIndex].target.GetInstanceID(), out editorContainer)) { + string editorTitle = editorTarget == null ? + "Nothing Selected" : + $"{editor.GetType().Name}_{editorTarget.GetType().Name}_{editorTarget.GetInstanceID()}"; editorContainer = new EditorElement(editorIndex, this) { name = editorTitle }; editorsElement.Add(editorContainer); } @@ -1502,6 +1692,16 @@ void DrawEditors(Editor[] editors) } } + void RestoreVerticalScrollIfNeeded() + { + if (m_LastInspectedObjectInstanceID == -1) + return; + var inspectedObjectInstanceID = GetInspectedObject()?.GetInstanceID() ?? -1; + if (inspectedObjectInstanceID == m_LastInspectedObjectInstanceID && inspectedObjectInstanceID != -1) + m_ScrollView.verticalScroller.value = m_LastVerticalScrollValue; + m_LastInspectedObjectInstanceID = -1; // reset to make sure the restore occurs once + } + void AddRemovedPrefabComponentElement(GameObject targetGameObject, Component nextInSource, VisualElement element) { if (ShouldDisplayRemovedComponent(targetGameObject, nextInSource)) @@ -1612,7 +1812,8 @@ internal bool ShouldCullEditor(Editor[] editors, int editorIndex) Object currentTarget = editors[editorIndex].target; // Editors that should always be hidden - if (currentTarget is ParticleSystemRenderer) + if (currentTarget is ParticleSystemRenderer + || currentTarget is UnityEngine.VFX.VFXRenderer) return true; // Hide regular AssetImporters (but not inherited types) @@ -1891,28 +2092,30 @@ Dictionary ProcessEditorElementsToRebuild(Editor[] editors) while (newEditorsIndex < editors.Length && previousEditorsIndex < currentElements.Count) { var ed = editors[newEditorsIndex]; - var currentEd = currentElements[previousEditorsIndex]; + var currentElement = currentElements[previousEditorsIndex]; + var currentEditor = currentElement.editor; - if (currentEd.editor == null) + if (currentEditor == null) { ++previousEditorsIndex; continue; } // We won't have an EditorElement for editors that are normally culled so we should skip this + if (ShouldCullEditor(editors, newEditorsIndex)) { ++newEditorsIndex; continue; } - if (ed.target != currentEd.editor.target) + if (currentEditor && ed.target != currentEditor.target) { return null; } - currentEd.Reinit(newEditorsIndex); - editorToElementMap[ed.target.GetInstanceID()] = currentEd; + currentElement.Reinit(newEditorsIndex); + editorToElementMap[ed.target.GetInstanceID()] = currentElement; ++newEditorsIndex; ++previousEditorsIndex; } diff --git a/Editor/Mono/Inspector/Joint2DEditor.cs b/Editor/Mono/Inspector/Joint2DEditor.cs index adfed78668..2352bcac5a 100644 --- a/Editor/Mono/Inspector/Joint2DEditor.cs +++ b/Editor/Mono/Inspector/Joint2DEditor.cs @@ -27,6 +27,7 @@ public override void OnInspectorGUI() { var joint = target as Joint2D; if (joint.connectedBody != null && + joint.gameObject.activeInHierarchy && joint.connectedBody.gameObject.activeInHierarchy && joint.gameObject.scene.GetPhysicsScene2D() != joint.connectedBody.gameObject.scene.GetPhysicsScene2D()) { EditorGUILayout.HelpBox("This joint will not function because it is connected to a Rigidbody2D in a different physics scene. This is not supported.", MessageType.Warning); diff --git a/Editor/Mono/Inspector/JointEditor.cs b/Editor/Mono/Inspector/JointEditor.cs index 9466513f3c..4cc1f23416 100644 --- a/Editor/Mono/Inspector/JointEditor.cs +++ b/Editor/Mono/Inspector/JointEditor.cs @@ -19,10 +19,20 @@ public static void CheckConnectedBody(Editor editor) if (editor.targets.Length == 1) { var joint = editor.target as Joint; - if (joint.connectedBody != null && - joint.gameObject.scene.GetPhysicsScene() != joint.connectedBody.gameObject.scene.GetPhysicsScene()) + + if (joint.connectedBody != null) { - EditorGUILayout.HelpBox("This joint will not function because it is connected to a Rigidbody in a different physics scene. This is not supported.", MessageType.Warning); + var bodyScene = joint.gameObject.scene; + var connectedBodyScene = joint.connectedBody.gameObject.scene; + + // scenes can be invalid when the joint belongs to a prefab + if (bodyScene.IsValid() && connectedBodyScene.IsValid()) + { + if (bodyScene.GetPhysicsScene() != connectedBodyScene.GetPhysicsScene()) + { + EditorGUILayout.HelpBox("This joint will not function because it is connected to a Rigidbody in a different physics scene. This is not supported.", MessageType.Warning); + } + } } } } @@ -58,7 +68,7 @@ public override void OnInspectorGUI() protected void DoInspectorEditButtons() { T joint = (T)target; - EditorGUI.BeginDisabledGroup(joint.gameObject.activeSelf == false); + EditorGUI.BeginDisabledGroup(joint.gameObject.activeInHierarchy == false); EditorGUILayout.EditorToolbarForTarget(EditorGUIUtility.TrTempContent("Edit Angular Limits"), target); EditorGUI.EndDisabledGroup(); } diff --git a/Editor/Mono/Inspector/LODGroupEditor.cs b/Editor/Mono/Inspector/LODGroupEditor.cs index 2dba9a03e6..8e0e0ede99 100644 --- a/Editor/Mono/Inspector/LODGroupEditor.cs +++ b/Editor/Mono/Inspector/LODGroupEditor.cs @@ -706,13 +706,28 @@ private static void UpdateCamera(float desiredPercentage, LODGroup group) var worldReferencePoint = LODUtility.CalculateWorldReferencePoint(group); var percentage = Mathf.Max(desiredPercentage / QualitySettings.lodBias, 0.000001f); + var sceneView = SceneView.lastActiveSceneView; + var sceneCamera = sceneView.camera; + // Figure out a distance based on the percentage - var distance = LODUtility.CalculateDistance(SceneView.lastActiveSceneView.camera, percentage, group); + var distance = LODUtility.CalculateDistance(sceneCamera, percentage, group); - if (SceneView.lastActiveSceneView.camera.orthographic) - distance *= Mathf.Sqrt(2 * SceneView.lastActiveSceneView.camera.aspect); + // We need to do inverse of SceneView.cameraDistance: + // given the distance, need to figure out "size" to focus the scene view on. + float size; + if (sceneCamera.orthographic) + { + size = distance; + if (sceneCamera.aspect < 1.0) + size *= sceneCamera.aspect; + } + else + { + var fov = sceneCamera.fieldOfView; + size = distance * Mathf.Sin(fov * 0.5f * Mathf.Deg2Rad); + } - SceneView.lastActiveSceneView.LookAtDirect(worldReferencePoint, SceneView.lastActiveSceneView.camera.transform.rotation, distance); + SceneView.lastActiveSceneView.LookAtDirect(worldReferencePoint, sceneCamera.transform.rotation, size); } private void UpdateSelectedLODFromCamera(IEnumerable lods, float cameraPercent) diff --git a/Editor/Mono/Inspector/LightEditor.cs b/Editor/Mono/Inspector/LightEditor.cs index 8ce72e2081..efaf02a84a 100644 --- a/Editor/Mono/Inspector/LightEditor.cs +++ b/Editor/Mono/Inspector/LightEditor.cs @@ -682,7 +682,7 @@ private void CommandBufferGUI() { light.RemoveCommandBuffer(le, cb); SceneView.RepaintAll(); - PreviewEditorWindow.RepaintAll(); + PlayModeView.RepaintAll(); GUIUtility.ExitGUI(); } } @@ -696,7 +696,7 @@ private void CommandBufferGUI() { light.RemoveAllCommandBuffers(); SceneView.RepaintAll(); - PreviewEditorWindow.RepaintAll(); + PlayModeView.RepaintAll(); } } EditorGUI.indentLevel--; diff --git a/Editor/Mono/Inspector/LightProbeGroupInspector.cs b/Editor/Mono/Inspector/LightProbeGroupInspector.cs index ccca7002b2..93d310fed4 100644 --- a/Editor/Mono/Inspector/LightProbeGroupInspector.cs +++ b/Editor/Mono/Inspector/LightProbeGroupInspector.cs @@ -542,7 +542,6 @@ public void OnEnable() m_Editor.drawTetrahedra = new SavedBool($"{target.GetType()}.drawTetrahedra", true); - SceneView.duringSceneGui += OnSceneGUIDelegate; Undo.undoRedoPerformed += UndoRedoPerformed; EditMode.editModeStarted += OnEditModeStarted; EditMode.editModeEnded += OnEditModeEnded; @@ -596,7 +595,6 @@ public void OnDisable() { EndEditProbes(); Undo.undoRedoPerformed -= UndoRedoPerformed; - SceneView.duringSceneGui -= OnSceneGUIDelegate; EditMode.editModeStarted -= OnEditModeStarted; EditMode.editModeEnded -= OnEditModeEnded; @@ -716,20 +714,20 @@ private Vector3 CalculateDeltaAndClamp(Vector3 vec1, Vector3 vec2) if (float.IsInfinity(vec1.z) || float.IsNaN(vec1.z)) vec1.z = 0; - Mathf.Clamp(vec1.x, float.MinValue, float.MaxValue); - Mathf.Clamp(vec1.y, float.MinValue, float.MaxValue); - Mathf.Clamp(vec1.z, float.MinValue, float.MaxValue); + vec1.x = Mathf.Clamp(vec1.x, float.MinValue, float.MaxValue); + vec1.y = Mathf.Clamp(vec1.y, float.MinValue, float.MaxValue); + vec1.z = Mathf.Clamp(vec1.z, float.MinValue, float.MaxValue); return vec1 - vec2; } private void InternalOnSceneView() { - if (!EditorGUIUtility.IsGizmosAllowedForObject(target)) - return; - if (SceneView.lastActiveSceneView != null) { + if (!SceneView.lastActiveSceneView.drawGizmos) + return; + if (m_ShouldFocus) { m_ShouldFocus = false; @@ -751,14 +749,7 @@ private void InternalOnSceneView() public void OnSceneGUI() { - if (Event.current.type != EventType.Repaint) - InternalOnSceneView(); - } - - public void OnSceneGUIDelegate(SceneView sceneView) - { - if (Event.current.type == EventType.Repaint) - InternalOnSceneView(); + InternalOnSceneView(); } public bool HasFrameBounds() diff --git a/Editor/Mono/Inspector/LineRendererCurveEditor.cs b/Editor/Mono/Inspector/LineRendererCurveEditor.cs index dbd876d242..84a4b50193 100644 --- a/Editor/Mono/Inspector/LineRendererCurveEditor.cs +++ b/Editor/Mono/Inspector/LineRendererCurveEditor.cs @@ -100,6 +100,9 @@ private void UndoRedoPerformed() public void CheckCurveChangedExternally() { + if (Event.current.type != EventType.Repaint) + return; + CurveWrapper cw = m_Editor.GetCurveWrapperFromID(0); if (m_WidthCurve != null) { diff --git a/Editor/Mono/Inspector/LineRendererEditor.cs b/Editor/Mono/Inspector/LineRendererEditor.cs index bb4db687c9..cf5ddbbe82 100644 --- a/Editor/Mono/Inspector/LineRendererEditor.cs +++ b/Editor/Mono/Inspector/LineRendererEditor.cs @@ -134,6 +134,8 @@ public override void OnEnable() m_PositionsView = new LineRendererPositionsView(m_Positions); m_PositionsView.selectionChangedCallback += PositionsViewSelectionChanged; + if (targets.Length == 1) + m_PositionsView.lineRenderer = target as LineRenderer; m_ShowPositionsAnimation = new AnimBool(false, Repaint) { value = m_Positions.isExpanded }; EditorApplication.contextualPropertyMenu += OnPropertyContextMenu; diff --git a/Editor/Mono/Inspector/LineRendererPositionsView.cs b/Editor/Mono/Inspector/LineRendererPositionsView.cs index 6cc1824c9b..14bc21a89c 100644 --- a/Editor/Mono/Inspector/LineRendererPositionsView.cs +++ b/Editor/Mono/Inspector/LineRendererPositionsView.cs @@ -29,6 +29,7 @@ public LineRendererPositionsView(SerializedProperty positions) : showBorder = true; showAlternatingRowBackgrounds = true; useScrollView = false; + rowHeight = 15.0f; MultiColumnHeaderState.Column[] columns = new MultiColumnHeaderState.Column[4]; for (int i = 0; i < columns.Length; ++i) @@ -123,7 +124,7 @@ protected override void DoubleClickedItem(int id) var prop = m_Positions.GetArrayElementAtIndex(id); var sceneView = SceneView.lastActiveSceneView; - if (sceneView != null) + if (sceneView != null && lineRenderer != null) { var pos = prop.vector3Value; if (!lineRenderer.useWorldSpace) diff --git a/Editor/Mono/Inspector/MaterialEditor.cs b/Editor/Mono/Inspector/MaterialEditor.cs index 1c880f10d9..cd6d91e428 100644 --- a/Editor/Mono/Inspector/MaterialEditor.cs +++ b/Editor/Mono/Inspector/MaterialEditor.cs @@ -21,7 +21,7 @@ public partial class MaterialEditor : Editor private static class Styles { public static readonly GUIStyle inspectorBigInner = "IN BigTitle inner"; - public static readonly GUIStyle kReflectionProbePickerStyle = "PaneOptions"; + public static readonly GUIContent reflectionProbePickerIcon = EditorGUIUtility.TrIconContent("ReflectionProbeSelector"); public static readonly GUIContent lightmapEmissiveLabel = EditorGUIUtility.TrTextContent("Global Illumination", "Controls if the emission is baked or realtime.\n\nBaked only has effect in scenes where baked global illumination is enabled.\n\nRealtime uses realtime global illumination if enabled in the scene. Otherwise the emission won't light up other objects."); public static GUIContent[] lightmapEmissiveStrings = { EditorGUIUtility.TextContent("Realtime"), EditorGUIUtility.TrTextContent("Baked"), EditorGUIUtility.TrTextContent("None") }; public static int[] lightmapEmissiveValues = { (int)MaterialGlobalIlluminationFlags.RealtimeEmissive, (int)MaterialGlobalIlluminationFlags.BakedEmissive, (int)MaterialGlobalIlluminationFlags.None }; @@ -1609,7 +1609,7 @@ internal static Renderer[] PrepareMaterialPropertiesForAnimationMode(MaterialPro public void SetDefaultGUIWidths() { EditorGUIUtility.fieldWidth = EditorGUI.kObjectFieldThumbnailHeight; - EditorGUIUtility.labelWidth = GUIClip.visibleRect.width - EditorGUIUtility.fieldWidth - 17; + EditorGUIUtility.labelWidth = GUIClip.visibleRect.width - EditorGUIUtility.fieldWidth - 25; } private bool IsMaterialEditor(string customEditorName) @@ -1879,18 +1879,11 @@ public override void OnPreviewSettings() DefaultPreviewSettingsGUI(); } - private bool PreviewSettingsMenuButton(out Rect buttonRect) + private bool DoReflectionProbePicker(out Rect buttonRect) { - buttonRect = GUILayoutUtility.GetRect(14, 24, 14, 20); + buttonRect = GUILayoutUtility.GetRect(Styles.reflectionProbePickerIcon, EditorStyles.toolbarDropDownRight); - const float iconWidth = 16f; - const float iconHeight = 16f; - Rect iconRect = new Rect(buttonRect.x + (buttonRect.width - iconWidth) / 2, buttonRect.y + (buttonRect.height - iconHeight) / 2, iconWidth, iconHeight); - - if (Event.current.type == EventType.Repaint) - Styles.kReflectionProbePickerStyle.Draw(iconRect, false, false, false, false); - - if (EditorGUI.DropdownButton(buttonRect, GUIContent.none, FocusType.Passive, GUIStyle.none)) + if (EditorGUI.DropdownButton(buttonRect, Styles.reflectionProbePickerIcon, FocusType.Passive, EditorStyles.toolbarDropDownRight)) return true; return false; @@ -1912,7 +1905,7 @@ public void DefaultPreviewSettingsGUI() m_LightMode = PreviewGUI.CycleButton(m_LightMode, s_LightIcons); Rect settingsButton; - if (PreviewSettingsMenuButton(out settingsButton)) + if (DoReflectionProbePicker(out settingsButton)) PopupWindow.Show(settingsButton, m_ReflectionProbePicker); } } @@ -2105,9 +2098,35 @@ internal void OnSceneDrag(SceneView sceneView) if (EditorMaterialUtility.IsBackgroundMaterial((target as Material))) { HandleSkybox(go, evt); + ClearDragMaterialRendering(); } else if (go && go.GetComponent()) HandleRenderer(go.GetComponent(), materialIndex, evt); + else + ClearDragMaterialRendering(); + } + + private void TryRevertDragChanges() + { + if (s_previousDraggedUponRenderer != null) + { + bool hasRevert = false; + if (!s_previousAlreadyHadPrefabModification && PrefabUtility.IsPartOfAnyPrefab(s_previousDraggedUponRenderer)) + { + var materialRendererSerializedObject = new SerializedObject(s_previousDraggedUponRenderer).FindProperty("m_Materials"); + PrefabUtility.RevertPropertyOverride(materialRendererSerializedObject, InteractionMode.AutomatedAction); + hasRevert = true; + } + if (!hasRevert) + s_previousDraggedUponRenderer.sharedMaterials = s_previousMaterialValue; + } + } + + private void ClearDragMaterialRendering() + { + TryRevertDragChanges(); + s_previousDraggedUponRenderer = null; + s_previousMaterialValue = null; } internal void HandleSkybox(GameObject go, Event evt) @@ -2144,6 +2163,9 @@ internal void HandleSkybox(GameObject go, Event evt) } } + static Renderer s_previousDraggedUponRenderer; + static Material[] s_previousMaterialValue; + static bool s_previousAlreadyHadPrefabModification; internal void HandleRenderer(Renderer r, int materialIndex, Event evt) { if (r.GetType().GetCustomAttributes(typeof(RejectDragAndDropMaterial), true).Length > 0) @@ -2160,12 +2182,29 @@ internal void HandleRenderer(Renderer r, int materialIndex, Event evt) case EventType.DragPerform: DragAndDrop.AcceptDrag(); applyAndConsumeEvent = true; + + ClearDragMaterialRendering(); + Undo.RecordObject(r, "Assign Material"); break; } if (applyAndConsumeEvent) { - Undo.RecordObject(r, "Assign Material"); + if (evt.type != EventType.DragPerform) + { + ClearDragMaterialRendering(); + s_previousDraggedUponRenderer = r; + s_previousMaterialValue = r.sharedMaterials; + + // Update prefab modification status cache + s_previousAlreadyHadPrefabModification = false; + if (PrefabUtility.IsPartOfAnyPrefab(s_previousDraggedUponRenderer)) + { + var materialRendererSerializedObject = new SerializedObject(s_previousDraggedUponRenderer).FindProperty("m_Materials"); + s_previousAlreadyHadPrefabModification = materialRendererSerializedObject.prefabOverride; + } + } + var materials = r.sharedMaterials; bool altIsDown = evt.alt; diff --git a/Editor/Mono/Inspector/MaterialPropertyDrawer.cs b/Editor/Mono/Inspector/MaterialPropertyDrawer.cs index dee107748e..dcd212b64c 100644 --- a/Editor/Mono/Inspector/MaterialPropertyDrawer.cs +++ b/Editor/Mono/Inspector/MaterialPropertyDrawer.cs @@ -165,7 +165,10 @@ private static MaterialPropertyDrawer GetShaderPropertyDrawer(string attrib, out private static MaterialPropertyHandler GetShaderPropertyHandler(Shader shader, string name) { - string[] attribs = ShaderUtil.GetShaderPropertyAttributes(shader, name); + int propertyIndex = shader.FindPropertyIndex(name); + if (propertyIndex < 0) + return null; + string[] attribs = shader.GetPropertyAttributes(propertyIndex); if (attribs == null || attribs.Length == 0) return null; diff --git a/Editor/Mono/Inspector/MonoScriptInspector.cs b/Editor/Mono/Inspector/MonoScriptInspector.cs index 5e93c94c96..8d93c2cfae 100644 --- a/Editor/Mono/Inspector/MonoScriptInspector.cs +++ b/Editor/Mono/Inspector/MonoScriptInspector.cs @@ -68,6 +68,14 @@ internal override void OnHeaderIconGUI(Rect iconRect) protected override bool needsApplyRevert => false; // Clear default references + // ReSharper disable once UnusedMember.Local - registers as menu handler + [MenuItem("CONTEXT/MonoImporter/Reset", isValidateFunction: true)] + static bool ResetDefaultReferencesValidate(MenuCommand command) + { + return AssetDatabase.IsOpenForEdit(command.context); + } + + // ReSharper disable once UnusedMember.Local - registers as menu handler [MenuItem("CONTEXT/MonoImporter/Reset")] static void ResetDefaultReferences(MenuCommand command) { diff --git a/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsEditor.cs b/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsEditor.cs index 20a2f9f04b..5d93dd5e77 100644 --- a/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsEditor.cs +++ b/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsEditor.cs @@ -14,6 +14,7 @@ using System.Linq; using UnityEditor.Modules; using UnityEngine.Events; +using UnityEngine.SceneManagement; using GraphicsDeviceType = UnityEngine.Rendering.GraphicsDeviceType; using VR = UnityEditorInternal.VR; using TargetAttributes = UnityEditor.BuildTargetDiscovery.TargetAttributes; @@ -88,7 +89,6 @@ class SettingsContent public static readonly GUIContent iPhoneScriptCallOptimization = EditorGUIUtility.TrTextContent("Script Call Optimization*"); public static readonly GUIContent enableInternalProfiler = EditorGUIUtility.TrTextContent("Enable Internal Profiler* (Deprecated)", "Internal profiler counters should be accessed by scripts using UnityEngine.Profiling::Profiler API."); public static readonly GUIContent stripUnusedMeshComponents = EditorGUIUtility.TrTextContent("Optimize Mesh Data*", "Remove unused mesh components"); - public static readonly GUIContent protectGraphicsMemory = EditorGUIUtility.TrTextContent("Protect Graphics Memory", "Protect GPU memory from being read (on supported devices). Will prevent user from taking screenshots"); public static readonly GUIContent enableFrameTimingStats = EditorGUIUtility.TrTextContent("Enable Frame Timing Stats", "Enable gathering of CPU/GPU frame timing statistics."); public static readonly GUIContent useOSAutoRotation = EditorGUIUtility.TrTextContent("Use Animated Autorotation", "If set OS native animated autorotation method will be used. Otherwise orientation will be changed immediately."); public static readonly GUIContent UIPrerenderedIcon = EditorGUIUtility.TrTextContent("Prerendered Icon"); @@ -135,11 +135,13 @@ class SettingsContent public static readonly GUIContent framebufferDepthMemorylessMode = EditorGUIUtility.TrTextContent("Memoryless Depth", "Memoryless mode of framebuffer depth"); public static readonly GUIContent[] memorylessModeNames = { EditorGUIUtility.TrTextContent("Unused"), EditorGUIUtility.TrTextContent("Forced"), EditorGUIUtility.TrTextContent("Automatic")}; public static readonly GUIContent vulkanEnableSetSRGBWrite = EditorGUIUtility.TrTextContent("SRGB Write Mode*", "If set, enables Graphics.SetSRGBWrite() for toggling sRGB write mode during the frame but may decrease performance especially on tiled GPUs."); + public static readonly GUIContent vulkanNumSwapchainBuffers = EditorGUIUtility.TrTextContent("Number of swapchain buffers"); public static readonly GUIContent mTRendering = EditorGUIUtility.TrTextContent("Multithreaded Rendering*"); public static readonly GUIContent staticBatching = EditorGUIUtility.TrTextContent("Static Batching"); public static readonly GUIContent dynamicBatching = EditorGUIUtility.TrTextContent("Dynamic Batching"); - public static readonly GUIContent graphicsJobs = EditorGUIUtility.TrTextContent("Graphics Jobs"); - public static readonly GUIContent graphicsJobsMode = EditorGUIUtility.TrTextContent("Graphics Jobs Mode*"); + public static readonly GUIContent graphicsJobsNonExperimental = EditorGUIUtility.TrTextContent("Graphics Jobs"); + public static readonly GUIContent graphicsJobsExperimental = EditorGUIUtility.TrTextContent("Graphics Jobs (Experimental)"); + public static readonly GUIContent graphicsJobsMode = EditorGUIUtility.TrTextContent("Graphics Jobs Mode"); public static readonly GUIContent applicationBuildNumber = EditorGUIUtility.TrTextContent("Build"); public static readonly GUIContent appleDeveloperTeamID = EditorGUIUtility.TrTextContent("iOS Developer Team ID", "Developers can retrieve their Team ID by visiting the Apple Developer site under Account > Membership."); public static readonly GUIContent useOnDemandResources = EditorGUIUtility.TrTextContent("Use on-demand resources*"); @@ -161,7 +163,6 @@ class SettingsContent public static readonly GUIContent require32 = EditorGUIUtility.TrTextContent("Require ES3.2"); public static readonly GUIContent skinOnGPU = EditorGUIUtility.TrTextContent("GPU Skinning*", "Use DX11/ES3 GPU Skinning"); public static readonly GUIContent skinOnGPUCompute = EditorGUIUtility.TrTextContent("Compute Skinning*", "Use Compute pipeline for Skinning"); - public static readonly GUIContent disableStatistics = EditorGUIUtility.TrTextContent("Disable HW Statistics*", "Disables HW Statistics"); public static readonly GUIContent scriptingDefineSymbols = EditorGUIUtility.TrTextContent("Scripting Define Symbols"); public static readonly GUIContent scriptingBackend = EditorGUIUtility.TrTextContent("Scripting Backend"); public static readonly GUIContent managedStrippingLevel = EditorGUIUtility.TrTextContent("Managed Stripping Level", "If scripting backend is IL2CPP, managed stripping can't be disabled."); @@ -195,7 +196,8 @@ class SettingsContent public static string undoChangedIconString { get { return LocalizationDatabase.GetLocalizedString("Changed Icon"); } } public static string undoChangedGraphicsAPIString { get { return LocalizationDatabase.GetLocalizedString("Changed Graphics API Settings"); } } public static string undoChangedScriptingDefineString { get { return LocalizationDatabase.GetLocalizedString("Changed Scripting Define Settings"); } } - public static string undoChangedGraphicsJobsString { get { return LocalizationDatabase.GetLocalizedString("Changed Graphics Jobs Settings"); } } + public static string undoChangedGraphicsJobsString { get { return LocalizationDatabase.GetLocalizedString("Changed Graphics Jobs Setting"); } } + public static string undoChangedGraphicsJobModeString { get { return LocalizationDatabase.GetLocalizedString("Changed Graphics Job Mode Setting"); } } } // Icon layout constants @@ -231,6 +233,9 @@ PlayerSettingsSplashScreenEditor splashScreenEditor SerializedProperty m_UseMacAppStoreValidation; SerializedProperty m_MacAppStoreCategory; + // vulkan + SerializedProperty m_VulkanNumSwapchainBuffers; + // iOS, tvOS #pragma warning disable 169 SerializedProperty m_IPhoneApplicationDisplayName; @@ -470,6 +475,7 @@ void OnEnable() m_ResizableWindow = FindPropertyAssert("resizableWindow"); m_UseMacAppStoreValidation = FindPropertyAssert("useMacAppStoreValidation"); m_MacAppStoreCategory = FindPropertyAssert("macAppStoreCategory"); + m_VulkanNumSwapchainBuffers = FindPropertyAssert("vulkanNumSwapchainBuffers"); m_FullscreenMode = FindPropertyAssert("fullscreenMode"); m_VisibleInBackground = FindPropertyAssert("visibleInBackground"); m_AllowFullscreenSwitch = FindPropertyAssert("allowFullscreenSwitch"); @@ -481,7 +487,7 @@ void OnEnable() m_RequireES31AEP = FindPropertyAssert("openGLRequireES31AEP"); m_RequireES32 = FindPropertyAssert("openGLRequireES32"); - m_LegacyClampBlendShapeWeights = FindPropertyAssert("legacyClampBlendShapeWeights"); + m_LegacyClampBlendShapeWeights = FindPropertyAssert("legacyClampBlendShapeWeights"); m_SettingsExtensions = new ISettingEditorExtension[validPlatforms.Length]; for (int i = 0; i < validPlatforms.Length; i++) @@ -816,11 +822,6 @@ private static bool TargetSupportsOptionalBuiltinSplashScreen(BuildTargetGroup t return targetGroup == BuildTargetGroup.Standalone; } - private static bool TargetSupportsProtectedGraphicsMem(BuildTargetGroup targetGroup) - { - return BuildTargetDiscovery.PlatformGroupHasFlag(targetGroup, TargetAttributes.ProtectedGraphicsMem); - } - public void ResolutionSectionGUI(BuildTargetGroup targetGroup, ISettingEditorExtension settingsExtension, int sectionIndex = 0) { if (BeginSettingsBox(sectionIndex, SettingsContent.resolutionPresentationTitle)) @@ -879,7 +880,7 @@ public void ResolutionSectionGUI(BuildTargetGroup targetGroup, ISettingEditorExt EditorGUILayout.PropertyField(m_DefaultScreenOrientation, SettingsContent.defaultScreenOrientation); - if (PlayerSettings.virtualRealitySupported) + if (VR.VREditor.GetVREnabledOnTargetGroup(targetGroup)) { EditorGUILayout.HelpBox(SettingsContent.vrOrientationInfo.text, MessageType.Warning); } @@ -1060,17 +1061,57 @@ private ChangeGraphicsApiAction CheckApplyGraphicsAPIList(BuildTarget target, bo // If we're changing the first API for relevant editor, this will cause editor to switch: ask for scene save & confirmation if (firstEntryChanged && WillEditorUseFirstGraphicsAPI(target)) { - if (EditorUtility.DisplayDialog("Changing editor graphics device", - "You've changed the active graphics API. This requires a restart of the Editor.", - "Restart Editor", "Not now")) + // If we have dirty scenes we need to save or discard changes before we restart editor. + // Otherwise user will get a dialog later on where they can click cancel and put editor in a bad device state. + var dirtyScenes = new List(); + for (int i = 0; i < EditorSceneManager.sceneCount; ++i) { - if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) + var scene = EditorSceneManager.GetSceneAt(i); + if (scene.isDirty) + dirtyScenes.Add(scene); + } + if (dirtyScenes.Count != 0) + { + var result = EditorUtility.DisplayDialogComplex("Changing editor graphics API", + "You've changed the active graphics API. This requires a restart of the Editor. Do you want to save the Scene when restarting?", + "Save and Restart", "Cancel Changing API", "Discard Changes and Restart"); + if (result == 1) + { + doRestart = false; // Cancel was selected + } + else + { doRestart = true; + if (result == 0) // Save and Restart was selected + { + for (int i = 0; i < dirtyScenes.Count; ++i) + { + var saved = EditorSceneManager.SaveScene(dirtyScenes[i]); + if (saved == false) + { + doRestart = false; + } + } + } + else // Discard Changes and Restart was selected + { + for (int i = 0; i < dirtyScenes.Count; ++i) + EditorSceneManager.ClearSceneDirtiness(dirtyScenes[i]); + } + } } else - doRestart = false; + { + doRestart = EditorUtility.DisplayDialog("Changing editor graphics API", + "You've changed the active graphics API. This requires a restart of the Editor.", + "Restart Editor", "Not now"); + } + return new ChangeGraphicsApiAction(doRestart, doRestart); + } + else + { + return new ChangeGraphicsApiAction(true, false); } - return new ChangeGraphicsApiAction(true, doRestart); } private void ApplyChangeGraphicsApiAction(BuildTarget target, GraphicsDeviceType[] apis, ChangeGraphicsApiAction action) @@ -1449,9 +1490,6 @@ public void OtherSectionGUI(BuildPlatform platform, BuildTargetGroup targetGroup EndSettingsBox(); } - [Serializable] - private struct HwStatsServiceState { public bool hwstats; } - private void OtherSectionRenderingGUI(BuildPlatform platform, BuildTargetGroup targetGroup, ISettingEditorExtension settingsExtension) { // Rendering related settings @@ -1499,7 +1537,10 @@ private void OtherSectionRenderingGUI(BuildPlatform platform, BuildTargetGroup t } // Graphics APIs - GraphicsAPIsGUI(targetGroup, platform.defaultTarget); + using (new EditorGUI.DisabledScope(EditorApplication.isPlaying)) + { + GraphicsAPIsGUI(targetGroup, platform.defaultTarget); + } // Output color spaces ColorGamutGUI(targetGroup); @@ -1620,7 +1661,7 @@ private void OtherSectionRenderingGUI(BuildPlatform platform, BuildTargetGroup t { GraphicsDeviceType[] gfxAPIs = PlayerSettings.GetGraphicsAPIs(platform.defaultTarget); - hdrDisplaySupported = gfxAPIs[0] == GraphicsDeviceType.Direct3D11 || gfxAPIs[0] == GraphicsDeviceType.Direct3D12; + hdrDisplaySupported = gfxAPIs[0] == GraphicsDeviceType.Direct3D11 || gfxAPIs[0] == GraphicsDeviceType.Direct3D12 || gfxAPIs[0] == GraphicsDeviceType.Vulkan; } } @@ -1643,6 +1684,7 @@ private void OtherSectionRenderingGUI(BuildPlatform platform, BuildTargetGroup t bool graphicsJobsOptionEnabled = true; bool graphicsJobs = PlayerSettings.GetGraphicsJobsForPlatform(platform.defaultTarget); bool newGraphicsJobs = graphicsJobs; + bool graphicsJobsModeOptionEnabled = graphicsJobs; if (targetGroup == BuildTargetGroup.XboxOne) { @@ -1650,31 +1692,44 @@ private void OtherSectionRenderingGUI(BuildPlatform platform, BuildTargetGroup t // no need for a drop down popup for XBoxOne // also if XboxOneD3D12 is selected as GraphicsAPI, then we want to set graphics jobs and disable the user option GraphicsDeviceType[] gfxAPIs = PlayerSettings.GetGraphicsAPIs(platform.defaultTarget); + GraphicsJobMode newGfxJobMode = GraphicsJobMode.Legacy; if (gfxAPIs[0] == GraphicsDeviceType.XboxOneD3D12) { - PlayerSettings.graphicsJobMode = GraphicsJobMode.Native; + newGfxJobMode = GraphicsJobMode.Native; graphicsJobsOptionEnabled = false; if (graphicsJobs == false) { PlayerSettings.SetGraphicsJobsForPlatform(platform.defaultTarget, true); + graphicsJobs = true; newGraphicsJobs = true; } } - else - { - PlayerSettings.graphicsJobMode = GraphicsJobMode.Legacy; - } + PlayerSettings.SetGraphicsJobModeForPlatform(platform.defaultTarget, newGfxJobMode); + graphicsJobsModeOptionEnabled = false; } EditorGUI.BeginChangeCheck(); + GUIContent graphicsJobsGUI = SettingsContent.graphicsJobsNonExperimental; + switch (platform.defaultTarget) + { + case BuildTarget.StandaloneOSX: + case BuildTarget.iOS: + case BuildTarget.tvOS: + case BuildTarget.Android: + graphicsJobsGUI = SettingsContent.graphicsJobsExperimental; + break; + default: + break; + } + using (new EditorGUI.DisabledScope(!graphicsJobsOptionEnabled)) { if (GUI.enabled) { - newGraphicsJobs = EditorGUILayout.Toggle(SettingsContent.graphicsJobs, graphicsJobs); + newGraphicsJobs = EditorGUILayout.Toggle(graphicsJobsGUI, graphicsJobs); } else { - EditorGUILayout.Toggle(SettingsContent.graphicsJobs, false); + EditorGUILayout.Toggle(graphicsJobsGUI, graphicsJobs); } } if (EditorGUI.EndChangeCheck() && (newGraphicsJobs != graphicsJobs)) @@ -1685,13 +1740,15 @@ private void OtherSectionRenderingGUI(BuildPlatform platform, BuildTargetGroup t if (gfxJobModesSupported) { - using (new EditorGUI.DisabledScope(!graphicsJobs)) + EditorGUI.BeginChangeCheck(); + using (new EditorGUI.DisabledScope(!graphicsJobsModeOptionEnabled)) { - GraphicsJobMode currGfxJobMode = PlayerSettings.graphicsJobMode; + GraphicsJobMode currGfxJobMode = PlayerSettings.GetGraphicsJobModeForPlatform(platform.defaultTarget); GraphicsJobMode newGfxJobMode = BuildEnumPopup(SettingsContent.graphicsJobsMode, currGfxJobMode, m_GfxJobModeValues, m_GfxJobModeNames); - if (newGfxJobMode != currGfxJobMode) + if (EditorGUI.EndChangeCheck() && (newGfxJobMode != currGfxJobMode)) { - PlayerSettings.graphicsJobMode = newGfxJobMode; + Undo.RecordObject(target, SettingsContent.undoChangedGraphicsJobModeString); + PlayerSettings.SetGraphicsJobModeForPlatform(platform.defaultTarget, newGfxJobMode); } } } @@ -1770,11 +1827,6 @@ private void OtherSectionRenderingGUI(BuildPlatform platform, BuildTargetGroup t } } - if (TargetSupportsProtectedGraphicsMem(targetGroup)) - { - PlayerSettings.protectGraphicsMemory = EditorGUILayout.Toggle(SettingsContent.protectGraphicsMemory, PlayerSettings.protectGraphicsMemory); - } - if (targetGroup == BuildTargetGroup.Standalone || (settingsExtension != null && settingsExtension.SupportsFrameTimingStatistics())) { PlayerSettings.enableFrameTimingStats = EditorGUILayout.Toggle(SettingsContent.enableFrameTimingStats, PlayerSettings.enableFrameTimingStats); @@ -1847,6 +1899,11 @@ private void OtherSectionVulkanSettingsGUI(BuildTargetGroup targetGroup, ISettin GUILayout.Label(SettingsContent.vulkanSettingsTitle, EditorStyles.boldLabel); PlayerSettings.vulkanEnableSetSRGBWrite = EditorGUILayout.Toggle(SettingsContent.vulkanEnableSetSRGBWrite, PlayerSettings.vulkanEnableSetSRGBWrite); + EditorGUILayout.PropertyField(m_VulkanNumSwapchainBuffers, SettingsContent.vulkanNumSwapchainBuffers); + PlayerSettings.vulkanNumSwapchainBuffers = (UInt32)m_VulkanNumSwapchainBuffers.intValue; + + if (settingsExtension != null && settingsExtension.ShouldShowVulkanSettings()) + settingsExtension.VulkanSectionGUI(); EditorGUILayout.Space(); } @@ -1941,6 +1998,11 @@ internal static void ShowBuildNumberUI(SerializedObject serializedObject, BuildT } } + private bool ShouldRestartEditorToApplySetting() + { + return EditorUtility.DisplayDialog("Unity editor restart required", "The Unity editor must be restarted for this change to take effect. Cancel to revert changes.", "Apply", "Cancel"); + } + private void OtherSectionConfigurationGUI(BuildPlatform platform, BuildTargetGroup targetGroup, ISettingEditorExtension settingsExtension) { // Configuration @@ -2007,13 +2069,36 @@ private void OtherSectionConfigurationGUI(BuildPlatform platform, BuildTargetGro using (new EditorGUI.DisabledScope(!gcIncrementalEnabled)) { - if (gcIncrementalEnabled) - EditorGUILayout.PropertyField(m_GCIncremental, SettingsContent.gcIncremental); - else - EditorGUILayout.Toggle(SettingsContent.gcIncremental, false); + var oldValue = m_GCIncremental.boolValue; + EditorGUILayout.PropertyField(m_GCIncremental, SettingsContent.gcIncremental); + if (m_GCIncremental.boolValue != oldValue) + { + // Give the user a chance to change mind and revert changes. + if (ShouldRestartEditorToApplySetting()) + { + m_EnableInputSystem.serializedObject.ApplyModifiedProperties(); + if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) + EditorApplication.OpenProject(Environment.CurrentDirectory); + } + else + m_GCIncremental.boolValue = oldValue; + } } } + bool showPrivacyPermissions = + targetGroup == BuildTargetGroup.iOS || targetGroup == BuildTargetGroup.tvOS || + platform.defaultTarget == BuildTarget.StandaloneOSX; + + if (showPrivacyPermissions) + { + EditorGUILayout.PropertyField(m_CameraUsageDescription, SettingsContent.cameraUsageDescription); + EditorGUILayout.PropertyField(m_MicrophoneUsageDescription, SettingsContent.microphoneUsageDescription); + + if (targetGroup == BuildTargetGroup.iOS || targetGroup == BuildTargetGroup.tvOS) + EditorGUILayout.PropertyField(m_LocationUsageDescription, SettingsContent.locationUsageDescription); + } + bool showMobileSection = targetGroup == BuildTargetGroup.iOS || targetGroup == BuildTargetGroup.tvOS || @@ -2033,13 +2118,6 @@ private void OtherSectionConfigurationGUI(BuildPlatform platform, BuildTargetGro if (supportsAccelerometerFrequency) EditorGUILayout.PropertyField(m_AccelerometerFrequency, SettingsContent.accelerometerFrequency); - if (targetGroup == BuildTargetGroup.iOS || targetGroup == BuildTargetGroup.tvOS) - { - EditorGUILayout.PropertyField(m_CameraUsageDescription, SettingsContent.cameraUsageDescription); - EditorGUILayout.PropertyField(m_LocationUsageDescription, SettingsContent.locationUsageDescription); - EditorGUILayout.PropertyField(m_MicrophoneUsageDescription, SettingsContent.microphoneUsageDescription); - } - if (targetGroup == BuildTargetGroup.iOS || targetGroup == BuildTargetGroup.tvOS || targetGroup == BuildTargetGroup.Android) { EditorGUILayout.PropertyField(m_MuteOtherAudioSources, SettingsContent.muteOtherAudioSources); @@ -2062,17 +2140,6 @@ private void OtherSectionConfigurationGUI(BuildPlatform platform, BuildTargetGro } } - //Disable HW Statistics - { - bool oldDisableAnalytics = !m_SubmitAnalytics.boolValue; - bool newDisableAnalytics = EditorGUILayout.Toggle(SettingsContent.disableStatistics, oldDisableAnalytics); - if (oldDisableAnalytics != newDisableAnalytics) - { - m_SubmitAnalytics.boolValue = !newDisableAnalytics; - EditorAnalytics.SendEventServiceInfo(new HwStatsServiceState() { hwstats = !newDisableAnalytics }); - } - } - if (settingsExtension != null) settingsExtension.ConfigurationSectionGUI(); @@ -2109,13 +2176,13 @@ private void OtherSectionConfigurationGUI(BuildPlatform platform, BuildTargetGro if (inputOption != oldInputOption) { // Give the user a chance to change mind and revert changes. - if (EditorUtility.DisplayDialog("Unity editor restart required", "The Unity editor must be restarted for this change to take effect. Cancel to revert changes.", "Apply", "Cancel")) + if (ShouldRestartEditorToApplySetting()) { m_EnableInputSystem.boolValue = (inputOption == 1 || inputOption == 2); m_DisableInputManager.boolValue = !(inputOption == 0 || inputOption == 2); m_EnableInputSystem.serializedObject.ApplyModifiedProperties(); if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) - EditorApplication.OpenProject(Environment.CurrentDirectory); + EditorApplication.RestartEditorAndRecompileScripts(); } } EditorGUIUtility.ExitGUI(); diff --git a/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsSplashScreenEditor.cs b/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsSplashScreenEditor.cs index e5457d5b0b..d2b0bde8ff 100644 --- a/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsSplashScreenEditor.cs +++ b/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsSplashScreenEditor.cs @@ -198,14 +198,14 @@ private void DrawLogoListElementCallback(Rect rect, int index, bool isActive, bo logo.objectReferenceValue = value; // Properties - var oldLabelWidth = EditorGUIUtility.labelWidth; - EditorGUIUtility.labelWidth = k_LogoListPropertyLabelWidth; var propertyRect = new Rect(rect.x + unityLogoWidth, rect.y + EditorGUIUtility.standardVerticalSpacing, rect.width - unityLogoWidth, EditorGUIUtility.singleLineHeight); var duration = element.FindPropertyRelative("duration"); - EditorGUIUtility.labelWidth = oldLabelWidth; EditorGUI.BeginChangeCheck(); + var oldLabelWidth = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth = k_LogoListPropertyLabelWidth; var newDurationVal = EditorGUI.Slider(propertyRect, k_Texts.logoDuration, duration.floatValue, k_MinLogoTime, k_MaxLogoTime); + EditorGUIUtility.labelWidth = oldLabelWidth; if (EditorGUI.EndChangeCheck()) duration.floatValue = newDurationVal; @@ -331,11 +331,11 @@ private void BuiltinCustomSplashScreenGUI() if (SplashScreen.isFinished) { SplashScreen.Begin(); - PreviewEditorWindow.RepaintAll(); - var preview = PreviewEditorWindow.GetMainPreviewWindow(); - if (preview) + PlayModeView.RepaintAll(); + var playModeView = PlayModeView.GetMainPlayModeView(); + if (playModeView) { - preview.Focus(); + playModeView.Focus(); } EditorApplication.update += PollSplashState; } @@ -423,7 +423,7 @@ private void BuiltinCustomSplashScreenGUI() void PollSplashState() { // Force the GameViews to repaint whilst showing the splash(1166664) - PreviewEditorWindow.RepaintAll(); + PlayModeView.RepaintAll(); // When the splash screen is playing we need to keep track so that we can update the preview button when it has finished. if (SplashScreen.isFinished) diff --git a/Editor/Mono/Inspector/PolygonCollider2DEditor.cs b/Editor/Mono/Inspector/PolygonCollider2DEditor.cs index beb35aac8a..54228514d6 100644 --- a/Editor/Mono/Inspector/PolygonCollider2DEditor.cs +++ b/Editor/Mono/Inspector/PolygonCollider2DEditor.cs @@ -117,12 +117,14 @@ public void OnEnable() { EditorTools.EditorTools.activeToolChanged += OnActiveToolChanged; EditorTools.EditorTools.activeToolChanging += OnActiveToolChanging; + Selection.selectionChanged += OnSelectionChanged; } public void OnDisable() { EditorTools.EditorTools.activeToolChanged -= OnActiveToolChanged; EditorTools.EditorTools.activeToolChanging -= OnActiveToolChanging; + Selection.selectionChanged -= OnSelectionChanged; } public override void OnToolGUI(EditorWindow window) @@ -132,9 +134,18 @@ public override void OnToolGUI(EditorWindow window) void OnActiveToolChanged() { - // We don't support multi-selection editing - if (EditorTools.EditorTools.IsActiveTool(this) && target != null) - polyUtility.StartEditing(target as Collider2D); + var collider = target as Collider2D; + if (EditorTools.EditorTools.IsActiveTool(this) && IsAvailable() && collider) + polyUtility.StartEditing(collider); + } + + void OnSelectionChanged() + { + if (EditorTools.EditorTools.IsActiveTool(this)) + polyUtility.StopEditing(); + var collider = target as Collider2D; + if (EditorTools.EditorTools.IsActiveTool(this) && IsAvailable() && collider != null) + polyUtility.StartEditing(collider); } void OnActiveToolChanging() @@ -145,6 +156,7 @@ void OnActiveToolChanging() public override bool IsAvailable() { + // We don't support multi-selection editing return targets.Count() == 1 && !targets.FirstOrDefault((x) => { var sr = (x as Component).GetComponent(); diff --git a/Editor/Mono/Inspector/PreviewRenderUtility.cs b/Editor/Mono/Inspector/PreviewRenderUtility.cs index 86672b6e2e..8905859bc2 100644 --- a/Editor/Mono/Inspector/PreviewRenderUtility.cs +++ b/Editor/Mono/Inspector/PreviewRenderUtility.cs @@ -94,6 +94,11 @@ public class PreviewRenderUtility private string m_Type; + // This is used to track colour space changes + // and try to keep colour values in sync + private ColorSpace colorSpace; + private Color defaultBackgroundColor; + public PreviewRenderUtility(bool renderFullScene) : this() {} @@ -120,6 +125,11 @@ public PreviewRenderUtility() m_PixelPerfect = false; + // Set a default background color + defaultBackgroundColor = new Color(49.0f / 255.0f, 49.0f / 255.0f, 49.0f / 255.0f, 1.0f); + colorSpace = QualitySettings.activeColorSpace; + camera.backgroundColor = colorSpace == ColorSpace.Gamma ? defaultBackgroundColor : defaultBackgroundColor.linear; + if (Unsupported.IsDeveloperMode()) { var stackTrace = new StackTrace(); @@ -231,6 +241,16 @@ public void BeginPreview(Rect r, GUIStyle previewBackground) } InitPreview(r); + + if (previewBackground == null || previewBackground == GUIStyle.none || previewBackground.normal.background == null) + return; + + Graphics.DrawTexture( + previewBackground.overflow.Add(new Rect(0, 0, m_RenderTexture.width, m_RenderTexture.height)), + previewBackground.normal.background, new Rect(0, 0, 1, 1), previewBackground.border.left, + previewBackground.border.right, previewBackground.border.top, previewBackground.border.bottom, + new Color(.5f, .5f, .5f, 0.5f), null + ); } public void BeginStaticPreview(Rect r) @@ -259,9 +279,15 @@ public void BeginStaticPreview(Rect r) private void InitPreview(Rect r) { - camera.backgroundColor = new Color(49.0f / 255.0f, 49.0f / 255.0f, 49.0f / 255.0f, 1.0f); - if (QualitySettings.activeColorSpace == ColorSpace.Linear) - camera.backgroundColor = camera.backgroundColor.linear; + // If the background colour has changed then we can't make any assumptions + // about colour space, otherwise flip to the background colour to the correct one + if (colorSpace != QualitySettings.activeColorSpace + && (camera.backgroundColor == defaultBackgroundColor || camera.backgroundColor.linear == defaultBackgroundColor.linear)) + { + camera.backgroundColor = QualitySettings.activeColorSpace == ColorSpace.Linear + ? defaultBackgroundColor.linear + : defaultBackgroundColor; + } m_TargetRect = r; float scaleFac = GetScaleFactor(r.width, r.height); diff --git a/Editor/Mono/Inspector/PreviewWindow.cs b/Editor/Mono/Inspector/PreviewWindow.cs index 6c02e269cd..c2c0a223ab 100644 --- a/Editor/Mono/Inspector/PreviewWindow.cs +++ b/Editor/Mono/Inspector/PreviewWindow.cs @@ -100,17 +100,19 @@ protected void DrawPreview() bool hasPreview = (editor != null) && editor.HasPreviewGUI(); // Toolbar - Rect toolbarRect = EditorGUILayout.BeginHorizontal(GUIContent.none, Styles.preToolbar, GUILayout.Height(kBottomToolbarHeight)); + Rect toolbarRect = EditorGUILayout.BeginHorizontal(GUIContent.none, EditorStyles.toolbar, GUILayout.Height(kBottomToolbarHeight)); { - GUILayout.FlexibleSpace(); - var labelRect = GUILayoutUtility.GetLastRect(); // Label string label = string.Empty; if ((editor != null)) { label = editor.GetPreviewTitle().text; } - GUI.Label(labelRect, label, Styles.preToolbar2); + + GUILayout.Label(label, Styles.preToolbarLabel); + + GUILayout.FlexibleSpace(); + if (hasPreview) editor.OnPreviewSettings(); } EditorGUILayout.EndHorizontal(); @@ -137,7 +139,10 @@ protected void DrawPreview() editor.DrawPreview(previewPosition); } - public override void AddItemsToMenu(GenericMenu menu) {} + public override void AddItemsToMenu(GenericMenu menu) + { + menu.AddItem(EditorGUIUtility.TrTextContent("Dock Preview to Inspector"), false, Close); + } protected override void ShowButton(Rect r) {} } diff --git a/Editor/Mono/Inspector/QualitySettingsEditor.cs b/Editor/Mono/Inspector/QualitySettingsEditor.cs index 84e1288005..f85ea5dae6 100644 --- a/Editor/Mono/Inspector/QualitySettingsEditor.cs +++ b/Editor/Mono/Inspector/QualitySettingsEditor.cs @@ -523,7 +523,7 @@ public override void OnInspectorGUI() GUILayout.Label(EditorGUIUtility.TempContent("Rendering"), EditorStyles.boldLabel); - EditorGUILayout.PropertyField(customRenderPipeline); + customRenderPipeline.objectReferenceValue = EditorGUILayout.ObjectField(customRenderPipeline.objectReferenceValue, typeof(RenderPipelineAsset), false); if (!usingSRP) EditorGUILayout.PropertyField(pixelLightCountProperty); @@ -598,8 +598,10 @@ public override void OnInspectorGUI() GUILayout.Label(EditorGUIUtility.TempContent("Other"), EditorStyles.boldLabel); EditorGUILayout.PropertyField(skinWeightsProperty); EditorGUILayout.PropertyField(vSyncCountProperty, Content.kVSyncCountLabel); - EditorGUILayout.PropertyField(lodBiasProperty, Content.kLODBiasLabel); - EditorGUILayout.PropertyField(maximumLODLevelProperty); + if (!SupportedRenderingFeatures.active.overridesLODBias) + EditorGUILayout.PropertyField(lodBiasProperty, Content.kLODBiasLabel); + if (!SupportedRenderingFeatures.active.overridesMaximumLODLevel) + EditorGUILayout.PropertyField(maximumLODLevelProperty); EditorGUILayout.PropertyField(particleRaycastBudgetProperty); EditorGUILayout.PropertyField(asyncUploadTimeSliceProperty); EditorGUILayout.PropertyField(asyncUploadBufferSizeProperty); diff --git a/Editor/Mono/Inspector/RayTracingShaderInspector.cs b/Editor/Mono/Inspector/RayTracingShaderInspector.cs index 0980402cf7..1eed7656fd 100644 --- a/Editor/Mono/Inspector/RayTracingShaderInspector.cs +++ b/Editor/Mono/Inspector/RayTracingShaderInspector.cs @@ -238,12 +238,22 @@ public override void OnInspectorGUI() ShowShaderErrors(rts); } + ShaderMessage[] m_ShaderMessages; private void ShowShaderErrors(RayTracingShader s) { - int n = ShaderUtil.GetRayTracingShaderMessageCount(s); - if (n < 1) + if (Event.current.type == EventType.Layout) + { + int n = ShaderUtil.GetRayTracingShaderMessageCount(s); + m_ShaderMessages = null; + if (n >= 1) + { + m_ShaderMessages = ShaderUtil.GetRayTracingShaderMessages(s); + } + } + + if (m_ShaderMessages == null) return; - ShaderInspector.ShaderErrorListUI(s, ShaderUtil.GetRayTracingShaderMessages(s), ref m_ScrollPosition); + ShaderInspector.ShaderErrorListUI(s, m_ShaderMessages, ref m_ScrollPosition); } } } diff --git a/Editor/Mono/Inspector/RectTransformEditor.cs b/Editor/Mono/Inspector/RectTransformEditor.cs index 33e538bdfe..039975e4ab 100644 --- a/Editor/Mono/Inspector/RectTransformEditor.cs +++ b/Editor/Mono/Inspector/RectTransformEditor.cs @@ -795,7 +795,7 @@ void UpdateTemporaryRect() { s_ParentDragTime = Time.realtimeSinceStartup; Canvas.ForceUpdateCanvases(); - PreviewEditorWindow.RepaintAll(); + PlayModeView.RepaintAll(); return; } @@ -816,7 +816,7 @@ void UpdateTemporaryRect() } Canvas.ForceUpdateCanvases(); SceneView.RepaintAll(); - PreviewEditorWindow.RepaintAll(); + PlayModeView.RepaintAll(); } void AllAnchorsSceneGUI(RectTransform gui, RectTransform guiParent, Transform parentSpace, Transform transform) diff --git a/Editor/Mono/Inspector/RendererEditorBase.cs b/Editor/Mono/Inspector/RendererEditorBase.cs index 1021ccf069..5a80abed7c 100644 --- a/Editor/Mono/Inspector/RendererEditorBase.cs +++ b/Editor/Mono/Inspector/RendererEditorBase.cs @@ -165,7 +165,8 @@ internal void RenderLightProbeUsage(int selectionCount, Renderer renderer, bool } } - var tree = renderer.GetComponent(); + Tree tree; + renderer.TryGetComponent(out tree); if ((tree != null) && (m_LightProbeUsage.intValue == (int)LightProbeUsage.UseProxyVolume)) { EditorGUI.indentLevel++; diff --git a/Editor/Mono/Inspector/Rigidbody2DEditor.cs b/Editor/Mono/Inspector/Rigidbody2DEditor.cs index 92227aed1d..18a820a8b3 100644 --- a/Editor/Mono/Inspector/Rigidbody2DEditor.cs +++ b/Editor/Mono/Inspector/Rigidbody2DEditor.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.Collections.Generic; using System.Linq; using UnityEditor; using UnityEditorInternal; @@ -38,9 +39,10 @@ internal class Rigidbody2DEditor : Editor static readonly GUIContent m_FreezePositionLabel = EditorGUIUtility.TrTextContent("Freeze Position"); static readonly GUIContent m_FreezeRotationLabel = EditorGUIUtility.TrTextContent("Freeze Rotation"); - static ContactPoint2D[] m_Contacts = new ContactPoint2D[100]; + static List m_Contacts = new List(64); private SavedBool m_ShowInfoFoldout; + private bool m_RequiresConstantRepaint; const int k_ToggleOffset = 30; @@ -73,6 +75,8 @@ public void OnEnable() m_ShowInfo.value = m_ShowInfoFoldout.value; m_ShowContacts.valueChanged.AddListener(Repaint); m_ContactScrollPosition = Vector2.zero; + + m_RequiresConstantRepaint = false; } public void OnDisable() @@ -157,10 +161,6 @@ public override void OnInspectorGUI() ToggleFreezeRotation(constraints, m_FreezeRotationLabel, 2); EditorGUI.indentLevel--; } - - // Provide end-user warning about the equivalence of all constraints on versus no Rigidbody2D component. - if (constraints == RigidbodyConstraints2D.FreezeAll) - EditorGUILayout.HelpBox("Rather than turning on all constraints, you may want to consider removing the Rigidbody2D component which makes any colliders static. This gives far better performance overall.", MessageType.Info); } EditorGUILayout.EndFadeGroup(); } @@ -172,6 +172,8 @@ public override void OnInspectorGUI() private void ShowBodyInfoProperties() { + m_RequiresConstantRepaint = false; + m_ShowInfoFoldout.value = m_ShowInfo.target = EditorGUILayout.Foldout(m_ShowInfo.target, "Info", true); if (EditorGUILayout.BeginFadeGroup(m_ShowInfo.faded)) { @@ -192,7 +194,7 @@ private void ShowBodyInfoProperties() ShowContacts(body); // We need to repaint as some of the above properties can change without causing a repaint. - Repaint(); + m_RequiresConstantRepaint = true; } else { @@ -205,7 +207,7 @@ private void ShowBodyInfoProperties() void ShowContacts(Rigidbody2D body) { EditorGUI.indentLevel++; - m_ShowContacts.target = EditorGUILayout.Foldout(m_ShowContacts.target, "Contacts"); + m_ShowContacts.target = EditorGUILayout.Foldout(m_ShowContacts.target, "Contacts", true); if (EditorGUILayout.BeginFadeGroup(m_ShowContacts.faded)) { var contactCount = body.GetContacts(m_Contacts); @@ -281,5 +283,10 @@ void ToggleFreezeRotation(RigidbodyConstraints2D constraints, GUIContent label, ConstraintToggle(r, "Z", constraints, z); GUILayout.EndHorizontal(); } + + public override bool RequiresConstantRepaint() + { + return m_RequiresConstantRepaint; + } } } diff --git a/Editor/Mono/Inspector/RigidbodyEditor.cs b/Editor/Mono/Inspector/RigidbodyEditor.cs index 8b0832e1df..994dd98921 100644 --- a/Editor/Mono/Inspector/RigidbodyEditor.cs +++ b/Editor/Mono/Inspector/RigidbodyEditor.cs @@ -18,11 +18,17 @@ internal class RigidbodyEditor : Editor static GUIContent m_FreezeRotationLabel = EditorGUIUtility.TrTextContent("Freeze Rotation"); readonly AnimBool m_ShowInfo = new AnimBool(); + private bool m_RequiresConstantRepaint; + private SavedBool m_ShowInfoFoldout; public void OnEnable() { m_Constraints = serializedObject.FindProperty("m_Constraints"); m_ShowInfo.valueChanged.AddListener(Repaint); + + m_RequiresConstantRepaint = false; + m_ShowInfoFoldout = new SavedBool($"{target.GetType()}.ShowFoldout", false); + m_ShowInfo.value = m_ShowInfoFoldout.value; } public void OnDisable() @@ -87,7 +93,10 @@ public override void OnInspectorGUI() private void ShowBodyInfoProperties() { - m_ShowInfo.target = EditorGUILayout.Foldout(m_ShowInfo.target, "Info", true); + m_RequiresConstantRepaint = false; + + Rect position = EditorGUILayout.GetControlRect(); + m_ShowInfoFoldout.value = m_ShowInfo.target = EditorGUI.Foldout(position, m_ShowInfo.target, "Info", true); if (EditorGUILayout.BeginFadeGroup(m_ShowInfo.faded)) { if (targets.Length == 1) @@ -107,7 +116,7 @@ private void ShowBodyInfoProperties() // We need to repaint as some of the above properties can change without causing a repaint. if (EditorApplication.isPlaying) - Repaint(); + m_RequiresConstantRepaint = true; } else { @@ -116,5 +125,10 @@ private void ShowBodyInfoProperties() } EditorGUILayout.EndFadeGroup(); } + + public override bool RequiresConstantRepaint() + { + return m_RequiresConstantRepaint; + } } } diff --git a/Editor/Mono/Inspector/ShaderGUI.cs b/Editor/Mono/Inspector/ShaderGUI.cs index a48ba6cbb5..4dd559d3c1 100644 --- a/Editor/Mono/Inspector/ShaderGUI.cs +++ b/Editor/Mono/Inspector/ShaderGUI.cs @@ -61,22 +61,18 @@ internal static class ShaderGUIUtility { private static Type ExtractCustomEditorType(string customEditorName) { - if (string.IsNullOrEmpty(customEditorName)) return null; + if (string.IsNullOrEmpty(customEditorName)) + return null; // To allow users to implement their own ShaderGUI for the Standard shader we iterate in reverse order // because the UnityEditor assembly is assumed first in the assembly list. // Users can now place a copy of the StandardShaderGUI script in the project and start modifying that copy to make their own version. - - string unityEditorFullName = "UnityEditor." + customEditorName; // for convenience: adding UnityEditor namespace is not needed in the shader - - var editorAssemblies = EditorAssemblies.loadedAssemblies; - for (int i = editorAssemblies.Length - 1; i >= 0; i--) + var unityEditorFullName = $"UnityEditor.{customEditorName}"; // for convenience: adding UnityEditor namespace is not needed in the shader + foreach (var type in TypeCache.GetTypesDerivedFrom()) { - foreach (var type in AssemblyHelper.GetTypesFromAssembly(editorAssemblies[i])) - { - if (type.FullName.Equals(customEditorName, StringComparison.Ordinal) || type.FullName.Equals(unityEditorFullName, StringComparison.Ordinal)) - return typeof(ShaderGUI).IsAssignableFrom(type) ? type : null; - } + if (type.FullName.Equals(customEditorName, StringComparison.Ordinal) || + type.FullName.Equals(unityEditorFullName, StringComparison.Ordinal)) + return typeof(ShaderGUI).IsAssignableFrom(type) ? type : null; } return null; } diff --git a/Editor/Mono/Inspector/ShaderImporterInspector.cs b/Editor/Mono/Inspector/ShaderImporterInspector.cs index 3eaa39c349..053eac64f5 100644 --- a/Editor/Mono/Inspector/ShaderImporterInspector.cs +++ b/Editor/Mono/Inspector/ShaderImporterInspector.cs @@ -63,16 +63,16 @@ protected override void InitializeExtraDataInstance(Object extraTarget, int targ if (shader == null) return; - var propertyCount = ShaderUtil.GetPropertyCount(shader); + var propertyCount = shader.GetPropertyCount(); for (var i = 0; i < propertyCount; i++) { - if (ShaderUtil.GetPropertyType(shader, i) != ShaderUtil.ShaderPropertyType.TexEnv) + if (shader.GetPropertyType(i) != ShaderPropertyType.Texture) continue; - var propertyName = ShaderUtil.GetPropertyName(shader, i); - var displayName = ShaderUtil.GetPropertyDescription(shader, i); // might be empty - var modifiable = !ShaderUtil.IsShaderPropertyNonModifiableTexureProperty(shader, i); + var propertyName = shader.GetPropertyName(i); + var displayName = shader.GetPropertyDescription(i); // might be empty + var modifiable = (shader.GetPropertyFlags(i) & ShaderPropertyFlags.NonModifiableTextureData) == 0; Texture tex; if (!modifiable) @@ -84,7 +84,7 @@ protected override void InitializeExtraDataInstance(Object extraTarget, int targ { propertyName = propertyName, texture = tex, - dimension = ShaderUtil.GetTexDim(shader, i), + dimension = shader.GetPropertyTextureDimension(i), displayName = displayName, modifiable = modifiable }; @@ -171,11 +171,11 @@ protected override void Apply() private static int GetNumberOfTextures(Shader shader) { int numberOfTextures = 0; - var propertyCount = ShaderUtil.GetPropertyCount(shader); + var propertyCount = shader.GetPropertyCount(); for (var i = 0; i < propertyCount; i++) { - if (ShaderUtil.GetPropertyType(shader, i) == ShaderUtil.ShaderPropertyType.TexEnv) + if (shader.GetPropertyType(i) == ShaderPropertyType.Texture) numberOfTextures++; } return numberOfTextures; diff --git a/Editor/Mono/Inspector/ShaderIncludePathAttribute.cs b/Editor/Mono/Inspector/ShaderIncludePathAttribute.cs index 0f4babdc29..e84d338a24 100644 --- a/Editor/Mono/Inspector/ShaderIncludePathAttribute.cs +++ b/Editor/Mono/Inspector/ShaderIncludePathAttribute.cs @@ -11,6 +11,6 @@ namespace UnityEditor public class ShaderIncludePathAttribute : Attribute { [RequiredSignature] - static extern string[] GetIncludePaths(); + static string[] GetIncludePaths() { throw new InvalidOperationException(); } } } diff --git a/Editor/Mono/Inspector/ShaderInspector.cs b/Editor/Mono/Inspector/ShaderInspector.cs index 082bd99238..d246dc497a 100644 --- a/Editor/Mono/Inspector/ShaderInspector.cs +++ b/Editor/Mono/Inspector/ShaderInspector.cs @@ -59,6 +59,19 @@ internal class Styles static readonly int kErrorViewHash = "ShaderErrorView".GetHashCode(); Vector2 m_ScrollPosition = Vector2.zero; + private Material m_SrpCompatibilityCheckMaterial = null; + + public Material srpCompatibilityCheckMaterial + { + get + { + if (m_SrpCompatibilityCheckMaterial == null) + { + m_SrpCompatibilityCheckMaterial = new Material(target as Shader); + } + return m_SrpCompatibilityCheckMaterial; + } + } public virtual void OnEnable() { @@ -66,12 +79,20 @@ public virtual void OnEnable() ShaderUtil.FetchCachedMessages(s); } + public virtual void OnDisable() + { + if (m_SrpCompatibilityCheckMaterial != null) + { + GameObject.DestroyImmediate(m_SrpCompatibilityCheckMaterial); + } + } + private static string GetPropertyType(Shader s, int index) { - ShaderUtil.ShaderPropertyType type = ShaderUtil.GetPropertyType(s, index); - if (type == ShaderUtil.ShaderPropertyType.TexEnv) + var type = s.GetPropertyType(index); + if (type == ShaderPropertyType.Texture) { - return kTextureTypes[(int)ShaderUtil.GetTexDim(s, index)]; + return kTextureTypes[(int)s.GetPropertyTextureDimension(index)]; } return kPropertyTypes[(int)type]; } @@ -116,8 +137,8 @@ public override void OnInspectorGUI() // If any SRP is active, then display the SRP Batcher compatibility status if (RenderPipelineManager.currentPipeline != null) { - var mat = new Material(s); - mat.SetPass(0); // NOTE: Force the shader compilation to ensure GetSRPBatcherCompatibilityCode will be up to date + // NOTE: Force the shader compilation to ensure GetSRPBatcherCompatibilityCode will be up to date + srpCompatibilityCheckMaterial.SetPass(0); int subShader = ShaderUtil.GetShaderActiveSubshaderIndex(s); int SRPErrCode = ShaderUtil.GetSRPBatcherCompatibilityCode(s, subShader); string result = (0 == SRPErrCode) ? "compatible" : "not compatible"; @@ -127,6 +148,7 @@ public override void OnInspectorGUI() EditorGUILayout.HelpBox(ShaderUtil.GetSRPBatcherCompatibilityIssueReason(s, subShader, SRPErrCode), MessageType.Info); } } + ShowShaderProperties(s); } } @@ -164,11 +186,11 @@ private static void ShowShaderProperties(Shader s) { GUILayout.Space(kSpace); GUILayout.Label("Properties:", EditorStyles.boldLabel); - int n = ShaderUtil.GetPropertyCount(s); + int n = s.GetPropertyCount(); for (int i = 0; i < n; ++i) { - string pname = ShaderUtil.GetPropertyName(s, i); - string pdesc = GetPropertyType(s, i) + ShaderUtil.GetPropertyDescription(s, i); + string pname = s.GetPropertyName(i); + string pdesc = s.GetPropertyType(i) + s.GetPropertyDescription(i); EditorGUILayout.LabelField(pname, pdesc); } } @@ -297,12 +319,22 @@ internal static void ShaderErrorListUI(Object shader, ShaderMessage[] messages, GUILayout.EndScrollView(); } + ShaderMessage[] m_ShaderMessages; private void ShowShaderErrors(Shader s) { - int n = ShaderUtil.GetShaderMessageCount(s); - if (n < 1) + if (Event.current.type == EventType.Layout) + { + int n = ShaderUtil.GetShaderMessageCount(s); + m_ShaderMessages = null; + if (n >= 1) + { + m_ShaderMessages = ShaderUtil.GetShaderMessages(s); + } + } + + if (m_ShaderMessages == null) return; - ShaderErrorListUI(s, ShaderUtil.GetShaderMessages(s), ref m_ScrollPosition); + ShaderErrorListUI(s, m_ShaderMessages, ref m_ScrollPosition); } // Compiled shader code button+dropdown @@ -567,6 +599,7 @@ static void InitializeShaderPlatforms() } s_ShaderPlatformNames = names.ToArray(); s_ShaderPlatformIndices = indices.ToArray(); + currentPlatformMask &= platformMask; } public override Vector2 GetWindowSize() diff --git a/Editor/Mono/Inspector/SkinnedMeshRendererEditor.cs b/Editor/Mono/Inspector/SkinnedMeshRendererEditor.cs index 0c0ee28acf..b4e3f06841 100644 --- a/Editor/Mono/Inspector/SkinnedMeshRendererEditor.cs +++ b/Editor/Mono/Inspector/SkinnedMeshRendererEditor.cs @@ -91,6 +91,8 @@ public void OnMeshUI() { SkinnedMeshRenderer renderer = (SkinnedMeshRenderer)target; + EditorGUILayout.PropertyField(m_Mesh, Styles.mesh); + if (renderer.sharedMesh != null) { bool haveClothComponent = renderer.gameObject.GetComponent() != null; @@ -100,8 +102,6 @@ public void OnMeshUI() EditorGUILayout.HelpBox(Styles.meshNotSupportingSkinningInfo.text, MessageType.Info); } } - - EditorGUILayout.PropertyField(m_Mesh, Styles.mesh); } public void OnBlendShapeUI() diff --git a/Editor/Mono/Inspector/SpeedTreeMaterialInspector.cs b/Editor/Mono/Inspector/SpeedTreeMaterialInspector.cs index 4a75a69585..b13a72c0d0 100644 --- a/Editor/Mono/Inspector/SpeedTreeMaterialInspector.cs +++ b/Editor/Mono/Inspector/SpeedTreeMaterialInspector.cs @@ -2,10 +2,10 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License -using UnityEngine; using System; using System.Collections.Generic; using System.Linq; +using UnityEngine; namespace UnityEditor { diff --git a/Editor/Mono/Inspector/StandardParticlesShaderGUI.cs b/Editor/Mono/Inspector/StandardParticlesShaderGUI.cs index 6b9f103e66..8166da07ce 100644 --- a/Editor/Mono/Inspector/StandardParticlesShaderGUI.cs +++ b/Editor/Mono/Inspector/StandardParticlesShaderGUI.cs @@ -484,7 +484,7 @@ void DoVertexStreamsArea(Material material) bool useGPUInstancing = ShaderUtil.HasProceduralInstancing(material.shader); if (useGPUInstancing && m_RenderersUsingThisMaterial.Count > 0) { - if (!m_RenderersUsingThisMaterial[0].enableGPUInstancing) + if (!m_RenderersUsingThisMaterial[0].enableGPUInstancing || m_RenderersUsingThisMaterial[0].renderMode != ParticleSystemRenderMode.Mesh) useGPUInstancing = false; } @@ -790,9 +790,13 @@ void CacheRenderersUsingThisMaterial(Material material) { m_RenderersUsingThisMaterial.Clear(); - ParticleSystemRenderer[] renderers = UnityEngine.Object.FindObjectsOfType(typeof(ParticleSystemRenderer)) as ParticleSystemRenderer[]; + ParticleSystemRenderer[] renderers = Resources.FindObjectsOfTypeAll(typeof(ParticleSystemRenderer)) as ParticleSystemRenderer[]; foreach (ParticleSystemRenderer renderer in renderers) { + var go = renderer.gameObject; + if (go.hideFlags == HideFlags.NotEditable || go.hideFlags == HideFlags.HideAndDontSave) + continue; + if (renderer.sharedMaterial == material) m_RenderersUsingThisMaterial.Add(renderer); } diff --git a/Editor/Mono/Inspector/TextureInspector.cs b/Editor/Mono/Inspector/TextureInspector.cs index 338b561fe0..d6c081cfa9 100644 --- a/Editor/Mono/Inspector/TextureInspector.cs +++ b/Editor/Mono/Inspector/TextureInspector.cs @@ -109,7 +109,7 @@ public Styles() toolbarButton = "toolbarbutton"; previewSlider = "preSlider"; previewSliderThumb = "preSliderThumb"; - previewLabel = "preLabelUpper"; + previewLabel = "toolbarLabel"; } } static Styles s_Styles; diff --git a/Editor/Mono/Inspector/UnityEventDrawer.cs b/Editor/Mono/Inspector/UnityEventDrawer.cs index 05e024f227..c4782eef78 100644 --- a/Editor/Mono/Inspector/UnityEventDrawer.cs +++ b/Editor/Mono/Inspector/UnityEventDrawer.cs @@ -40,6 +40,12 @@ protected class State internal const string kBoolArgument = "m_BoolArgument"; internal const string kObjectArgumentAssemblyTypeName = "m_ObjectArgumentAssemblyTypeName"; + //property path splits and separators + private const string kDotString = "."; + private const string kArrayDataString = "Array.data["; + private static readonly char[] kDotSeparator = { '.' }; + private static readonly char[] kClosingSquareBraceSeparator = { ']' }; + string m_Text; UnityEventBase m_DummyEvent; SerializedProperty m_Prop; @@ -391,22 +397,39 @@ static UnityEventBase GetDummyEvent(SerializedProperty prop) if (tgtobj == null) return new UnityEvent(); - string propPath = prop.propertyPath; + UnityEventBase ret = null; Type ft = tgtobj.GetType(); + var bindflags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; + do + { + ret = GetDummyEventHelper(prop.propertyPath, ft, bindflags); + //no need to look for public members again since the base type covered that + bindflags = BindingFlags.Instance | BindingFlags.NonPublic; + ft = ft.BaseType; + } + while (ret == null && ft != null); + // go up the class hierarchy if it exists and the property is not found on the child + return (ret == null) ? new UnityEvent() : ret; + } + + private static UnityEventBase GetDummyEventHelper(string propPath, Type targetObjectType, BindingFlags flags) + { + if (targetObjectType == null) + return null; while (propPath.Length != 0) { //we could have a leftover '.' if the previous iteration handled an array element - if (propPath.StartsWith(".")) + if (propPath.StartsWith(kDotString)) propPath = propPath.Substring(1); - var splits = propPath.Split(new[] { '.' }, 2); - var newField = ft.GetField(splits[0]); + var splits = propPath.Split(kDotSeparator, 2); + var newField = targetObjectType.GetField(splits[0], flags); if (newField == null) - break; + return GetDummyEventHelper(propPath, targetObjectType.BaseType, flags); - ft = newField.FieldType; - if (ft.IsArrayOrList()) - ft = ft.GetArrayOrListElementType(); + targetObjectType = newField.FieldType; + if (targetObjectType.IsArrayOrList()) + targetObjectType = targetObjectType.GetArrayOrListElementType(); //the last item in the property path could have been an array element //bail early in that case @@ -414,12 +437,12 @@ static UnityEventBase GetDummyEvent(SerializedProperty prop) break; propPath = splits[1]; - if (propPath.StartsWith("Array.data[")) - propPath = propPath.Split(new[] { ']' }, 2)[1]; + if (propPath.StartsWith(kArrayDataString)) + propPath = propPath.Split(kClosingSquareBraceSeparator, 2)[1]; } - if (ft.IsSubclassOf(typeof(UnityEventBase))) - return Activator.CreateInstance(ft) as UnityEventBase; - return new UnityEvent(); + if (targetObjectType.IsSubclassOf(typeof(UnityEventBase))) + return Activator.CreateInstance(targetObjectType) as UnityEventBase; + return null; } struct ValidMethodMap diff --git a/Editor/Mono/InternalEditorUtility.bindings.cs b/Editor/Mono/InternalEditorUtility.bindings.cs index e99cce90f8..cfe87a24b1 100644 --- a/Editor/Mono/InternalEditorUtility.bindings.cs +++ b/Editor/Mono/InternalEditorUtility.bindings.cs @@ -198,8 +198,11 @@ internal static void FixNormalmapTexture(MaterialProperty prop) [FreeFunction("InternalEditorUtilityBindings::CreateScriptableObjectUnchecked")] extern internal static int CreateScriptableObjectUnchecked(MonoScript script); - [StaticAccessor("GetApplication()", StaticAccessorType.Dot)] - extern public static void RequestScriptReload(); + [Obsolete("RequestScriptReload has been deprecated. Use UnityEditor.EditorUtility.RequestScriptReload instead (UnityUpgradable) -> [UnityEditor] UnityEditor.EditorUtility.RequestScriptReload(*)")] + public static void RequestScriptReload() + { + EditorUtility.RequestScriptReload(); + } // Repaint all views on next tick. Used when the user changes skins in the prefs. [StaticAccessor("GetApplication()", StaticAccessorType.Dot)] @@ -328,6 +331,9 @@ public static LayerMask ConcatenatedLayersMaskToLayerMask(int concatenatedLayers [FreeFunction("TryOpenErrorFileFromConsole")] public extern static bool TryOpenErrorFileFromConsole(string path, int line, int column); + [FreeFunction("TryOpenErrorFileFromConsoleInternal")] + internal extern static bool TryOpenErrorFileFromConsoleInternal(string path, int line, int column, bool isDryRun); + public static bool TryOpenErrorFileFromConsole(string path, int line) { return TryOpenErrorFileFromConsole(path, line, 0); diff --git a/Editor/Mono/InternalEditorUtility.cs b/Editor/Mono/InternalEditorUtility.cs index 0a1e272ff0..2e490d7845 100644 --- a/Editor/Mono/InternalEditorUtility.cs +++ b/Editor/Mono/InternalEditorUtility.cs @@ -150,52 +150,163 @@ public static void ShowGameView() WindowLayout.ShowAppropriateViewOnEnterExitPlaymode(true); } - // Multi selection handling. Returns new list of selected instanceIDs + internal struct AssetReference + { + // The guid is always valid. Assets not yet present will not have an instanceID yet. + // Could be because it is on-demand imported. + public string guid; + public int instanceID; // instanceID of an object in an asset if the asset is available in imported form. Else 0. + + public sealed class GuidThenInstanceIDEqualityComparer : IEqualityComparer + { + public bool Equals(AssetReference x, AssetReference y) + { + if (!string.IsNullOrEmpty(x.guid) || !string.IsNullOrEmpty(y.guid)) + string.Equals(x.guid, y.guid); + + // Both guids are nullOrEmpty now + return x.instanceID == y.instanceID; + } + + public int GetHashCode(AssetReference assetReference) + { + return (assetReference.instanceID * 397) + ^ (assetReference.guid != null ? assetReference.guid.GetHashCode() : 0); + } + } + } + public static List GetNewSelection(int clickedInstanceID, List allInstanceIDs, List selectedInstanceIDs, int lastClickedInstanceID, bool keepMultiSelection, bool useShiftAsActionKey, bool allowMultiSelection) { - List newSelection = new List(); + return GetNewSelection(clickedInstanceID, allInstanceIDs, selectedInstanceIDs, lastClickedInstanceID, keepMultiSelection, useShiftAsActionKey, allowMultiSelection, Event.current.shift, EditorGUI.actionKey); + } + + internal static List GetNewSelection(int clickedInstanceID, List allInstanceIDs, List selectedInstanceIDs, int lastClickedInstanceID, bool keepMultiSelection, bool useShiftAsActionKey, bool allowMultiSelection, bool shiftKeyIsDown, bool actionKeyIsDown) + { + List allGuids = null; + var clicked = new AssetReference() { guid = null, instanceID = clickedInstanceID }; - bool useShift = Event.current.shift || (EditorGUI.actionKey && useShiftAsActionKey); - bool useActionKey = EditorGUI.actionKey && !useShiftAsActionKey; + return GetNewSelection(ref clicked, allInstanceIDs, allGuids, selectedInstanceIDs, lastClickedInstanceID, keepMultiSelection, useShiftAsActionKey, allowMultiSelection, shiftKeyIsDown, actionKeyIsDown); + } + + internal static void EnsureInstanceId(ref AssetReference entry) + { + if (entry.instanceID != 0 || string.IsNullOrEmpty(entry.guid)) + return; + + var guids = new string[] { entry.guid }; + UnityEditor.Experimental.AssetDatabaseExperimental.GetArtifactHashes(guids); + string path = AssetDatabase.GUIDToAssetPath(entry.guid); + entry.instanceID = AssetDatabase.GetMainAssetInstanceID(path); + } + + internal static List EnsureInstanceIds(List entryInstanceIds, List entryInstanceGuids, int from, int to) + { + List guids = new List(); + + for (int i = from; i <= to; ++i) + { + if (entryInstanceIds[i] == 0) + guids.Add(entryInstanceGuids[i]); + } + + // Force import if needed so that we can get an instance ID for the entry + + if (guids.Count == 0) + { + return entryInstanceIds.GetRange(from, to - from + 1); + } + else + { + UnityEditor.Experimental.AssetDatabaseExperimental.GetArtifactHashes(guids.ToArray()); + var newSelection = new List(to - from + 1); + + for (int i = from; i <= to; ++i) + { + int instanceID = entryInstanceIds[i]; + if (instanceID == 0) + { + string path = AssetDatabase.GUIDToAssetPath(entryInstanceGuids[i]); + instanceID = AssetDatabase.GetMainAssetInstanceID(path); + entryInstanceIds[i] = instanceID; + } + newSelection.Add(instanceID); + } + return newSelection; + } + } + + internal static List GetNewSelection(ref AssetReference clickedEntry, List allEntryInstanceIDs, List allEntryGuids, List selectedInstanceIDs, int lastClickedInstanceID, bool keepMultiSelection, bool useShiftAsActionKey, bool allowMultiSelection) + { + return GetNewSelection(ref clickedEntry, allEntryInstanceIDs, allEntryGuids, selectedInstanceIDs, lastClickedInstanceID, keepMultiSelection, useShiftAsActionKey, allowMultiSelection, Event.current.shift, EditorGUI.actionKey); + } + + // Multi selection handling. Returns new list of selected instanceIDs + internal static List GetNewSelection(ref AssetReference clickedEntry, List allEntryInstanceIDs, List allEntryGuids, List selectedInstanceIDs, int lastClickedInstanceID, bool keepMultiSelection, bool useShiftAsActionKey, bool allowMultiSelection, bool shiftKeyIsDown, bool actionKeyIsDown) + { + bool useShift = shiftKeyIsDown || (actionKeyIsDown && useShiftAsActionKey); + bool useActionKey = actionKeyIsDown && !useShiftAsActionKey; if (!allowMultiSelection) useShift = useActionKey = false; // Toggle selected node from selection if (useActionKey) { - newSelection.AddRange(selectedInstanceIDs); - if (newSelection.Contains(clickedInstanceID)) - newSelection.Remove(clickedInstanceID); + var newSelection = new List(selectedInstanceIDs); + if (newSelection.Contains(clickedEntry.instanceID)) + newSelection.Remove(clickedEntry.instanceID); else - newSelection.Add(clickedInstanceID); + { + EnsureInstanceId(ref clickedEntry); + newSelection.Add(clickedEntry.instanceID); + } + return newSelection; } // Select everything between the first selected object and the selected else if (useShift) { - if (clickedInstanceID == lastClickedInstanceID) + if (clickedEntry.instanceID == lastClickedInstanceID) { - newSelection.AddRange(selectedInstanceIDs); - return newSelection; + return new List(selectedInstanceIDs); } int firstIndex; int lastIndex; - if (!GetFirstAndLastSelected(allInstanceIDs, selectedInstanceIDs, out firstIndex, out lastIndex)) + if (!GetFirstAndLastSelected(allEntryInstanceIDs, selectedInstanceIDs, out firstIndex, out lastIndex)) { // We had no selection - newSelection.Add(clickedInstanceID); + EnsureInstanceId(ref clickedEntry); + var newSelection = new List(1); + newSelection.Add(clickedEntry.instanceID); return newSelection; } int newIndex = -1; int prevIndex = -1; - for (int i = 0; i < allInstanceIDs.Count; ++i) + + // Only valid in case the selection concerns assets + + EnsureInstanceId(ref clickedEntry); + int clickedInstanceID = clickedEntry.instanceID; + + if (lastClickedInstanceID != 0) { - if (allInstanceIDs[i] == clickedInstanceID) - newIndex = i; - if (lastClickedInstanceID != 0) - if (allInstanceIDs[i] == lastClickedInstanceID) + for (int i = 0; i < allEntryInstanceIDs.Count; ++i) + { + if (allEntryInstanceIDs[i] == clickedInstanceID) + newIndex = i; + + if (allEntryInstanceIDs[i] == lastClickedInstanceID) prevIndex = i; + } + } + else + { + for (int i = 0; i < allEntryInstanceIDs.Count; ++i) + { + if (allEntryInstanceIDs[i] == clickedInstanceID) + newIndex = i; + } } System.Diagnostics.Debug.Assert(newIndex != -1); // new item should be part of visible folder set @@ -229,10 +340,11 @@ public static List GetNewSelection(int clickedInstanceID, List allInst } // Outcomment to debug - //Debug.Log (clickedInstanceID + ", firstIndex " + firstIndex + ", lastIndex " + lastIndex + ", newIndex " + newIndex + " " + ", lastClickedIndex " + prevIndex + ", from " + from + ", to " + to); - - for (int i = from; i <= to; ++i) - newSelection.Add(allInstanceIDs[i]); + //Debug.Log (clickedEntry + ", firstIndex " + firstIndex + ", lastIndex " + lastIndex + ", newIndex " + newIndex + " " + ", lastClickedIndex " + prevIndex + ", from " + from + ", to " + to); + if (allEntryGuids == null) + return allEntryInstanceIDs.GetRange(from, to - from + 1); + else + return EnsureInstanceIds(allEntryInstanceIDs, allEntryGuids, from, to); } // Just set the selection to the clicked object else @@ -241,26 +353,26 @@ public static List GetNewSelection(int clickedInstanceID, List allInst { // Don't change selection on mouse down when clicking on selected item. // This is for dragging in case with multiple items selected or right click (mouse down should not unselect the rest). - if (selectedInstanceIDs.Contains(clickedInstanceID)) + if (selectedInstanceIDs.Contains(clickedEntry.instanceID)) { - newSelection.AddRange(selectedInstanceIDs); - return newSelection; + return new List(selectedInstanceIDs); } } - newSelection.Add(clickedInstanceID); + EnsureInstanceId(ref clickedEntry); + var newSelection = new List(1); + newSelection.Add(clickedEntry.instanceID); + return newSelection; } - - return newSelection; } - static bool GetFirstAndLastSelected(List allInstanceIDs, List selectedInstanceIDs, out int firstIndex, out int lastIndex) + static bool GetFirstAndLastSelected(List allEntries, List selectedInstanceIDs, out int firstIndex, out int lastIndex) { firstIndex = -1; lastIndex = -1; - for (int i = 0; i < allInstanceIDs.Count; ++i) + for (int i = 0; i < allEntries.Count; ++i) { - if (selectedInstanceIDs.Contains(allInstanceIDs[i])) + if (selectedInstanceIDs.Contains(allEntries[i])) { if (firstIndex == -1) firstIndex = i; @@ -424,10 +536,10 @@ internal static string[] GetCompilationDefines(EditorScriptCompilationOptions op public static void SetShowGizmos(bool value) { - var view = PreviewEditorWindow.GetMainPreviewWindow(); + var view = PlayModeView.GetMainPlayModeView(); if (view == null) - view = PreviewEditorWindow.GetRenderingPreview(); + view = PlayModeView.GetRenderingView(); if (view == null) return; diff --git a/Editor/Mono/LogEntries.bindings.cs b/Editor/Mono/LogEntries.bindings.cs index eaa5850ea4..b857b18102 100644 --- a/Editor/Mono/LogEntries.bindings.cs +++ b/Editor/Mono/LogEntries.bindings.cs @@ -45,6 +45,7 @@ internal sealed class LogEntries public static extern int consoleFlags { get; set; } public static extern void SetConsoleFlag(int bit, bool value); public static extern void SetFilteringText(string filteringText); + public static extern string GetFilteringText(); public static extern void EndGettingEntries(); diff --git a/Editor/Mono/MaterialProperty.cs b/Editor/Mono/MaterialProperty.cs index 65abf1352c..8f5659bf5a 100644 --- a/Editor/Mono/MaterialProperty.cs +++ b/Editor/Mono/MaterialProperty.cs @@ -6,46 +6,15 @@ using System.Runtime.InteropServices; using UnityEngine; using Object = UnityEngine.Object; +using ShaderPropertyType = UnityEngine.Rendering.ShaderPropertyType; +using ShaderPropertyFlags = UnityEngine.Rendering.ShaderPropertyFlags; namespace UnityEditor { // match MonoMaterialProperty layout! [StructLayout(LayoutKind.Sequential)] - public sealed class MaterialProperty + public sealed partial class MaterialProperty { - public enum PropType - { - Color, - Vector, - Float, - Range, - Texture, - } - - [Obsolete("Use UnityEngine.Rendering.TextureDimension instead", false)] - public enum TexDim - { - Unknown = -1, - None = 0, - Tex2D = 2, - Tex3D = 3, - Cube = 4, - Any = 6, - } - - [Flags] - public enum PropFlags - { - None = 0, - HideInInspector = (1 << 0), - PerRendererData = (1 << 1), - NoScaleOffset = (1 << 2), - Normal = (1 << 3), - HDR = (1 << 4), - Gamma = (1 << 5), - NonModifiableTextureData = (1 << 6), - } - public delegate bool ApplyPropertyCallback(MaterialProperty prop, int changeMask, object previousValue); private Object[] m_Targets; @@ -55,17 +24,17 @@ public enum PropFlags private System.Object m_Value; private Vector4 m_TextureScaleAndOffset; private Vector2 m_RangeLimits; - private PropType m_Type; - private PropFlags m_Flags; + private ShaderPropertyType m_Type; + private ShaderPropertyFlags m_Flags; private UnityEngine.Rendering.TextureDimension m_TextureDimension; private int m_MixedValueMask; public Object[] targets { get { return m_Targets; } } - public PropType type { get { return m_Type; } } + public PropType type { get { return (PropType)m_Type; } } public string name { get { return m_Name; } } public string displayName { get { return m_DisplayName; } } - public PropFlags flags { get { return m_Flags; } } + public PropFlags flags { get { return (PropFlags)m_Flags; } } public UnityEngine.Rendering.TextureDimension textureDimension { get { return m_TextureDimension; } } public Vector2 rangeLimits { get { return m_RangeLimits; } } public bool hasMixedValue { get { return (m_MixedValueMask & 1) != 0; } } @@ -88,13 +57,13 @@ public Color colorValue { get { - if (m_Type == PropType.Color) + if (m_Type == ShaderPropertyType.Color) return (Color)m_Value; return Color.black; } set { - if (m_Type != PropType.Color) + if (m_Type != ShaderPropertyType.Color) return; if (!hasMixedValue && value == (Color)m_Value) return; @@ -107,13 +76,13 @@ public Vector4 vectorValue { get { - if (m_Type == PropType.Vector) + if (m_Type == ShaderPropertyType.Vector) return (Vector4)m_Value; return Vector4.zero; } set { - if (m_Type != PropType.Vector) + if (m_Type != ShaderPropertyType.Vector) return; if (!hasMixedValue && value == (Vector4)m_Value) return; @@ -132,13 +101,13 @@ public float floatValue { get { - if (m_Type == PropType.Float || m_Type == PropType.Range) + if (m_Type == ShaderPropertyType.Float || m_Type == ShaderPropertyType.Range) return (float)m_Value; return 0.0f; } set { - if (m_Type != PropType.Float && m_Type != PropType.Range) + if (m_Type != ShaderPropertyType.Float && m_Type != ShaderPropertyType.Range) return; if (!hasMixedValue && value == (float)m_Value) return; @@ -151,13 +120,13 @@ public Texture textureValue { get { - if (m_Type == PropType.Texture) + if (m_Type == ShaderPropertyType.Texture) return (Texture)m_Value; return null; } set { - if (m_Type != PropType.Texture) + if (m_Type != ShaderPropertyType.Texture) return; if (!hasMixedValue && value == (Texture)m_Value) return; @@ -174,13 +143,13 @@ public Vector4 textureScaleAndOffset { get { - if (m_Type == PropType.Texture) + if (m_Type == ShaderPropertyType.Texture) return m_TextureScaleAndOffset; return Vector4.zero; } set { - if (m_Type != PropType.Texture) + if (m_Type != ShaderPropertyType.Texture) return; if (!hasMixedValue && value == m_TextureScaleAndOffset) return; diff --git a/Editor/Mono/MaterialProperty.deprecated.cs b/Editor/Mono/MaterialProperty.deprecated.cs new file mode 100644 index 0000000000..a21532222c --- /dev/null +++ b/Editor/Mono/MaterialProperty.deprecated.cs @@ -0,0 +1,53 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; + +namespace UnityEditor +{ + partial class MaterialProperty + { + // We can't deprecate them yet (Sept 2019): SRP uses them, and we have the rule of not running APIUpdater on verified packages. + // Because of this rule, we can't upgrade MaterialProperty.propType and MaterialProperty.flags to the new enum type in UnityEngine + // namespace but to keep them separate. (We could have two differently named propType/flags properties, one returning old enum type and the + // other returning the new ShaderPropertyType and proceed with api deprecation with staged approach, but that will create even + // more confusion.) + // We won't even have deprecation warnings for them because some Katana tests expect no warnings from SRP. + + //[Obsolete("Use UnityEngine.Rendering.ShaderPropertyType instead. (UnityUpgradable) -> UnityEngine.Rendering.ShaderPropertyType")] + public enum PropType + { + Color, + Vector, + Float, + Range, + Texture, + } + + //[Obsolete("Use UnityEngine.Rendering.ShaderPropertyFlags instead. (UnityUpgradable) -> UnityEngine.Rendering.ShaderPropertyFlags")] + [Flags] + public enum PropFlags + { + None = 0, + HideInInspector = (1 << 0), + PerRendererData = (1 << 1), + NoScaleOffset = (1 << 2), + Normal = (1 << 3), + HDR = (1 << 4), + Gamma = (1 << 5), + NonModifiableTextureData = (1 << 6), + } + + [Obsolete("Use UnityEngine.Rendering.TextureDimension instead.", false)] + public enum TexDim + { + Unknown = -1, + None = 0, + Tex2D = 2, + Tex3D = 3, + Cube = 4, + Any = 6, + } + } +} diff --git a/Editor/Mono/Media/Bindings/MediaEncoder.bindings.cs b/Editor/Mono/Media/Bindings/MediaEncoder.bindings.cs index f812e94fbe..26a8276c75 100644 --- a/Editor/Mono/Media/Bindings/MediaEncoder.bindings.cs +++ b/Editor/Mono/Media/Bindings/MediaEncoder.bindings.cs @@ -6,6 +6,7 @@ using UnityEditor; using UnityEngine; using UnityEngine.Bindings; +using UnityEngineInternal.Video; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Object = UnityEngine.Object; @@ -103,12 +104,13 @@ public MediaRational rate public struct VideoTrackAttributes { - public MediaRational frameRate; - public uint width; - public uint height; - public bool includeAlpha; // For webm only; not applicable to mp4. - public VideoBitrateMode bitRateMode; - internal VideoCodec codec; + public MediaRational frameRate; + public uint width; + public uint height; + public bool includeAlpha; // For webm only; not applicable to mp4. + public VideoBitrateMode bitRateMode; + internal VideoCodec codec; + internal VideoAlphaLayout alphaLayout; } public struct AudioTrackAttributes diff --git a/Editor/Mono/Modules/DefaultCompilationExtension.cs b/Editor/Mono/Modules/DefaultCompilationExtension.cs index 7954452017..946058c89f 100644 --- a/Editor/Mono/Modules/DefaultCompilationExtension.cs +++ b/Editor/Mono/Modules/DefaultCompilationExtension.cs @@ -18,11 +18,6 @@ public virtual string[] GetCompilerExtraAssemblyPaths(bool isEditor, string asse return new string[] {}; } - public virtual IAssemblyResolver GetAssemblyResolver(bool buildingForEditor, string assemblyPath, string[] searchDirectories) - { - return null; - } - public virtual IEnumerable GetWindowsMetadataReferences() { return new string[0]; diff --git a/Editor/Mono/Modules/DefaultPlayerSettingsEditorExtension.cs b/Editor/Mono/Modules/DefaultPlayerSettingsEditorExtension.cs index a59db88288..52c1a32ad2 100644 --- a/Editor/Mono/Modules/DefaultPlayerSettingsEditorExtension.cs +++ b/Editor/Mono/Modules/DefaultPlayerSettingsEditorExtension.cs @@ -134,5 +134,7 @@ public virtual bool ShouldShowVulkanSettings() { return false; } + + public virtual void VulkanSectionGUI() {} } } diff --git a/Editor/Mono/Modules/DefaultPluginImporterExtension.cs b/Editor/Mono/Modules/DefaultPluginImporterExtension.cs index 4e27dd1fb8..e80811c52a 100644 --- a/Editor/Mono/Modules/DefaultPluginImporterExtension.cs +++ b/Editor/Mono/Modules/DefaultPluginImporterExtension.cs @@ -3,12 +3,13 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Collections.Generic; using System.ComponentModel; +using System.IO; +using System.Linq; using System.Text; +using UnityEditor.Compilation; using UnityEngine; -using System.Linq; -using System.IO; -using System.Collections.Generic; namespace UnityEditor.Modules { @@ -106,6 +107,8 @@ public virtual void Apply(PluginImporterInspector inspector) { p.Apply(inspector); } + + hasModified = false; } public virtual void OnEnable(PluginImporterInspector inspector) @@ -150,7 +153,14 @@ public virtual string CalculateFinalPluginPath(string platformName, PluginImport protected Dictionary> GetCompatiblePlugins(string buildTargetName) { - IEnumerable plugins = PluginImporter.GetAllImporters().Where(imp => imp.GetCompatibleWithPlatformOrAnyPlatformBuildTarget(buildTargetName)); + var assemblies = CompilationPipeline.GetAssemblies(); + var assemblyDefines = new Dictionary(assemblies.Length); + foreach (var assembly in assemblies) + { + assemblyDefines.Add(assembly.name, assembly.defines); + } + + IEnumerable plugins = PluginImporter.GetAllImporters().Where(imp => imp.GetCompatibleWithPlatformOrAnyPlatformBuildTarget(buildTargetName) && imp.IsCompatibleWithDefines(assemblyDefines.ContainsKey(imp.name) ? assemblyDefines[imp.name] : null)); Dictionary> matchingPlugins = new Dictionary>(); foreach (var plugin in plugins) diff --git a/Editor/Mono/Modules/PlatformSupportModule.cs b/Editor/Mono/Modules/PlatformSupportModule.cs index d399afc71c..3fe6024421 100644 --- a/Editor/Mono/Modules/PlatformSupportModule.cs +++ b/Editor/Mono/Modules/PlatformSupportModule.cs @@ -229,6 +229,8 @@ internal interface ISettingEditorExtension bool ShouldShowVulkanSettings(); + void VulkanSectionGUI(); + bool SupportsFrameTimingStatistics(); } @@ -410,7 +412,6 @@ internal enum CSharpCompiler internal interface ICompilationExtension { string[] GetCompilerExtraAssemblyPaths(bool isEditor, string assemblyPathName); - IAssemblyResolver GetAssemblyResolver(bool buildingForEditor, string assemblyPath, string[] searchDirectories); // Returns an array of windows metadata files (.winmd) that should be referenced when compiling scripts. // Only WinRT based platforms need these references. diff --git a/Editor/Mono/MonoCecil/MonoCecilHelper.cs b/Editor/Mono/MonoCecil/MonoCecilHelper.cs index 3bc7119c6a..95cec64baa 100644 --- a/Editor/Mono/MonoCecil/MonoCecilHelper.cs +++ b/Editor/Mono/MonoCecil/MonoCecilHelper.cs @@ -85,7 +85,16 @@ private static AssemblyDefinition ReadAssembly(string assemblyPath) AssemblyResolver = assemblyResolver, ReadingMode = ReadingMode.Deferred }; - return AssemblyDefinition.ReadAssembly(assemblyPath, readerParameters); + + try + { + return AssemblyDefinition.ReadAssembly(assemblyPath, readerParameters); + } + catch (Exception exception) + { + Debug.Log(exception.Message); + return null; + } } private static IEnumerable AggregateAllTypeDefinitions(IEnumerable types) diff --git a/Editor/Mono/Networking/PlayerConnection/AttachToPlayerGUI.cs b/Editor/Mono/Networking/PlayerConnection/AttachToPlayerGUI.cs index d8a39190c8..036df96d35 100644 --- a/Editor/Mono/Networking/PlayerConnection/AttachToPlayerGUI.cs +++ b/Editor/Mono/Networking/PlayerConnection/AttachToPlayerGUI.cs @@ -118,7 +118,7 @@ public string connectionName get { string name = ProfilerDriver.GetConnectionIdentifier(ProfilerDriver.connectedProfiler); - if (m_EditorModeTargetState.HasValue && name.StartsWith(k_EditorConnectionName)) + if (m_EditorModeTargetState.HasValue && name.Contains(k_EditorConnectionName)) { if (m_EditorModeTargetConnectionStatus(EditorConnectionTarget.MainEditorProcessEditmode)) name = Content.Editmode.text; @@ -177,7 +177,7 @@ private static void SuccessfullyConnectedToPlayer(string player, EditorConnectio } else { - if (player.StartsWith(k_EditorConnectionName)) + if (player.Contains(k_EditorConnectionName)) { // if e.g. the console or the memory profiler connects to the Editor, the profiler should switch to PlayMode profiling, not to Editmode profiling // especially since falling back onto the Editor is the default. @@ -261,9 +261,9 @@ void AddAvailablePlayerConnections(GenericMenu menuOptions, ref bool hasOpenConn } if (enabled) { - if (m_EditorModeTargetState.HasValue && name.StartsWith(k_EditorConnectionName)) + if (m_EditorModeTargetState.HasValue && name.Contains(k_EditorConnectionName)) { - menuOptions.AddItem(Content.Playmode, isConnected && m_EditorModeTargetConnectionStatus(EditorConnectionTarget.MainEditorProcessPlaymode), () => + menuOptions.AddItem(Content.Playmode, isConnected && !m_EditorModeTargetConnectionStatus(EditorConnectionTarget.MainEditorProcessPlaymode), () => { ProfilerDriver.connectedProfiler = guid; SuccessfullyConnectedToPlayer(connectionName, EditorConnectionTarget.MainEditorProcessPlaymode); @@ -279,7 +279,10 @@ void AddAvailablePlayerConnections(GenericMenu menuOptions, ref bool hasOpenConn menuOptions.AddItem(new GUIContent(name), isConnected, () => { ProfilerDriver.connectedProfiler = guid; - SuccessfullyConnectedToPlayer(connectionName); + if (ProfilerDriver.connectedProfiler == guid) + { + SuccessfullyConnectedToPlayer(connectionName); + } }); } } diff --git a/Editor/Mono/ObjectListArea.cs b/Editor/Mono/ObjectListArea.cs index ea3cf67bc5..1cd84b3363 100644 --- a/Editor/Mono/ObjectListArea.cs +++ b/Editor/Mono/ObjectListArea.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using UnityEditorInternal; +using AssetReference = UnityEditorInternal.InternalEditorUtility.AssetReference; namespace UnityEditor { @@ -111,7 +112,7 @@ static GUIStyle GetStyle(string styleName) public bool foldersFirst { get; set; } int m_KeyboardControlID; - Dictionary m_InstanceIDToCroppedNameMap = new Dictionary(); + Dictionary m_AssetReferenceToCroppedNameMap = new Dictionary(new AssetReference.GuidThenInstanceIDEqualityComparer()); int m_WidthUsedForCroppingName; bool m_AllowRenameOnMouseUp = true; @@ -143,7 +144,7 @@ static GUIStyle GetStyle(string styleName) int m_MinGridSize = 16; int m_MaxGridSize = 96; bool m_AllowThumbnails = true; - const int kSpaceForScrollBar = 16; + const int kSpaceForScrollBar = 13; int m_LeftPaddingForPinging = 0; bool m_FrameLastClickedItem = false; @@ -427,7 +428,6 @@ void RequeryAssetStore() void ClearAssetStoreGroups() { - EndRename(true); m_Groups.Clear(); m_Groups.Add(m_LocalAssets); m_StoreAssets.Clear(); @@ -529,9 +529,12 @@ public void OnGUI(Rect position, int keyboardControlID) // For key navigation: Auto set selection to first element if selection is not shown currently when tabbing if (evt.keyCode == KeyCode.Tab && evt.type == EventType.KeyDown && !hasKeyboardFocus && !IsShowingAny(GetSelection())) { - int firstInstanceID; - if (m_LocalAssets.InstanceIdAtIndex(0, out firstInstanceID)) - Selection.activeInstanceID = firstInstanceID; + AssetReference firstAssetReference; + if (m_LocalAssets.AssetReferenceAtIndex(0, out firstAssetReference)) + { + m_LocalAssets.GetNewSelection(ref firstAssetReference, false, false); + Selection.activeInstanceID = firstAssetReference.instanceID; + } } @@ -760,8 +763,12 @@ public bool IsLastClickedItemVisible() public void SelectAll() { - List list = m_LocalAssets.GetInstanceIDs(); - SetSelection(list.ToArray(), false); + List instanceIDs; + List guids; + m_LocalAssets.GetAssetReferences(out instanceIDs, out guids); + if (instanceIDs.Count != 0) + InternalEditorUtility.EnsureInstanceIds(instanceIDs, guids, 0, instanceIDs.Count - 1); + SetSelection(instanceIDs.ToArray(), false); } void SetSelection(int[] selectedInstanceIDs, bool doubleClicked) @@ -1044,19 +1051,11 @@ public void SelectFirst() SetSelectedAssetByIdx(startIndex); } - public int GetInstanceIDByIndex(int index) - { - int instanceID; - if (m_LocalAssets.InstanceIdAtIndex(index, out instanceID)) - return instanceID; - return 0; - } - void SetSelectedAssetByIdx(int selectedIdx) { // instanceID can be 0 if 'None' item is at index - int instanceID; - if (m_LocalAssets.InstanceIdAtIndex(selectedIdx, out instanceID)) + AssetReference assetReference; + if (m_LocalAssets.AssetReferenceAtIndex(selectedIdx, out assetReference)) { Rect r = m_LocalAssets.m_Grid.CalcRect(selectedIdx, 0f); ScrollToPosition(AdjustRectForFraming(r)); @@ -1064,12 +1063,12 @@ void SetSelectedAssetByIdx(int selectedIdx) int[] newSelection; if (IsLocalAssetsCurrentlySelected()) - newSelection = m_LocalAssets.GetNewSelection(instanceID, false, true).ToArray(); // Handle multi selection + newSelection = m_LocalAssets.GetNewSelection(ref assetReference, false, true).ToArray(); // Handle multi selection else - newSelection = new[] {instanceID}; // If current selection is asset store asset do not allow multiselection + newSelection = m_LocalAssets.GetNewSelection(ref assetReference, false, false).ToArray(); // If current selection is asset store asset do not allow multiselection SetSelection(newSelection, false); - m_State.m_LastClickedInstanceID = instanceID; + m_State.m_LastClickedInstanceID = assetReference.instanceID; return; } @@ -1530,20 +1529,20 @@ public void OnInspectorUpdate() void ClearCroppedLabelCache() { - m_InstanceIDToCroppedNameMap.Clear(); + m_AssetReferenceToCroppedNameMap.Clear(); } - protected string GetCroppedLabelText(int instanceID, string fullText, float cropWidth) + protected string GetCroppedLabelText(AssetReference assetReference, string fullText, float cropWidth) { // Clear when width changes if (m_WidthUsedForCroppingName != (int)cropWidth) ClearCroppedLabelCache(); string croppedText; - if (!m_InstanceIDToCroppedNameMap.TryGetValue(instanceID, out croppedText)) + if (!m_AssetReferenceToCroppedNameMap.TryGetValue(assetReference, out croppedText)) { // Ensure to clean up once in a while - if (m_InstanceIDToCroppedNameMap.Count > GetMaxNumVisibleItems() * 2 + 30) + if (m_AssetReferenceToCroppedNameMap.Count > GetMaxNumVisibleItems() * 2 + 30) ClearCroppedLabelCache(); // Check if we need to crop @@ -1559,7 +1558,7 @@ protected string GetCroppedLabelText(int instanceID, string fullText, float crop else croppedText = fullText; - m_InstanceIDToCroppedNameMap[instanceID] = croppedText; + m_AssetReferenceToCroppedNameMap[assetReference] = croppedText; m_WidthUsedForCroppingName = (int)cropWidth; } return croppedText; @@ -1636,8 +1635,8 @@ public void BeginPing(int instanceID) m_pingIndex = index; float vcPadding = s_VCEnabled ? k_ListModeVersionControlOverlayPadding : 0f; - - GUIContent cont = new GUIContent(m_LocalAssets.ListMode ? name : GetCroppedLabelText(instanceID, name, m_WidthUsedForCroppingName)); + var assetReference = new AssetReference() { instanceID = instanceID }; + GUIContent cont = new GUIContent(m_LocalAssets.ListMode ? name : GetCroppedLabelText(assetReference, name, m_WidthUsedForCroppingName)); string label = cont.text; if (m_LocalAssets.ListMode) diff --git a/Editor/Mono/ObjectListAssetStoreGroup.cs b/Editor/Mono/ObjectListAssetStoreGroup.cs index 5fd769ca52..a1a6cee1d0 100644 --- a/Editor/Mono/ObjectListAssetStoreGroup.cs +++ b/Editor/Mono/ObjectListAssetStoreGroup.cs @@ -250,7 +250,8 @@ void DrawLabel(Rect position, AssetStoreAsset assetStoreResource, bool selected) { // We crop the text in Preview mode int pseudoInstanceID = assetStoreResource.id + 10000000; // we add a large offset to the asset store id to ensure there is no overlap with instanceIDs - string labeltext = m_Owner.GetCroppedLabelText(pseudoInstanceID, assetStoreResource.displayName, position.width); + var assetReference = new InternalEditorUtility.AssetReference() { instanceID = pseudoInstanceID }; + string labeltext = m_Owner.GetCroppedLabelText(assetReference, assetStoreResource.displayName, position.width); position.height -= s_Styles.resultsGridLabel.fixedHeight; // The -1 is to ensure the label has same width as the image and that it aligns with the bottom of the image s_Styles.resultsGridLabel.Draw(new Rect(position.x, position.yMax + 1, position.width - 1, diff --git a/Editor/Mono/ObjectListGroup.cs b/Editor/Mono/ObjectListGroup.cs index fea946f05b..aca03b8efd 100644 --- a/Editor/Mono/ObjectListGroup.cs +++ b/Editor/Mono/ObjectListGroup.cs @@ -179,13 +179,14 @@ protected void DrawDropShadowOverlay(Rect position, bool selected, bool isDropTa s_Styles.iconDropShadow.Draw(dropShadowRect, GUIContent.none, false, false, selected || isDropTarget, m_Owner.HasFocus() || isRenaming || isDropTarget); } - protected void DrawHeaderBackground(Rect rect, bool firstHeader) + protected void DrawHeaderBackground(Rect rect, bool firstHeader, bool expanded) { if (Event.current.type != EventType.Repaint) return; // Draw the group bar background - GUI.Label(rect, GUIContent.none, firstHeader ? s_Styles.groupHeaderTop : s_Styles.groupHeaderMiddle); + (firstHeader ? s_Styles.groupHeaderTop : s_Styles.groupHeaderMiddle)?.Draw(rect, GUIContent.none, + rect.Contains(Event.current.mousePosition), false, expanded, false); } protected float GetHeaderYPosInScrollArea(float yOffset) @@ -204,7 +205,7 @@ virtual protected void DrawHeader(float yOffset, bool collapsable) const int foldoutSpacing = 3; Rect rect = new Rect(0, GetHeaderYPosInScrollArea(yOffset), m_Owner.GetVisibleWidth(), kGroupSeparatorHeight - 1); - DrawHeaderBackground(rect, yOffset == 0); + DrawHeaderBackground(rect, yOffset == 0, Visible); // Draw the group toggle rect.x += 7; diff --git a/Editor/Mono/ObjectListLocalGroup.cs b/Editor/Mono/ObjectListLocalGroup.cs index 0fbb018377..2855a8cef0 100644 --- a/Editor/Mono/ObjectListLocalGroup.cs +++ b/Editor/Mono/ObjectListLocalGroup.cs @@ -11,6 +11,7 @@ using System.Linq; using Math = System.Math; using IndexOutOfRangeException = System.IndexOutOfRangeException; +using AssetReference = UnityEditorInternal.InternalEditorUtility.AssetReference; namespace UnityEditor { @@ -108,7 +109,7 @@ override protected void DrawHeader(float yOffset, bool collapsable) { Rect rect = new Rect(0, GetHeaderYPosInScrollArea(yOffset), m_Owner.GetVisibleWidth(), kGroupSeparatorHeight); - base.DrawHeaderBackground(rect, true); + base.DrawHeaderBackground(rect, true, Visible); // Draw the group toggle if (collapsable) @@ -290,7 +291,7 @@ protected override void HandleUnusedDragEvents(float yOffset) } } - void HandleMouseWithDragging(int instanceID, int controlID, Rect rect) + void HandleMouseWithDragging(ref AssetReference assetReference, int controlID, Rect rect) { // Handle mouse down on entire line Event evt = Event.current; @@ -303,17 +304,30 @@ void HandleMouseWithDragging(int instanceID, int controlID, Rect rect) if (evt.clickCount == 2) { // Double clicked - m_Owner.SetSelection(new[] {instanceID}, true); + var newSelection = GetNewSelection(ref assetReference, false, false); + m_Owner.SetSelection(newSelection.ToArray(), true); m_DragSelection.Clear(); } else { // Begin drag - m_DragSelection = GetNewSelection(instanceID, true, false); + var newSelection = GetNewSelection(ref assetReference, false, false); + var oldItemControlID = controlID; + controlID = GetControlIDFromInstanceID(assetReference.instanceID); + if (controlID == oldItemControlID) + { + newSelection = GetNewSelection(ref assetReference, true, false); + m_DragSelection = newSelection; + DragAndDropDelay delay = (DragAndDropDelay)GUIUtility.GetStateObject(typeof(DragAndDropDelay), controlID); + delay.mouseDownPosition = Event.current.mousePosition; + m_Owner.ScrollToPosition(ObjectListArea.AdjustRectForFraming(rect)); + } + else + { + m_Owner.SetSelection(newSelection.ToArray(), false); + m_DragSelection.Clear(); + } GUIUtility.hotControl = controlID; - DragAndDropDelay delay = (DragAndDropDelay)GUIUtility.GetStateObject(typeof(DragAndDropDelay), controlID); - delay.mouseDownPosition = Event.current.mousePosition; - m_Owner.ScrollToPosition(ObjectListArea.AdjustRectForFraming(rect)); } evt.Use(); @@ -321,7 +335,7 @@ void HandleMouseWithDragging(int instanceID, int controlID, Rect rect) else if (Event.current.button == 1 && rect.Contains(Event.current.mousePosition)) { // Right mouse down selection (do NOT use event since we need ContextClick event, which is not fired if right click is used) - m_Owner.SetSelection(GetNewSelection(instanceID, true, false).ToArray(), false); + m_Owner.SetSelection(GetNewSelection(ref assetReference, true, false).ToArray(), false); } break; case EventType.MouseDrag: @@ -330,7 +344,7 @@ void HandleMouseWithDragging(int instanceID, int controlID, Rect rect) DragAndDropDelay delay = (DragAndDropDelay)GUIUtility.GetStateObject(typeof(DragAndDropDelay), controlID); if (delay.CanStartDrag()) { - StartDrag(instanceID, m_DragSelection); + StartDrag(assetReference.instanceID, m_DragSelection); GUIUtility.hotControl = 0; } @@ -343,7 +357,7 @@ void HandleMouseWithDragging(int instanceID, int controlID, Rect rect) bool perform = evt.type == EventType.DragPerform; if (rect.Contains(evt.mousePosition)) { - DragAndDropVisualMode mode = DoDrag(instanceID, perform); + DragAndDropVisualMode mode = DoDrag(assetReference.instanceID, perform); if (mode != DragAndDropVisualMode.None) { if (perform) @@ -382,13 +396,13 @@ void HandleMouseWithDragging(int instanceID, int controlID, Rect rect) } List selected = m_Owner.m_State.m_SelectedInstanceIDs; - if (clickedOnText && m_Owner.allowRenaming && m_Owner.m_AllowRenameOnMouseUp && selected.Count == 1 && selected[0] == instanceID && !EditorGUIUtility.HasHolddownKeyModifiers(evt)) + if (clickedOnText && m_Owner.allowRenaming && m_Owner.m_AllowRenameOnMouseUp && selected.Count == 1 && selected[0] == assetReference.instanceID && !EditorGUIUtility.HasHolddownKeyModifiers(evt)) { m_Owner.BeginRename(0.5f); } else { - List newSelection = GetNewSelection(instanceID, false, false); + List newSelection = GetNewSelection(ref assetReference, false, false); m_Owner.SetSelection(newSelection.ToArray(), false); } @@ -413,7 +427,7 @@ void HandleMouseWithDragging(int instanceID, int controlID, Rect rect) } } - void HandleMouseWithoutDragging(int instanceID, int controlID, Rect position) + void HandleMouseWithoutDragging(ref AssetReference assetReference, int controlID, Rect position) { Event evt = Event.current; @@ -430,7 +444,7 @@ void HandleMouseWithoutDragging(int instanceID, int controlID, Rect position) } evt.Use(); - List newSelection = GetNewSelection(instanceID, false, false); + List newSelection = GetNewSelection(ref assetReference, false, false); m_Owner.SetSelection(newSelection.ToArray(), evt.clickCount == 2); } break; @@ -439,7 +453,8 @@ void HandleMouseWithoutDragging(int instanceID, int controlID, Rect position) if (position.Contains(evt.mousePosition)) { // Select it - m_Owner.SetSelection(new[] {instanceID}, false); + List newSelection = GetNewSelection(ref assetReference, false, false); + m_Owner.SetSelection(newSelection.ToArray(), false); Rect overlayPos = position; overlayPos.x += 2; @@ -595,27 +610,28 @@ void DrawItem(Rect position, FilteredHierarchy.FilterResult filterItem, BuiltinR Event evt = Event.current; Rect itemRect = position; - int instanceID = 0; + var assetReference = new AssetReference() { instanceID = 0 }; bool showFoldout = false; if (filterItem != null) { - instanceID = filterItem.instanceID; + assetReference.instanceID = filterItem.instanceID; + assetReference.guid = filterItem.guid; showFoldout = filterItem.hasChildren && !filterItem.isFolder && isFolderBrowsing; // we do not want to be able to expand folders } else if (builtinResource != null) { - instanceID = builtinResource.m_InstanceID; + assetReference.instanceID = builtinResource.m_InstanceID; } - int controlID = GetControlIDFromInstanceID(instanceID); + int controlID = GetControlIDFromInstanceID(assetReference.instanceID); bool selected; if (m_Owner.allowDragging) - selected = m_DragSelection.Count > 0 ? m_DragSelection.Contains(instanceID) : m_Owner.IsSelected(instanceID); + selected = m_DragSelection.Count > 0 ? m_DragSelection.Contains(assetReference.instanceID) : m_Owner.IsSelected(assetReference.instanceID); else - selected = m_Owner.IsSelected(instanceID); + selected = m_Owner.IsSelected(assetReference.instanceID); - if (selected && instanceID == m_Owner.m_State.m_LastClickedInstanceID) + if (selected && assetReference.instanceID == m_Owner.m_State.m_LastClickedInstanceID) m_LastClickedDrawTime = EditorApplication.timeSinceStartup; Rect foldoutRect = new Rect(position.x + s_Styles.groupFoldout.margin.left, position.y, s_Styles.groupFoldout.padding.left, position.height); // ListMode foldout @@ -649,10 +665,10 @@ void DrawItem(Rect position, FilteredHierarchy.FilterResult filterItem, BuiltinR case KeyCode.LeftArrow: if (ListMode || m_Owner.IsPreviewIconExpansionModifierPressed()) { - if (IsExpanded(instanceID)) + if (IsExpanded(assetReference.instanceID)) toggleState = true; else - SelectAndFrameParentOf(instanceID); + SelectAndFrameParentOf(assetReference.instanceID); evt.Use(); } break; @@ -661,7 +677,7 @@ void DrawItem(Rect position, FilteredHierarchy.FilterResult filterItem, BuiltinR case KeyCode.RightArrow: if (ListMode || m_Owner.IsPreviewIconExpansionModifierPressed()) { - if (!IsExpanded(instanceID)) + if (!IsExpanded(assetReference.instanceID)) toggleState = true; evt.Use(); } @@ -675,15 +691,15 @@ void DrawItem(Rect position, FilteredHierarchy.FilterResult filterItem, BuiltinR if (toggleState) { - bool expanded = !IsExpanded(instanceID); + bool expanded = !IsExpanded(assetReference.instanceID); if (expanded) - m_ItemFader.Start(m_FilteredHierarchy.GetSubAssetInstanceIDs(instanceID)); - ChangeExpandedState(instanceID, expanded); + m_ItemFader.Start(m_FilteredHierarchy.GetSubAssetInstanceIDs(assetReference.instanceID)); + ChangeExpandedState(assetReference.instanceID, expanded); evt.Use(); GUIUtility.ExitGUI(); } - bool isRenaming = IsRenaming(instanceID); + bool isRenaming = IsRenaming(assetReference.instanceID); Rect labelRect = position; if (!ListMode) @@ -723,14 +739,21 @@ void DrawItem(Rect position, FilteredHierarchy.FilterResult filterItem, BuiltinR m_Content.image = null; Texture2D icon; - if (m_Owner.GetCreateAssetUtility().instanceID == instanceID && m_Owner.GetCreateAssetUtility().icon != null) + if (string.IsNullOrEmpty(assetReference.guid) && m_Owner.GetCreateAssetUtility().instanceID == assetReference.instanceID && m_Owner.GetCreateAssetUtility().icon != null) { // If we are creating a new asset we might have an icon to use icon = m_Owner.GetCreateAssetUtility().icon; } else { - icon = filterItem != null ? filterItem.icon : AssetPreview.GetAssetPreview(instanceID, m_Owner.GetAssetPreviewManagerID()); + icon = filterItem != null ? filterItem.icon : null; + if (icon == null) + { + if (assetReference.instanceID != 0) + icon = AssetPreview.GetAssetPreview(assetReference.instanceID, m_Owner.GetAssetPreviewManagerID()); + else if (!string.IsNullOrEmpty(assetReference.guid)) + icon = AssetPreview.GetAssetPreviewFromGUID(assetReference.guid, m_Owner.GetAssetPreviewManagerID()); + } } if (selected) @@ -744,13 +767,13 @@ void DrawItem(Rect position, FilteredHierarchy.FilterResult filterItem, BuiltinR // Foldout if (showFoldout) - s_Styles.groupFoldout.Draw(foldoutRect, !ListMode, !ListMode, IsExpanded(instanceID), false); + s_Styles.groupFoldout.Draw(foldoutRect, !ListMode, !ListMode, IsExpanded(assetReference.instanceID), false); } else // Icon grid { // Get icon bool drawDropShadow = false; - if (m_Owner.GetCreateAssetUtility().instanceID == instanceID && m_Owner.GetCreateAssetUtility().icon != null) + if (string.IsNullOrEmpty(assetReference.guid) && m_Owner.GetCreateAssetUtility().instanceID == assetReference.instanceID && m_Owner.GetCreateAssetUtility().icon != null) { // If we are creating a new asset we might have an icon to use m_Content.image = m_Owner.GetCreateAssetUtility().icon; @@ -758,7 +781,13 @@ void DrawItem(Rect position, FilteredHierarchy.FilterResult filterItem, BuiltinR else { // Check for asset preview - m_Content.image = AssetPreview.GetAssetPreview(instanceID, m_Owner.GetAssetPreviewManagerID()); + Texture image = null; + if (assetReference.instanceID != 0) + image = AssetPreview.GetAssetPreview(assetReference.instanceID, m_Owner.GetAssetPreviewManagerID()); + else if (!string.IsNullOrEmpty(assetReference.guid)) + image = AssetPreview.GetAssetPreviewFromGUID(assetReference.guid, m_Owner.GetAssetPreviewManagerID()); + + m_Content.image = image; if (m_Content.image != null) drawDropShadow = true; @@ -836,7 +865,7 @@ void DrawItem(Rect position, FilteredHierarchy.FilterResult filterItem, BuiltinR if (isDropTarget) s_Styles.resultsLabel.Draw(new Rect(labelRect.x - 10, labelRect.y, labelRect.width + 20, labelRect.height), GUIContent.none, true, true, false, false); - labeltext = m_Owner.GetCroppedLabelText(instanceID, labeltext, position.width); + labeltext = m_Owner.GetCroppedLabelText(assetReference, labeltext, position.width); var labelNewRect = s_Styles.resultsGridLabel.CalcSizeWithConstraints(GUIContent.Temp(labeltext), position.size); labelRect.x = position.x + (position.width - labelNewRect.x) / 2.0f; labelRect.width = labelNewRect.x; @@ -855,7 +884,7 @@ void DrawItem(Rect position, FilteredHierarchy.FilterResult filterItem, BuiltinR { style = s_Styles.subAssetExpandButtonMedium; } - style.Draw(foldoutRect, !ListMode, !ListMode, IsExpanded(instanceID), false); + style.Draw(foldoutRect, !ListMode, !ListMode, IsExpanded(assetReference.instanceID), false); } if (filterItem != null && filterItem.isMainRepresentation) @@ -893,9 +922,12 @@ void DrawItem(Rect position, FilteredHierarchy.FilterResult filterItem, BuiltinR // Mouse handling (must be after rename overlay to ensure overlay get mouseevents) if (m_Owner.allowDragging) - HandleMouseWithDragging(instanceID, controlID, position); + HandleMouseWithDragging(ref assetReference, controlID, position); else - HandleMouseWithoutDragging(instanceID, controlID, position); + HandleMouseWithoutDragging(ref assetReference, controlID, position); + + if (filterItem != null && filterItem.instanceID == 0) + filterItem.instanceID = assetReference.instanceID; } private static Rect ActualImageDrawPosition(Rect position, float imageWidth, float imageHeight) @@ -939,37 +971,51 @@ private void BeginPing(int instanceID) { } - public List GetInstanceIDs() + public void GetAssetReferences(out List instanceIDs, out List guids) { - List result = new List(); + instanceIDs = new List(); + guids = new List(); // 1. None item if (m_NoneList.Length > 0) - result.Add(m_NoneList[0].m_InstanceID); // 0 + { + instanceIDs.Add(m_NoneList[0].m_InstanceID); // 0 + guids.Add(null); + } // 2. Project Assets foreach (FilteredHierarchy.FilterResult r in m_FilteredHierarchy.results) - result.Add(r.instanceID); + { + instanceIDs.Add(r.instanceID); + guids.Add(r.guid); + } + if (m_Owner.m_State.m_NewAssetIndexInList >= 0) - result.Add(m_Owner.GetCreateAssetUtility().instanceID); + { + instanceIDs.Add(m_Owner.GetCreateAssetUtility().instanceID); + guids.Add(null); + } // 3. Builtin for (int i = 0; i < m_ActiveBuiltinList.Length; ++i) - result.Add(m_ActiveBuiltinList[i].m_InstanceID); - - return result; + { + instanceIDs.Add(m_ActiveBuiltinList[i].m_InstanceID); + guids.Add(null); + } } // Returns list of selected instanceIDs - public List GetNewSelection(int clickedInstanceID, bool beginOfDrag, bool useShiftAsActionKey) + public List GetNewSelection(ref AssetReference clickedAssetReference, bool beginOfDrag, bool useShiftAsActionKey) { // Flatten grid - List allInstanceIDs = GetInstanceIDs(); + List instanceIDs; + List guids; + GetAssetReferences(out instanceIDs, out guids); List selectedInstanceIDs = m_Owner.m_State.m_SelectedInstanceIDs; int lastClickedInstanceID = m_Owner.m_State.m_LastClickedInstanceID; bool allowMultiselection = m_Owner.allowMultiSelect; - return InternalEditorUtility.GetNewSelection(clickedInstanceID, allInstanceIDs, selectedInstanceIDs, lastClickedInstanceID, beginOfDrag, useShiftAsActionKey, allowMultiselection); + return InternalEditorUtility.GetNewSelection(ref clickedAssetReference, instanceIDs, guids, selectedInstanceIDs, lastClickedInstanceID, beginOfDrag, useShiftAsActionKey, allowMultiselection); } public override void UpdateFilter(HierarchyType hierarchyType, SearchFilter searchFilter, bool foldersFirst) @@ -1228,9 +1274,9 @@ public FilteredHierarchy.FilterResult LookupByInstanceID(int instanceID) } // Returns true if index was valid. Note that instance can be 0 if 'None' item was found at index - public bool InstanceIdAtIndex(int index, out int instanceID) + public bool AssetReferenceAtIndex(int index, out AssetReference assetReference) { - instanceID = 0; + assetReference = new AssetReference() { instanceID = 0 }; if (index >= m_Grid.rows * m_Grid.columns) return false; @@ -1248,7 +1294,8 @@ public bool InstanceIdAtIndex(int index, out int instanceID) // 2. Project assets foreach (FilteredHierarchy.FilterResult r in m_FilteredHierarchy.results) { - instanceID = r.instanceID; + assetReference.instanceID = r.instanceID; + assetReference.guid = r.guid; if (idx == index) return true; idx++; @@ -1257,7 +1304,7 @@ public bool InstanceIdAtIndex(int index, out int instanceID) // 3. Builtin resources foreach (BuiltinResource b in m_ActiveBuiltinList) { - instanceID = b.m_InstanceID; + assetReference.instanceID = b.m_InstanceID; if (idx == index) return true; idx++; @@ -1359,6 +1406,14 @@ public static void DrawIconAndLabel(Rect rect, FilteredHierarchy.FilterResult fi iconRect.width = k_IconWidth; iconRect.x += vcPadding * 0.5f; + if (selected && focus) + { + var activeIcon = EditorUtility.GetIconInActiveState(icon) as Texture2D; + + if (activeIcon) + icon = activeIcon; + } + if (icon != null) GUI.DrawTexture(iconRect, icon, ScaleMode.ScaleToFit); diff --git a/Editor/Mono/PackageUtility.bindings.cs b/Editor/Mono/PackageUtility.bindings.cs index 894406cdd4..48be8e8650 100644 --- a/Editor/Mono/PackageUtility.bindings.cs +++ b/Editor/Mono/PackageUtility.bindings.cs @@ -59,6 +59,8 @@ internal class PackageUtility [NativeThrows] public static extern ExportPackageItem[] BuildExportPackageItemsList(string[] guids, bool dependencies); [NativeThrows] + public static extern ExportPackageItem[] BuildExportPackageItemsListWithPackageManagerWarning(string[] guids, bool dependencies, bool warnPackageManagerDependencies); + [NativeThrows] public static extern void ExportPackage(string[] guids, string fileName); [NativeThrows] public static extern void ExportPackageAndPackageManagerManifest(string[] guids, string fileName); diff --git a/Editor/Mono/PerceptionRemoting/HolographicEmulation/HolographicEmulationWindow.cs b/Editor/Mono/PerceptionRemoting/HolographicEmulation/HolographicEmulationWindow.cs index f5725033e8..3a8f06d5b8 100644 --- a/Editor/Mono/PerceptionRemoting/HolographicEmulation/HolographicEmulationWindow.cs +++ b/Editor/Mono/PerceptionRemoting/HolographicEmulation/HolographicEmulationWindow.cs @@ -19,13 +19,17 @@ namespace UnityEngine.XR.WSA { internal class HolographicEmulationWindow : EditorWindow { + static HolographicEmulationWindow s_ActiveWindow; + private bool m_InPlayMode = false; private bool m_OperatingSystemChecked = false; private bool m_OperatingSystemValid = false; + private bool m_LoggedLastConnectionFailure = false; private HolographicStreamerConnectionState m_LastConnectionState = HolographicStreamerConnectionState.Disconnected; + // EmulationMode is internal so that it can be accessed for WindowsMR automation tests [SerializeField] - private EmulationMode m_Mode = EmulationMode.None; + internal EmulationMode m_Mode = EmulationMode.None; [SerializeField] private RemoteDeviceVersion m_DeviceVersion = RemoteDeviceVersion.V1; [SerializeField] @@ -94,32 +98,49 @@ internal class HolographicEmulationWindow : EditorWindow EditorGUIUtility.TrTextContent("Right Controller"), }; - internal EmulationMode emulationMode + private static void UpdatePlayModeState(PlayModeStateChange state) { - get { return m_Mode; } - set + if (state == PlayModeStateChange.ExitingEditMode || state == PlayModeStateChange.EnteredPlayMode) + s_ActiveWindow.m_InPlayMode = true; + else if (state == PlayModeStateChange.EnteredEditMode || state == PlayModeStateChange.ExitingPlayMode) + s_ActiveWindow.m_InPlayMode = false; + + if (s_ActiveWindow.m_Mode == EmulationMode.Simulated) { - HolographicAutomation.SetEmulationMode(value); - m_Mode = value; - Repaint(); + if (state == PlayModeStateChange.ExitingEditMode) + HolographicAutomation.SetEmulationMode(s_ActiveWindow.m_Mode); + + if (state == PlayModeStateChange.EnteredPlayMode) + s_ActiveWindow.LoadCurrentRoom(); } } internal static void Init() { - EditorWindow.GetWindow(false); + if (s_ActiveWindow == null) + { + HolographicEmulationWindow window = EditorWindow.GetWindow(false); + s_ActiveWindow = window; + } + + s_ActiveWindow.m_InPlayMode = EditorApplication.isPlayingOrWillChangePlaymode; + + EditorApplication.playModeStateChanged += UpdatePlayModeState; } private bool RemoteMachineNameSpecified { get { return !String.IsNullOrEmpty(m_RemoteMachineAddress); } } private void OnEnable() { - titleContent = EditorGUIUtility.TrTextContent("Holographic"); - m_InPlayMode = EditorApplication.isPlayingOrWillChangePlaymode; - + titleContent = EditorGUIUtility.TrTextContent("Holographic (Deprecated)"); m_RemoteMachineHistory = EditorPrefs.GetString("HolographicRemoting.RemoteMachineHistory").Split(','); } + private void OnDestroy() + { + EditorApplication.playModeStateChanged -= UpdatePlayModeState; + } + private void LoadCurrentRoom() { if (m_RoomIndex == 0) @@ -131,6 +152,9 @@ private void LoadCurrentRoom() private void Connect() { + m_LoggedLastConnectionFailure = false; + HolographicAutomation.SetEmulationMode(m_Mode); + PerceptionRemoting.SetRemoteDeviceVersion(m_DeviceVersion); PerceptionRemoting.SetVideoEncodingParameters(m_MaxBitrateKbps); PerceptionRemoting.SetEnableVideo(m_EnableVideo); PerceptionRemoting.SetEnableAudio(m_EnableAudio); @@ -206,7 +230,6 @@ private void UpdateRemoteMachineHistory() private void RemotingPreferencesOnGUI() { m_DeviceVersion = (RemoteDeviceVersion)EditorGUILayout.Popup(s_DeviceVersionText, (int)m_DeviceVersion, s_DeviceVersionStrings); - PerceptionRemoting.SetRemoteDeviceVersion(m_DeviceVersion); EditorGUI.BeginChangeCheck(); m_RemoteMachineAddress = EditorGUILayout.DelayedTextFieldDropDown(s_RemoteMachineText, m_RemoteMachineAddress, m_RemoteMachineHistory); @@ -303,7 +326,6 @@ private void DrawRemotingMode() { if (previousMode == EmulationMode.RemoteDevice) Disconnect(); - HolographicAutomation.SetEmulationMode(m_Mode); } } @@ -319,6 +341,8 @@ private bool CheckOperatingSystem() void OnGUI() { + EditorGUILayout.HelpBox("Support for built-in XR is deprecated and will be retired in a future version of Unity. We recommend you use the Unity XR Plugin System, which you can install from Project Settings > XR Plugin Manager.", MessageType.Info); + if (!CheckOperatingSystem()) { EditorGUILayout.HelpBox("You must be running Windows build 14318 or later to use Holographic Simulation or Remoting.", MessageType.Warning); @@ -331,6 +355,9 @@ void OnGUI() return; } + if (s_ActiveWindow == null) + Init(); + EditorGUILayout.Space(); EditorGUI.BeginDisabledGroup(m_InPlayMode); @@ -384,18 +411,17 @@ void Update() { Repaint(); } - var lastConnectionFailureReason = PerceptionRemoting.CheckForDisconnect(); - if (lastConnectionFailureReason == HolographicStreamerConnectionFailureReason.Unreachable - || lastConnectionFailureReason == HolographicStreamerConnectionFailureReason.ConnectionLost) - { - Debug.LogWarning("Disconnected with failure reason " + lastConnectionFailureReason + ", attempting to reconnect."); - Connect(); - } - else if (lastConnectionFailureReason != HolographicStreamerConnectionFailureReason.None) + m_LastConnectionState = connectionState; + + if (connectionState == HolographicStreamerConnectionState.Disconnected && !m_LoggedLastConnectionFailure) { - Debug.LogError("Disconnected with error " + lastConnectionFailureReason); + var lastConnectionFailureReason = PerceptionRemoting.CheckForDisconnect(); + if (lastConnectionFailureReason != HolographicStreamerConnectionFailureReason.None) + { + Debug.LogError("Disconnected with error " + lastConnectionFailureReason); + m_LoggedLastConnectionFailure = true; + } } - m_LastConnectionState = connectionState; break; } } diff --git a/Editor/Mono/Performance.bindings.cs b/Editor/Mono/Performance.bindings.cs index 3c9bd8fc32..4ad5989a96 100644 --- a/Editor/Mono/Performance.bindings.cs +++ b/Editor/Mono/Performance.bindings.cs @@ -3,34 +3,24 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Runtime.CompilerServices; using UnityEngine.Bindings; -namespace UnityEditor +[assembly: InternalsVisibleTo("Unity.PerformanceTracking.Editor")] + +namespace UnityEditor.Profiling { [NativeHeader("Editor/Src/Utility/Performance.h"), StaticAccessor("Performance::Bindings", StaticAccessorType.DoubleColon)] - internal static class Performance - { - public static extern bool IsTrackerExists(string name); - public static extern int GetTrackerSampleCount(string name); - public static extern double GetTrackerPeakTime(string name); - public static extern double GetTrackerAverageTime(string name); - public static extern double GetTrackerTotalTime(string name); - public static extern double GetTrackerTotalUsage(string name); - - internal static extern int StartTracker(string name); - internal static extern void StopTracker(int trackerToken); - } - - internal struct PerformanceTracker : IDisposable + internal struct EditorPerformanceTracker : IDisposable { private bool m_Disposed; private readonly int m_WatchHandle; - public PerformanceTracker(string name) + public EditorPerformanceTracker(string name) { m_Disposed = false; - m_WatchHandle = Performance.StartTracker(name); + m_WatchHandle = StartTracker(name); } public void Dispose() @@ -38,7 +28,23 @@ public void Dispose() if (m_Disposed) return; m_Disposed = true; - Performance.StopTracker(m_WatchHandle); + StopTracker(m_WatchHandle); } + + public static extern string[] GetAvailableTrackers(); + public static extern bool Exists(string trackerName); + public static extern void Reset(string trackerName); + public static extern int GetSampleCount(string trackerName); + public static extern double GetLastTime(string trackerName); + public static extern double GetPeakTime(string trackerName); + public static extern double GetAverageTime(string trackerName); + public static extern double GetTotalTime(string trackerName); + public static extern double GetTotalUsage(string trackerName); + public static extern double GetTimestamp(string trackerName); + public static extern void LogCallstack(string trackerName); + public static extern void GetCallstack(string trackerName, Action onCallstackCaptured); + + internal static extern int StartTracker(string trackerName); + internal static extern void StopTracker(int trackerToken); } } diff --git a/Editor/Mono/PerformanceTools/FrameDebugger.cs b/Editor/Mono/PerformanceTools/FrameDebugger.cs index e03ab8932a..eecb2231f8 100644 --- a/Editor/Mono/PerformanceTools/FrameDebugger.cs +++ b/Editor/Mono/PerformanceTools/FrameDebugger.cs @@ -485,10 +485,10 @@ private void ClickEnableFrameDebugger() // Make sure game view is visible when enabling frame debugger locally if (FrameDebuggerUtility.IsLocalEnabled()) { - var previewWindow = PreviewEditorWindow.GetMainPreviewWindow(); - if (previewWindow) + var playModeView = PlayModeView.GetMainPlayModeView(); + if (playModeView) { - previewWindow.ShowTab(); + playModeView.ShowTab(); } } @@ -627,7 +627,7 @@ private bool DrawToolbar(FrameDebuggerEvent[] descs) EditorGUI.BeginChangeCheck(); using (new EditorGUI.DisabledScope(!isSupported)) { - GUILayout.Toggle(FrameDebuggerUtility.IsLocalEnabled() || FrameDebuggerUtility.IsRemoteEnabled(), styles.recordButton, EditorStyles.toolbarButton, GUILayout.MinWidth(80)); + GUILayout.Toggle(FrameDebuggerUtility.IsLocalEnabled() || FrameDebuggerUtility.IsRemoteEnabled(), styles.recordButton, EditorStyles.toolbarButtonLeft, GUILayout.MinWidth(80)); } if (EditorGUI.EndChangeCheck()) { @@ -675,7 +675,7 @@ private bool DrawToolbar(FrameDebuggerEvent[] descs) } using (new EditorGUI.DisabledScope(newLimit >= FrameDebuggerUtility.count)) { - if (GUILayout.Button(styles.nextFrame, EditorStyles.toolbarButton)) + if (GUILayout.Button(styles.nextFrame, EditorStyles.toolbarButtonRight)) { ChangeFrameEventLimit(newLimit + 1); } diff --git a/Editor/Mono/Preview/PreviewEditorWindow.cs b/Editor/Mono/PlayModeView/PlayModeView.cs similarity index 52% rename from Editor/Mono/Preview/PreviewEditorWindow.cs rename to Editor/Mono/PlayModeView/PlayModeView.cs index f25d814aff..292e800c23 100644 --- a/Editor/Mono/Preview/PreviewEditorWindow.cs +++ b/Editor/Mono/PlayModeView/PlayModeView.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using UnityEditor.Modules; using UnityEditorInternal; @@ -13,14 +14,28 @@ namespace UnityEditor { + internal static class PreviewEditorWindow + { + internal static void RepaintAll() + { + PlayModeView.RepaintAll(); + } + } + [Serializable] - internal abstract class PreviewEditorWindow : EditorWindow + internal abstract class PlayModeView : EditorWindow { - static List s_PreviewWindows = new List(); - static PreviewEditorWindow s_LastFocused; - static PreviewEditorWindow s_RenderingPreview; + static List s_PlayModeViews = new List(); + static PlayModeView s_LastFocused; + static PlayModeView s_RenderingView; + + private readonly string m_ViewsCache = Path.GetFullPath(Directory.GetCurrentDirectory() + "/Library/PlayModeViewStates/"); - [SerializeField] string m_PreviewName; + [SerializeField] private List m_SerializedViewNames = new List(); + [SerializeField] private List m_SerializedViewValues = new List(); + [SerializeField] private List m_SerializedCustomFieldsNames = new List(); + [SerializeField] private List m_SerializedCustomFieldsValues = new List(); + [SerializeField] string m_PlayModeViewName; [SerializeField] bool m_ShowGizmos; [SerializeField] int m_TargetDisplay; [SerializeField] Color m_ClearColor; @@ -29,13 +44,14 @@ internal abstract class PreviewEditorWindow : EditorWindow [SerializeField] HideFlags m_TextureHideFlags = HideFlags.HideAndDontSave; [SerializeField] bool m_RenderIMGUI; [SerializeField] bool m_MaximizeOnPlay; + [SerializeField] bool m_UseMipMap; private Dictionary m_AvailableWindowTypes; - protected string previewName + protected string playModeViewName { - get { return m_PreviewName; } - set { m_PreviewName = value; } + get { return m_PlayModeViewName; } + set { m_PlayModeViewName = value; } } protected bool showGizmos @@ -62,7 +78,12 @@ protected Color clearColor protected Vector2 targetSize { get { return m_TargetSize; } - set { m_TargetSize = value; } + set + { + if (this == GetMainPlayModeView()) + SetMainPlayModeViewSize(value); + m_TargetSize = value; + } } protected FilterMode textureFilterMode @@ -89,16 +110,22 @@ public bool maximizeOnPlay set { m_MaximizeOnPlay = value; } } + protected bool useMipMap + { + get { return m_UseMipMap; } + set { m_UseMipMap = value; } + } + RenderTexture m_TargetTexture; ColorSpace m_CurrentColorSpace = ColorSpace.Uninitialized; - class RenderingPreview : IDisposable + class RenderingView : IDisposable { bool disposed = false; - public RenderingPreview(PreviewEditorWindow previewWindow) + public RenderingView(PlayModeView playModeView) { - PreviewEditorWindow.s_RenderingPreview = previewWindow; + PlayModeView.s_RenderingView = playModeView; } public void Dispose() @@ -115,22 +142,22 @@ protected virtual void Dispose(bool disposing) if (disposing) { - PreviewEditorWindow.s_RenderingPreview = null; + PlayModeView.s_RenderingView = null; } disposed = true; } } - protected PreviewEditorWindow() + protected PlayModeView() { RegisterWindow(); - SetPlayModeView(); + SetPlayModeView(true); } - protected RenderTexture RenderPreview(Vector2 mousePosition, bool clearTexture) + protected RenderTexture RenderView(Vector2 mousePosition, bool clearTexture) { - using (var renderingPreview = new RenderingPreview(this)) + using (var renderingView = new RenderingView(this)) { SetPlayModeViewSize(targetSize); var currentTargetDisplay = 0; @@ -141,7 +168,7 @@ protected RenderTexture RenderPreview(Vector2 mousePosition, bool clearTexture) currentTargetDisplay = targetDisplay; } - ConfigureTargetTexture((int)targetSize.x, (int)targetSize.y, clearTexture, previewName); + ConfigureTargetTexture((int)targetSize.x, (int)targetSize.y, clearTexture, playModeViewName); if (Event.current == null || Event.current.type != EventType.Repaint) return m_TargetTexture; @@ -150,7 +177,8 @@ protected RenderTexture RenderPreview(Vector2 mousePosition, bool clearTexture) GUIUtility.s_EditorScreenPointOffset = Vector2.zero; SavedGUIState oldState = SavedGUIState.Create(); - EditorGUIUtility.RenderPreviewCamerasInternal(m_TargetTexture, currentTargetDisplay, mousePosition, showGizmos, renderIMGUI); + if (m_TargetTexture.IsCreated()) + EditorGUIUtility.RenderPlayModeViewCamerasInternal(m_TargetTexture, currentTargetDisplay, mousePosition, showGizmos, renderIMGUI); oldState.ApplyAndForget(); GUIUtility.s_EditorScreenPointOffset = oldOffset; @@ -167,18 +195,95 @@ protected string GetWindowTitle(Type type) protected Dictionary GetAvailableWindowTypes() { - return m_AvailableWindowTypes ?? (m_AvailableWindowTypes = TypeCache.GetTypesDerivedFrom(typeof(PreviewEditorWindow)).OrderBy(GetWindowTitle).ToDictionary(t => t, GetWindowTitle)); + return m_AvailableWindowTypes ?? (m_AvailableWindowTypes = TypeCache.GetTypesDerivedFrom(typeof(PlayModeView)).OrderBy(GetWindowTitle).ToDictionary(t => t, GetWindowTitle)); } - protected void SwapMainWindow(Type type) + protected virtual string SerializeView() + { + return null; + } + + protected virtual void DeserializeView(string serializedView) + { + } + + private void SetSerializedViews(Dictionary serializedViews) + { + m_SerializedViewNames = serializedViews.Keys.ToList(); + m_SerializedViewValues = serializedViews.Values.ToList(); + } + + private void SetSerializedCustomFields(Dictionary serializedCustomFields) + { + m_SerializedCustomFieldsNames = serializedCustomFields.Keys.ToList(); + m_SerializedCustomFieldsValues = serializedCustomFields.Values.ToList(); + } + + private string GetTypeName() { - if (type.BaseType != typeof(PreviewEditorWindow)) - throw new ArgumentException("Type should derive from " + typeof(PreviewEditorWindow).Name); + return GetType().ToString(); + } + private Dictionary ListsToDictionary(List keys, List values) + { + var dict = keys.Select((key, val) => new { key, val = values[val] }).ToDictionary(x => x.key, x => x.val); + return dict; + } + + protected void SwapMainWindow(Type type) + { + if (type.BaseType != typeof(PlayModeView)) + throw new ArgumentException("Type should derive from " + typeof(PlayModeView).Name); if (type.Name != GetType().Name) { - var window = CreateInstance(type) as PreviewEditorWindow; + var serializedViews = ListsToDictionary(m_SerializedViewNames, m_SerializedViewValues); + var serializedCustomFields = ListsToDictionary(m_SerializedCustomFieldsNames, m_SerializedCustomFieldsValues); + + // Clear serialized views so they wouldn't be serialized again + m_SerializedViewNames.Clear(); + m_SerializedViewValues.Clear(); + m_SerializedCustomFieldsNames.Clear(); + m_SerializedCustomFieldsValues.Clear(); + + var serializedObject = SerializeView(); + if (serializedObject != null) + serializedCustomFields.Add(GetTypeName(), serializedObject); + + var guid = GUID.Generate(); + var serializedViewPath = Path.GetFullPath(Path.Combine(m_ViewsCache, guid.ToString())); + if (!Directory.Exists(m_ViewsCache)) + Directory.CreateDirectory(m_ViewsCache); + + InternalEditorUtility.SaveToSerializedFileAndForget(new[] {this}, serializedViewPath, true); + serializedViews.Add(GetTypeName(), serializedViewPath); + + PlayModeView window = null; + if (serializedViews.ContainsKey(type.ToString())) + { + var path = serializedViews[type.ToString()]; + serializedViews.Remove(type.ToString()); + if (File.Exists(path)) + { + window = InternalEditorUtility.LoadSerializedFileAndForget(path)[0] as PlayModeView; + File.Delete(path); + } + } + + if (!window) + window = CreateInstance(type) as PlayModeView; + + + if (serializedCustomFields.ContainsKey(window.GetTypeName())) + { + window.DeserializeView(serializedCustomFields[window.GetTypeName()]); + serializedCustomFields.Remove(window.GetTypeName()); + } + window.autoRepaintOnSceneChange = true; + + window.SetSerializedViews(serializedViews); + window.SetSerializedCustomFields(serializedCustomFields); + var da = m_Parent as DockArea; if (da) { @@ -202,8 +307,10 @@ private void ClearTargetTexture() private void ConfigureTargetTexture(int width, int height, bool clearTexture, string name) { - // Changing color space requires destroying the entire RT object and recreating it - if (m_TargetTexture && m_CurrentColorSpace != QualitySettings.activeColorSpace) + // Requires destroying the entire RT object and recreating it if + // 1. color space is changed; + // 2. using mipmap is changed. + if (m_TargetTexture && (m_CurrentColorSpace != QualitySettings.activeColorSpace || m_TargetTexture.useMipMap != m_UseMipMap)) { UnityEngine.Object.DestroyImmediate(m_TargetTexture); } @@ -214,6 +321,7 @@ private void ConfigureTargetTexture(int width, int height, bool clearTexture, st m_TargetTexture.name = name + " RT"; m_TargetTexture.filterMode = textureFilterMode; m_TargetTexture.hideFlags = textureHideFlags; + m_TargetTexture.useMipMap = useMipMap; } // Changes to these attributes require a release of the texture @@ -234,18 +342,18 @@ private void ConfigureTargetTexture(int width, int height, bool clearTexture, st } } - internal static PreviewEditorWindow GetRenderingPreview() + internal static PlayModeView GetRenderingView() { - return s_RenderingPreview; + return s_RenderingView; } - internal static PreviewEditorWindow GetMainPreviewWindow() + internal static PlayModeView GetMainPlayModeView() { - if (s_LastFocused == null && s_PreviewWindows != null) + if (s_LastFocused == null && s_PlayModeViews != null) { RemoveDisabledWindows(); - if (s_PreviewWindows.Count > 0) - s_LastFocused = s_PreviewWindows[0]; + if (s_PlayModeViews.Count > 0) + s_LastFocused = s_PlayModeViews[0]; } return s_LastFocused; @@ -253,21 +361,21 @@ internal static PreviewEditorWindow GetMainPreviewWindow() private static void RemoveDisabledWindows() { - if (s_PreviewWindows == null) + if (s_PlayModeViews == null) return; - s_PreviewWindows.RemoveAll(window => window == null); + s_PlayModeViews.RemoveAll(window => window == null); } - internal static Vector2 GetMainPreviewTargetSize() + internal static Vector2 GetMainPlayModeViewTargetSize() { - var prevWindow = GetMainPreviewWindow(); + var prevWindow = GetMainPlayModeView(); if (prevWindow) - return prevWindow.GetPreviewSize(); + return prevWindow.GetPlayModeViewSize(); return new Vector2(640f, 480f); } - internal Vector2 GetPreviewSize() + internal Vector2 GetPlayModeViewSize() { return targetSize; } @@ -275,8 +383,8 @@ internal Vector2 GetPreviewSize() private void RegisterWindow() { RemoveDisabledWindows(); - if (!s_PreviewWindows.Contains(this)) - s_PreviewWindows.Add(this); + if (!s_PlayModeViews.Contains(this)) + s_PlayModeViews.Add(this); } public bool IsShowingGizmos() @@ -305,29 +413,32 @@ protected void SetFocus(bool focused) { InternalEditorUtility.OnGameViewFocus(true); m_Parent.SetAsLastPlayModeView(); + m_Parent.SetMainPlayModeViewSize(targetSize); s_LastFocused = this; Repaint(); } } - internal static bool IsPreviewWindowOpen() + internal static bool IsPlayModeViewOpen() { - return GetMainPreviewWindow() != null; + return GetMainPlayModeView() != null; } internal static void RepaintAll() { - if (s_PreviewWindows == null) + if (s_PlayModeViews == null) return; - foreach (PreviewEditorWindow previewWindow in s_PreviewWindows) - previewWindow.Repaint(); + foreach (PlayModeView playModeView in s_PlayModeViews) + playModeView.Repaint(); + } + + public void OnBeforeSerialize() + { } - [RequiredByNativeCode] - private static void GetMainPreviewTargetSizeNoBox(out Vector2 result) + public void OnAfterDeserialize() { - result = GetMainPreviewTargetSize(); } } } diff --git a/Editor/Mono/PlayerSettings.bindings.cs b/Editor/Mono/PlayerSettings.bindings.cs index b4c303b380..198c88bfe4 100644 --- a/Editor/Mono/PlayerSettings.bindings.cs +++ b/Editor/Mono/PlayerSettings.bindings.cs @@ -496,6 +496,7 @@ public static Guid productGUID [NativeProperty("FullscreenMode")] public static extern FullScreenMode fullScreenMode { get; set; } + [Obsolete("This API is obsolete, and should no longer be used. Please use XRManagerSettings in the XR Management package instead.")] [StaticAccessor("PlayerSettingsBindings", StaticAccessorType.DoubleColon)] public static extern bool virtualRealitySupported { get; set; } @@ -508,7 +509,8 @@ public static bool singlePassStereoRendering public static extern StereoRenderingPath stereoRenderingPath { get; set; } - public static extern bool protectGraphicsMemory { get; set; } + [Obsolete("protectGraphicsMemory is deprecated. This field has no effect.", false)] + public static bool protectGraphicsMemory { get { return false; } set {} } public static extern bool enableFrameTimingStats { get; set; } @@ -977,24 +979,17 @@ public static bool graphicsJobs [StaticAccessor("PlayerSettingsBindings", StaticAccessorType.DoubleColon)] internal static extern void SetGraphicsJobsForPlatform(BuildTarget platform, bool graphicsJobs); - public static extern GraphicsJobMode graphicsJobMode { get; set; } + public static GraphicsJobMode graphicsJobMode + { + get { return GetGraphicsJobModeForPlatform(EditorUserBuildSettings.activeBuildTarget); } + set { SetGraphicsJobModeForPlatform(EditorUserBuildSettings.activeBuildTarget, value); } + } - [Obsolete("GetPlatformVuforiaEnabled(BuildTargetGroup targetGroup) has been deprecated. Use vuforiaEnabled instead.")] [StaticAccessor("PlayerSettingsBindings", StaticAccessorType.DoubleColon)] - public static extern bool GetPlatformVuforiaEnabled(BuildTargetGroup targetGroup); + internal static extern GraphicsJobMode GetGraphicsJobModeForPlatform(BuildTarget platform); - [Obsolete("SetPlatformVuforiaEnabled(BuildTargetGroup targetGroup, bool enabled) has been deprecated. Use vuforiaEnabled instead.")] [StaticAccessor("PlayerSettingsBindings", StaticAccessorType.DoubleColon)] - public static extern void SetPlatformVuforiaEnabled(BuildTargetGroup targetGroup, bool enabled); - - public static extern bool vuforiaEnabled - { - [StaticAccessor("GetPlayerSettings().GetEditorOnly()")] - get; - - [StaticAccessor("GetPlayerSettings().GetEditorOnlyForUpdate()")] - set; - } + internal static extern void SetGraphicsJobModeForPlatform(BuildTarget platform, GraphicsJobMode gfxJobMode); [StaticAccessor("GetPlayerSettings()")] public static extern bool GetWsaHolographicRemotingEnabled(); diff --git a/Editor/Mono/PlayerSettingsAndroid.bindings.cs b/Editor/Mono/PlayerSettingsAndroid.bindings.cs index 5920ebd5f3..9758466583 100644 --- a/Editor/Mono/PlayerSettingsAndroid.bindings.cs +++ b/Editor/Mono/PlayerSettingsAndroid.bindings.cs @@ -434,6 +434,15 @@ public static extern bool renderOutsideSafeArea // App Bundle size which should cause warning message appear internal static extern int appBundleSizeToValidate { get; set; } + + // Use Swappy to decrease fluctuations in framerate + internal static extern bool optimizedFramePacing + { + [NativeMethod("GetAndroidUseSwappy")] + get; + [NativeMethod("SetAndroidUseSwappy")] + set; + } } } } diff --git a/Editor/Mono/PlayerSettingsIOS.bindings.cs b/Editor/Mono/PlayerSettingsIOS.bindings.cs index 80e42de233..68853528d2 100644 --- a/Editor/Mono/PlayerSettingsIOS.bindings.cs +++ b/Editor/Mono/PlayerSettingsIOS.bindings.cs @@ -121,20 +121,14 @@ public enum iOSLaunchScreenImageType // extern splash screen type (on iOS) public enum iOSLaunchScreenType { - // Default Default = 0, - - // Image and background (relative size) ImageAndBackgroundRelative = 1, + ImageAndBackgroundConstant = 4, - // extern XIB file CustomXib = 2, - // None + [Obsolete("Launch Images are no longer supported by Apple. (UnityUpgradable) -> Default", true)] None = 3, - - // Image and background (constant size) - ImageAndBackgroundConstant = 4 } internal enum iOSAutomaticallySignValue diff --git a/Editor/Mono/PlayerSettingsMacOS.bindings.cs b/Editor/Mono/PlayerSettingsMacOS.bindings.cs index cae03b1091..a701772545 100644 --- a/Editor/Mono/PlayerSettingsMacOS.bindings.cs +++ b/Editor/Mono/PlayerSettingsMacOS.bindings.cs @@ -22,6 +22,14 @@ public static string buildNumber [NativeProperty("MacAppStoreCategory")] extern internal static string applicationCategoryType { get; set; } + + // these two are internal because we are not yet sure if we want to have them back in general PlayerSettings + // as we have several platforms that might want to use it + [NativeProperty("CameraUsageDescription")] + internal extern static string cameraUsageDescription { get; set; } + + [NativeProperty("MicrophoneUsageDescription")] + internal extern static string microphoneUsageDescription { get; set; } } } } diff --git a/Editor/Mono/PlayerSettingsPS4.bindings.cs b/Editor/Mono/PlayerSettingsPS4.bindings.cs index 42c59fe7a9..deed701e07 100644 --- a/Editor/Mono/PlayerSettingsPS4.bindings.cs +++ b/Editor/Mono/PlayerSettingsPS4.bindings.cs @@ -203,6 +203,7 @@ public static string SdkOverride [NativeProperty("ps4UseAudio3dBackend", false, TargetType.Field)] extern public static bool useAudio3dBackend { get; set; } [NativeProperty("ps4Audio3dVirtualSpeakerCount", false, TargetType.Field)] extern public static int audio3dVirtualSpeakerCount { get; set; } [NativeProperty("ps4ScriptOptimizationLevel", false, TargetType.Field)] extern public static int scriptOptimizationLevel { get; set; } + [NativeProperty("ps4UseLowGarlicFragmentationMode", true, TargetType.Field)] extern public static bool useLowGarlicFragmentationMode { get; set; } [NativeProperty("ps4SocialScreenEnabled", false, TargetType.Field)] extern public static int socialScreenEnabled { get; set; } [NativeProperty("ps4attribUserManagement", false, TargetType.Field)] extern public static bool attribUserManagement { get; set; } [NativeProperty("ps4attribMoveSupport", false, TargetType.Field)] extern public static bool attribMoveSupport { get; set; } @@ -218,6 +219,7 @@ public static string SdkOverride [NativeProperty(TargetType = TargetType.Field)] extern public static bool enableApplicationExit { get; set; } [NativeProperty(TargetType = TargetType.Field)] extern public static bool resetTempFolder { get; set; } [NativeProperty(TargetType = TargetType.Field)] extern public static int playerPrefsMaxSize { get; set; } + [NativeProperty("ps4attribVROutputEnabled", false, TargetType.Field)] extern public static bool attribVROutputEnabled { get; set; } } } } diff --git a/Editor/Mono/PlayerSettingsSwitch.bindings.cs b/Editor/Mono/PlayerSettingsSwitch.bindings.cs index 37613967b1..44ff9ef970 100644 --- a/Editor/Mono/PlayerSettingsSwitch.bindings.cs +++ b/Editor/Mono/PlayerSettingsSwitch.bindings.cs @@ -97,7 +97,8 @@ public enum RatingCategories PEGIBBFC = 8, Russian = 9, ACB = 10, - OFLC = 11 + OFLC = 11, + IARCGeneric = 12, } private enum SupportedNpadStyleBits diff --git a/Editor/Mono/PlayerSettingsVRGoogle.bindings.cs b/Editor/Mono/PlayerSettingsVRGoogle.bindings.cs index 193814a88a..17f7222a8c 100644 --- a/Editor/Mono/PlayerSettingsVRGoogle.bindings.cs +++ b/Editor/Mono/PlayerSettingsVRGoogle.bindings.cs @@ -2,6 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; using UnityEngine; using UnityEngine.Bindings; @@ -21,6 +22,7 @@ public sealed partial class PlayerSettings : UnityEngine.Object [NativeHeader("Editor/Mono/PlayerSettingsVRGoogle.bindings.h")] public static class VRCardboard { + [Obsolete("This API is obsolete, and should no longer be used. Existing projects should continue to use 2018.4 LTS.")] [NativeProperty("depthFormat", TargetType.Field)] public extern static int depthFormat { @@ -32,42 +34,55 @@ public extern static int depthFormat [NativeHeader("Runtime/Graphics/Texture2D.h")] public static class VRDaydream { + [Obsolete("This API is obsolete, and should no longer be used. More information with the latest recommendation can be found here .")] [NativeProperty("daydreamIconForeground")] public extern static Texture2D daydreamIcon { [StaticAccessor("GetGoogleVREditorOnlySettings()", StaticAccessorType.Dot)] get; [StaticAccessor("GetGoogleVREditorOnlySettingsForUpdate()", StaticAccessorType.Dot)] set; } + + [Obsolete("This API is obsolete, and should no longer be used. More information with the latest recommendation can be found here .")] [NativeProperty("daydreamIconBackground")] public extern static Texture2D daydreamIconBackground { [StaticAccessor("GetGoogleVREditorOnlySettings()", StaticAccessorType.Dot)] get; [StaticAccessor("GetGoogleVREditorOnlySettingsForUpdate()", StaticAccessorType.Dot)] set; } + + [Obsolete("This API is obsolete, and should no longer be used. More information with the latest recommendation can be found here .")] [NativeProperty("depthFormat", TargetType.Field)] public extern static int depthFormat { [StaticAccessor("GetGoogleVRSettings()", StaticAccessorType.Dot)] get; [StaticAccessor("GetGoogleVRSettingsForUpdate()", StaticAccessorType.Dot)] set; } + + [Obsolete("This API is obsolete, and should no longer be used. More information with the latest recommendation can be found here .")] [NativeProperty("minimumSupportedHeadTracking", TargetType.Field)] public extern static XR.Daydream.SupportedHeadTracking minimumSupportedHeadTracking { [StaticAccessor("GetGoogleVRSettings()", StaticAccessorType.Dot)] get; [StaticAccessor("GetGoogleVRSettingsForUpdate()", StaticAccessorType.Dot)] set; } + + [Obsolete("This API is obsolete, and should no longer be used. More information with the latest recommendation can be found here .")] [NativeProperty("maximumSupportedHeadTracking", TargetType.Field)] public extern static XR.Daydream.SupportedHeadTracking maximumSupportedHeadTracking { [StaticAccessor("GetGoogleVRSettings()", StaticAccessorType.Dot)] get; [StaticAccessor("GetGoogleVRSettingsForUpdate()", StaticAccessorType.Dot)] set; } + + [Obsolete("This API is obsolete, and should no longer be used. More information with the latest recommendation can be found here .")] [NativeProperty("enableVideoLayer", TargetType.Field)] public extern static bool enableVideoSurface { [StaticAccessor("GetGoogleVRSettings()", StaticAccessorType.Dot)] get; [StaticAccessor("GetGoogleVRSettingsForUpdate()", StaticAccessorType.Dot)] set; } + + [Obsolete("This API is obsolete, and should no longer be used. More information with the latest recommendation can be found here .")] [NativeProperty("useProtectedVideoMemory", TargetType.Field)] public extern static bool enableVideoSurfaceProtectedMemory { diff --git a/Editor/Mono/PlayerSettingsVulkan.bindings.cs b/Editor/Mono/PlayerSettingsVulkan.bindings.cs index 139c0a339d..595c2bb6b4 100644 --- a/Editor/Mono/PlayerSettingsVulkan.bindings.cs +++ b/Editor/Mono/PlayerSettingsVulkan.bindings.cs @@ -10,6 +10,7 @@ namespace UnityEditor public partial class PlayerSettings : UnityEngine.Object { public static extern bool vulkanEnableSetSRGBWrite { get; set; } + public static extern UInt32 vulkanNumSwapchainBuffers { get; set; } [Obsolete("Vulkan SW command buffers are deprecated, vulkanUseSWCommandBuffers will be ignored.")] public static bool vulkanUseSWCommandBuffers { get { return false; } set {} } diff --git a/Editor/Mono/PlayerSettingsWindowsMR.bindings.cs b/Editor/Mono/PlayerSettingsWindowsMR.bindings.cs index 8facbe1b9a..daa1b5c4fa 100644 --- a/Editor/Mono/PlayerSettingsWindowsMR.bindings.cs +++ b/Editor/Mono/PlayerSettingsWindowsMR.bindings.cs @@ -17,16 +17,19 @@ public sealed partial class PlayerSettings : UnityEngine.Object [NativeHeader("Runtime/Misc/PlayerSettings.h")] public static partial class VRWindowsMixedReality { + [Obsolete("This enum is obsolete, and should no longer be used. Please update to the Unity Windows MR XR Plugin package.")] public enum DepthBufferFormat { DepthBufferFormat16Bit = 0, DepthBufferFormat24Bit = 1 } + [Obsolete("This API is obsolete, and should no longer be used. Please update to the Unity Windows MR XR Plugin package.")] [StaticAccessor("GetPlayerSettings()", StaticAccessorType.Dot)] [NativeProperty("vrSettings.hololens.depthFormat", TargetType.Field)] public static extern DepthBufferFormat depthBufferFormat { get; set; } + [Obsolete("This API is obsolete, and should no longer be used. Please update to the Unity Windows MR XR Plugin package.")] [StaticAccessor("GetPlayerSettings()", StaticAccessorType.Dot)] [NativeProperty("vrSettings.hololens.depthBufferSharingEnabled", TargetType.Field)] public static extern bool depthBufferSharingEnabled { get; set; } diff --git a/Editor/Mono/PlayerSettingsXboxOne.bindings.cs b/Editor/Mono/PlayerSettingsXboxOne.bindings.cs index 6b81984410..dafdf51f44 100644 --- a/Editor/Mono/PlayerSettingsXboxOne.bindings.cs +++ b/Editor/Mono/PlayerSettingsXboxOne.bindings.cs @@ -138,6 +138,10 @@ extern public static XboxOnePackageUpdateGranularity PackageUpdateGranularity [NativeProperty("XboxOneOverrideIdentityName", false, TargetType.Function)] extern public static string OverrideIdentityName { get; set; } + // Xbox One override auto generated identity publisher in app manifest (does not work with app manifest override). + [NativeProperty("XboxOneOverrideIdentityPublisher", false, TargetType.Function)] + extern public static string OverrideIdentityPublisher { get; set; } + // Optional override path for app manifest. [NativeProperty("XboxOneAppManifestOverridePath", false, TargetType.Function)] extern public static string AppManifestOverridePath { get; set; } @@ -240,11 +244,21 @@ extern public static string[] AllowedProductIds extern public static void UpdateAllowedProductId(int idx, string id); // *undocumented* + [Obsolete("Starting May 11th 2020 any new base game submission releasing digital only, " + + "digital and disc, or disc only, should not include a ratings element in the " + + "AppxManifest. This ratings policy update applies to all Xbox supported ratings. " + + "New base submissions that come in on or after this date will be " + + "rejected by your Microsoft Representative if a ratings element is present.", false)] [NativeMethod("SetXboxOneGameRating")] [StaticAccessor("GetPlayerSettings().GetEditorOnlyForUpdate()", StaticAccessorType.Dot)] extern public static void SetGameRating(string name, int value); // *undocumented* + [Obsolete("Starting May 11th 2020 any new base game submission releasing digital only, " + + "digital and disc, or disc only, should not include a ratings element in the " + + "AppxManifest. This ratings policy update applies to all Xbox supported ratings. " + + "New base submissions that come in on or after this date will be " + + "rejected by your Microsoft Representative if a ratings element is present.", false)] [NativeMethod("GetXboxOneGameRating")] [StaticAccessor("GetPlayerSettings().GetEditorOnly()", StaticAccessorType.Dot)] extern public static int GetGameRating(string name); @@ -271,6 +285,11 @@ extern private static uint persistentLocalStorageSizeInternal set; } + // + [NativeProperty("XboxOneEnableTypeOptimization")] + [StaticAccessor("GetPlayerSettings()", StaticAccessorType.Dot)] + extern public static bool EnableTypeOptimization { get; set; } + // Whether we have enabled mono trace logs on xboxOne for debugging purposes. [NativeProperty("XboxOneMonoLoggingLevel")] [StaticAccessor("GetPlayerSettings()", StaticAccessorType.Dot)] diff --git a/Editor/Mono/Plugins/PluginsHelper.cs b/Editor/Mono/Plugins/PluginsHelper.cs index 764f85c5e5..12ffd4c269 100644 --- a/Editor/Mono/Plugins/PluginsHelper.cs +++ b/Editor/Mono/Plugins/PluginsHelper.cs @@ -19,7 +19,7 @@ public static bool CheckFileCollisions(BuildTarget buildTarget) pluginImporterExtension = ModuleManager.GetPluginImporterExtension(buildTarget); if (pluginImporterExtension == null) { - // Some platforms don't have platform specific settings for plugins, but we still wan't to check that plugins don't collide, use default path in this case + // Some platforms don't have platform specific settings for plugins, but we still want to check that plugins don't collide, use default path in this case if (BuildPipeline.GetBuildTargetGroup(buildTarget) == BuildTargetGroup.Standalone) pluginImporterExtension = new DesktopPluginImporterExtension(); else diff --git a/Editor/Mono/Prefabs/PrefabImporterEditor.cs b/Editor/Mono/Prefabs/PrefabImporterEditor.cs index 1377abaa0a..31ff331b70 100644 --- a/Editor/Mono/Prefabs/PrefabImporterEditor.cs +++ b/Editor/Mono/Prefabs/PrefabImporterEditor.cs @@ -32,6 +32,7 @@ static class Styles List m_PrefabsWithMissingScript = new List(); bool m_SavingHasFailed; List m_TempComponentsResults = new List(); + List m_DirtyPrefabAssets = new List(); public override bool showImportedObject { get { return !hasMissingScripts; } } @@ -85,7 +86,7 @@ void OnDestroy() { // Ensure to save unsaved changes (regardless of hotcontrol etc) if (!m_SavingHasFailed && !hasMissingScripts) - SaveDirtyPrefabAssets(); + SaveDirtyPrefabAssets(false); } void Update() @@ -96,12 +97,12 @@ void Update() m_NextUpdate = time + 0.2; if (readyToAutoSave && HasDirtyPrefabAssets()) - SaveDirtyPrefabAssets(); + SaveDirtyPrefabAssets(true); } } // Internal for testing framework - internal void SaveDirtyPrefabAssets() + internal void SaveDirtyPrefabAssets(bool rebuildInspectors) { if (assetTargets == null) return; @@ -109,6 +110,7 @@ internal void SaveDirtyPrefabAssets() if (assetTarget == null) return; + m_DirtyPrefabAssets.Clear(); foreach (var asset in assetTargets) { // The asset could have been deleted when this method is called from OnDestroy(). @@ -119,21 +121,47 @@ internal void SaveDirtyPrefabAssets() if (!EditorUtility.IsPersistent(asset)) continue; + if (!(asset is GameObject)) + continue; + var rootGameObject = (GameObject)asset; if (IsDirty(rootGameObject)) + m_DirtyPrefabAssets.Add(rootGameObject); + } + + if (m_DirtyPrefabAssets.Count > 0) + { + bool savedPrefab = false; + AssetDatabase.StartAssetEditing(); + try { - bool savedSuccesfully; - PrefabUtility.SavePrefabAsset(rootGameObject, out savedSuccesfully); - if (!savedSuccesfully) + foreach (var rootGameObject in m_DirtyPrefabAssets) { - string title = L10n.Tr("Saving Failed"); - string message = L10n.Tr("Check the Console window to get more insight into what needs to be fixed on the Prefab Asset.\n\nYou can open Prefab Mode to fix any issues on child GameObjects"); - EditorUtility.DisplayDialog(title, message, L10n.Tr("OK")); + bool savedSuccesfully; + PrefabUtility.SavePrefabAsset(rootGameObject, out savedSuccesfully); + if (!savedSuccesfully) + { + string title = L10n.Tr("Saving Failed"); + string message = L10n.Tr("Check the Console window to get more insight into what needs to be fixed on the Prefab Asset.\n\nYou can open Prefab Mode to fix any issues on child GameObjects"); + EditorUtility.DisplayDialog(title, message, L10n.Tr("OK")); - m_SavingHasFailed = true; - return; + m_SavingHasFailed = true; + break; + } + + savedPrefab |= savedSuccesfully; } } + finally + { + AssetDatabase.StopAssetEditing(); + + // All inspectors needs to be rebuild to ensure property changes are reflected after saving the Prefab shown. + // (Saving clears the m_DirtyIndex of the target which is used for updating inspectors via SerializedObject::UpdateIfRequiredOrScript() + // and thus the cached dirty index in SerializedObject is not updated meaning the source object is not reloaded even though it changed) + if (savedPrefab && rebuildInspectors) + EditorUtility.ForceRebuildInspectors(); + } } } @@ -142,6 +170,9 @@ internal bool HasDirtyPrefabAssets() if (assetTarget == null) return false; + if (typeof(GameObject) != assetTarget.GetType()) + return false; + // We just check one target since we assume that a multi-edit will // always edit that target. So no need to spend resources on checking // all targets in multiselection. @@ -209,6 +240,11 @@ internal override string targetTitle internal override void OnHeaderControlsGUI() { + if (assetTarget is DefaultAsset) + { + return; + } + var variantBase = PrefabUtility.GetCorrespondingObjectFromSource(assetTarget); if (variantBase != null) { @@ -230,6 +266,11 @@ internal override void OnHeaderControlsGUI() public override void OnInspectorGUI() { + if (assetTarget is DefaultAsset) + { + return; + } + EditorGUILayout.BeginVertical(EditorStyles.inspectorFullWidthMargins); // Allow opening prefab even if file is not open for edit. diff --git a/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverride.cs b/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverride.cs index 97fe906114..c2cfcd1b41 100644 --- a/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverride.cs +++ b/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverride.cs @@ -106,11 +106,12 @@ public override void Apply(string prefabAssetPath) public override void Revert() { + var coupledComponent = instanceComponent.GetCoupledComponent(); + PrefabUtility.RevertAddedComponent( instanceComponent, InteractionMode.UserAction); - var coupledComponent = instanceComponent.GetCoupledComponent(); if (coupledComponent != null) { PrefabUtility.RevertAddedComponent( diff --git a/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesTreeView.cs b/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesTreeView.cs index da24ffbc5c..7b5eda253c 100644 --- a/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesTreeView.cs +++ b/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesTreeView.cs @@ -240,6 +240,10 @@ bool AddTreeViewItemRecursive(TreeViewItem parentItem, GameObject gameObject, Di // Added components and component modifications foreach (var component in gameObject.GetComponents(typeof(Component))) { + // GetComponents will return Missing Script components as null, we will skip them here to prevent NullReferenceExceptions. (case 1197599) + if (component == null) + continue; + // Skip coupled components (they are merged into the display of their owning component) if (component.IsCoupledComponent()) continue; @@ -319,6 +323,9 @@ bool AddTreeViewItemRecursive(TreeViewItem parentItem, GameObject gameObject, Di if (shouldAddGameObjectItemToParent) { parentItem.AddChild(gameObjectItem); + if (maxDepthItem == null || gameObjectItem.depth > maxDepthItem.depth) + maxDepthItem = gameObjectItem; + return true; } @@ -448,6 +455,11 @@ public GameObject selectedGameObject GameObject m_SelectedGameObject; + public float maxItemWidth { get { return maxDepthItem != null ? GetContentIndent(maxDepthItem) + k_FixedContentWidth : 0; } } + + const float k_FixedContentWidth = 150f; + TreeViewItem maxDepthItem { get; set; } + struct ChangedModification { public Object target { get; set; } @@ -547,7 +559,7 @@ public override void OnGUI(Rect rect) EditorGUIUtility.comparisonViewMode = EditorGUIUtility.ComparisonViewMode.Original; EditorGUIUtility.wideMode = true; - EditorGUIUtility.labelWidth = 100; + EditorGUIUtility.labelWidth = 120; int middleCol = Mathf.RoundToInt(rect.width * 0.5f); if (Event.current.type == EventType.Repaint) @@ -664,6 +676,8 @@ void Apply(object prefabAssetPathObject) if (!PrefabUtility.PromptAndCheckoutPrefabIfNeeded(prefabAssetPath, PrefabUtility.SaveVerb.Apply)) return; m_Modification.Apply(prefabAssetPath); + EditorUtility.ForceRebuildInspectors(); // handles applying RemovedComponents + UpdateAndClose(); } diff --git a/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesUtility.cs b/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesUtility.cs index f6ccf7c5cb..84f8b1c015 100644 --- a/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesUtility.cs +++ b/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesUtility.cs @@ -35,28 +35,28 @@ public static List GetObjectOverrides(GameObject prefabInstance, if (PrefabUtility.IsDisconnectedFromPrefabAsset(prefabInstance)) return modifiedObjects; - System.Action checkMethod; + Func checkMethod; if (includeDefaultOverrides) checkMethod = CheckForModifiedObjectsIncludingDefaultOverrides; else checkMethod = CheckForModifiedObjectsExcludingDefaultOverrides; - transformVisitor.VisitAll(prefabInstanceRoot.transform, checkMethod, modifiedObjects); + transformVisitor.VisitPrefabInstanceTransforms(prefabInstanceRoot.transform, checkMethod, modifiedObjects); return modifiedObjects; } - static void CheckForModifiedObjectsIncludingDefaultOverrides(Transform transform, object userData) + static bool CheckForModifiedObjectsIncludingDefaultOverrides(Transform transform, object userData) { var modifiedObjects = (List)userData; - CheckForModifiedObjects(transform, modifiedObjects, true); + return CheckForModifiedObjects(transform, modifiedObjects, true); } - static void CheckForModifiedObjectsExcludingDefaultOverrides(Transform transform, object userData) + static bool CheckForModifiedObjectsExcludingDefaultOverrides(Transform transform, object userData) { var modifiedObjects = (List)userData; - CheckForModifiedObjects(transform, modifiedObjects, false); + return CheckForModifiedObjects(transform, modifiedObjects, false); } - static void CheckForModifiedObjects(Transform transform, List modifiedObjects, bool includeDefaultOverrides) + static bool CheckForModifiedObjects(Transform transform, List modifiedObjects, bool includeDefaultOverrides) { GameObject gameObject = transform.gameObject; if (PrefabUtility.HasObjectOverride(gameObject, includeDefaultOverrides)) @@ -73,6 +73,8 @@ static void CheckForModifiedObjects(Transform transform, List mo if (PrefabUtility.HasObjectOverride(component, includeDefaultOverrides)) modifiedObjects.Add(new ObjectOverride() { instanceObject = component }); } + + return true; } public static List GetAddedComponents(GameObject prefabInstance) @@ -87,17 +89,18 @@ public static List GetAddedComponents(GameObject prefabInstance) if (PrefabUtility.IsDisconnectedFromPrefabAsset(prefabInstance)) return addedComponents; - transformVisitor.VisitAll(prefabInstanceRoot.transform, CheckForAddedComponents, addedComponents); + transformVisitor.VisitPrefabInstanceTransforms(prefabInstanceRoot.transform, CheckForAddedComponents, addedComponents); return addedComponents; } - static void CheckForAddedComponents(Transform transform, object userData) + // Return value indicates if caller should traverse children of the the input transform or not + static bool CheckForAddedComponents(Transform transform, object userData) { s_ComponentList.Clear(); transform.gameObject.GetComponents(s_ComponentList); var assetGameObject = PrefabUtility.GetCorrespondingObjectFromSource(transform.gameObject); if (assetGameObject == null) - return; // If this is an added normal GameObject then we do not record added compoenents + return false; // If this is an added normal GameObject then we do not record added components foreach (var component in s_ComponentList) { @@ -105,6 +108,10 @@ static void CheckForAddedComponents(Transform transform, object userData) if (component == null) continue; + // Don't list DontSave objects as they won't get applied or reverted. + if ((component.hideFlags & HideFlags.DontSaveInEditor) != 0) + continue; + bool isAddedObject = PrefabUtility.GetCorrespondingObjectFromSource(component) == null; if (isAddedObject) { @@ -112,6 +119,8 @@ static void CheckForAddedComponents(Transform transform, object userData) addedComponents.Add(new AddedComponent() { instanceComponent = component }); } } + + return true; } public static List GetRemovedComponents(GameObject prefabInstance) @@ -125,7 +134,7 @@ public static List GetRemovedComponents(GameObject prefabInsta if (PrefabUtility.IsDisconnectedFromPrefabAsset(prefabInstance)) return removedComponents; TransformVisitor transformVisitor = new TransformVisitor(); - transformVisitor.VisitAll(prefabInstanceRoot.transform, CheckForRemovedComponents, removedComponents); + transformVisitor.VisitPrefabInstanceTransforms(prefabInstanceRoot.transform, CheckForRemovedComponents, removedComponents); return removedComponents; } @@ -140,12 +149,13 @@ public static List GetRemovedComponentsForSingleGameObject(Gam return removedComponents; } - static void CheckForRemovedComponents(Transform transform, object userData) + // Return value indicates if caller should traverse children of the the input transform or not + static bool CheckForRemovedComponents(Transform transform, object userData) { GameObject instanceGameObject = transform.gameObject; GameObject assetGameObject = PrefabUtility.GetCorrespondingObjectFromSource(instanceGameObject); if (assetGameObject == null) - return; // skip added GameObjects (non of its components will be in the asset) + return false; // skip added GameObjects (non of its components will be in the asset) // Compare asset with instance component lists s_ComponentList.Clear(); @@ -177,6 +187,7 @@ static void CheckForRemovedComponents(Transform transform, object userData) removedComponents.Add(new RemovedComponent() { assetComponent = assetComponent, containingInstanceGameObject = instanceGameObject }); } } + return true; } internal class AddedGameObjectUserData @@ -206,6 +217,10 @@ public static List GetAddedGameObjects(GameObject prefabInstanc static bool CheckForAddedGameObjectAndIfSoAddItAndReturnFalse(Transform transform, object userData) { + // Don't list DontSave objects or their children as they won't get applied or reverted. + if ((transform.gameObject.hideFlags & HideFlags.DontSaveInEditor) != 0) + return false; + var addedGameObjectUserData = (AddedGameObjectUserData)userData; if (IsAddedGameObject(addedGameObjectUserData.contextGameObject, transform.gameObject)) { diff --git a/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesWindow.cs b/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesWindow.cs index 377099b659..0847fa8122 100644 --- a/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesWindow.cs +++ b/Editor/Mono/Prefabs/PrefabOverrides/PrefabOverridesWindow.cs @@ -89,8 +89,28 @@ internal PrefabOverridesWindow(GameObject[] selectedGameObjects) RefreshStatus(); } + public override void OnOpen() + { + Undo.undoRedoPerformed += OnUndoRedoPerformed; + base.OnOpen(); + } + + public override void OnClose() + { + Undo.undoRedoPerformed -= OnUndoRedoPerformed; + base.OnClose(); + } + + void OnUndoRedoPerformed() + { + RefreshStatus(); + } + internal void RefreshStatus() { + if (m_TreeView != null) + m_TreeView.Reload(); + if (m_SelectedGameObjects.Length == 1) UpdateTextSingle(PrefabUtility.GetCorrespondingObjectFromSource(m_SelectedGameObjects[0])); else @@ -154,6 +174,9 @@ bool IsShowingApplyWarning() public override Vector2 GetWindowSize() { + const float k_MaxAllowedTreeViewWidth = 1800f; + const float k_MaxAllowedTreeViewHeight = 1000f; + var width = 300f; var height = k_HeaderHeight; if (!IsShowingActionButton()) @@ -163,7 +186,10 @@ public override Vector2 GetWindowSize() else { if (DisplayingTreeView()) - height += k_TreeViewPadding.top + m_TreeView.totalHeight + k_TreeViewPadding.bottom; + { + height += k_TreeViewPadding.top + Mathf.Min(k_MaxAllowedTreeViewHeight, m_TreeView.totalHeight) + k_TreeViewPadding.bottom; + width = Mathf.Max(Mathf.Min(m_TreeView.maxItemWidth, k_MaxAllowedTreeViewWidth), width); + } height += k_ApplyButtonHeight + k_HelpBoxHeight; @@ -173,7 +199,7 @@ public override Vector2 GetWindowSize() // Width should be no smaller than minimum width, but we could potentially improve // width handling by making it expand if needed based on tree view content. - return new Vector2(300, height); + return new Vector2(width, height); } Color headerBgColor { get { return EditorGUIUtility.isProSkin ? new Color(0.5f, 0.5f, 0.5f, 0.2f) : new Color(0.9f, 0.9f, 0.9f, 0.6f); } } @@ -231,7 +257,7 @@ public override void OnGUI(Rect rect) } else if (m_AnyOverrides) { - Rect treeViewRect = GUILayoutUtility.GetRect(100, 1000, 0, 1000); + Rect treeViewRect = GUILayoutUtility.GetRect(100, 10000, 0, 10000); m_TreeView.OnGUI(treeViewRect); // Display info message telling user they can click on individual items for more detailed actions. diff --git a/Editor/Mono/Prefabs/PrefabOverrides/TransformVisitor.cs b/Editor/Mono/Prefabs/PrefabOverrides/TransformVisitor.cs index 9e436fc066..c10465cc67 100644 --- a/Editor/Mono/Prefabs/PrefabOverrides/TransformVisitor.cs +++ b/Editor/Mono/Prefabs/PrefabOverrides/TransformVisitor.cs @@ -64,5 +64,29 @@ static void VisitAndConditionallyEnterChildrenRecursively(Transform transform, F for (int i = 0; i < transform.childCount; ++i) VisitAndConditionallyEnterChildrenRecursively(transform.GetChild(i), visitorFunc, userData); } + + // Only visit transforms under the same PrefabInstance + public void VisitPrefabInstanceTransforms(Transform transform, Func visitorFunc, object userData) + { + Assert.IsNotNull(transform, "Please provide a valid transform"); + Assert.IsNotNull(visitorFunc, "Please provide a valid visitorFunc"); + Assert.IsNotNull(PrefabUtility.GetPrefabInstanceHandle(transform), "Please provide a Prefab instance"); + + VisitPrefabInstanceTransformsRecursively(transform, visitorFunc, userData); + } + + static void VisitPrefabInstanceTransformsRecursively(Transform transform, Func visitorFunc, object userData) + { + if (!visitorFunc(transform, userData)) + return; + + var prefabInstanceHandle = PrefabUtility.GetPrefabInstanceHandle(transform); + for (int i = 0; i < transform.childCount; ++i) + { + var child = transform.GetChild(i); + if (PrefabUtility.GetPrefabInstanceHandle(child) == prefabInstanceHandle) + VisitPrefabInstanceTransformsRecursively(child, visitorFunc, userData); + } + } } } diff --git a/Editor/Mono/Prefabs/PrefabUtility.cs b/Editor/Mono/Prefabs/PrefabUtility.cs index fa3ee9b0a3..c6978d94b5 100644 --- a/Editor/Mono/Prefabs/PrefabUtility.cs +++ b/Editor/Mono/Prefabs/PrefabUtility.cs @@ -638,10 +638,32 @@ static void ApplySingleProperty( return; } + SerializedProperty sourceProperty = prefabSourceSerializedObject.FindProperty(instanceProperty.propertyPath); + if (sourceProperty == null) + { + bool cancel; + var instanceArrayProperty = GetArrayPropertyIfGivenPropertyIsPartOfArrayElementInInstanceWhichDoesNotExistInAsset(instanceProperty, prefabSourceSerializedObject, InteractionMode.AutomatedAction, out cancel); + if (instanceArrayProperty != null) + { + prefabSourceSerializedObject.CopyFromSerializedProperty(instanceArrayProperty); + sourceProperty = prefabSourceSerializedObject.FindProperty(instanceProperty.propertyPath); + if (sourceProperty == null) + { + Debug.LogError($"ApplySingleProperty full array copy error: SerializedProperty could not be found for {instanceProperty.propertyPath}. Please report a bug."); + return; + } + } + else + { + Debug.LogError($"ApplySingleProperty copy state error: SerializedProperty could not be found for {instanceProperty.propertyPath}. Please report a bug."); + return; + } + } + + // Copy overridden property value to asset prefabSourceSerializedObject.CopyFromSerializedProperty(instanceProperty); // Abort if property has reference to object in scene. - SerializedProperty sourceProperty = prefabSourceSerializedObject.FindProperty(instanceProperty.propertyPath); if (sourceProperty.propertyType == SerializedPropertyType.ObjectReference) { MapObjectReferencePropertyToSourceIfApplicable(sourceProperty, assetPath); @@ -688,11 +710,21 @@ static void ApplySingleProperty( outerPrefabSerializedObject = serializedObjects[sourceIndex]; } + // Case 1172835: When applying a new array size to an inner Prefab, this change won't yet have propogated to the outer Prefabs. + // This means properties inside the array may not yet exist here. + // To handle this, we first copy the serialized value (which correctly handles array size changes) + // before we clear the overrides below (by setting outerPrefabProp.prefabOverride = false). + var propertyType = instanceProperty.propertyType; + if (propertyType == SerializedPropertyType.ArraySize) + outerPrefabSerializedObject.CopyFromSerializedProperty(instanceProperty); + SerializedProperty outerPrefabProp = outerPrefabSerializedObject.FindProperty(instanceProperty.propertyPath); - if (outerPrefabProp.prefabOverride) + if (outerPrefabProp != null && outerPrefabProp.prefabOverride) { outerPrefabProp.prefabOverride = false; } + if (outerPrefabProp == null) + Debug.LogError($"ApplySingleProperty clear overrides error: SerializedProperty could not be found for {instanceProperty.propertyPath}. Please report a bug."); outerPrefabObject = PrefabUtility.GetCorrespondingObjectFromSource(outerPrefabObject); sourceIndex++; @@ -760,6 +792,25 @@ public static void ApplyAddedComponent(Component component, string assetPath, In var actionName = "Apply Added Component"; if (action == InteractionMode.UserAction) { + GameObject gameObject = component.gameObject; + List addedComponentsOnGO = + GetAddedComponents(gameObject) + .Select(e => e.instanceComponent) + .Where(e => e.gameObject == gameObject) + .ToList(); + string dependentComponents = string.Join( + ", ", + GetComponentsWhichThisDependsOn(component, addedComponentsOnGO).Select(e => ObjectNames.GetInspectorTitle(e)).ToArray()); + if (!string.IsNullOrEmpty(dependentComponents)) + { + string error = String.Format( + L10n.Tr("Can't apply added component {0} because it depends on {1}."), + ObjectNames.GetInspectorTitle(component), + dependentComponents); + EditorUtility.DisplayDialog(L10n.Tr("Can't apply added component"), error, L10n.Tr("OK")); + return; + } + Undo.RegisterFullObjectHierarchyUndo(prefabSourceGameObject, actionName); Undo.RegisterFullObjectHierarchyUndo(component, actionName); } @@ -805,13 +856,19 @@ public static void RevertAddedComponent(Component component, InteractionMode act if (action == InteractionMode.UserAction) { + GameObject gameObject = component.gameObject; + List addedComponentsOnGO = + GetAddedComponents(gameObject) + .Select(e => e.instanceComponent) + .Where(e => e.gameObject == gameObject) + .ToList(); string dependentComponents = string.Join( ", ", - GetDependentComponents(component).Select(e => ObjectNames.GetInspectorTitle(e)).ToArray()); + GetComponentsWhichDependOnThis(component, addedComponentsOnGO).Select(e => ObjectNames.GetInspectorTitle(e)).ToArray()); if (!string.IsNullOrEmpty(dependentComponents)) { string error = String.Format( - L10n.Tr("Can't revert added component {0} because {1} depends on it"), + L10n.Tr("Can't revert added component {0} because {1} depends on it."), ObjectNames.GetInspectorTitle(component), dependentComponents); EditorUtility.DisplayDialog(L10n.Tr("Can't revert added component"), error, L10n.Tr("OK")); @@ -866,6 +923,25 @@ public static void ApplyRemovedComponent(GameObject instanceGameObject, Componen if (action == InteractionMode.UserAction) { + GameObject assetGameObject = assetComponent.gameObject; + List removedComponentsOnAssetGO = + GetRemovedComponents(instanceGameObject) + .Select(e => e.assetComponent) + .Where(e => e.gameObject == assetGameObject) + .ToList(); + string dependentComponents = string.Join( + ", ", + GetComponentsWhichDependOnThis(assetComponent, removedComponentsOnAssetGO).Select(e => ObjectNames.GetInspectorTitle(e)).ToArray()); + if (!string.IsNullOrEmpty(dependentComponents)) + { + string error = String.Format( + L10n.Tr("Can't apply removed component {0} because {1} component in the Prefab Asset depends on it."), + ObjectNames.GetInspectorTitle(assetComponent), + dependentComponents); + EditorUtility.DisplayDialog(L10n.Tr("Can't apply removed component"), error, L10n.Tr("OK")); + return; + } + Undo.DestroyObjectUndoable(assetComponent, actionName); // Undo.DestroyObjectUndoable saves prefab asset internally. } @@ -920,7 +996,28 @@ public static void RevertRemovedComponent(GameObject instanceGameObject, Compone var prefabInstanceObject = PrefabUtility.GetPrefabInstanceHandle(instanceGameObject); if (action == InteractionMode.UserAction) + { + GameObject assetGameObject = assetComponent.gameObject; + List removedComponentsOnAssetGO = + GetRemovedComponents(instanceGameObject) + .Select(e => e.assetComponent) + .Where(e => e.gameObject == assetGameObject) + .ToList(); + string dependentComponents = string.Join( + ", ", + GetComponentsWhichThisDependsOn(assetComponent, removedComponentsOnAssetGO).Select(e => ObjectNames.GetInspectorTitle(e)).ToArray()); + if (!string.IsNullOrEmpty(dependentComponents)) + { + string error = String.Format( + L10n.Tr("Can't revert removed component {0} because it depends on {1}."), + ObjectNames.GetInspectorTitle(assetComponent), + dependentComponents); + EditorUtility.DisplayDialog(L10n.Tr("Can't revert removed component"), error, L10n.Tr("OK")); + return; + } + Undo.RegisterCompleteObjectUndo(instanceGameObject, actionName); + } RemoveRemovedComponentOverride(prefabInstanceObject, assetComponent); @@ -1588,9 +1685,9 @@ internal static bool PromptAndCheckoutPrefabIfNeeded(string[] assetPaths, SaveVe // in order for localization strings to have sufficient context. string prefabNoun = assetPaths.Length > 1 ? L10n.Tr("Prefabs") : L10n.Tr("Prefab"); - bool result = Provider.PromptAndCheckoutIfNeeded( + bool result = AssetDatabase.MakeEditable( assetPaths, - String.Format( + string.Format( saveVerb == SaveVerb.Save ? L10n.Tr("The version control requires you to check out the {0} before saving changes.") : L10n.Tr("The version control requires you to check out the {0} before applying changes."), @@ -1654,6 +1751,9 @@ internal static bool HasInvalidComponent(Object gameObjectOrComponent) gameObjectOrComponent = (GameObject)comp.gameObject; } + if (!(gameObjectOrComponent is GameObject)) + return false; + GameObject go; go = (GameObject)gameObjectOrComponent; TransformVisitor transformVisitor = new TransformVisitor(); @@ -1863,27 +1963,52 @@ internal static List GetApplyTargets(Object instanceOrAssetObject, bool return applyTargets; } - static List GetDependentComponents(Component component) + static List GetComponentsWhichDependOnThis(Component component, List componentsToConsider) { List dependencies = new List(); var componentType = component.GetType(); - // Iterate all components on *this* GameObject. - // We don't care about other components on the Prefab instance. - var allComponents = component.gameObject.GetComponents(); - for (int i = 0; i < allComponents.Length; i++) + // Iterate all components. + for (int i = 0; i < componentsToConsider.Count; i++) { - var comp = allComponents[i]; + var comp = componentsToConsider[i]; - // Ignore components that are not added. - if (GetCorrespondingObjectFromSource(comp) != null) + // Ignore component itself. + if (comp == component) continue; - // Ignore component itself the user is reverting. + var requiredComps = comp.GetType().GetCustomAttributes(typeof(RequireComponent), inherit: true); + foreach (RequireComponent reqComp in requiredComps) + { + if (reqComp.m_Type0 == componentType || reqComp.m_Type1 == componentType || reqComp.m_Type2 == componentType) + { + // We might get the same component type requirement from multiple sources. + // Make sure we don't add the same component more than once. + if (!dependencies.Contains(comp)) + dependencies.Add(comp); + } + } + } + return dependencies; + } + + static List GetComponentsWhichThisDependsOn(Component component, List componentsToConsider) + { + var requiredComps = component.GetType().GetCustomAttributes(typeof(RequireComponent), inherit: true); + List dependencies = new List(); + if (requiredComps.Count() == 0) + return dependencies; + + // Iterate all components. + for (int i = 0; i < componentsToConsider.Count; i++) + { + var comp = componentsToConsider[i]; + + // Ignore component itself. if (comp == component) continue; - var requiredComps = comp.GetType().GetCustomAttributes(typeof(RequireComponent), inherit: true); + var componentType = comp.GetType(); foreach (RequireComponent reqComp in requiredComps) { if (reqComp.m_Type0 == componentType || reqComp.m_Type1 == componentType || reqComp.m_Type2 == componentType) diff --git a/Editor/Mono/PreferencesWindow/CacheServerPreferences.cs b/Editor/Mono/PreferencesWindow/CacheServerPreferences.cs index cdee1c88fd..be6dc4079c 100644 --- a/Editor/Mono/PreferencesWindow/CacheServerPreferences.cs +++ b/Editor/Mono/PreferencesWindow/CacheServerPreferences.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System; using System.IO; +using UnityEditor.Experimental; namespace UnityEditor { @@ -23,8 +24,8 @@ internal class Properties public static readonly GUIContent cleanCache = EditorGUIUtility.TrTextContent("Clean Cache"); public static readonly GUIContent enumerateCache = EditorGUIUtility.TrTextContent("Check Cache Size", "Check the size of the local asset cache server - can take a while"); public static readonly GUIContent browseCacheLocation = EditorGUIUtility.TrTextContent("Browse for local asset cache server location"); - public static readonly GUIContent assetPipelineVersion1 = EditorGUIUtility.TrTextContent("Asset pipeline v1"); - public static readonly GUIContent assetPipelineVersion2 = EditorGUIUtility.TrTextContent("Asset pipeline v2 (experimental)"); + public static readonly GUIContent assetPipelineVersion1 = EditorGUIUtility.TrTextContent("Asset pipeline v1 (deprecated)"); + public static readonly GUIContent assetPipelineVersion2 = EditorGUIUtility.TrTextContent("Asset pipeline v2"); public static readonly GUIContent newProjectsAssetPipeline = EditorGUIUtility.TrTextContent("New Projects default asset pipeline", "The default asset pipeline used when creating a new project. Note that this default can be overridden by using a -adb1 or -adb2 command line argument."); public static readonly GUIContent activeAssetPipelineVersionLabel = EditorGUIUtility.TrTextContent("Active version", activeAssetPipelineVersionTooltip); public static readonly GUIContent activeAssetPipelineVersion = new GUIContent(AssetDatabase.IsV1Enabled() ? "1" : "2", activeAssetPipelineVersionTooltip); @@ -139,6 +140,11 @@ public static void WritePreferences() EditorPrefs.SetBool(LocalCacheServer.CustomPathKey, s_EnableCustomPath); LocalCacheServer.Setup(); + if (AssetDatabase.IsV2Enabled()) + { + AssetDatabaseExperimental.RefreshConnectionToCacheServer(); + } + if (changedDir) { //Call ExitGUI after bringing up a dialog to avoid an exception @@ -160,7 +166,7 @@ public static string GetCommandLineRemoteAddressOverride() [SettingsProvider] internal static SettingsProvider CreateSettingsProvider() { - return new SettingsProvider("Preferences/Cache Server", SettingsScope.User) + return new SettingsProvider("Preferences/Cache Server (global)", SettingsScope.User) { guiHandler = searchContext => { @@ -281,7 +287,7 @@ static void CacheServerVersion1GUI(bool allowCacheServerChanges, string override using (new EditorGUI.DisabledScope(!allowCacheServerChanges)) { var displayAddress = overrideAddress != null ? overrideAddress : s_CacheServerIPAddress; - s_CacheServerIPAddress = EditorGUILayout.DelayedTextField("IP Address", displayAddress); + s_CacheServerIPAddress = EditorGUILayout.TextField("IP Address", displayAddress); if (GUI.changed) { @@ -411,7 +417,7 @@ static void CacheServerVersion2GUI(bool allowCacheServerChanges, string override s_CacheServer2Mode = (CacheServer2Mode)EditorGUILayout.EnumPopup(Properties.cacheServerDefaultMode, s_CacheServer2Mode); - s_CacheServer2IPAddress = EditorGUILayout.DelayedTextField(Properties.cacheServerIPLabel, s_CacheServer2IPAddress); + s_CacheServer2IPAddress = EditorGUILayout.TextField(Properties.cacheServerIPLabel, s_CacheServer2IPAddress); if (GUI.changed != changeStateBeforeControls) { @@ -424,10 +430,17 @@ static void CacheServerVersion2GUI(bool allowCacheServerChanges, string override { if (GUILayout.Button("Check Connection", GUILayout.Width(150))) { - if (InternalEditorUtility.CanConnectToCacheServer()) + var address = s_CacheServer2IPAddress.Split(':'); + var ip = address[0]; + UInt16 port = 0; + if (address.Length == 2) + port = Convert.ToUInt16(address[1]); + + if (AssetDatabaseExperimental.CanConnectToCacheServer(ip, port)) s_ConnectionState = ConnectionState.Success; else s_ConnectionState = ConnectionState.Failure; + } } @@ -458,7 +471,17 @@ private static void OnPreferencesReadGUI() if (shouldTryConnect) { - if (InternalEditorUtility.CanConnectToCacheServer()) + var canConnect = false; + if (AssetDatabase.IsV1Enabled()) + { + canConnect = InternalEditorUtility.CanConnectToCacheServer(); + } + else + { + canConnect = AssetDatabaseExperimental.IsConnectedToCacheServer(); + } + + if (canConnect) s_ConnectionState = ConnectionState.Success; else s_ConnectionState = ConnectionState.Failure; diff --git a/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs b/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs index 5318819e18..5d9085f6bd 100644 --- a/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs +++ b/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs @@ -12,6 +12,7 @@ using JetBrains.Annotations; using Unity.CodeEditor; using UnityEditor.Connect; +using UnityEditor.VisualStudioIntegration; using UnityEngine.UIElements; using UnityEditor.Experimental; using UnityEngine.TestTools; @@ -63,7 +64,16 @@ internal class GeneralProperties public static readonly GUIContent[] editorSkinOptions = { EditorGUIUtility.TrTextContent("Personal"), EditorGUIUtility.TrTextContent("Professional") }; public static readonly GUIContent enableAlphaNumericSorting = EditorGUIUtility.TrTextContent("Enable Alphanumeric Sorting"); public static readonly GUIContent asyncShaderCompilation = EditorGUIUtility.TrTextContent("Asynchronous Shader Compilation"); - public static readonly GUIContent codeCoverageEnabled = EditorGUIUtility.TrTextContent("Enable Code Coverage", "Check this to enable Code Coverage."); + public static readonly GUIContent codeCoverageEnabled = EditorGUIUtility.TrTextContent("Enable Code Coverage", "Check this to enable Code Coverage. Code Coverage lets you see how much of your code is executed when it is run. Note that Code Coverage lowers Editor performance."); + public static readonly GUIContent applicationFrameThrottling = EditorGUIUtility.TrTextContent("Frame throttling (milliseconds)", "Number of milliseconds to wait between each editor frames."); + public static readonly GUIContent interactionMode = EditorGUIUtility.TrTextContent("Interaction Mode", "Define how the editor application gets updated and throttled."); + public static readonly GUIContent[] interactionModes = + { + EditorGUIUtility.TrTextContent("Default", "The editor application will be throttled up to 4 ms per frame."), + EditorGUIUtility.TrTextContent("No throttling", "The editor application will not be throttled and run as fast as possible."), + EditorGUIUtility.TrTextContent("Monitor Refresh Rate", "The editor will wait up to the monitor refresh rate in milliseconds (i.e. ~16 ms)."), + EditorGUIUtility.TrTextContent("Custom", "Specify how many milliseconds at most the application will be idle per frame."), + }; } internal class ExternalProperties @@ -127,6 +137,7 @@ internal class LanguageProperties private bool m_DeveloperModeDirty; private bool m_AllowAttachedDebuggingOfEditor; private bool m_AllowAttachedDebuggingOfEditorStateChangedThisSession; + private string m_GpuDeviceInUse; private string m_GpuDevice; private string[] m_CachedGpuDevices; private bool m_ContentScaleChangedThisSession; @@ -161,6 +172,10 @@ private struct GICacheSettings private bool m_AllowAlphaNumericHierarchy = false; private bool m_EnableCodeCoverage = false; + private bool m_EnableCodeCoverageChangedInThisSession = false; + private readonly string kEnablingCodeCoverageMessage = L10n.Tr("Enabling Code Coverage will not take effect until Unity is restarted. Note that Code Coverage lowers Editor performance."); + private readonly string kDisablingCodeCoverageMessage = L10n.Tr("Disabling Code Coverage will not take effect until Unity is restarted."); + private readonly string kCodeCoverageEnabledMessage = L10n.Tr("Code Coverage collection is enabled for this Unity session. Note that Code Coverage lowers Editor performance."); private string[] m_ScriptApps; private string[] m_ScriptAppsEditions; @@ -345,7 +360,7 @@ private static void OnGUI(string searchContext, Action drawAction) private void ShowExternalApplications(string searchContext) { // Applications - FilePopup(ExternalProperties.externalScriptEditor, m_ScriptEditorPath, ref m_ScriptAppDisplayNames, ref m_ScriptApps, m_ScriptEditorPath, "internal", OnScriptEditorChanged); + FilePopup(ExternalProperties.externalScriptEditor, ScriptEditorUtility.GetExternalScriptEditor(), ref m_ScriptAppDisplayNames, ref m_ScriptApps, m_ScriptEditorPath, CodeEditor.SystemDefaultPath, OnScriptEditorChanged); #pragma warning disable 618 if (ScriptEditorUtility.GetScriptEditorFromPath(CodeEditor.CurrentEditorInstallation) == ScriptEditorUtility.ScriptEditor.Other) @@ -354,13 +369,7 @@ private void ShowExternalApplications(string searchContext) } else { - var prevGenerate = EditorPrefs.GetBool(k_UnityGenerateAll, false); - var generateAll = EditorGUILayout.Toggle("Generate all .csproj files.", prevGenerate); - if (generateAll != prevGenerate) - { - EditorPrefs.SetBool(k_UnityGenerateAll, generateAll); - } - SyncVS.Synchronizer.GenerateAll(generateAll); + GenerateAllProjectSettings(); } DoUnityProjCheckbox(); @@ -415,6 +424,41 @@ private void ShowExternalApplications(string searchContext) ApplyChangesToPrefs(); } + void GenerateAllProjectSettings() + { + EditorGUILayout.LabelField("Generate .csproj files for:"); + EditorGUI.indentLevel++; + SettingsButton(ProjectGenerationFlag.Embedded, "Embedded packages", ""); + SettingsButton(ProjectGenerationFlag.Local, "Local packages", ""); + SettingsButton(ProjectGenerationFlag.Registry, "Registry packages", ""); + SettingsButton(ProjectGenerationFlag.Git, "Git packages", ""); + SettingsButton(ProjectGenerationFlag.BuiltIn, "Built-in packages", ""); + SettingsButton(ProjectGenerationFlag.LocalTarBall, "Local tarball", ""); + SettingsButton(ProjectGenerationFlag.Unknown, "Packages from unknown sources", ""); + RegenerateProjectFiles(); + EditorGUI.indentLevel--; + } + + void RegenerateProjectFiles() + { + var rect = EditorGUI.IndentedRect(EditorGUILayout.GetControlRect(new GUILayoutOption[] {})); + rect.width = 252; + if (GUI.Button(rect, "Regenerate project files")) + { + SyncVS.SyncSolution(); + } + } + + void SettingsButton(ProjectGenerationFlag preference, string guiMessage, string toolTip) + { + var prevValue = SyncVS.Synchronizer.AssemblyNameProvider.HasFlag(preference); + var newValue = EditorGUILayout.Toggle(new GUIContent(guiMessage, toolTip), prevValue); + if (newValue != prevValue) + { + SyncVS.Synchronizer.AssemblyNameProvider.ToggleProjectGeneration(preference); + } + } + private void DoUnityProjCheckbox() { bool isConfigurable = false; @@ -457,7 +501,7 @@ private void ShowUnityConnectPrefs(string searchContext) private void EnableGeneral() { - var fontNames = new List(EditorResources.GetSupportedFonts()); + var fontNames = new List(EditorResources.supportedFontNames); fontNames.Sort(); @@ -550,7 +594,6 @@ private void ShowGeneral(string searchContext) // Refresh skin to get new font Unsupported.ClearSkinCache(); - EditorResources.BuildCatalog(); InternalEditorUtility.RequestScriptReload(); InternalEditorUtility.RepaintAllViews(); } @@ -575,17 +618,48 @@ private void ShowGeneral(string searchContext) if (currentGpuDeviceIndex == -1) currentGpuDeviceIndex = 0; + if (string.IsNullOrEmpty(m_GpuDeviceInUse)) + { + m_GpuDeviceInUse = m_CachedGpuDevices[currentGpuDeviceIndex]; + + if (string.IsNullOrEmpty(m_GpuDevice)) + { + m_GpuDevice = m_GpuDeviceInUse; + } + } + var newGpuDeviceIndex = EditorGUILayout.Popup("Device To Use", currentGpuDeviceIndex, m_CachedGpuDevices); if (currentGpuDeviceIndex != newGpuDeviceIndex) { m_GpuDevice = m_CachedGpuDevices[newGpuDeviceIndex]; - InternalEditorUtility.SetGpuDeviceAndRecreateGraphics(newGpuDeviceIndex - 1, m_GpuDevice); + } + + if (m_GpuDevice != m_GpuDeviceInUse) + { + EditorGUILayout.HelpBox(ExternalProperties.changingThisSettingRequiresRestart.text, MessageType.Warning); } } m_EnableCodeCoverage = EditorGUILayout.Toggle(GeneralProperties.codeCoverageEnabled, m_EnableCodeCoverage); if (m_EnableCodeCoverage != Coverage.enabled) - EditorGUILayout.HelpBox((m_EnableCodeCoverage ? "Enabling " : "Disabling ") + "Code Coverage will not take effect until Unity is restarted.", MessageType.Warning); + { + if (m_EnableCodeCoverage) + { + EditorGUILayout.HelpBox(kEnablingCodeCoverageMessage, MessageType.Warning); + } + else + { + EditorGUILayout.HelpBox(kDisablingCodeCoverageMessage, MessageType.Warning); + } + m_EnableCodeCoverageChangedInThisSession = true; + } + else + { + if (Coverage.enabled) + { + EditorGUILayout.HelpBox(kCodeCoverageEnabledMessage, MessageType.Warning); + } + } ApplyChangesToPrefs(); @@ -596,6 +670,65 @@ private void ShowGeneral(string searchContext) { ProjectBrowser.ShowAssetStoreHitsWhileSearchingLocalAssetsChanged(); } + + DrawInteractionModeOptions(); + } + + enum InteractionMode + { + Default, // 4 ms + NoThrottling, // 0 ms (will never idle) + MonitorRefreshRate, // ~16 ms + Custom // Between 1 ms and 33 ms + } + + private void DrawInteractionModeOptions() + { + const int defaultIdleTimeMs = 4; + int monitorRefreshDelayMs = (int)(1f / Math.Max(Screen.currentResolution.refreshRate, 1) * 1000f); + const string idleTimePrefKeyName = "ApplicationIdleTime"; + const string interactionModePrefKeyName = "InteractionMode"; + var idleTimeMs = EditorPrefs.GetInt(idleTimePrefKeyName, defaultIdleTimeMs); + var interactionModeOption = (InteractionMode)EditorPrefs.GetInt(interactionModePrefKeyName, (int)InteractionMode.Default); + + if (Event.current.type == EventType.MouseDown) + GeneralProperties.interactionModes[(int)InteractionMode.MonitorRefreshRate].text = $"Monitor Refresh Rate ({monitorRefreshDelayMs} ms)"; + + EditorGUI.BeginChangeCheck(); + interactionModeOption = (InteractionMode)EditorGUILayout.Popup(GeneralProperties.interactionMode, (int)interactionModeOption, GeneralProperties.interactionModes); + if (interactionModeOption == InteractionMode.Default) + { + if (EditorGUI.EndChangeCheck()) + { + EditorPrefs.DeleteKey(idleTimePrefKeyName); + EditorPrefs.DeleteKey(interactionModePrefKeyName); + } + } + else if (interactionModeOption == InteractionMode.NoThrottling) + { + if (EditorGUI.EndChangeCheck()) + { + EditorPrefs.SetInt(idleTimePrefKeyName, 0); + EditorPrefs.SetInt(interactionModePrefKeyName, (int)interactionModeOption); + } + } + else if (interactionModeOption == InteractionMode.MonitorRefreshRate) + { + if (EditorGUI.EndChangeCheck()) + { + EditorPrefs.SetInt(idleTimePrefKeyName, monitorRefreshDelayMs); + EditorPrefs.SetInt(interactionModePrefKeyName, (int)interactionModeOption); + } + } + else + { + idleTimeMs = EditorGUILayout.IntSlider(GeneralProperties.applicationFrameThrottling, idleTimeMs, 0, 33); + if (EditorGUI.EndChangeCheck()) + { + EditorPrefs.SetInt(idleTimePrefKeyName, idleTimeMs); + EditorPrefs.SetInt(interactionModePrefKeyName, (int)interactionModeOption); + } + } } public void ApplyChangesToPrefs(bool force = false) @@ -803,7 +936,7 @@ private void ShowLanguage(string searchContext) { SystemLanguage lang = (SystemLanguage)Enum.Parse(typeof(SystemLanguage), m_SelectedLanguage); EditorGUIUtility.NotifyLanguageChanged(lang); - InternalEditorUtility.RequestScriptReload(); + EditorUtility.RequestScriptReload(); } ApplyChangesToPrefs(); @@ -937,6 +1070,13 @@ private void WritePreferences() EditorPrefs.SetBool("AllowAlphaNumericHierarchy", m_AllowAlphaNumericHierarchy); EditorPrefs.SetBool("CodeCoverageEnabled", m_EnableCodeCoverage); + + if (m_EnableCodeCoverageChangedInThisSession) + { + EditorPrefs.SetBool("CodeCoverageEnabledMessageShown", false); + m_EnableCodeCoverageChangedInThisSession = false; + } + EditorPrefs.SetString("GpuDeviceName", m_GpuDevice); EditorPrefs.SetBool("GICacheEnableCustomPath", m_GICacheSettings.m_EnableCustomPath); @@ -965,7 +1105,7 @@ private void ReadPreferences() m_ExternalEditorSupportsUnityProj = EditorPrefs.GetBool("kExternalEditorSupportsUnityProj", false); m_ImageAppPath.str = EditorPrefs.GetString("kImagesDefaultApp"); - m_ScriptApps = BuildAppPathList(m_ScriptEditorPath, kRecentScriptAppsKey, "internal"); + m_ScriptApps = BuildAppPathList(m_ScriptEditorPath, kRecentScriptAppsKey, CodeEditor.SystemDefaultPath); m_ScriptAppsEditions = new string[m_ScriptApps.Length]; if (Application.platform == RuntimePlatform.WindowsEditor) @@ -1167,7 +1307,7 @@ private string[] BuildFriendlyAppNameList(string[] appPathList, string[] appEdit { var appPath = appPathList[i]; - if (appPath == "internal" || appPath == "") // use built-in + if (appPath == CodeEditor.SystemDefaultPath) // use built-in list.Add(defaultBuiltIn); else { diff --git a/Editor/Mono/ProjectBrowser.cs b/Editor/Mono/ProjectBrowser.cs index c3439d35f9..1b628238f0 100644 --- a/Editor/Mono/ProjectBrowser.cs +++ b/Editor/Mono/ProjectBrowser.cs @@ -2165,7 +2165,7 @@ void ToggleExpansionAnimationPreference() { bool oldValue = EditorPrefs.GetBool(TreeViewController.kExpansionAnimationPrefKey, false); EditorPrefs.SetBool(TreeViewController.kExpansionAnimationPrefKey, !oldValue); - InternalEditorUtility.RequestScriptReload(); + EditorUtility.RequestScriptReload(); } public virtual void AddItemsToMenu(GenericMenu menu) @@ -2458,6 +2458,9 @@ void FolderTreeSelectionChanged(bool folderWasSelected) m_LastFolders = m_SearchFilter.folders; } + // End any rename that might be in progress. + EndRenaming(); + RefreshSearchText(); InitListArea(); } diff --git a/Editor/Mono/ProjectBrowserColumnOne.cs b/Editor/Mono/ProjectBrowserColumnOne.cs index 7656301614..2617fa4654 100644 --- a/Editor/Mono/ProjectBrowserColumnOne.cs +++ b/Editor/Mono/ProjectBrowserColumnOne.cs @@ -3,7 +3,6 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System.IO; -using System.Linq; using System.Collections.Generic; using UnityEditor.IMGUI.Controls; using UnityEngine; @@ -112,7 +111,17 @@ bool IsVisibleRootNode(TreeViewItem item) protected override Texture GetIconForItem(TreeViewItem item) { if (item != null && item.icon != null) - return item.icon; + { + var icon = item.icon; + AssetsTreeViewDataSource.FolderTreeItemBase folderItem = item as AssetsTreeViewDataSource.FolderTreeItemBase; + + if (folderItem != null && m_TreeView.data.IsExpanded(folderItem)) + { + icon = openFolderTexture; + } + + return icon; + } SearchFilterTreeItem searchFilterItem = item as SearchFilterTreeItem; if (searchFilterItem != null) @@ -189,9 +198,10 @@ override protected void RenameEnded() //------------------------------------------------ // DataSource section - internal class ProjectBrowserColumnOneTreeViewDataSource : TreeViewDataSource + internal class ProjectBrowserColumnOneTreeViewDataSource : LazyTreeViewDataSource { static string kProjectBrowserString = "ProjectBrowser"; + static Texture2D s_FolderIcon = EditorGUIUtility.FindTexture(EditorResources.folderIconName); public bool skipHiddenPackages { get; set; } @@ -203,25 +213,6 @@ public ProjectBrowserColumnOneTreeViewDataSource(TreeViewController treeView, bo SavedSearchFilters.AddChangeListener(ReloadData); // We reload on change } - public override bool SetExpanded(int id, bool expand) - { - if (base.SetExpanded(id, expand)) - { - // Persist expanded state for ProjectBrowsers - InternalEditorUtility.expandedProjectWindowItems = expandedIDs.ToArray(); - - if (m_RootItem.hasChildren) - { - // Set global expanded state of roots (Assets folder and Favorites root) - foreach (TreeViewItem item in m_RootItem.children) - if (item.id == id) - EditorPrefs.SetBool(kProjectBrowserString + item.displayName, expand); - } - return true; - } - return false; - } - public override bool IsExpandable(TreeViewItem item) { return item.hasChildren && (item != m_RootItem || rootIsCollapsable); @@ -262,82 +253,131 @@ public override bool IsRenamingItemAllowed(TreeViewItem item) public override void FetchData() { + bool firstInitialize = !isInitialized; m_RootItem = new TreeViewItem(0, 0, null, "Invisible Root Item"); SetExpanded(m_RootItem, true); // ensure always visible - // We want three roots: Favorites, Assets, and Saved Filters + // We want three roots: Favorites, Assets, and Packages List visibleRoots = new List(); - // Fetch asset folders + // Favorites root + TreeViewItem savedFiltersRootItem = SavedSearchFilters.ConvertToTreeView(); + visibleRoots.Add(savedFiltersRootItem); + + // Assets root int assetsFolderInstanceID = AssetDatabase.GetMainAssetOrInProgressProxyInstanceID("Assets"); int depth = 0; - string displayName = "Assets"; //CreateDisplayName (assetsFolderInstanceID); - TreeViewItem assetRootItem = new TreeViewItem(assetsFolderInstanceID, depth, m_RootItem, displayName); - ReadAssetDatabase("Assets", assetRootItem, depth + 1); + string displayName = "Assets"; + AssetsTreeViewDataSource.RootTreeItem assetRootItem = new AssetsTreeViewDataSource.RootTreeItem(assetsFolderInstanceID, depth, m_RootItem, displayName); + assetRootItem.icon = s_FolderIcon; + visibleRoots.Add(assetRootItem); - // Fetch packages folder + // Packages root displayName = PackageManager.Folders.GetPackagesPath(); - TreeViewItem packagesRootItem = new TreeViewItem(ProjectBrowser.kPackagesFolderInstanceId, depth, m_RootItem, displayName); - depth++; - - Texture2D folderIcon = EditorGUIUtility.FindTexture(EditorResources.folderIconName); - Texture2D emptyFolderIcon = EditorGUIUtility.FindTexture(EditorResources.emptyFolderIconName); + AssetsTreeViewDataSource.RootTreeItem packagesRootItem = new AssetsTreeViewDataSource.RootTreeItem(ProjectBrowser.kPackagesFolderInstanceId, depth, m_RootItem, displayName); + packagesRootItem.icon = s_FolderIcon; + visibleRoots.Add(packagesRootItem); - packagesRootItem.icon = emptyFolderIcon; + m_RootItem.children = visibleRoots; - var packages = PackageManagerUtilityInternal.GetAllVisiblePackages(skipHiddenPackages); - foreach (var package in packages) + // Set global expanded state for roots from EditorPrefs (must be before building the rows) + if (firstInitialize) { - var packageFolderInstanceId = AssetDatabase.GetMainAssetOrInProgressProxyInstanceID(package.assetPath); - - displayName = !string.IsNullOrEmpty(package.displayName) ? package.displayName : package.name; - TreeViewItem packageItem = new TreeViewItem(packageFolderInstanceId, depth, packagesRootItem, displayName); - packagesRootItem.AddChild(packageItem); - ReadAssetDatabase(package.assetPath, packageItem, depth + 1); - packagesRootItem.icon = folderIcon; + foreach (TreeViewItem item in m_RootItem.children) + { + // Do not expand Packages root item + if (item.id == ProjectBrowser.kPackagesFolderInstanceId) + continue; + bool expanded = EditorPrefs.GetBool(kProjectBrowserString + item.displayName, true); + SetExpanded(item, expanded); + } } - // Fetch saved filters - TreeViewItem savedFiltersRootItem = SavedSearchFilters.ConvertToTreeView(); - savedFiltersRootItem.parent = m_RootItem; + // Build rows + //----------- + m_Rows = new List(100); - // Order - visibleRoots.Add(savedFiltersRootItem); - visibleRoots.Add(assetRootItem); - visibleRoots.Add(packagesRootItem); + // Favorites + savedFiltersRootItem.parent = m_RootItem; + m_Rows.Add(savedFiltersRootItem); + if (IsExpanded(savedFiltersRootItem)) + { + foreach (var f in savedFiltersRootItem.children) + m_Rows.Add(f); + } + else + { + savedFiltersRootItem.children = CreateChildListForCollapsedParent(); + } - m_RootItem.children = visibleRoots; + // Asset folders + m_Rows.Add(assetRootItem); + ReadAssetDatabase("Assets", assetRootItem, depth + 1, m_Rows); - // Get global expanded state of roots - foreach (TreeViewItem item in m_RootItem.children) + // Individual Package folders (under the Packages root item) + m_Rows.Add(packagesRootItem); + var packages = PackageManagerUtilityInternal.GetAllVisiblePackages(skipHiddenPackages); + if (IsExpanded(packagesRootItem)) { - // Do not expand Packages root item - if (item.id == ProjectBrowser.kPackagesFolderInstanceId) - continue; - bool expanded = EditorPrefs.GetBool(kProjectBrowserString + item.displayName, true); - SetExpanded(item, expanded); + depth++; + foreach (var package in packages) + { + var packageFolderInstanceId = AssetDatabase.GetMainAssetOrInProgressProxyInstanceID(package.assetPath); + + displayName = !string.IsNullOrEmpty(package.displayName) ? package.displayName : package.name; + AssetsTreeViewDataSource.PackageTreeItem packageItem = new AssetsTreeViewDataSource.PackageTreeItem(packageFolderInstanceId, depth, packagesRootItem, displayName); + packageItem.icon = s_FolderIcon; + packagesRootItem.AddChild(packageItem); + m_Rows.Add(packageItem); + ReadAssetDatabase(package.assetPath, packageItem, depth + 1, m_Rows); + } } + else + { + if (packages.Length > 0) + packagesRootItem.children = CreateChildListForCollapsedParent(); + } + + m_NeedRefreshRows = false; + } - m_NeedRefreshRows = true; + static bool HasSubFolders(IHierarchyProperty property) + { + var path = AssetDatabase.GUIDToAssetPath(property.guid); + var subFolders = AssetDatabase.GetSubFolders(path); + return subFolders.Length > 0; } - private void ReadAssetDatabase(string assetFolderRootPath, TreeViewItem parent, int baseDepth) + private void ReadAssetDatabase(string assetFolderRootPath, TreeViewItem parent, int baseDepth, IList allRows) { // Read from Assets directory IHierarchyProperty property = new HierarchyProperty(assetFolderRootPath); property.Reset(); + if (!IsExpanded(parent)) + { + if (HasSubFolders(property)) + parent.children = CreateChildListForCollapsedParent(); + return; + } + Texture2D folderIcon = EditorGUIUtility.FindTexture(EditorResources.folderIconName); - Texture2D emptyFolderIcon = EditorGUIUtility.FindTexture(EditorResources.emptyFolderIconName); List allFolders = new List(); - while (property.Next(null)) + var expandedIDs = m_TreeView.state.expandedIDs.ToArray(); + while (property.Next(expandedIDs)) { if (property.isFolder) { - TreeViewItem folderItem = new TreeViewItem(property.instanceID, baseDepth + property.depth, null, property.name); - folderItem.icon = property.hasChildren ? folderIcon : emptyFolderIcon; + AssetsTreeViewDataSource.FolderTreeItem folderItem = new AssetsTreeViewDataSource.FolderTreeItem(property.guid, property.instanceID, baseDepth + property.depth, null, property.name); + folderItem.icon = folderIcon; allFolders.Add(folderItem); + allRows.Add(folderItem); + if (!IsExpanded(folderItem)) + { + if (HasSubFolders(property)) + folderItem.children = CreateChildListForCollapsedParent(); + } } } @@ -345,6 +385,57 @@ private void ReadAssetDatabase(string assetFolderRootPath, TreeViewItem parent, TreeViewUtility.SetChildParentReferences(allFolders, parent); } + public override void SetExpandedWithChildren(int id, bool expand) + { + base.SetExpandedWithChildren(id, expand); + PersistExpandedState(id, expand); + } + + public override bool SetExpanded(int id, bool expand) + { + if (base.SetExpanded(id, expand)) + { + PersistExpandedState(id, expand); + return true; + } + return false; + } + + void PersistExpandedState(int id, bool expand) + { + // Persist expanded state for ProjectBrowsers + InternalEditorUtility.expandedProjectWindowItems = expandedIDs.ToArray(); + + if (m_RootItem.hasChildren) + { + // Set global expanded state of roots (Assets folder and Favorites root) + foreach (TreeViewItem item in m_RootItem.children) + if (item.id == id) + EditorPrefs.SetBool(kProjectBrowserString + item.displayName, expand); + } + } + + protected override void GetParentsAbove(int id, HashSet parentsAbove) + { + if (SavedSearchFilters.IsSavedFilter(id)) + { + parentsAbove.Add(SavedSearchFilters.GetRootInstanceID()); + } + else + { + // AssetDatabase folders (in Assets or Packages) + var path = AssetDatabase.GetAssetPath(id); + if (Directory.Exists(path)) + parentsAbove.UnionWith(ProjectWindowUtil.GetAncestors(id)); + } + } + + protected override void GetParentsBelow(int id, HashSet parentsBelow) + { + var extra = GetParentsBelow(id); + parentsBelow.UnionWith(extra); + } + private HashSet GetParentsBelow(int id) { // Add all children expanded ids to hashset @@ -354,18 +445,17 @@ private HashSet GetParentsBelow(int id) if (id == ProjectBrowser.kPackagesFolderInstanceId) { parentsBelow.Add(id); - var item = m_TreeView.FindItem(id); - foreach (var child in item.children) + var packages = PackageManagerUtilityInternal.GetAllVisiblePackages(skipHiddenPackages); + foreach (var package in packages) { - if (child.hasChildren) - parentsBelow.UnionWith(GetParentsBelow(child.id)); + var packageFolderInstanceId = AssetDatabase.GetMainAssetOrInProgressProxyInstanceID(package.assetPath); + parentsBelow.UnionWith(GetParentsBelow(packageFolderInstanceId)); } return parentsBelow; } var path = AssetDatabase.GetAssetPath(id); - var pathComponents = path.Split('/'); - IHierarchyProperty search = new HierarchyProperty(pathComponents[0]); + IHierarchyProperty search = new HierarchyProperty(path); if (search.Find(id, null)) { parentsBelow.Add(id); @@ -373,25 +463,12 @@ private HashSet GetParentsBelow(int id) int depth = search.depth; while (search.Next(null) && search.depth > depth) { - if (search.hasChildren) + if (search.isFolder && search.hasChildren) parentsBelow.Add(search.instanceID); } } return parentsBelow; } - - override public void SetExpandedWithChildren(int id, bool expand) - { - HashSet oldExpandedSet = new HashSet(expandedIDs); - HashSet candidates = GetParentsBelow(id); - - if (expand) - oldExpandedSet.UnionWith(candidates); - else - oldExpandedSet.ExceptWith(candidates); - - SetExpandedIDs(oldExpandedSet.ToArray()); - } } internal class ProjectBrowserColumnOneTreeViewDragging : AssetsTreeViewDragging diff --git a/Editor/Mono/ProjectWindow/CachedFilteredHierachy.cs b/Editor/Mono/ProjectWindow/CachedFilteredHierachy.cs index 6519d69063..ab112805c5 100644 --- a/Editor/Mono/ProjectWindow/CachedFilteredHierachy.cs +++ b/Editor/Mono/ProjectWindow/CachedFilteredHierachy.cs @@ -33,7 +33,8 @@ public Texture2D icon // Note: Do not set m_Icon as GetCachedIcon uses its own cache that is cleared on reaching a max limit. // This is because when having e.g very large projects (1000s of textures with unique icons) we do not want all icons loaded // at the same time so don't keep a reference in m_Icon here - string path = AssetDatabase.GetAssetPath(instanceID); + string path = instanceID == 0 ? null : AssetDatabase.GetAssetPath(instanceID); + if (path != null) // Finding icon based on only file extension fails in several ways, and a different approach have to be found. // Using InternalEditorUtility.FindIconForFile first in revision f25945218bb6 / 29b23dbe4b5c introduced several regressions. @@ -44,6 +45,10 @@ public Texture2D icon // Support for specific file types based on file extensiom have to be supported inside AssetDatabase.GetCachedIcon // itself to work correctly and universally. for e.g. uxml files from within GetCachedIcon without relying on FindIconForFile. return AssetDatabase.GetCachedIcon(path) as Texture2D; + + path = string.IsNullOrEmpty(m_Guid) ? null : AssetDatabase.GUIDToAssetPath(m_Guid); + if (path != null) + return UnityEditorInternal.InternalEditorUtility.FindIconForFile(path); } else if (type == HierarchyType.GameObjects) { @@ -65,15 +70,21 @@ public Texture2D icon } private Texture2D m_Icon; + internal string m_Guid; + public string guid { get { if (type == HierarchyType.Assets) { - string path = AssetDatabase.GetAssetPath(instanceID); - if (path != null) - return AssetDatabase.AssetPathToGUID(path); + if (instanceID != 0 && string.IsNullOrEmpty(m_Guid)) + { + string path = AssetDatabase.GetAssetPath(instanceID); + if (path != null) + m_Guid = AssetDatabase.AssetPathToGUID(path); + } + return m_Guid; } return null; } @@ -162,7 +173,7 @@ void CopyPropertyData(ref FilterResult result, HierarchyProperty property) if (result == null) result = new FilterResult(); - result.instanceID = property.instanceID; + result.instanceID = property.GetInstanceIDIfImported(); result.name = property.name; result.hasChildren = property.hasChildren; result.colorCode = property.colorCode; @@ -176,10 +187,11 @@ void CopyPropertyData(ref FilterResult result, HierarchyProperty property) // Otherwise, don't - as this may cause Textures to load unintendedly (e.g if we have 3000 search results we do not want to load icons before needed when rendering) if (!property.isMainRepresentation) result.icon = property.icon; - else if (property.isFolder && !property.hasChildren) - result.icon = EditorGUIUtility.FindTexture(EditorResources.emptyFolderIconName); else result.icon = null; + + if (m_HierarchyType == HierarchyType.Assets) + result.m_Guid = property.guid; } void SearchAllAssets(SearchFilter.SearchArea area) @@ -456,7 +468,13 @@ public void Reset() public int instanceID { - get { return m_Hierarchy.results[m_Position].instanceID; } + get + { + var id = m_Hierarchy.results[m_Position].instanceID; + if (id == 0) + m_Hierarchy.results[m_Position].instanceID = AssetDatabase.GetMainAssetInstanceID(guid); + return m_Hierarchy.results[m_Position].instanceID; + } } public Object pptrValue @@ -591,5 +609,10 @@ public int CountRemaining(int[] expanded) { return m_Hierarchy.results.Length - m_Position - 1; } + + public int GetInstanceIDIfImported() + { + return m_Hierarchy.results[m_Position].instanceID; + } } } diff --git a/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs b/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs index 6aa10d95f0..4faeaefa3c 100644 --- a/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs +++ b/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs @@ -259,14 +259,14 @@ public static void CreateAsset(Object asset, string pathName) // Create a folder public static void CreateFolder() { - StartNameEditingIfProjectWindowExists(0, ScriptableObject.CreateInstance(), "New Folder", EditorGUIUtility.IconContent(EditorResources.emptyFolderIconName).image as Texture2D, null); + StartNameEditingIfProjectWindowExists(0, ScriptableObject.CreateInstance(), "New Folder", EditorGUIUtility.IconContent(EditorResources.folderIconName).image as Texture2D, null); } internal static void CreateFolderWithTemplates(string defaultName, params string[] templates) { var endNameEditAction = ScriptableObject.CreateInstance(); endNameEditAction.templates = templates; - StartNameEditingIfProjectWindowExists(0, endNameEditAction, defaultName, EditorGUIUtility.IconContent(EditorResources.emptyFolderIconName).image as Texture2D, null); + StartNameEditingIfProjectWindowExists(0, endNameEditAction, defaultName, EditorGUIUtility.IconContent(EditorResources.folderIconName).image as Texture2D, null); } public static void CreateScene() @@ -784,8 +784,6 @@ internal static bool DeleteAssets(List instanceIDs, bool askIfSure) internal static IEnumerable DuplicateAssets(IEnumerable assets) { - AssetDatabase.Refresh(); - var copiedPaths = new List(); Object firstDuplicatedObjectToFail = null; @@ -847,8 +845,6 @@ internal static IEnumerable DuplicateAssets(IEnumerable assets) Debug.LogError(errString, firstDuplicatedObjectToFail); } - AssetDatabase.Refresh(); - return copiedPaths.Select(AssetDatabase.LoadMainAssetAtPath); } diff --git a/Editor/Mono/ProjectWindow/SearchableEditorWindow.cs b/Editor/Mono/ProjectWindow/SearchableEditorWindow.cs index 26c2dd57d6..1442c42159 100644 --- a/Editor/Mono/ProjectWindow/SearchableEditorWindow.cs +++ b/Editor/Mono/ProjectWindow/SearchableEditorWindow.cs @@ -145,8 +145,8 @@ internal static void SearchForReferencesToInstanceID(int instanceID) { string searchFilter; - // only main assets have unique paths (remove "Assets" to make string simpler) - string path = AssetDatabase.GetAssetPath(instanceID).Substring(7); + // Don't remove "Assets" prefix, we need to support Packages as well (https://fogbugz.unity3d.com/f/cases/1161019/) + string path = AssetDatabase.GetAssetPath(instanceID); if (path.IndexOf(' ') != -1) path = '"' + path + '"'; diff --git a/Editor/Mono/RagdollBuilder.cs b/Editor/Mono/RagdollBuilder.cs index 785a2c58b7..88f1a1f04e 100644 --- a/Editor/Mono/RagdollBuilder.cs +++ b/Editor/Mono/RagdollBuilder.cs @@ -271,7 +271,7 @@ void BuildCapsules() } } - CapsuleCollider collider = (CapsuleCollider)bone.anchor.gameObject.AddComponent(); + CapsuleCollider collider = Undo.AddComponent(bone.anchor.gameObject); collider.direction = direction; Vector3 center = Vector3.zero; @@ -291,15 +291,15 @@ void Cleanup() Component[] joints = bone.anchor.GetComponentsInChildren(typeof(Joint)); foreach (Joint joint in joints) - DestroyImmediate(joint); + Undo.DestroyObjectImmediate(joint); Component[] bodies = bone.anchor.GetComponentsInChildren(typeof(Rigidbody)); foreach (Rigidbody body in bodies) - DestroyImmediate(body); + Undo.DestroyObjectImmediate(body); Component[] colliders = bone.anchor.GetComponentsInChildren(typeof(Collider)); foreach (Collider collider in colliders) - DestroyImmediate(collider); + Undo.DestroyObjectImmediate(collider); } } @@ -307,7 +307,7 @@ void BuildBodies() { foreach (BoneInfo bone in bones) { - bone.anchor.gameObject.AddComponent(); + Undo.AddComponent(bone.anchor.gameObject); bone.anchor.GetComponent().mass = bone.density; } } @@ -319,7 +319,7 @@ void BuildJoints() if (bone.parent == null) continue; - CharacterJoint joint = bone.anchor.gameObject.AddComponent(); + CharacterJoint joint = Undo.AddComponent(bone.anchor.gameObject); bone.joint = joint; // Setup connection and axis @@ -479,12 +479,12 @@ void AddBreastColliders() // Middle spine bounds bounds = Clip(GetBreastBounds(pelvis), pelvis, middleSpine, false); - box = (BoxCollider)pelvis.gameObject.AddComponent(); + box = Undo.AddComponent(pelvis.gameObject); box.center = bounds.center; box.size = bounds.size; bounds = Clip(GetBreastBounds(middleSpine), middleSpine, middleSpine, true); - box = (BoxCollider)middleSpine.gameObject.AddComponent(); + box = Undo.AddComponent(middleSpine.gameObject); box.center = bounds.center; box.size = bounds.size; } @@ -500,7 +500,7 @@ void AddBreastColliders() Vector3 size = bounds.size; size[SmallestComponent(bounds.size)] = size[LargestComponent(bounds.size)] / 2.0F; - BoxCollider box = pelvis.gameObject.AddComponent(); + BoxCollider box = Undo.AddComponent(pelvis.gameObject); box.center = bounds.center; box.size = size; } @@ -514,7 +514,7 @@ void AddHeadCollider() float radius = Vector3.Distance(leftArm.transform.position, rightArm.transform.position); radius /= 4; - SphereCollider sphere = head.gameObject.AddComponent(); + SphereCollider sphere = Undo.AddComponent(head.gameObject); sphere.radius = radius; Vector3 center = Vector3.zero; diff --git a/Editor/Mono/RetainedMode.cs b/Editor/Mono/RetainedMode.cs index 5056f22c59..28ee642a80 100644 --- a/Editor/Mono/RetainedMode.cs +++ b/Editor/Mono/RetainedMode.cs @@ -57,13 +57,9 @@ static void UpdateSchedulers() { DataWatchService.sharedInstance.PollNativeData(); - UIElementsUtility.GetAllPanels(panelsIteration); + UIElementsUtility.GetAllPanels(panelsIteration, ContextType.Editor); foreach (var panel in panelsIteration) { - // Game panels' scheduler are ticked by the engine - if (panel.contextType != ContextType.Editor) - continue; - // Dispatch all timer update messages to each scheduled item panel.timerEventScheduler.UpdateScheduledEvents(); panel.UpdateAnimations(); diff --git a/Editor/Mono/SceneHierarchy.cs b/Editor/Mono/SceneHierarchy.cs index 01b082910c..f217f43dd2 100644 --- a/Editor/Mono/SceneHierarchy.cs +++ b/Editor/Mono/SceneHierarchy.cs @@ -201,7 +201,7 @@ internal TreeViewState treeViewState get { return m_TreeViewState; } } - private TreeViewController treeView + internal TreeViewController treeView { get { @@ -854,9 +854,9 @@ public void GameObjectCreateDropdownButton() GUIUtility.hotControl = 0; GenericMenu menu = new GenericMenu(); - var context = m_CustomParentForNewGameObjects != null ? new[] { m_CustomParentForNewGameObjects.gameObject } : null; var targetSceneHandle = m_CustomParentForNewGameObjects != null ? m_CustomParentForNewGameObjects.gameObject.scene.handle : kInvalidSceneHandle; - AddCreateGameObjectItemsToMenu(menu, context, true, false, targetSceneHandle); + // The context should be null, just like it is in the main menu. Case 1185434. + AddCreateGameObjectItemsToMenu(menu, null, true, false, targetSceneHandle); menu.DropDown(rect); Event.current.Use(); diff --git a/Editor/Mono/SceneHierarchyStageHandling.cs b/Editor/Mono/SceneHierarchyStageHandling.cs index dd69e10f36..f140867675 100644 --- a/Editor/Mono/SceneHierarchyStageHandling.cs +++ b/Editor/Mono/SceneHierarchyStageHandling.cs @@ -21,7 +21,7 @@ static class Styles static Styles() { - prefabHeaderBg = new GUIStyle("SceneTopBarBg"); + prefabHeaderBg = new GUIStyle("HeaderButton"); prefabHeaderBg.fixedHeight = 0; prefabHeaderBg.border = new RectOffset(3, 3, 3, 3); } @@ -277,23 +277,25 @@ public void PrefabStageHeaderGUI(Rect rect) m_LastPrefabStageModifiedState = prefabStage.HasSceneBeenModified(); // Background - GUI.Label(rect, GUIContent.none, Styles.prefabHeaderBg); + GUI.Box(rect, GUIContent.none, Styles.prefabHeaderBg); - // Back button - if (Event.current.type == EventType.Repaint) - { - Rect renderRect = new Rect( - rect.x + Styles.leftArrow.margin.left, - rect.y + (rect.height - Styles.leftArrow.fixedHeight) / 2, - Styles.leftArrow.fixedWidth, - Styles.leftArrow.fixedHeight); - Styles.leftArrow.Draw(renderRect, GUIContent.none, false, false, false, false); - } Rect interactionRect = new Rect( rect.x, rect.y, Styles.leftArrow.fixedWidth + Styles.leftArrow.margin.horizontal, - rect.height); + rect.height - 1); /*bottom borer*/ + + // Back button + if (Event.current.type == EventType.Repaint) + { + // Resets the fixed size to stretch the button + float oldW = Styles.leftArrow.fixedWidth, oldH = Styles.leftArrow.fixedHeight; + + Styles.leftArrow.fixedWidth = 0; Styles.leftArrow.fixedHeight = 0; + Styles.leftArrow.Draw(interactionRect, GUIContent.none, interactionRect.Contains(Event.current.mousePosition), false, false, false); + Styles.leftArrow.fixedWidth = oldW; Styles.leftArrow.fixedHeight = oldH; + } + if (GUI.Button(interactionRect, GUIContent.none, GUIStyle.none)) { StageNavigationManager.instance.NavigateBack(StageNavigationManager.Analytics.ChangeType.NavigateBackViaHierarchyHeaderLeftArrow); diff --git a/Editor/Mono/SceneHierarchyWindow.cs b/Editor/Mono/SceneHierarchyWindow.cs index 99c712d73f..1c99bdc5c4 100644 --- a/Editor/Mono/SceneHierarchyWindow.cs +++ b/Editor/Mono/SceneHierarchyWindow.cs @@ -288,13 +288,13 @@ internal abstract class HierarchySorting internal class TransformSorting : HierarchySorting { - readonly GUIContent m_Content = EditorGUIUtility.TrIconContent("DefaultSorting", "Transform Child Order"); + readonly ScalableGUIContent m_Content = new ScalableGUIContent(null, "Transform Child Order", "DefaultSorting"); public override GUIContent content { get { return m_Content; } } } internal class AlphabeticalSorting : HierarchySorting { - readonly GUIContent m_Content = EditorGUIUtility.TrIconContent("AlphabeticalSorting", "Alphabetical Order"); + readonly ScalableGUIContent m_Content = new ScalableGUIContent(null, "Alphabetical Order", "AlphabeticalSorting"); public override GUIContent content { get { return m_Content; } } } } diff --git a/Editor/Mono/SceneManagement/StageManager/Stage.cs b/Editor/Mono/SceneManagement/StageManager/Stage.cs index 0ac2f887fa..b49d72e5ba 100644 --- a/Editor/Mono/SceneManagement/StageManager/Stage.cs +++ b/Editor/Mono/SceneManagement/StageManager/Stage.cs @@ -40,7 +40,7 @@ public T FindComponentOfType() where T : Component for (int i = 0; i < components.Length; i++) { T obj = components[i]; - if (!EditorSceneManager.IsPreviewScene(obj.gameObject.scene)) + if (!EditorUtility.IsPersistent(obj) && !EditorSceneManager.IsPreviewScene(obj.gameObject.scene)) return obj; } } @@ -68,7 +68,7 @@ public T[] FindComponentsOfType() where T : Component for (int i = 0; i < components.Length; i++) { T obj = components[i]; - if (!EditorSceneManager.IsPreviewScene(obj.gameObject.scene)) + if (!EditorUtility.IsPersistent(obj) && !EditorSceneManager.IsPreviewScene(obj.gameObject.scene)) componentList.Add(obj); } } diff --git a/Editor/Mono/SceneModeWindows/LightingWindow.cs b/Editor/Mono/SceneModeWindows/LightingWindow.cs index 5d2475efa6..32da822636 100644 --- a/Editor/Mono/SceneModeWindows/LightingWindow.cs +++ b/Editor/Mono/SceneModeWindows/LightingWindow.cs @@ -99,7 +99,7 @@ void OnSelectionChange() static internal void RepaintSceneAndGameViews() { SceneView.RepaintAll(); - PreviewEditorWindow.RepaintAll(); + PlayModeView.RepaintAll(); } void OnGUI() diff --git a/Editor/Mono/SceneModeWindows/LightingWindowBakeSettings.cs b/Editor/Mono/SceneModeWindows/LightingWindowBakeSettings.cs index baec1cdd39..a6055e5f90 100644 --- a/Editor/Mono/SceneModeWindows/LightingWindowBakeSettings.cs +++ b/Editor/Mono/SceneModeWindows/LightingWindowBakeSettings.cs @@ -265,6 +265,7 @@ void RealtimeLightingGUI() void OnMixedModeSelected(object userData) { m_MixedBakeMode.intValue = (int)userData; + m_LightmapSettingsSO.ApplyModifiedProperties(); } void MixedLightingGUI() @@ -396,16 +397,19 @@ private void ClampFilterType(SerializedProperty filter) void OnDirectDenoiserSelected(object userData) { m_PVRDenoiserTypeDirect.intValue = (int)userData; + m_LightmapSettingsSO.ApplyModifiedProperties(); } void OnIndirectDenoiserSelected(object userData) { m_PVRDenoiserTypeIndirect.intValue = (int)userData; + m_LightmapSettingsSO.ApplyModifiedProperties(); } void OnAODenoiserSelected(object userData) { m_PVRDenoiserTypeAO.intValue = (int)userData; + m_LightmapSettingsSO.ApplyModifiedProperties(); } public enum DenoiserTarget @@ -472,6 +476,7 @@ bool DenoiserSupported(LightmapEditorSettings.DenoiserType denoiserType) void OnBakeBackedSelected(object userData) { m_BakeBackend.intValue = (int)userData; + m_LightmapSettingsSO.ApplyModifiedProperties(); } void BakeBackendGUI() @@ -500,7 +505,9 @@ void BakeBackendGUI() menu.DropDown(rect); } if (EditorGUI.EndChangeCheck()) + { InspectorWindow.RepaintAllInspectors(); // We need to repaint other inspectors that might need to update based on the selected backend. + } EditorGUI.EndProperty(); @@ -796,8 +803,8 @@ public void OnGUI() InitSettings(); } - m_LightmapSettingsSO.UpdateIfRequiredOrScript(); - m_RenderSettingsSO.UpdateIfRequiredOrScript(); + m_LightmapSettingsSO.Update(); + m_RenderSettingsSO.Update(); RealtimeLightingGUI(); MixedLightingGUI(); diff --git a/Editor/Mono/SceneModeWindows/NavigationWindow.cs b/Editor/Mono/SceneModeWindows/NavigationWindow.cs index 732f6c9bbd..6219e2d8a7 100644 --- a/Editor/Mono/SceneModeWindows/NavigationWindow.cs +++ b/Editor/Mono/SceneModeWindows/NavigationWindow.cs @@ -454,7 +454,7 @@ public void OnBecameInvisible() static void RepaintSceneAndGameViews() { SceneView.RepaintAll(); - PreviewEditorWindow.RepaintAll(); + PlayModeView.RepaintAll(); } public void OnSceneViewGUI(SceneView sceneView) @@ -956,7 +956,10 @@ private void AreaSettings() InitAreas(); m_NavMeshProjectSettingsObject.Update(); - m_AreasList.DoLayoutList(); + using (new GUILayout.VerticalScope(EditorStyles.defaultContentMargins)) + { + m_AreasList.DoLayoutList(); + } m_NavMeshProjectSettingsObject.ApplyModifiedProperties(); } @@ -970,7 +973,10 @@ private void AgentSettings() if (m_AgentsList.index < 0) m_AgentsList.index = 0; - m_AgentsList.DoLayoutList(); + using (new GUILayout.VerticalScope(EditorStyles.defaultContentMargins)) + { + m_AgentsList.DoLayoutList(); + } if (m_AgentsList.index >= 0 && m_AgentsList.index < m_Agents.arraySize) { diff --git a/Editor/Mono/SceneModeWindows/OcclusionCullingWindow.cs b/Editor/Mono/SceneModeWindows/OcclusionCullingWindow.cs index 51ed489cc1..54bc187337 100644 --- a/Editor/Mono/SceneModeWindows/OcclusionCullingWindow.cs +++ b/Editor/Mono/SceneModeWindows/OcclusionCullingWindow.cs @@ -19,6 +19,8 @@ internal class OcclusionCullingWindow : EditorWindow static bool s_IsVisible = false; private bool m_PreVis; + private bool m_ClearCacheData = true; + private bool m_ClearBakeData = true; private string m_Warning; static OcclusionCullingWindow ms_OcclusionCullingWindow; @@ -262,15 +264,24 @@ void BakeButtons() GUILayout.FlexibleSpace(); bool allowBaking = !EditorApplication.isPlayingOrWillChangePlaymode; + bool bakeRunning = StaticOcclusionCulling.isRunning; - // Clear Tome button - GUI.enabled = StaticOcclusionCulling.umbraDataSize != 0 && allowBaking; - if (GUILayout.Button("Clear", GUILayout.Width(buttonWidth))) - StaticOcclusionCulling.Clear(); - GUI.enabled = allowBaking; + GUI.enabled = !bakeRunning && allowBaking; + if (CustomDropdownButton("Clear", buttonWidth)) + { + if (m_ClearBakeData) + { + StaticOcclusionCulling.Clear(); + } - // Is occlusion culling running - if (StaticOcclusionCulling.isRunning) + if (m_ClearCacheData) + { + StaticOcclusionCulling.RemoveCacheFolder(); + } + } + + GUI.enabled = allowBaking; + if (bakeRunning) { if (GUILayout.Button("Cancel", GUILayout.Width(buttonWidth))) StaticOcclusionCulling.Cancel(); @@ -284,10 +295,55 @@ void BakeButtons() } GUILayout.EndHorizontal(); - GUI.enabled = true; } + private bool CustomDropdownButton(string name, float width, params GUILayoutOption[] options) + { + var content = EditorGUIUtility.TrTextContent(name); + var rect = GUILayoutUtility.GetRect(content, EditorStyles.dropDownList, options); + + var halfDiff = (width - rect.width) / 2f; + rect.xMin -= halfDiff; + rect.xMax += halfDiff; + var currPos = rect.position; + currPos.x -= halfDiff; + rect.position = currPos; + + var dropDownRect = rect; + const float kDropDownButtonWidth = 20f; + dropDownRect.xMin = dropDownRect.xMax - kDropDownButtonWidth; + + string[] names = { "Bake Data", "Cache Data" }; + bool[] values = { m_ClearBakeData, m_ClearCacheData }; + if (Event.current.type == EventType.MouseDown && dropDownRect.Contains(Event.current.mousePosition)) + { + var menu = new GenericMenu(); + for (int i = 0; i != names.Length; i++) + menu.AddItem(new GUIContent(names[i]), values[i], CustomDropdownCallback, i); + + menu.DropDown(rect); + Event.current.Use(); + + return false; + } + + return GUI.Button(rect, content, EditorStyles.dropDownList); + } + + private void CustomDropdownCallback(object userData) + { + int index = (int)userData; + if (index == 0) + { + m_ClearBakeData = !m_ClearBakeData; + } + else if (index == 1) + { + m_ClearCacheData = !m_ClearCacheData; + } + } + void ModeToggle() { EditorGUILayout.BeginHorizontal(); diff --git a/Editor/Mono/SceneView/SceneView.cs b/Editor/Mono/SceneView/SceneView.cs index 96a09aabac..c4ab7e702a 100644 --- a/Editor/Mono/SceneView/SceneView.cs +++ b/Editor/Mono/SceneView/SceneView.cs @@ -21,6 +21,7 @@ using Object = UnityEngine.Object; using RequiredByNativeCodeAttribute = UnityEngine.Scripting.RequiredByNativeCodeAttribute; using UnityEditor.EditorTools; +using UnityEditor.Profiling; using UnityEditor.Snap; namespace UnityEditor @@ -347,6 +348,7 @@ public bool audioPlay [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] public delegate void OnSceneFunc(SceneView sceneView); + // Marked obsolete 2018-11-28 [Obsolete("onSceneGUIDelegate has been deprecated. Use duringSceneGui instead.")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] public static OnSceneFunc onSceneGUIDelegate; @@ -436,20 +438,12 @@ public SceneViewState sceneViewState } [SerializeField] - SceneViewGrid grid; + SceneViewGrid m_Grid; public bool showGrid { - get { return grid.showGrid; } - set - { - if (grid.showGrid != value) - { - grid.showGrid = value; - if (gridVisibilityChanged != null) - gridVisibilityChanged(grid.showGrid); - } - } + get { return sceneViewGrids.showGrid; } + set { sceneViewGrids.showGrid = value; } } [SerializeField] @@ -493,7 +487,7 @@ public class CameraSettings bool m_AccelerationEnabled; [SerializeField] - float m_FieldOfView; + float m_FieldOfViewHorizontalOrVertical; // either horizontal or vertical depending on aspect ratio [SerializeField] float m_NearClip; [SerializeField] @@ -511,7 +505,7 @@ public CameraSettings() m_SpeedMax = 2f; m_EasingEnabled = true; m_EasingDuration = defaultEasingDuration; - fieldOfView = kPerspectiveFov; + fieldOfView = kDefaultPerspectiveFov; m_DynamicClip = true; m_OcclusionCulling = false; m_NearClip = .03f; @@ -629,8 +623,8 @@ internal void SetSpeedMinMax(float[] floatValues) public float fieldOfView { - get { return m_FieldOfView; } - set { m_FieldOfView = value; } + get { return m_FieldOfViewHorizontalOrVertical; } + set { m_FieldOfViewHorizontalOrVertical = value; } } public float nearClip @@ -669,7 +663,7 @@ public CameraSettings cameraSettings internal SceneViewGrid sceneViewGrids { - get { return grid; } + get { return m_Grid; } } public void ResetCameraSettings() @@ -718,16 +712,39 @@ internal static void AddCursorRect(Rect rect, MouseCursor cursor) s_MouseRects.Add(new CursorRect(rect, cursor)); } + static float GetPerspectiveCameraDistance(float objectSize, float fov) + { + // A + // |\ We want to place camera at a + // | \ distance that, at the given FOV, + // | \ would enclose a sphere of radius + // _..+.._\ "size". Here |BC|=size, and we + // .' | '\ need to find |AB|. ACB is a right + // / | _C angle, andBAC is half the FOV. So + // | | _- | that gives: sin(BAC)=|BC|/|AB|, + // | B | and thus |AB|=|BC|/sin(BAC). + // | | + // \ / + // '._ _.' + // ````` + return objectSize / Mathf.Sin(fov * 0.5f * Mathf.Deg2Rad); + } + public float cameraDistance { get { - float fov = m_Ortho.Fade(kPerspectiveFov, 0); - + float res; if (!camera.orthographic) - return size / Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad); + { + float fov = m_Ortho.Fade(perspectiveFov, 0); + res = GetPerspectiveCameraDistance(size, fov); + } + else + res = size * 2f; - return size * 2f; + // clamp to allowed range in case scene view size was huge + return Mathf.Clamp(res, -k_MaxSceneViewSize, k_MaxSceneViewSize); } } @@ -738,7 +755,7 @@ public float cameraDistance RectSelection m_RectSelection; - const float kPerspectiveFov = 90; + const float kDefaultPerspectiveFov = 60; static ArrayList s_SceneViews = new ArrayList(); public static ArrayList sceneViews { get { return s_SceneViews; } } @@ -770,14 +787,13 @@ internal static class Styles public static GUIContent gizmosContent = EditorGUIUtility.TrTextContent("Gizmos", "Toggle visibility of all Gizmos in the Scene view"); public static GUIContent gizmosDropDownContent = EditorGUIUtility.TrTextContent("", "Toggle the visibility of different Gizmos in the Scene view."); public static GUIContent mode2DContent = EditorGUIUtility.TrIconContent("SceneView2D", "When toggled on, the Scene is in 2D view. When toggled off, the Scene is in 3D view."); - public static GUIContent gridXToolbarContent = EditorGUIUtility.TrIconContent("SceneViewGridPopup_X", "Toggle the visibility of the grid"); - public static GUIContent gridYToolbarContent = EditorGUIUtility.TrIconContent("SceneViewGridPopup_Y", "Toggle the visibility of the grid"); - public static GUIContent gridZToolbarContent = EditorGUIUtility.TrIconContent("SceneViewGridPopup_Z", "Toggle the visibility of the grid"); - public static GUIContent snapMoveValue = EditorGUIUtility.TrIconContent("SnapModeGrid", "Toggle snapping on or off."); + public static GUIContent gridXToolbarContent = EditorGUIUtility.TrIconContent("GridAxisX", "Toggle the visibility of the grid"); + public static GUIContent gridYToolbarContent = EditorGUIUtility.TrIconContent("GridAxisY", "Toggle the visibility of the grid"); + public static GUIContent gridZToolbarContent = EditorGUIUtility.TrIconContent("GridAxisZ", "Toggle the visibility of the grid"); public static GUIContent isolationModeOverlayContent = EditorGUIUtility.TrTextContent("Isolation View", ""); public static GUIContent isolationModeExitButton = EditorGUIUtility.TrTextContent("Exit", "Exit isolation mode"); public static GUIContent renderDocContent; - public static GUIContent sceneVisToolbarButtonContent = EditorGUIUtility.TrIconContent("scenevis_hidden", "Number of hidden objects, click to toggle scene visibility"); + public static GUIContent sceneVisToolbarButtonContent = EditorGUIUtility.TrIconContent("SceneViewVisibility", "Number of hidden objects, click to toggle scene visibility"); public static GUIStyle gizmoButtonStyle; public static GUIContent sceneViewCameraContent = EditorGUIUtility.TrIconContent("SceneViewCamera", "Settings for the Scene view camera."); public static GUIContent exposureIcon = EditorGUIUtility.TrIconContent("Exposure", "Controls the number of stops to over or under expose the precomputed and baked lighting debug views."); @@ -915,13 +931,6 @@ internal override void SetSearchFilter(string searchFilter, SearchMode mode, boo internal void OnLostFocus() { - // don't bleed our scene view rendering into game view - var previewWindow = PreviewEditorWindow.GetMainPreviewWindow(); - if (previewWindow && previewWindow.m_Parent != null && m_Parent != null && previewWindow.m_Parent == m_Parent) - { - previewWindow.m_Parent.backgroundValid = false; - } - if (s_LastActiveSceneView == this) SceneViewMotion.ResetMotion(); } @@ -931,11 +940,12 @@ public override void OnEnable() titleContent = GetLocalizedTitleContent(); m_RectSelection = new RectSelection(this); - if (grid == null) - grid = new SceneViewGrid(); - grid.OnEnable(); - grid.Register(this); - ResetGrid(); + if (m_Grid == null) + m_Grid = new SceneViewGrid(); + + sceneViewGrids.OnEnable(this); + + ResetGridPivot(); if (svRot == null) svRot = new SceneViewRotation(); @@ -947,10 +957,10 @@ public override void OnEnable() m_Position.valueChanged.AddListener(Repaint); m_Size.valueChanged.AddListener(Repaint); m_Ortho.valueChanged.AddListener(Repaint); + sceneViewGrids.gridVisibilityChanged += GridOnGridVisibilityChanged; wantsMouseMove = true; wantsMouseEnterLeaveWindow = true; - dontClearBackground = true; s_SceneViews.Add(this); m_SceneViewOverlay = new SceneViewOverlay(this); @@ -987,6 +997,12 @@ public override void OnEnable() s_ActiveEditorsDirty = true; } + void GridOnGridVisibilityChanged(bool visible) + { + if (gridVisibilityChanged != null) + gridVisibilityChanged(visible); + } + protected virtual bool SupportsStageHandling() { return true; @@ -1070,6 +1086,9 @@ public override void OnDisable() Lightmapping.lightingDataUpdated -= RepaintAll; ActiveEditorTracker.editorTrackerRebuilt -= OnEditorTrackerRebuilt; Selection.selectedObjectWasDestroyed -= OnSelectedObjectWasDestroyed; + sceneViewGrids.gridVisibilityChanged -= GridOnGridVisibilityChanged; + + sceneViewGrids.OnDisable(this); if (m_Camera) DestroyImmediate(m_Camera.gameObject, true); @@ -1133,8 +1152,8 @@ void ToolbarDisplayStateGUI() // render mode popup GUIContent modeContent = EditorGUIUtility.TextContent(cameraMode.name); modeContent.tooltip = L10n.Tr("The Draw Mode used to display the Scene."); - Rect modeRect = GUILayoutUtility.GetRect(modeContent, EditorStyles.toolbarDropDown, GUILayout.Width(120)); - if (EditorGUI.DropdownButton(modeRect, modeContent, FocusType.Passive, EditorStyles.toolbarDropDown)) + Rect modeRect = GUILayoutUtility.GetRect(modeContent, EditorStyles.toolbarDropDownLeft, GUILayout.Width(120)); + if (EditorGUI.DropdownButton(modeRect, modeContent, FocusType.Passive, EditorStyles.toolbarDropDownLeft)) { Rect rect = GUILayoutUtility.topLevel.GetLast(); PopupWindow.Show(rect, new SceneRenderModeWindow(this)); @@ -1169,10 +1188,10 @@ void ToolbarDisplayStateGUI() void ToolbarGridDropdownGUI() { - bool toggled = grid.showGrid; + bool toggled = sceneViewGrids.showGrid; GUIContent gridIcon = GUIContent.none; - switch (grid.gridAxis) + switch (sceneViewGrids.gridAxis) { case SceneViewGrid.GridRenderAxis.X: gridIcon = Styles.gridXToolbarContent; @@ -1196,34 +1215,7 @@ void ToolbarGridDropdownGUI() } if (EditorGUI.EndChangeCheck()) - grid.showGrid = toggled; - } - - void ToolbarSnapSettingsDropdownGUI() - { - bool toggled = EditorSnapSettings.active; - - GUIContent content = Styles.snapMoveValue; - - content.text = GetSnapMoveValueString("#.##"); - - if (EditorGUILayout.DropDownToggle(ref toggled, content, EditorStyles.toolbarDropDownToggle)) - { - Rect rect = GUILayoutUtility.topLevel.GetLast(); - PopupWindow.Show(rect, new SnapSettingsWindow()); - GUIUtility.ExitGUI(); - } - - if (!EditorSnapSettings.hotkeyActive) - EditorSnapSettings.enabled = toggled; - } - - string GetSnapMoveValueString(string format) - { - if (SnapSettingsWindow.IsMoveSnapValueMixed()) - return EditorGUI.mixedValueContent.text; - - return EditorSnapSettings.move.x.ToString(format, CultureInfo.InvariantCulture); + sceneViewGrids.showGrid = toggled; } void ToolbarGizmosDropdownGUI() @@ -1294,7 +1286,6 @@ void DoToolbarGUI() GUILayout.FlexibleSpace(); - ToolbarSnapSettingsDropdownGUI(); ToolbarRenderDocGUI(); ToolbarSceneToolsGUI(); ToolbarSceneCameraGUI(); @@ -1417,8 +1408,7 @@ private void LoadRenderDoc() { if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) { - RenderDoc.Load(); - ShaderUtil.RecreateGfxDevice(); + ShaderUtil.RequestLoadRenderDoc(); } } @@ -1797,7 +1787,7 @@ private void DoDrawCamera(Rect windowSpaceCameraRect, Rect groupSpaceCameraRect, bool oldAsync = ShaderUtil.allowAsyncCompilation; ShaderUtil.allowAsyncCompilation = EditorSettings.asyncShaderCompilation; - DrawGridParameters gridParam = grid.PrepareGridRender(camera, pivot, m_Rotation.target, size, m_Ortho.target); + DrawGridParameters gridParam = sceneViewGrids.PrepareGridRender(camera, pivot, m_Rotation.target, size, m_Ortho.target); Event evt = Event.current; if (UseSceneFiltering()) @@ -1811,9 +1801,7 @@ private void DoDrawCamera(Rect windowSpaceCameraRect, Rect groupSpaceCameraRect, GUI.BeginGroup(windowSpaceCameraRect); if (evt.type == EventType.Repaint) - { Graphics.DrawTexture(groupSpaceCameraRect, m_SceneTargetTexture, new Rect(0, 0, 1, 1), 0, 0, 0, 0, GUI.color, GUI.blitMaterial); - } Handles.SetCamera(groupSpaceCameraRect, m_Camera); } else @@ -2364,9 +2352,10 @@ protected virtual void OnGUI() { s_MouseRects.Clear(); Tools.InvalidateHandlePosition(); // Some cases that should invalidate the cached position are not handled correctly yet so we refresh it once per frame - Profiler.BeginSample("SceneView.Repaint"); } + sceneViewGrids.UpdateGridColor(); + Color origColor = GUI.color; Rect origCameraRect = m_Camera.rect; Rect windowSpaceCameraRect = cameraRect; @@ -2545,11 +2534,6 @@ protected virtual void OnGUI() HandleMouseCursor(); - if (evt.type == EventType.Repaint) - { - Profiler.EndSample(); - } - s_CurrentDrawingSceneView = null; m_Camera.rect = origCameraRect; } @@ -2848,10 +2832,10 @@ public void FixNegativeSize() if (size < 0) { - float distance = size / Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad); + float distance = GetPerspectiveCameraDistance(size, fov); Vector3 p = m_Position.value + rotation * new Vector3(0, 0, -distance); size = -size; - distance = size / Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad); + distance = GetPerspectiveCameraDistance(size, fov); m_Position.value = p + rotation * new Vector3(0, 0, distance); } } @@ -2859,11 +2843,10 @@ public void FixNegativeSize() float CalcCameraDist() { float fov = m_Ortho.Fade(perspectiveFov, 0); - if (fov > kOrthoThresholdAngle) { m_Camera.orthographic = false; - return size / Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad); + return GetPerspectiveCameraDistance(size, fov); } return 0; } @@ -2979,20 +2962,11 @@ void SetupCamera() if (fov > kOrthoThresholdAngle) { m_Camera.orthographic = false; - - // Old calculations were strange and were more zoomed in for tall aspect ratios than for wide ones. - //m_Camera.fieldOfView = Mathf.Sqrt((fov * fov) / (1 + aspect)); - // 1:1: Sqrt((90*90) / (1+1)) = 63.63 degrees = atan(0.6204) - means we have 0.6204 x 0.6204 in tangents - // 2:1: Sqrt((90*90) / (1+2)) = 51.96 degrees = atan(0.4873) - means we have 0.9746 x 0.4873 in tangents - // 1:2: Sqrt((90*90) / (1+0.5)) = 73.48 degrees = atan(0.7465) - means we have 0.3732 x 0.7465 in tangents - 25% more zoomed in! - m_Camera.fieldOfView = GetVerticalFOV(fov); } else { m_Camera.orthographic = true; - - //m_Camera.orthographicSize = Mathf.Sqrt((size * size) / (1 + aspect)); m_Camera.orthographicSize = GetVerticalOrthoSize(); } @@ -3096,13 +3070,26 @@ void UpdateAnimatedMaterials() internal float GetVerticalFOV(float aspectNeutralFOV) { - float verticalHalfFovTangent = Mathf.Tan(aspectNeutralFOV * 0.5f * Mathf.Deg2Rad) * kOneOverSqrt2 / Mathf.Sqrt(m_Camera.aspect); - return Mathf.Atan(verticalHalfFovTangent) * 2 * Mathf.Rad2Deg; + // We want Scene view camera "FOV" to be the vertical FOV if the + // Scene view is wider than tall, and the horizontal FOV otherwise. + float multiplier = 1.0f; + if (m_Camera.aspect < 1) + multiplier /= m_Camera.aspect; + float halfFovRad = aspectNeutralFOV * 0.5f * Mathf.Deg2Rad; + float halfFovTan = Mathf.Tan(halfFovRad) * multiplier; + return Mathf.Atan(halfFovTan) * 2 * Mathf.Rad2Deg; } internal float GetVerticalOrthoSize() { - return size * kOneOverSqrt2 / Mathf.Sqrt(m_Camera.aspect); + // We want scene view ortho size to enclose sphere of + // radius "size". If scene view is more tall than wide, + // we want to take that into account so that the bounds + // fit in horizontally. + float res = size; + if (m_Camera.aspect < 1.0) + res /= m_Camera.aspect; + return res; } // Look at a specific point. @@ -3468,7 +3455,7 @@ public virtual bool FrameSelected(bool lockView, bool instant) public bool Frame(Bounds bounds, bool instant = true) { - float newSize = bounds.extents.magnitude * 1.5f; + float newSize = bounds.extents.magnitude; if (float.IsInfinity(newSize)) return false; @@ -3477,7 +3464,7 @@ public bool Frame(Bounds bounds, bool instant = true) newSize = 10; // We snap instantly into target on playmode, because things might be moving fast and lerping lags behind - LookAt(bounds.center, m_Rotation.target, newSize * 2.2f, m_Ortho.value, instant); + LookAt(bounds.center, m_Rotation.target, newSize, m_Ortho.value, instant); return true; } @@ -3523,30 +3510,38 @@ void CallOnSceneGUI() { foreach (Editor editor in activeEditors) { - if (!EditorGUIUtility.IsGizmosAllowedForObject(editor.target)) + if (!drawGizmos || !EditorGUIUtility.IsGizmosAllowedForObject(editor.target)) continue; - MethodInfo method = editor.GetType().GetMethod("OnSceneGUI", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy); + MethodInfo method = editor.GetType().GetMethod( + "OnSceneGUI", + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy, + null, + Type.EmptyTypes, + null); if (method != null) { - Editor.m_AllowMultiObjectAccess = true; - for (int n = 0; n < editor.targets.Length; n++) + using (new EditorPerformanceTracker($"Editor.{editor.GetType().Name}.OnSceneGUI")) { - ResetOnSceneGUIState(); - editor.referenceTargetIndex = n; - - EditorGUI.BeginChangeCheck(); - // Ironically, only allow multi object access inside OnSceneGUI if editor does NOT support multi-object editing. - // since there's no harm in going through the serializedObject there if there's always only one target. - Editor.m_AllowMultiObjectAccess = !editor.canEditMultipleObjects; - method.Invoke(editor, null); Editor.m_AllowMultiObjectAccess = true; - if (EditorGUI.EndChangeCheck()) - editor.serializedObject.SetIsDifferentCacheDirty(); - } + for (int n = 0; n < editor.targets.Length; n++) + { + ResetOnSceneGUIState(); + editor.referenceTargetIndex = n; + + EditorGUI.BeginChangeCheck(); + // Ironically, only allow multi object access inside OnSceneGUI if editor does NOT support multi-object editing. + // since there's no harm in going through the serializedObject there if there's always only one target. + Editor.m_AllowMultiObjectAccess = !editor.canEditMultipleObjects; + method.Invoke(editor, null); + Editor.m_AllowMultiObjectAccess = true; + if (EditorGUI.EndChangeCheck()) + editor.serializedObject.SetIsDifferentCacheDirty(); + } - ResetOnSceneGUIState(); + ResetOnSceneGUIState(); + } } } @@ -3655,9 +3650,9 @@ static void ShowCompileErrorNotification() internal static void ShowSceneViewPlayModeSaveWarning() { // In this case, we want to explicitly try the GameView before passing it on to whatever notificationView we have - var preview = (PreviewEditorWindow)WindowLayout.FindEditorWindowOfType(typeof(PreviewEditorWindow)); - if (preview != null && preview.hasFocus) - preview.ShowNotification(EditorGUIUtility.TrTextContent("You must exit play mode to save the scene!")); + var playModeView = (PlayModeView)WindowLayout.FindEditorWindowOfType(typeof(PlayModeView)); + if (playModeView != null && playModeView.hasFocus) + playModeView.ShowNotification(EditorGUIUtility.TrTextContent("You must exit play mode to save the scene!")); else ShowNotification("You must exit play mode to save the scene!"); } @@ -3758,9 +3753,9 @@ public static CameraMode GetBuiltinCameraMode(DrawCameraMode mode) return SceneRenderModeWindow.GetBuiltinCameraMode(mode); } - internal void ResetGrid() + internal void ResetGridPivot() { - grid.SetAllGridsPivot(Vector3.zero); + sceneViewGrids.SetAllGridsPivot(Vector3.zero); } } } // namespace diff --git a/Editor/Mono/SceneView/SceneViewGrid.cs b/Editor/Mono/SceneView/SceneViewGrid.cs index 869a272209..2a5f681682 100644 --- a/Editor/Mono/SceneView/SceneViewGrid.cs +++ b/Editor/Mono/SceneView/SceneViewGrid.cs @@ -2,15 +2,87 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; using UnityEditor.AnimatedValues; using UnityEngine; -using UnityEditor.Snap; namespace UnityEditor { + static class GridSettings + { + const float k_GridSizeMin = .0001f; + const float k_GridSizeMax = 1024f; + const float k_DefaultGridSize = 1.0f; + const int k_DefaultMultiplier = 0; + + static SavedFloat s_GridSizeX = new SavedFloat("GridSizeX", k_DefaultGridSize); + static SavedFloat s_GridSizeY = new SavedFloat("GridSizeY", k_DefaultGridSize); + static SavedFloat s_GridSizeZ = new SavedFloat("GridSizeZ", k_DefaultGridSize); + static SavedInt s_GridMultiplier = new SavedInt("GridMultiplier", k_DefaultMultiplier); + + static Vector3 rawSize + { + get { return new Vector3(s_GridSizeX, s_GridSizeY, s_GridSizeZ); } + } + + internal static event Action sizeChanged = delegate {}; + + public static Vector3 size + { + get { return ApplyMultiplier(rawSize, s_GridMultiplier); } + set + { + if (size == value) + return; + ResetSizeMultiplier(); + s_GridSizeX.value = Mathf.Min(k_GridSizeMax, Mathf.Max(k_GridSizeMin, value.x)); + s_GridSizeY.value = Mathf.Min(k_GridSizeMax, Mathf.Max(k_GridSizeMin, value.y)); + s_GridSizeZ.value = Mathf.Min(k_GridSizeMax, Mathf.Max(k_GridSizeMin, value.z)); + sizeChanged(size); + } + } + + internal static int sizeMultiplier + { + get { return s_GridMultiplier.value; } + set { s_GridMultiplier.value = value; } + } + + internal static void ResetSizeMultiplier() + { + s_GridMultiplier.value = k_DefaultMultiplier; + } + + static Vector3 ApplyMultiplier(Vector3 value, int mul) + { + if (mul > 0) + { + for (int i = 0; i < mul; i++) + value *= 2f; + } + else if (mul < 0) + { + for (int i = 0; i > mul; i--) + value /= 2f; + } + return value; + } + + public static void ResetGridSettings() + { + size = new Vector3(k_DefaultGridSize, k_DefaultGridSize, k_DefaultGridSize); + } + } + [System.Serializable] - internal class SceneViewGrid + class SceneViewGrid { + const float k_DefaultGridOpacity = .5f; + const GridRenderAxis k_DefaultRenderAxis = GridRenderAxis.Y; + const bool k_DefaultShowGrid = true; + + internal event Action gridVisibilityChanged = delegate(bool b) {}; + internal enum GridRenderAxis { X, @@ -84,18 +156,24 @@ internal DrawGridParameters PrepareGridRender(int gridID, float opacity) Grid zGrid = new Grid(); [SerializeField] - bool m_ShowGrid = true; + bool m_ShowGrid = k_DefaultShowGrid; [SerializeField] - GridRenderAxis m_GridAxis = GridRenderAxis.Y; + GridRenderAxis m_GridAxis = k_DefaultRenderAxis; [SerializeField] - float m_gridOpacity = 1.0f; + float m_gridOpacity = k_DefaultGridOpacity; internal bool showGrid { get { return m_ShowGrid; } - set { m_ShowGrid = value; } + set + { + if (value == m_ShowGrid) + return; + m_ShowGrid = value; + gridVisibilityChanged(m_ShowGrid); + } } internal float gridOpacity @@ -124,17 +202,37 @@ internal Grid activeGrid } } - internal void OnEnable() + internal void UpdateGridColor() { xGrid.color = yGrid.color = zGrid.color = kViewGridColor; } - internal void Register(SceneView source) + internal void OnEnable(SceneView view) { + UpdateGridColor(); + + GridSettings.sizeChanged += GridSizeChanged; + // hook up the anims, so repainting can work correctly - xGrid.fade.valueChanged.AddListener(source.Repaint); - yGrid.fade.valueChanged.AddListener(source.Repaint); - zGrid.fade.valueChanged.AddListener(source.Repaint); + xGrid.fade.valueChanged.AddListener(view.Repaint); + yGrid.fade.valueChanged.AddListener(view.Repaint); + zGrid.fade.valueChanged.AddListener(view.Repaint); + } + + internal void OnDisable(SceneView view) + { + GridSettings.sizeChanged -= GridSizeChanged; + + xGrid.fade.valueChanged.RemoveListener(view.Repaint); + yGrid.fade.valueChanged.RemoveListener(view.Repaint); + zGrid.fade.valueChanged.RemoveListener(view.Repaint); + } + + void GridSizeChanged(Vector3 size) + { + SetPivot(GridRenderAxis.X, Snapping.Snap(GetPivot(GridRenderAxis.X), GridSettings.size)); + SetPivot(GridRenderAxis.Y, Snapping.Snap(GetPivot(GridRenderAxis.Y), GridSettings.size)); + SetPivot(GridRenderAxis.Z, Snapping.Snap(GetPivot(GridRenderAxis.Z), GridSettings.size)); } internal void SetAllGridsPivot(Vector3 pivot) @@ -165,38 +263,53 @@ internal Vector3 GetPivot(GridRenderAxis axis) return Vector3.zero; } + internal void ResetPivot(GridRenderAxis axis) + { + if (axis == GridRenderAxis.X) + xGrid.pivot = Vector3.zero; + else if (axis == GridRenderAxis.Y) + yGrid.pivot = Vector3.zero; + else if (axis == GridRenderAxis.Z) + zGrid.pivot = Vector3.zero; + else if (axis == GridRenderAxis.All) + xGrid.pivot = yGrid.pivot = zGrid.pivot = Vector3.zero; + } + internal void UpdateGridsVisibility(Quaternion rotation, bool orthoMode) { - bool _xGrid = false, _yGrid = false, _zGrid = false; + bool showX = false, showY = false, showZ = false; if (showGrid) { if (orthoMode) { Vector3 fwd = rotation * Vector3.forward; - // Show horizontal grid as long as angle is not too small - if (Mathf.Abs(fwd.y) > 0.2f) - _yGrid = true; - // Show xy and zy planes only when straight on + + // Show xy, zy and xz planes only when straight on + if (fwd == Vector3.up || fwd == Vector3.down) + showY = true; else if (fwd == Vector3.left || fwd == Vector3.right) - _xGrid = true; + showX = true; else if (fwd == Vector3.forward || fwd == Vector3.back) - _zGrid = true; + showZ = true; } - else + + // Main path for perspective mode. + // In ortho, fallback on this path if camera is not aligned with X, Y or Z axis. + if (!showX && !showY && !showZ) { - _xGrid = (gridAxis == GridRenderAxis.X || gridAxis == GridRenderAxis.All); - _yGrid = (gridAxis == GridRenderAxis.Y || gridAxis == GridRenderAxis.All); - _zGrid = (gridAxis == GridRenderAxis.Z || gridAxis == GridRenderAxis.All); + showX = (gridAxis == GridRenderAxis.X || gridAxis == GridRenderAxis.All); + showY = (gridAxis == GridRenderAxis.Y || gridAxis == GridRenderAxis.All); + showZ = (gridAxis == GridRenderAxis.Z || gridAxis == GridRenderAxis.All); } } - xGrid.fade.target = _xGrid; - yGrid.fade.target = _yGrid; - zGrid.fade.target = _zGrid; + xGrid.fade.target = showX; + yGrid.fade.target = showY; + zGrid.fade.target = showZ; } - internal void ApplySnapConstraintsInPerspectiveMode() + void ApplySnapConstraintsInPerspectiveMode() { switch (gridAxis) { @@ -212,7 +325,7 @@ internal void ApplySnapConstraintsInPerspectiveMode() } } - internal void ApplySnapConstraintsInOrthogonalMode() + void ApplySnapConstraintsInOrthogonalMode() { if (xGrid.fade.target) ApplySnapContraintsOnXAxis(); @@ -224,17 +337,20 @@ internal void ApplySnapConstraintsInOrthogonalMode() void ApplySnapContraintsOnXAxis() { - xGrid.size = new Vector2(EditorSnapSettings.move.y, EditorSnapSettings.move.z); + Vector3 grid = GridSettings.size; + xGrid.size = new Vector2(grid.y, grid.z); } void ApplySnapContraintsOnYAxis() { - yGrid.size = new Vector2(EditorSnapSettings.move.z, EditorSnapSettings.move.x); + Vector3 grid = GridSettings.size; + yGrid.size = new Vector2(grid.z, grid.x); } void ApplySnapContraintsOnZAxis() { - zGrid.size = new Vector2(EditorSnapSettings.move.x, EditorSnapSettings.move.y); + Vector3 grid = GridSettings.size; + zGrid.size = new Vector2(grid.x, grid.y); } internal DrawGridParameters PrepareGridRender(Camera camera, Vector3 pivot, Quaternion rotation, @@ -252,7 +368,7 @@ internal DrawGridParameters PrepareGridRender(Camera camera, Vector3 pivot, Quat return PrepareGridRenderPerspectiveMode(camera, pivot, rotation, size); } - internal DrawGridParameters PrepareGridRenderPerspectiveMode(Camera camera, Vector3 pivot, Quaternion rotation, + DrawGridParameters PrepareGridRenderPerspectiveMode(Camera camera, Vector3 pivot, Quaternion rotation, float size) { DrawGridParameters parameters = default(DrawGridParameters); @@ -273,7 +389,7 @@ internal DrawGridParameters PrepareGridRenderPerspectiveMode(Camera camera, Vect return parameters; } - internal DrawGridParameters PrepareGridRenderOrthogonalMode(Camera camera, Vector3 pivot, Quaternion rotation, + DrawGridParameters PrepareGridRenderOrthogonalMode(Camera camera, Vector3 pivot, Quaternion rotation, float size) { Vector3 direction = camera.transform.TransformDirection(new Vector3(0, 0, 1)); @@ -285,14 +401,21 @@ internal DrawGridParameters PrepareGridRenderOrthogonalMode(Camera camera, Vecto // but if it's orbited rapidly, it can end up at this angle faster than the fading has kicked in. // For these cases hiding it abruptly looks better. // The popping isn't noticable because the user is orbiting rapidly to begin with. - if (Mathf.Abs(direction.x) >= k_AngleThresholdForOrthographicGrid) + if (xGrid.fade.target && Mathf.Abs(direction.x) >= k_AngleThresholdForOrthographicGrid) parameters = xGrid.PrepareGridRender(0, gridOpacity); - else if (Mathf.Abs(direction.y) >= k_AngleThresholdForOrthographicGrid) + else if (yGrid.fade.target && Mathf.Abs(direction.y) >= k_AngleThresholdForOrthographicGrid) parameters = yGrid.PrepareGridRender(1, gridOpacity); - else if (Mathf.Abs(direction.z) >= k_AngleThresholdForOrthographicGrid) + else if (zGrid.fade.target && Mathf.Abs(direction.z) >= k_AngleThresholdForOrthographicGrid) parameters = zGrid.PrepareGridRender(2, gridOpacity); return parameters; } + + internal void Reset() + { + gridOpacity = k_DefaultGridOpacity; + showGrid = k_DefaultShowGrid; + gridAxis = k_DefaultRenderAxis; + } } } // namespace diff --git a/Editor/Mono/SceneVisibilityHierarchyGUI.cs b/Editor/Mono/SceneVisibilityHierarchyGUI.cs index 30b3a316b6..7fb7cc9d08 100644 --- a/Editor/Mono/SceneVisibilityHierarchyGUI.cs +++ b/Editor/Mono/SceneVisibilityHierarchyGUI.cs @@ -101,6 +101,9 @@ public static void DrawBackground(Rect rect) public static void DoItemGUI(Rect rect, GameObjectTreeViewItem goItem, bool isSelected, bool isHovered, bool isFocused, bool isDragging) { + if (Event.current.isKey || Event.current.type == EventType.Layout) + return; + Rect iconRect = rect; iconRect.xMin += k_VisibilityIconPadding; iconRect.width = k_IconWidth; @@ -147,25 +150,25 @@ public static void DoItemGUI(Rect rect, GameObjectTreeViewItem goItem, bool isSe private static void DrawItemBackground(Rect rect, bool isScene, bool isSelected, bool isHovered, bool isFocused) { - if (Event.current.type == EventType.Repaint) - { - rect.width = utilityBarWidth; + if (Event.current.type != EventType.Repaint) + return; - if (isScene) + rect.width = utilityBarWidth; + + if (isScene) + { + if (isSelected) { - if (isSelected) - { - TreeViewGUI.Styles.selectionStyle.Draw(rect, false, false, true, isFocused); - } + TreeViewGUI.Styles.selectionStyle.Draw(rect, false, false, true, isFocused); } - else + } + else + { + using (new GUI.BackgroundColorScope(Styles.GetItemBackgroundColor(isHovered, isSelected, isFocused)) + ) { - using (new GUI.BackgroundColorScope(Styles.GetItemBackgroundColor(isHovered, isSelected, isFocused)) - ) - { - GUI.Label(rect, GUIContent.none, - GameObjectTreeViewGUI.GameObjectStyles.hoveredItemBackgroundStyle); - } + GUI.Label(rect, GUIContent.none, + GameObjectTreeViewGUI.GameObjectStyles.hoveredItemBackgroundStyle); } } } diff --git a/Editor/Mono/SceneVisibilityManager.cs b/Editor/Mono/SceneVisibilityManager.cs index c87c74f016..324af609c1 100644 --- a/Editor/Mono/SceneVisibilityManager.cs +++ b/Editor/Mono/SceneVisibilityManager.cs @@ -268,6 +268,9 @@ private void Show(Scene scene, bool sendContentChangedEvent) if (!scene.IsValid()) return; + if (!scene.isLoaded) + return; + SceneVisibilityState.ShowScene(scene); if (sendContentChangedEvent) @@ -281,6 +284,9 @@ private void EnablePickingNoUndo(Scene scene) if (!scene.IsValid()) return; + if (!scene.isLoaded) + return; + SceneVisibilityState.EnablePicking(scene); } @@ -289,6 +295,9 @@ public void Show(Scene scene) if (!scene.IsValid()) return; + if (!scene.isLoaded) + return; + Undo.RecordObject(SceneVisibilityState.GetInstance(), "Show Scene"); Show(scene, true); } @@ -298,6 +307,9 @@ public void EnablePicking(Scene scene) if (!scene.IsValid()) return; + if (!scene.isLoaded) + return; + Undo.RecordObject(SceneVisibilityState.GetInstance(), "Enable Picking Scene"); EnablePickingNoUndo(scene); } @@ -307,6 +319,9 @@ private void HideNoUndo(Scene scene) if (!scene.IsValid()) return; + if (!scene.isLoaded) + return; + SceneVisibilityState.ShowScene(scene); SceneVisibilityState.SetGameObjectsHidden(scene.GetRootGameObjects(), true, true); } @@ -316,6 +331,9 @@ internal void DisablePickingNoUndo(Scene scene) if (!scene.IsValid()) return; + if (!scene.isLoaded) + return; + SceneVisibilityState.EnablePicking(scene); SceneVisibilityState.SetGameObjectsPickingDisabled(scene.GetRootGameObjects(), true, true); } @@ -325,6 +343,9 @@ public void Hide(Scene scene) if (!scene.IsValid()) return; + if (!scene.isLoaded) + return; + Undo.RecordObject(SceneVisibilityState.GetInstance(), "Hide Scene"); HideNoUndo(scene); } @@ -334,6 +355,9 @@ public void DisablePicking(Scene scene) if (!scene.IsValid()) return; + if (!scene.isLoaded) + return; + Undo.RecordObject(SceneVisibilityState.GetInstance(), "Disable Picking Scene"); DisablePickingNoUndo(scene); } @@ -356,7 +380,7 @@ public bool IsPickingDisabled(GameObject gameObject, bool includeDescendants = f static bool IsIgnoredBySceneVisibility(GameObject go) { - var hideFlags = HideFlags.HideInHierarchy | HideFlags.DontSaveInBuild | HideFlags.DontSaveInEditor; + var hideFlags = HideFlags.HideInHierarchy; return (go.hideFlags & hideFlags) != 0; } diff --git a/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs b/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs index d15df0fdbc..aa7e9a5198 100644 --- a/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs +++ b/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs @@ -22,14 +22,14 @@ internal sealed class HeaderDrawer : DecoratorDrawer { public override void OnGUI(Rect position) { - position.y += 8; + position.yMin += EditorGUIUtility.singleLineHeight * 0.5f; position = EditorGUI.IndentedRect(position); GUI.Label(position, (attribute as HeaderAttribute).header, EditorStyles.boldLabel); } public override float GetHeight() { - return 24; + return EditorGUIUtility.singleLineHeight * 1.5f; } } } diff --git a/Editor/Mono/ScriptAttributeGUI/Implementations/PropertyDrawers.cs b/Editor/Mono/ScriptAttributeGUI/Implementations/PropertyDrawers.cs index 7da11f8c38..c2a8878d28 100644 --- a/Editor/Mono/ScriptAttributeGUI/Implementations/PropertyDrawers.cs +++ b/Editor/Mono/ScriptAttributeGUI/Implementations/PropertyDrawers.cs @@ -23,6 +23,57 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten } } + [CustomPropertyDrawer(typeof(MinAttribute))] + internal sealed class MinDrawer : PropertyDrawer + { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + EditorGUI.BeginChangeCheck(); + EditorGUI.DefaultPropertyField(position, property, label); + if (EditorGUI.EndChangeCheck()) + { + MinAttribute minAttribute = (MinAttribute)attribute; + if (property.propertyType == SerializedPropertyType.Float) + { + property.floatValue = Mathf.Max(minAttribute.min, property.floatValue); + } + else if (property.propertyType == SerializedPropertyType.Integer) + { + property.intValue = Mathf.Max((int)minAttribute.min, property.intValue); + } + else if (property.propertyType == SerializedPropertyType.Vector2) + { + var value = property.vector2Value; + property.vector2Value = new Vector2(Mathf.Max(minAttribute.min, value.x), Mathf.Max(minAttribute.min, value.y)); + } + else if (property.propertyType == SerializedPropertyType.Vector2Int) + { + var value = property.vector2IntValue; + property.vector2IntValue = new Vector2Int(Mathf.Max((int)minAttribute.min, value.x), Mathf.Max((int)minAttribute.min, value.y)); + } + else if (property.propertyType == SerializedPropertyType.Vector3) + { + var value = property.vector3Value; + property.vector3Value = new Vector3(Mathf.Max(minAttribute.min, value.x), Mathf.Max(minAttribute.min, value.y), Mathf.Max(minAttribute.min, value.z)); + } + else if (property.propertyType == SerializedPropertyType.Vector3Int) + { + var value = property.vector3IntValue; + property.vector3IntValue = new Vector3Int(Mathf.Max((int)minAttribute.min, value.x), Mathf.Max((int)minAttribute.min, value.y), Mathf.Max((int)minAttribute.min, value.z)); + } + else if (property.propertyType == SerializedPropertyType.Vector4) + { + var value = property.vector4Value; + property.vector4Value = new Vector4(Mathf.Max(minAttribute.min, value.x), Mathf.Max(minAttribute.min, value.y), Mathf.Max(minAttribute.min, value.z), Mathf.Max(minAttribute.min, value.w)); + } + else + { + EditorGUI.LabelField(position, label.text, "Use Min with float, int or Vector."); + } + } + } + } + [CustomPropertyDrawer(typeof(MultilineAttribute))] internal sealed class MultilineDrawer : PropertyDrawer { diff --git a/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs b/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs index 4ccb6a8b45..cce7ce545c 100644 --- a/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs +++ b/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs @@ -43,7 +43,7 @@ public bool empty } } - public void HandleAttribute(PropertyAttribute attribute, FieldInfo field, Type propertyType) + public void HandleAttribute(SerializedProperty property, PropertyAttribute attribute, FieldInfo field, Type propertyType) { if (attribute is TooltipAttribute) { @@ -63,12 +63,12 @@ public void HandleAttribute(PropertyAttribute attribute, FieldInfo field, Type p } // Look for its drawer type of this attribute - HandleDrawnType(attribute.GetType(), propertyType, field, attribute); + HandleDrawnType(property, attribute.GetType(), propertyType, field, attribute); } - public void HandleDrawnType(Type drawnType, Type propertyType, FieldInfo field, PropertyAttribute attribute) + public void HandleDrawnType(SerializedProperty property, Type drawnType, Type propertyType, FieldInfo field, PropertyAttribute attribute) { - Type drawerType = ScriptAttributeUtility.GetDrawerTypeForType(drawnType); + Type drawerType = ScriptAttributeUtility.GetDrawerTypeForPropertyAndType(property, drawnType); // If we found a drawer type, instantiate the drawer, cache it, and return it. if (drawerType != null) diff --git a/Editor/Mono/ScriptAttributeGUI/ScriptAttributeUtility.cs b/Editor/Mono/ScriptAttributeGUI/ScriptAttributeUtility.cs index 95448db5da..7192e71825 100644 --- a/Editor/Mono/ScriptAttributeGUI/ScriptAttributeUtility.cs +++ b/Editor/Mono/ScriptAttributeGUI/ScriptAttributeUtility.cs @@ -23,6 +23,7 @@ private struct DrawerKeySet internal static Stack s_DrawerStack = new Stack(); private static Dictionary s_DrawerTypeForType = null; private static Dictionary> s_BuiltinAttributes = null; + static Dictionary> s_AutoLoadProperties; private static PropertyHandler s_SharedNullHandler = new PropertyHandler(); private static PropertyHandler s_NextHandler = new PropertyHandler(); @@ -116,6 +117,15 @@ private static void BuildDrawerTypeForTypeDictionary() } } + /// + /// Builds the drawer cache and checks for the drawer cache for a statically + /// defined drawer for a given type. + /// NOTE: The world 'statically' in this context means that what is being + /// looked up is only what is in the cache, which might not play well with + /// Managed References types (where the dynamic type matters). + /// + /// + /// internal static Type GetDrawerTypeForType(Type type) { if (s_DrawerTypeForType == null) @@ -126,6 +136,7 @@ internal static Type GetDrawerTypeForType(Type type) if (drawerType.drawer != null) return drawerType.drawer; + // // now check for base generic versions of the drawers... if (type.IsGenericType) s_DrawerTypeForType.TryGetValue(type.GetGenericTypeDefinition(), out drawerType); @@ -133,6 +144,46 @@ internal static Type GetDrawerTypeForType(Type type) return drawerType.drawer; } + /// + /// Does the same thing as 'GetDrawerTypeForType' (with the same side effect of building the cache) + /// but also plays well with Managed References. If the property that is used as a reference for the drawer + /// query is of a managed reference type, the class parents are also looked up as fallbacks. + /// + /// + /// + /// The custom property drawer type or 'null' if not found. + internal static Type GetDrawerTypeForPropertyAndType(SerializedProperty property, Type type) + { + // As a side effect this also builds the drawer cache dict + var staticDrawerType = GetDrawerTypeForType(type); + if (staticDrawerType != null) + return staticDrawerType; + + // Special case for managed references. + // The custom property drawers for those are defined with 'useForChildren=false' + // (otherwise the dynamic type is not taking into account in the custom property + // drawer resolution) so even if 's_DrawerTypeForType' is built (based on static types) + // we have to check base types for custom property drawers manually. + // Managed references with no drawers should properly try to fallback + if (property.propertyType == SerializedPropertyType.ManagedReference) + { + DrawerKeySet drawerTypes; + + FieldInfo foundField = null; + for (Type currentType = type; foundField == null && currentType != null; currentType = currentType.BaseType) + { + s_DrawerTypeForType.TryGetValue(currentType, out drawerTypes); + if (drawerTypes.drawer != null) + { + s_DrawerTypeForType.Add(type, drawerTypes); + return drawerTypes.drawer; + } + } + } + + return null; + } + private static List GetFieldAttributes(FieldInfo field) { if (field == null) @@ -145,7 +196,14 @@ private static List GetFieldAttributes(FieldInfo field) return null; } - internal static FieldInfo GetFieldInfoFromProperty(SerializedProperty property, out Type type) + /// + /// Returns the field info and field type for the property. The types are based on the + /// static field definition. + /// + /// + /// + /// + internal static FieldInfo GetFieldInfoAndStaticTypeFromProperty(SerializedProperty property, out Type type) { var classType = GetScriptTypeFromProperty(property); if (classType == null) @@ -153,7 +211,88 @@ internal static FieldInfo GetFieldInfoFromProperty(SerializedProperty property, type = null; return null; } - return GetFieldInfoFromPropertyPath(classType, property.propertyPath, out type); + + var fieldPath = property.propertyPath; + if (property.isReferencingAManagedReferenceField) + { + // When the field we are trying to access is a dynamic instance, things are a bit more tricky + // since we cannot "statically" (looking only at the parent class field types) know the actual + // "classType" of the parent class. + + // The issue also is that at this point our only view on the object is the very limited SerializedProperty. + + // So we have to: + // 1. try to get the FQN from for the current managed type from the serialized data, + // 2. get the path *in the current managed instance* of the field we are pointing to, + // 3. foward that to 'GetFieldInfoFromPropertyPath' as if it was a regular field, + + var objectTypename = property.GetFullyQualifiedTypenameForCurrentTypeTreeInternal(); + GetTypeFromManagedReferenceFullTypeName(objectTypename, out classType); + + fieldPath = property.GetPropertyPathInCurrentManagedTypeTreeInternal(); + } + + if (classType == null) + { + type = null; + return null; + } + + return GetFieldInfoFromPropertyPath(classType, fieldPath, out type); + } + + /// + /// Returns the field info and type for the property. Contrary to GetFieldInfoAndStaticTypeFromProperty, + /// when confronted with a managed reference the dynamic instance type is returned. + /// + /// + /// + /// + internal static FieldInfo GetFieldInfoFromProperty(SerializedProperty property, out Type type) + { + var fieldInfo = GetFieldInfoAndStaticTypeFromProperty(property, out type); + if (fieldInfo == null) + return fieldInfo; + + // Managed references are a special case, we need to override the static type + // returned by 'GetFieldInfoFromPropertyPath' for custom property handler matching + // by the dynamic type of the instance. + if (property.propertyType == SerializedPropertyType.ManagedReference) + { + Type managedReferenceInstanceType; + + // Try to get a Type instance for the managed reference + if (GetTypeFromManagedReferenceFullTypeName(property.managedReferenceFullTypename, out managedReferenceInstanceType)) + { + type = managedReferenceInstanceType; + } + + // We keep the fallback to the field type returned by 'GetFieldInfoFromPropertyPath'. + } + + return fieldInfo; + } + + /// + /// Create a Type instance from the managed reference full type name description. + /// The expected format for the typename string is the one returned by SerializedProperty.managedReferenceFullTypename. + /// + /// + /// + /// + internal static bool GetTypeFromManagedReferenceFullTypeName(string managedReferenceFullTypename, out Type managedReferenceInstanceType) + { + managedReferenceInstanceType = null; + + var parts = managedReferenceFullTypename.Split(' '); + if (parts.Length == 2) + { + var assemblyPart = parts[0]; + var nsClassnamePart = parts[1]; + managedReferenceInstanceType = Type.GetType($"{nsClassnamePart}, {assemblyPart}"); + } + + return managedReferenceInstanceType != null; } private static Type GetScriptTypeFromProperty(SerializedProperty property) @@ -206,11 +345,24 @@ public override int GetHashCode() } } - static Dictionary s_FieldInfoFromPropertyPathCache = new Dictionary(); + class FieldInfoCache + { + public FieldInfo fieldInfo; + public Type type; + } + + static Dictionary s_FieldInfoFromPropertyPathCache = new Dictionary(); private static FieldInfo GetFieldInfoFromPropertyPath(Type host, string path, out Type type) { - FieldInfo field = null; + Cache cache = new Cache(host, path); + + FieldInfoCache fieldInfoCache = null; + if (s_FieldInfoFromPropertyPathCache.TryGetValue(cache, out fieldInfoCache)) + { + type = fieldInfoCache?.type; + return fieldInfoCache?.fieldInfo; + } const string arrayData = @"\.Array\.data\[[0-9]+\]"; // we are looking for array element only when the path ends with Array.data[x] @@ -218,16 +370,7 @@ private static FieldInfo GetFieldInfoFromPropertyPath(Type host, string path, ou // remove any Array.data[x] from the path because it is prevents cache searching. path = Regex.Replace(path, arrayData, ".___ArrayElement___"); - Cache cache = new Cache(host, path); - if (s_FieldInfoFromPropertyPathCache.TryGetValue(cache, out field)) - { - type = field?.FieldType; - // we want to get the element type if we are looking for Array.data[x] - if (lookingForArrayElement && type != null && type.IsArrayOrList()) - type = type.GetArrayOrListElementType(); - return field; - } - + FieldInfo fieldInfo = null; type = host; string[] parts = path.Split('.'); for (int i = 0; i < parts.Length; i++) @@ -248,8 +391,8 @@ private static FieldInfo GetFieldInfoFromPropertyPath(Type host, string path, ou return null; } - field = foundField; - type = field.FieldType; + fieldInfo = foundField; + type = fieldInfo.FieldType; // we want to get the element type if we are looking for Array.data[x] if (i < parts.Length - 1 && parts[i + 1] == "___ArrayElement___" && type.IsArrayOrList()) { @@ -257,8 +400,18 @@ private static FieldInfo GetFieldInfoFromPropertyPath(Type host, string path, ou type = type.GetArrayOrListElementType(); } } - s_FieldInfoFromPropertyPathCache.Add(cache, field); - return field; + + // we want to get the element type if we are looking for Array.data[x] + if (lookingForArrayElement && type != null && type.IsArrayOrList()) + type = type.GetArrayOrListElementType(); + + fieldInfoCache = new FieldInfoCache + { + type = type, + fieldInfo = fieldInfo + }; + s_FieldInfoFromPropertyPathCache.Add(cache, fieldInfoCache); + return fieldInfo; } internal static PropertyHandler GetHandler(SerializedProperty property) @@ -305,12 +458,12 @@ internal static PropertyHandler GetHandler(SerializedProperty property) if (attributes != null) { for (int i = attributes.Count - 1; i >= 0; i--) - handler.HandleAttribute(attributes[i], field, propertyType); + handler.HandleAttribute(property, attributes[i], field, propertyType); } // Field has no CustomPropertyDrawer attribute with matching drawer so look for default drawer for field type if (!handler.hasPropertyDrawer && propertyType != null) - handler.HandleDrawnType(propertyType, propertyType, field, null); + handler.HandleDrawnType(property, propertyType, propertyType, field, null); if (handler.empty) { @@ -325,5 +478,22 @@ internal static PropertyHandler GetHandler(SerializedProperty property) return handler; } + + internal static List GetAutoLoadProperties(Type type) + { + if (s_AutoLoadProperties == null) + s_AutoLoadProperties = new Dictionary>(); + + List list; + if (!s_AutoLoadProperties.TryGetValue(type, out list)) + { + list = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance) + .Where(f => f.FieldType == typeof(SerializedProperty) && f.IsDefined(typeof(CachePropertyAttribute), false)) + .ToList(); + s_AutoLoadProperties.Add(type, list); + } + + return list; + } } } diff --git a/Editor/Mono/ScriptEditorUtility.cs b/Editor/Mono/ScriptEditorUtility.cs index 11e4763727..5503cb99a7 100644 --- a/Editor/Mono/ScriptEditorUtility.cs +++ b/Editor/Mono/ScriptEditorUtility.cs @@ -39,7 +39,7 @@ public static ScriptEditor GetScriptEditorFromPath(string path) { string lowerCasePath = path.ToLower(); - if (lowerCasePath == "internal") + if (lowerCasePath == CodeEditor.SystemDefaultPath) return ScriptEditor.SystemDefault; if (lowerCasePath.Contains("monodevelop") || lowerCasePath.Contains("xamarinstudio") || lowerCasePath.Contains("xamarin studio")) @@ -65,21 +65,9 @@ internal static bool IsVisualStudioForMac(string path) return filename.StartsWith("visualstudio") && !filename.Contains("code") && filename.EndsWith(".app"); } - #pragma warning disable 618 public static string GetExternalScriptEditor() { - var editor = EditorPrefs.GetString("kScriptsDefaultApp"); - - if (!string.IsNullOrEmpty(editor)) - return editor; - - // If no script editor is set, try to use first found supported one. - var editorPaths = GetFoundScriptEditorPaths(Application.platform); - - if (editorPaths.Count > 0) - return editorPaths.Keys.ToArray()[0]; - - return string.Empty; + return CodeEditor.CurrentEditorInstallation; } [Obsolete("This method has been moved to CodeEditor.SetExternalScriptEditor", false)] @@ -135,7 +123,7 @@ public static void SetExternalScriptEditorArgs(string args) [Obsolete("Use UnityEditor.ScriptEditor.GetCurrentEditor()", false)] public static ScriptEditor GetScriptEditorFromPreferences() { - return GetScriptEditorFromPath(GetExternalScriptEditor()); + return GetScriptEditorFromPath(CodeEditor.CurrentEditorInstallation); } [Obsolete("This method is being internalized, please use UnityEditorInternal.CodeEditorUtility.GetFoundScriptEditorPaths", false)] diff --git a/Editor/Mono/ScriptableSingleton.cs b/Editor/Mono/ScriptableSingleton.cs index 0330c294c1..a751f9fc9d 100644 --- a/Editor/Mono/ScriptableSingleton.cs +++ b/Editor/Mono/ScriptableSingleton.cs @@ -6,7 +6,6 @@ using System; using System.IO; using UnityEditorInternal; -using Object = UnityEngine.Object; namespace UnityEditor @@ -17,7 +16,7 @@ namespace UnityEditor [AttributeUsage(AttributeTargets.Class)] internal class FilePathAttribute : Attribute { - public enum Location { PreferencesFolder, ProjectFolder, AppDataFolder } + public enum Location { PreferencesFolder, ProjectFolder } private string filePath; private string relativePath; @@ -62,8 +61,6 @@ static string GetFilePath(string relativePath, Location location) if (location == Location.PreferencesFolder) return InternalEditorUtility.unityPreferencesFolder + "/" + relativePath; - else if (location == Location.AppDataFolder) - return InternalEditorUtility.userAppDataFolder + "/" + relativePath; else //location == Location.ProjectFolder return relativePath; } diff --git a/Editor/Mono/Scripting/APIUpdater/APIUpdaterManager.bindings.cs b/Editor/Mono/Scripting/APIUpdater/APIUpdaterManager.bindings.cs index 12997385c3..71b687f033 100644 --- a/Editor/Mono/Scripting/APIUpdater/APIUpdaterManager.bindings.cs +++ b/Editor/Mono/Scripting/APIUpdater/APIUpdaterManager.bindings.cs @@ -83,17 +83,23 @@ internal static void UpdateAssemblies() if (assembliesToUpdate.Count == 0) return; - if (!WaitForVCSServerConnection(true)) - { - assembliesToUpdate.Clear(); - return; - } - var assemblyPaths = assembliesToUpdate.Select(c => c.Path); - if (Provider.enabled && !APIUpdaterHelper.CheckoutAndValidateVCSFiles(assemblyPaths)) + var anyAssemblyInAssetsFolder = assemblyPaths.Any(path => path.IndexOf("Assets/", StringComparison.OrdinalIgnoreCase) != -1); + + // Only try to connect to VCS if there are files under VCS that need to be updated + if (anyAssemblyInAssetsFolder) { - assembliesToUpdate.Clear(); - return; + var failedToConnectToVcs = false; + if (WaitForVCSServerConnection(true)) + { + failedToConnectToVcs = Provider.enabled && !APIUpdaterHelper.CheckoutAndValidateVCSFiles(assemblyPaths); + } + + if (failedToConnectToVcs) + { + assembliesToUpdate.Clear(); + return; + } } var sw = Stopwatch.StartNew(); diff --git a/Editor/Mono/Scripting/APIUpdater/AssemblyDependencyGraph.cs b/Editor/Mono/Scripting/APIUpdater/AssemblyDependencyGraph.cs index a22589992b..e6eb158d73 100644 --- a/Editor/Mono/Scripting/APIUpdater/AssemblyDependencyGraph.cs +++ b/Editor/Mono/Scripting/APIUpdater/AssemblyDependencyGraph.cs @@ -42,20 +42,20 @@ public void SetDependencies(string root, params string[] dependencies) private DependencyEntry InternalAddDependencies(string root, params string[] dependencies) { - var existsingDependency = FindAssembly(root); - if (existsingDependency == null) + var existingDependency = FindAssembly(root); + if (existingDependency == null) { - existsingDependency = FindInDependents(root) ?? new DependencyEntry(root); - m_Graph.Add(existsingDependency); + existingDependency = FindInDependents(root) ?? new DependencyEntry(root); + m_Graph.Add(existingDependency); } foreach (var dependency in dependencies) { var dep = FindAssembly(dependency) ?? InternalAddDependencies(dependency); - existsingDependency.m_Dependencies.Add(dep); + existingDependency.m_Dependencies.Add(dep); } - return existsingDependency; + return existingDependency; } private DependencyEntry FindInDependents(string root) @@ -117,11 +117,11 @@ public void RemoveRoot(string tbr, bool updateDependents = false) { foreach (var entry in m_Graph) { - var danglingRereference = entry.m_Dependencies.Find(e => e == toBeRemoved); - if (danglingRereference == null) + var danglingReference = entry.m_Dependencies.Find(e => e == toBeRemoved); + if (danglingReference == null) continue; - entry.m_Dependencies.Remove(danglingRereference); + entry.m_Dependencies.Remove(danglingReference); } } } @@ -133,11 +133,17 @@ public IEnumerable SortedDependents() var array = m_Graph.ToArray(); + m_Processed = new HashSet(); + LogCycles(array, m_Processed); + + m_Processed.Clear(); + bool exchangeElementsInLastPass; + var arrayLength = array.Length - 1; do { exchangeElementsInLastPass = false; - for (int i = 0; i < array.Length - 1; i++) + for (int i = 0; i < arrayLength; i++) { if (CompareElements(array[i], array[i + 1]) > 0) { @@ -148,6 +154,8 @@ public IEnumerable SortedDependents() exchangeElementsInLastPass = true; } } + + arrayLength--; } while (exchangeElementsInLastPass); @@ -161,46 +169,83 @@ public IEnumerable SortedDependents() */ private int CompareElements(DependencyEntry lhs, DependencyEntry rhs) { - var rshDependsOnLhs = HasDirectOrIndirectDependency(lhs, rhs); - if (rshDependsOnLhs) + var lhsDependsOnRhs = HasDirectOrIndirectDependency(lhs, rhs); + if (lhsDependsOnRhs) return 1; - var lhsDependsOnRhs = HasDirectOrIndirectDependency(rhs, lhs); - if (lhsDependsOnRhs) + var rshDependsOnLhs = HasDirectOrIndirectDependency(rhs, lhs); + if (rshDependsOnLhs) return -1; return 0; } - private static bool HasDirectOrIndirectDependency(DependencyEntry lhs, DependencyEntry rhs) + private bool HasDirectOrIndirectDependency(DependencyEntry lhs, DependencyEntry rhs) { - var lhsDependendsOnRhs = lhs.m_Dependencies.Contains(rhs); - if (lhsDependendsOnRhs) + var lhsDependsOnRhs = lhs.m_Dependencies.Contains(rhs); + if (lhsDependsOnRhs) return true; + m_Processed.Clear(); return HasDirectOrIndirectDependencyRecursive(rhs, lhs.m_Dependencies); } - private static bool HasDirectOrIndirectDependencyRecursive(DependencyEntry toBeLookedUp, IEnumerable dependencies) + bool HasDirectOrIndirectDependencyRecursive(DependencyEntry toBeLookedUp, IList dependencies) { foreach (var entry in dependencies) { if (entry == toBeLookedUp) return true; - if (HasDirectOrIndirectDependencyRecursive(toBeLookedUp, entry.m_Dependencies)) - return true; + if (m_Processed.Contains(entry.Name)) + { + // We've found a cycle in the assemblies (which has already been logged) + return false; + } + + m_Processed.Add(entry.Name); + try + { + if (HasDirectOrIndirectDependencyRecursive(toBeLookedUp, entry.m_Dependencies)) + return true; + } + finally + { + m_Processed.Remove(entry.Name); + } } return false; } + static void LogCycles(IEnumerable entries, HashSet seen) + { + foreach (var entry in entries) + { + if ((entry.Status & AssemblyStatus.NoCyclesDetected) == AssemblyStatus.NoCyclesDetected) + continue; + + if (seen.Contains(entry.Name)) + { + Console.WriteLine($"[APIUpdater] Warning: Cycle detected in assembly references: {string.Join("->", seen.ToArray())}->{entry.Name}. This is not supported and AssemblyUpdater may not work as expected."); + continue; + } + + seen.Add(entry.Name); + + LogCycles(entry.Dependencies, seen); + entry.Status |= AssemblyStatus.NoCyclesDetected; + + seen.Remove(entry.Name); + } + } + /// /// Serialized format: /// - /// +--------------+------------------+-----------------------------+ - /// | Hash Lengh | Hash of Payload | Payload (serialized data) | - /// +--------------+------------------+-----------------------------+ + /// +---------------+------------------+-----------------------------+ + /// | Hash Length | Hash of Payload | Payload (serialized data) | + /// +---------------+------------------+-----------------------------+ /// public void SaveTo(Stream stream) { @@ -213,15 +258,15 @@ public void SaveTo(Stream stream) stream.Write(h, 0, h.Length); // Write the hash length stream.Write(hash, 0, hash.Length); // and reserve space of the "payload" hash. - var formater = new BinaryFormatter(); - formater.Serialize(stream, this); + var formatter = new BinaryFormatter(); + formatter.Serialize(stream, this); var endOfStream = stream.Position; - stream.Position = hash.Length + h.Length; // Position the stream in the first byte of the serialized data (i.e, skip *hash lenght* and *hash* + stream.Position = hash.Length + h.Length; // Position the stream in the first byte of the serialized data (i.e, skip *hash length* and *hash* hash = hasher.ComputeHash(stream); - stream.Position = h.Length; // position the stream past the *hash lenght* (i.e, *hash first byte*) + stream.Position = h.Length; // position the stream past the *hash length* (i.e, *hash first byte*) stream.Write(hash, 0, hash.Length); stream.Position = endOfStream; @@ -300,13 +345,16 @@ public override string ToString() } } - private List m_Graph; + List m_Graph; + HashSet m_Processed; // used to ignore cycles. } [Flags] internal enum AssemblyStatus { None = 0x0, - PublishesUpdaterConfigurations = 0x02 + PublishesUpdaterConfigurations = 0x02, + + NoCyclesDetected = 0x04 } } diff --git a/Editor/Mono/Scripting/Compilers/APIUpdaterHelper.cs b/Editor/Mono/Scripting/Compilers/APIUpdaterHelper.cs index 69e36b4fc5..9127815214 100644 --- a/Editor/Mono/Scripting/Compilers/APIUpdaterHelper.cs +++ b/Editor/Mono/Scripting/Compilers/APIUpdaterHelper.cs @@ -17,10 +17,11 @@ using UnityEditorInternal.APIUpdating; using UnityEngine; using UnityEngine.Scripting.APIUpdating; +using Attribute = ICSharpCode.NRefactory.Ast.Attribute; namespace UnityEditor.Scripting.Compilers { - internal class APIUpdaterHelper + class APIUpdaterHelper { public static bool IsReferenceToTypeWithChangedNamespace(string normalizedErrorMessage) { @@ -28,6 +29,7 @@ public static bool IsReferenceToTypeWithChangedNamespace(string normalizedErrorM { var lines = normalizedErrorMessage.Split('\n'); var found = FindTypeMatchingMovedTypeBasedOnNamespaceFromError(lines); + return found != null; } catch (ReflectionTypeLoadException ex) @@ -51,43 +53,39 @@ public static bool IsReferenceToMissingObsoleteMember(string namespaceName, stri public static void UpdateScripts(string responseFile, string sourceExtension, string[] sourceFiles) { - if (!APIUpdaterManager.WaitForVCSServerConnection(true)) - { - return; - } - + bool anyFileInAssetsFolder = false; var pathMappingsFilePath = Path.GetTempFileName(); - var filePathMappings = new List(sourceFiles.Length); foreach (var source in sourceFiles) { - var f = CommandLineFormatter.PrepareFileName(source); - f = Paths.UnifyDirectorySeparator(f); + anyFileInAssetsFolder |= (source.IndexOf("Assets/", StringComparison.OrdinalIgnoreCase) != -1); - if (f != source) + var f = CommandLineFormatter.PrepareFileName(source); + if (f != source) // assume path represents a virtual path and needs to be mapped. + { + f = Paths.UnifyDirectorySeparator(f); filePathMappings.Add(f + " => " + source); + } + } + + // Only try to connect to VCS if there are files under VCS that need to be updated + if (anyFileInAssetsFolder && !APIUpdaterManager.WaitForVCSServerConnection(true)) + { + return; } File.WriteAllLines(pathMappingsFilePath, filePathMappings.ToArray()); var tempOutputPath = "Library/Temp/ScriptUpdater/" + new System.Random().Next() + "/"; - try { - RunUpdatingProgram( - "ScriptUpdater.exe", - sourceExtension - + " " - + CommandLineFormatter.PrepareFileName(MonoInstallationFinder.GetFrameWorksFolder()) - + " " - + tempOutputPath - + " \"" // Quote the filer (regex) to avoid issues when passing through command line arg. - + APIUpdaterManager.ConfigurationSourcesFilter - + "\" " - + pathMappingsFilePath - + " " - + responseFile, - tempOutputPath); + var arguments = ArgumentsForScriptUpdater( + sourceExtension, + tempOutputPath, + pathMappingsFilePath, + responseFile); + + RunUpdatingProgram("ScriptUpdater.exe", arguments, tempOutputPath, anyFileInAssetsFolder); } #pragma warning disable CS0618 // Type or member is obsolete catch (Exception ex) when (!(ex is StackOverflowException) && !(ex is ExecutionEngineException)) @@ -100,25 +98,38 @@ public static void UpdateScripts(string responseFile, string sourceExtension, st } } - private static void RunUpdatingProgram(string executable, string arguments, string tempOutputPath) + public static string ArgumentsForScriptUpdater(string sourceExtension, string tempOutputPath, string pathMappingsFilePath, string responseFile) { - var scriptUpdater = EditorApplication.applicationContentsPath + "/Tools/ScriptUpdater/" + executable; - var program = new ManagedProgram(MonoInstallationFinder.GetMonoInstallation("MonoBleedingEdge"), null, scriptUpdater, arguments, false, null); + return sourceExtension + + " " + + CommandLineFormatter.PrepareFileName(MonoInstallationFinder.GetFrameWorksFolder()) + + " " + + CommandLineFormatter.PrepareFileName(tempOutputPath) + + " \"" + APIUpdaterManager.ConfigurationSourcesFilter + "\" " // Quote the filter (regex) to avoid issues when passing through command line arg.) + + CommandLineFormatter.PrepareFileName(pathMappingsFilePath) + + " " + + responseFile; // Response file is always relative and without spaces, no need to quote. + } + + static void RunUpdatingProgram(string executable, string arguments, string tempOutputPath, bool anyFileInAssetsFolder) + { + var scriptUpdaterPath = EditorApplication.applicationContentsPath + "/Tools/ScriptUpdater/" + executable; // ManagedProgram will quote this path for us. + var program = new ManagedProgram(MonoInstallationFinder.GetMonoInstallation("MonoBleedingEdge"), null, scriptUpdaterPath, arguments, false, null); program.LogProcessStartInfo(); program.Start(); program.WaitForExit(); Console.WriteLine(string.Join(Environment.NewLine, program.GetStandardOutput())); - HandleUpdaterReturnValue(program, tempOutputPath); + HandleUpdaterReturnValue(program, tempOutputPath, anyFileInAssetsFolder); } - private static void HandleUpdaterReturnValue(ManagedProgram program, string tempOutputPath) + static void HandleUpdaterReturnValue(ManagedProgram program, string tempOutputPath, bool anyFileInAssetsFolder) { if (program.ExitCode == 0) { Console.WriteLine(string.Join(Environment.NewLine, program.GetErrorOutput())); - CopyUpdatedFiles(tempOutputPath); + CopyUpdatedFiles(tempOutputPath, anyFileInAssetsFolder); return; } @@ -129,18 +140,18 @@ private static void HandleUpdaterReturnValue(ManagedProgram program, string temp ReportAPIUpdaterCrash(program.GetErrorOutput()); } - private static void ReportAPIUpdaterCrash(IEnumerable errorOutput) + static void ReportAPIUpdaterCrash(IEnumerable errorOutput) { Debug.LogErrorFormat("Failed to run script updater.{0}Please, report a bug to Unity with these details{0}{1}", Environment.NewLine, errorOutput.Aggregate("", (acc, curr) => acc + Environment.NewLine + "\t" + curr)); } - private static void ReportAPIUpdaterFailure(IEnumerable errorOutput) + static void ReportAPIUpdaterFailure(IEnumerable errorOutput) { var msg = string.Format("APIUpdater encountered some issues and was not able to finish.{0}{1}", Environment.NewLine, errorOutput.Aggregate("", (acc, curr) => acc + Environment.NewLine + "\t" + curr)); APIUpdaterManager.ReportGroupedAPIUpdaterFailure(msg); } - private static void CopyUpdatedFiles(string tempOutputPath) + static void CopyUpdatedFiles(string tempOutputPath, bool anyFileInAssetsFolder) { if (!Directory.Exists(tempOutputPath)) return; @@ -148,7 +159,7 @@ private static void CopyUpdatedFiles(string tempOutputPath) var files = Directory.GetFiles(tempOutputPath, "*.*", SearchOption.AllDirectories); var pathsRelativeToTempOutputPath = files.Select(path => path.Replace(tempOutputPath, "")); - if (Provider.enabled && !CheckoutAndValidateVCSFiles(pathsRelativeToTempOutputPath)) + if (anyFileInAssetsFolder && Provider.enabled && !CheckoutAndValidateVCSFiles(pathsRelativeToTempOutputPath)) return; var destRelativeFilePaths = files.Select(sourceFileName => sourceFileName.Substring(tempOutputPath.Length)).ToArray(); @@ -167,7 +178,7 @@ private static void CopyUpdatedFiles(string tempOutputPath) // // If this ever changes we'll need to change our implementation (and remove the link instead of simply updating in place) otherwise updating a package // in one project would result in that package being updated in all projects in the local computer. - File.Copy(sourceFileName,  relativeDestFilePath, true); + File.Copy(sourceFileName, relativeDestFilePath, true); } if (destRelativeFilePaths.Length > 0) @@ -246,21 +257,23 @@ internal static bool CheckReadOnlyFiles(string[] destRelativeFilePaths) internal static bool CheckoutAndValidateVCSFiles(IEnumerable files) { + // We're only interested in files that would be under VCS, i.e. project + // assets or local packages. Incoming paths might use backward slashes; replace with + // forward ones as that's what Unity/VCS functions operate on. + files = files.Select(f => f.Replace('\\', '/')).Where(Provider.PathIsVersioned).ToArray(); + var assetList = new AssetList(); - foreach (string f in files) - assetList.Add(Provider.GetAssetByPath(f)); + assetList.AddRange(files.Select(Provider.GetAssetByPath)); // Verify that all the files are also in assetList // This is required to ensure the copy temp files to destination loop is only working on version controlled files // Provider.GetAssetByPath() can fail i.e. the asset database GUID can not be found for the input asset path - foreach (var rawAssetPath in files) + foreach (var assetPath in files) { - // VCS assets path separator is '/' , file path might be '\' or '/' - var assetPath = (Path.DirectorySeparatorChar == '\\') ? rawAssetPath.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) : rawAssetPath; - var foundAsset = assetList.Where(asset => (asset.path == assetPath)); + var foundAsset = assetList.Where(asset => (asset?.path == assetPath)); if (!foundAsset.Any()) { - Debug.LogErrorFormat("[API Updater] Files cannot be updated (failed to add file to list): {0}", rawAssetPath); + Debug.LogErrorFormat("[API Updater] Files cannot be updated (failed to add file to list): {0}", assetPath); APIUpdaterManager.ReportExpectedUpdateFailure(); return false; } @@ -285,37 +298,11 @@ internal static bool CheckoutAndValidateVCSFiles(IEnumerable files) return true; } - private struct Identifier : IEquatable - { - public string @namespace; - public string name; - - public bool Equals(Identifier other) - { - return (String.IsNullOrEmpty(@namespace) && String.IsNullOrEmpty(other.@namespace) || string.Equals(@namespace, other.@namespace)) && - string.Equals(name, other.name); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is Identifier && Equals((Identifier)obj); - } - - public override int GetHashCode() - { - unchecked - { - return ((@namespace != null ? @namespace.GetHashCode() : 0) * 397) ^ (name != null ? name.GetHashCode() : 0); - } - } - } - - // C# compiler does not emmit the full qualified type name when it fails to resolve a 'theorically', fully qualified type reference - // for instance, if 'NSBar', a namespace gets renamed to 'NSBar2', a refernce to 'NSFoo.NSBar.TypeBaz' will emit an error + // C# compiler does not emit the full qualified type name when it fails to resolve a 'theoretically', fully qualified type reference + // for instance, if 'NSBar', a namespace gets renamed to 'NSBar2', a reference to 'NSFoo.NSBar.TypeBaz' will emit an error // with only NSBar and NSFoo in the message. In this case we use NRefactory to dive in to the code, looking for type references // in the reported error line/column - private static Type FindTypeMatchingMovedTypeBasedOnNamespaceFromError(IEnumerable lines) + static Type FindTypeMatchingMovedTypeBasedOnNamespaceFromError(IEnumerable lines) { var value = GetValueFromNormalizedMessage(lines, "Line="); var line = (value != null) ? Int32.Parse(value) : -1; @@ -329,6 +316,7 @@ private static Type FindTypeMatchingMovedTypeBasedOnNamespaceFromError(IEnumerab return null; } + var entityName = GetValueFromNormalizedMessage(lines, "EntityName="); try { using (var scriptStream = File.Open(script, System.IO.FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) @@ -337,32 +325,46 @@ private static Type FindTypeMatchingMovedTypeBasedOnNamespaceFromError(IEnumerab parser.Lexer.EvaluateConditionalCompilation = false; parser.Parse(); - var self = new InvalidTypeOrNamespaceErrorTypeMapper(line, column); + var self = new InvalidTypeOrNamespaceErrorTypeMapper(line, column, entityName); parser.CompilationUnit.AcceptVisitor(self, null); - if (self.identifierParts.Count == 0) + if (self.identifiers.Count == 0) return null; - List candidateNamespaces = new List(self.usings.Where(u => !u.IsAlias).Select(u => u.Name)); - List candidateFullyQualifiedNames = - candidateNamespaces.Select(ns => new Identifier {@namespace = ns, name = self.identifierParts[0] }).ToList(); - - string @namespace = string.Empty; - foreach (var identifier in self.identifierParts) + var availableTypes = TypeCache.GetTypesWithAttribute(typeof(MovedFromAttribute)); + foreach (var ns in self.NamespacesInScope) { - candidateFullyQualifiedNames.Add(new Identifier { name = identifier, @namespace = @namespace }); - if (@namespace != string.Empty) - @namespace += "."; - - @namespace += identifier; + foreach (var i in self.identifiers) + { + foreach (var t in availableTypes) + { + var @namespace = ns; + foreach (var part in i.parts) + { + //If the usage + any of the candidate namespaces matches a real type, this usage is valid and does not need to be updated + //this is required to avoid false positives when a type that exists on *editor* (and is marked as moved) is used in a platform that + //does not support it. If we don't check the namespaces in scope we'll flag this as an error due to the type being moved (and + //whence, trigger the updater, whereas the real problem is that the type is not supported in the platform (see issue #96123) + //whence this is indeed a programing error that the user needs to fix. + if (t.Name == part && t.Namespace == @namespace) + { + return null; + } + + if (t.Name == part && NamespaceHasChanged(t, @namespace)) + { + return t; + } + + if (string.IsNullOrEmpty(@namespace)) + @namespace = part; + else + @namespace = @namespace + "." + part; + } + } + } } - - //If the usage + any of the candidate namespaces matches a real type, this usage is valid and does not need to be updated - if (FindTypeInLoadedAssemblies(t => candidateFullyQualifiedNames.Contains(new Identifier {name = t.Name, @namespace = t.Namespace})) != null) - return null; - - return FindTypeInLoadedAssemblies(t => - candidateFullyQualifiedNames.Any(id => id.name == t.Name && NamespaceHasChanged(t, id.@namespace))); + return null; } } catch (FileNotFoundException) @@ -371,15 +373,7 @@ private static Type FindTypeMatchingMovedTypeBasedOnNamespaceFromError(IEnumerab } } - private static string Name(string identifier, int genericArgumentCount) - { - if (genericArgumentCount > 0) - return identifier + '`' + genericArgumentCount; - - return identifier; - } - - private static string GetValueFromNormalizedMessage(IEnumerable lines, string marker) + static string GetValueFromNormalizedMessage(IEnumerable lines, string marker) { string value = null; var foundLine = lines.FirstOrDefault(l => l.StartsWith(marker)); @@ -390,7 +384,7 @@ private static string GetValueFromNormalizedMessage(IEnumerable lines, s return value; } - private static bool IsUpdateable(Type type) + static bool IsUpdateable(Type type) { var attrs = type.GetCustomAttributes(typeof(ObsoleteAttribute), false); if (attrs.Length != 1) @@ -400,7 +394,7 @@ private static bool IsUpdateable(Type type) return oa.Message.Contains("UnityUpgradable"); } - private static bool NamespaceHasChanged(Type type, string namespaceName) + static bool NamespaceHasChanged(Type type, string namespaceName) { var attrs = type.GetCustomAttributes(typeof(MovedFromAttribute), false); if (attrs.Length != 1) @@ -413,17 +407,17 @@ private static bool NamespaceHasChanged(Type type, string namespaceName) return from.data.nameSpace == namespaceName; } - private static Type FindTypeInLoadedAssemblies(Func predicate) + static Type FindTypeInLoadedAssemblies(Func predicate) { var found = AppDomain.CurrentDomain.GetAssemblies() .Where(assembly => !IsIgnoredAssembly(assembly.GetName())) - .SelectMany(a => GetValidTypesIn(a)) + .SelectMany(GetValidTypesIn) .FirstOrDefault(predicate); return found; } - private static IEnumerable GetValidTypesIn(Assembly a) + static IEnumerable GetValidTypesIn(Assembly a) { Type[] types; try @@ -438,102 +432,304 @@ private static IEnumerable GetValidTypesIn(Assembly a) return types.Where(t => t != null); } - private static bool IsIgnoredAssembly(AssemblyName assemblyName) + static bool IsIgnoredAssembly(AssemblyName assemblyName) { var name = assemblyName.Name; return _ignoredAssemblies.Any(candidate => Regex.IsMatch(name, candidate)); } - private static string[] _ignoredAssemblies = { "^UnityScript$", "^System\\..*", "^mscorlib$" }; + static string[] _ignoredAssemblies = { "^UnityScript$", "^System\\..*", "^mscorlib$" }; } - internal class InvalidTypeOrNamespaceErrorTypeMapper : AbstractAstVisitor + struct Identifier { - public List usings = new List(); - public List identifierParts = new List(); + public List parts; - private Expression lastMatchedExpression = null; - - private readonly int _line; - private readonly int _column; - - public override object VisitTypeReference(TypeReference typeReference, object data) + public override string ToString() { - var identifier = typeReference.Type; - if (MatchesPosition(typeReference.StartLocation, identifier.Length)) - { - var identifiers = Identifiers(typeReference.GenericTypes.Count, identifier).ToList(); + return string.Join(".", parts.ToArray()); + } + } - if (!identifiers.Any()) - return null; + /* + * Given the following source code with error(s): + * + * using Foo; + * using FooBar.Baz; // E1 + * using X = Foo.Baz.Bar; // E4 + * using Y = Foo.Baz.T; // E5 + * + * class C : T1 // E2 + * { + * public N1.T2 t2; // E3 + * } + * + * Errors my be reported by C# compiler as: + * + * - Type / Namespace does not exist (E1). Usually this happens when we have only static member access to types moved out + * from the imported namespace and part of namespace is still valid (for instance, FooBar exists but not FooBar.Baz) + * + * - Type / Namespace does not exist (E2/E3). Usually this happens if the original namespace (and none of its parents) + * does not exists. + * + * Handling: + * 1. Collect all imported namespaces + * + * 2. Collect all identifiers that can represent type references (T3) + * + * 3. Collect all types available in the current AppDomain + * + * 4. This scenario also represents an error that requires ScriptUpdater to run if any of the following represents a + * type marked as MoveFrom(imported-namespace): + * - N1, (ie, T2 is an inner type of N1) or + * - N1.T2 (N1 is a namespace, T2 is a type) (note that this is only valid if N1 is an inner namespace of the namespace + * from which T2 has been moved from, in this example it means the fully qualified name of T2 would be either + * Foo.N1.T2 or Foo.FooBar.Baz.N1.T2) + * + * - Main difference among E1 & E2/E3 is that for E1 we need to check T1 & N1.T2 only against "FooBar.Baz" and in E2/E3 + * we need to check those type references against *all* imported namespaces. + */ + class InvalidTypeOrNamespaceErrorTypeMapper : AbstractAstVisitor + { + public HashSet usings = new HashSet(); + public IDictionary aliases = new Dictionary(); + public List identifiers = new List(); - var alias = usings.FirstOrDefault(u => u.IsAlias && u.Name == identifiers[0]); - if (alias != null) - { - identifiers.RemoveAt(0); - identifierParts.AddRange(Identifiers(0, alias.Alias.Type)); - } + bool _isOffendingUsing; - identifierParts.AddRange(identifiers); + readonly int _line; + readonly int _column; + readonly string _entityName; + + public IEnumerable NamespacesInScope + { + get + { + return usings.Select(ns => ns.Name).Concat(new[] { string.Empty }); } + } - return null; + public override object VisitAttribute(Attribute attribute, object data) + { + var typeName = attribute.Name; + if (!typeName.EndsWith("Attribute")) + typeName = typeName + "Attribute"; + + AddIdentifierFromString(typeName, 0); + return base.VisitAttribute(attribute, data); } - private static string[] Identifiers(int genericArgumentCount, string identifier) + public override object VisitTypeReference(TypeReference typeReference, object data) { - var identifiers = identifier.Split('.').ToArray(); - if (genericArgumentCount > 0) - identifiers[identifiers.Length - 1] = identifiers[identifiers.Length - 1] + '`' + genericArgumentCount; + if (_isOffendingUsing || MatchesPosition(typeReference.StartLocation, typeReference.Type.Length)) + { + AddIdentifierFromString(typeReference.Type, typeReference.GenericTypes.Count); + } - return identifiers; + return base.VisitTypeReference(typeReference, data); } public override object VisitIdentifierExpression(IdentifierExpression identifierExpression, object data) { - var identifier = identifierExpression.Identifier; - if (MatchesPosition(identifierExpression.StartLocation, identifier.Length)) + if (_isOffendingUsing || MatchesPosition(identifierExpression.StartLocation, identifierExpression.Identifier.Length)) { - var alias = usings.FirstOrDefault(u => u.IsAlias && u.Name == identifier); - if (alias != null) - identifier = alias.Alias.Type; - - var identifiers = Identifiers(identifierExpression.TypeArguments.Count, identifier); - identifierParts.AddRange(identifiers); - lastMatchedExpression = identifierExpression; + var identifier = new Identifier { parts = new List() }; + AddIdentifierPartsTakingAliasesIntoAccount(ref identifier, identifierExpression.Identifier); + identifiers.Add(identifier); } return null; } - //For cases like "UnityEngine.Object", "UnityEngine" is an IdentifierExpression that will be matched in the call to base. - //Here we expand the Found value with the "member name" (".Object" in this example) public override object VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression, object data) { - base.VisitMemberReferenceExpression(memberReferenceExpression, data); - if (lastMatchedExpression == memberReferenceExpression.TargetObject) + var identifier = new Identifier { parts = new List(6) }; + var curr = memberReferenceExpression; + var last = memberReferenceExpression; + bool matchesPosition = false; + while (curr != null) { - lastMatchedExpression = memberReferenceExpression; - identifierParts.Add(memberReferenceExpression.MemberName); + if (!matchesPosition && MatchesPosition(curr.StartLocation, curr.EndLocation.Column)) + matchesPosition = true; + + AddIdentifierPartsTakingAliasesIntoAccount(ref identifier, curr.MemberName); + last = curr; + curr = curr.TargetObject as MemberReferenceExpression; } + + var lastIdentifier = last.TargetObject as IdentifierExpression; + if (lastIdentifier == null || (!matchesPosition && !MatchesPosition(lastIdentifier.StartLocation, lastIdentifier.EndLocation.Column))) + return base.VisitMemberReferenceExpression(memberReferenceExpression, data); + + AddIdentifierPartsTakingAliasesIntoAccount(ref identifier, lastIdentifier.Identifier); + identifiers.Add(identifier); + return null; } public override object VisitUsing(Using @using, object data) { - usings.Add(@using); + if (!_isOffendingUsing) + { + if (MatchesUsing(@using)) + { + usings.Clear(); // Scenario `E1`, use only the offending using when looking for types marked with MovedFromAttribute() + _isOffendingUsing = true; + } + + if (@using.IsAlias) + aliases[@using.Name] = @using.Alias; // remember aliases in order to *expand* them/if identifiers starts with the aliased name. + else + usings.Add(@using); + } + return base.VisitUsing(@using, data); } - private bool MatchesPosition(Location startLocation, int length) + bool MatchesUsing(Using @using) + { + var parent = @using.Parent as UsingDeclaration; + return parent != null + && parent.StartLocation.Line == _line + && parent.StartLocation.Column < _column + && parent.EndLocation.Column > _column + && (@using.Name == _entityName || (@using.IsAlias && @using.Alias.ToString() == _entityName)); + } + + bool MatchesPosition(Location startLocation, int length) { return _column >= startLocation.Column && _column < startLocation.Column + length && startLocation.Line == _line; } - public InvalidTypeOrNamespaceErrorTypeMapper(int line, int column) + void AddIdentifierPartsTakingAliasesIntoAccount(ref Identifier identifier, string name) + { + TypeReference aliased; + if (aliases.TryGetValue(name, out aliased)) + { + var parts = SplitIdentifier(aliased.ToString()); + identifier.parts.InsertRange(0, parts); + } + else + { + identifier.parts.Insert(0, name); + } + } + + /* + * Adds a identifier composed of the parts of the name (split at `.`) handling the cases in which: + * 1st part is an alias + * last part is a generic type (fixes the syntax to be able to match type names from reflection) + */ + void AddIdentifierFromString(string name, int genericTypesCount) + { + var parts = SplitIdentifier(name); + var first = parts[0]; + var last = parts[parts.Length - 1]; + var reminder = parts.Skip(1).Take(parts.Length - 2); // ignore first and last parts... + + var identifier = new Identifier { parts = reminder.ToList() }; + + // first element of a type reference may represent a type or an alias. + if (parts.Length > 1 || genericTypesCount == 0) + AddIdentifierPartsTakingAliasesIntoAccount(ref identifier, first); + + if (genericTypesCount > 0) + last = last + "`" + genericTypesCount; + + if (parts.Length > 1) + identifier.parts.Add(last); + + identifiers.Add(identifier); + } + + private unsafe static string[] SplitIdentifier(string identifier) + { + int last = 0; + var a = new List(); + var dotIndex = identifier.IndexOf('.'); + while (dotIndex != -1) + { + var genericCloseBraceIndex = -1; + var genericOpenBranceIndex = identifier.IndexOf('<', last, dotIndex - last); + if (dotIndex > genericOpenBranceIndex && genericOpenBranceIndex != -1) + genericCloseBraceIndex = FindClosingGenericBrance(identifier, genericOpenBranceIndex + 1); + + if (dotIndex < genericOpenBranceIndex || genericOpenBranceIndex == -1) + { + a.Add(identifier.Substring(last, dotIndex - last)); + last = dotIndex + 1; + } + else if (dotIndex > genericCloseBraceIndex) + { + a.Add(MapCSharpGenericNameToReflectionName(identifier.Substring(last, dotIndex - last))); + last = dotIndex + 1; + } + else if (genericCloseBraceIndex != -1) + { + dotIndex = genericCloseBraceIndex; + } + + dotIndex = identifier.IndexOf('.', dotIndex + 1); + } + + a.Add(MapCSharpGenericNameToReflectionName(identifier.Substring(last))); + + return a.ToArray(); + } + + private static int FindClosingGenericBrance(string identifier, int startIndex) + { + var index = startIndex; + byte balanceCount = 1; + while (balanceCount > 0 && index < identifier.Length) + { + switch (identifier[index]) + { + case '<': balanceCount++; break; + case '>': balanceCount--; break; + } + index++; + } + + return balanceCount == 0 ? (index - 1) : -1; + } + + /* + * maps names like A => A`1, A => A`2, A, D> => A`2 + */ + private static string MapCSharpGenericNameToReflectionName(string typeName) + { + var index = typeName.IndexOf('<'); + if (index == -1) + return typeName; + + var typeNameWithtoutGeneric = typeName.Substring(0, index); + + index++; // skip first '<' + byte genericArgumentCount = 1; + byte balanceCount = 1; + while (balanceCount > 0 && index < typeName.Length) + { + switch (typeName[index]) + { + case '<': balanceCount++; break; + case '>': balanceCount--; break; + case ',': + if (balanceCount == 1) + genericArgumentCount++; + break; + } + index++; + } + + return typeNameWithtoutGeneric + "`" + genericArgumentCount; + } + + public InvalidTypeOrNamespaceErrorTypeMapper(int line, int column, string entityName) { _line = line; _column = column; + _entityName = entityName; } } } diff --git a/Editor/Mono/Scripting/Compilers/MicrosoftCSharpCompiler.cs b/Editor/Mono/Scripting/Compilers/MicrosoftCSharpCompiler.cs index b30e496964..fcb8470dfc 100644 --- a/Editor/Mono/Scripting/Compilers/MicrosoftCSharpCompiler.cs +++ b/Editor/Mono/Scripting/Compilers/MicrosoftCSharpCompiler.cs @@ -32,17 +32,6 @@ static void FillCompilerOptions(List arguments, bool buildingForEditor, // Case 755238: Always use english for outputing errors, the same way as Mono compilers do arguments.Add("/preferreduilang:en-US"); arguments.Add("/langversion:latest"); - - var platformSupportModule = ModuleManager.FindPlatformSupportModule(ModuleManager.GetTargetStringFromBuildTarget(BuildTarget)); - if (platformSupportModule != null && !buildingForEditor) - { - var compilationExtension = platformSupportModule.CreateCompilationExtension(); - - arguments.AddRange(compilationExtension.GetAdditionalAssemblyReferences().Select(r => "/reference:\"" + r + "\"")); - arguments.AddRange(compilationExtension.GetWindowsMetadataReferences().Select(r => "/reference:\"" + r + "\"")); - arguments.AddRange(compilationExtension.GetAdditionalDefines().Select(d => "/define:" + d)); - arguments.AddRange(compilationExtension.GetAdditionalSourceFiles()); - } } internal static string GenerateResponseFile(ScriptAssembly assembly, EditorScriptCompilationOptions options, string tempBuildDirectory) diff --git a/Editor/Mono/Scripting/Compilers/ScriptCompilerBase.cs b/Editor/Mono/Scripting/Compilers/ScriptCompilerBase.cs index 24027f9cfe..c8920a1827 100644 --- a/Editor/Mono/Scripting/Compilers/ScriptCompilerBase.cs +++ b/Editor/Mono/Scripting/Compilers/ScriptCompilerBase.cs @@ -378,7 +378,7 @@ protected static string PrepareFileName(string fileName) //do not change the returntype, native unity depends on this one. public virtual CompilerMessage[] GetCompilerMessages() { - if (!Poll()) + if (process != null && !process.HasExited) Debug.LogWarning("Compile process is not finished yet. This should not happen."); if (process == null) diff --git a/Editor/Mono/Scripting/ScriptCompilation/AssemblyBuilder.cs b/Editor/Mono/Scripting/ScriptCompilation/AssemblyBuilder.cs index 060e9b77ea..0743683b16 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/AssemblyBuilder.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/AssemblyBuilder.cs @@ -100,7 +100,8 @@ internal bool Build(EditorCompilation editorCompilation) var scriptAssembly = editorCompilation.CreateScriptAssembly(this); compilationTask = new CompilationTask(new ScriptAssembly[] { scriptAssembly }, scriptAssembly.OutputDirectory, this, - EditorScriptCompilationOptions.BuildingEmpty, CompilationTaskOptions.StopOnFirstError, 1); + EditorScriptCompilationOptions.BuildingEmpty, CompilationTaskOptions.StopOnFirstError, 1, + editorCompilation.ILPostProcessing); compilationTask.OnCompilationTaskStarted += (context) => { diff --git a/Editor/Mono/Scripting/ScriptCompilation/AssetPathMetaData.cs b/Editor/Mono/Scripting/ScriptCompilation/AssetPathMetaData.cs index a075bc7b49..bbc1d4d11a 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/AssetPathMetaData.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/AssetPathMetaData.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Diagnostics; using System.Runtime.InteropServices; using UnityEngine.Bindings; using UnityEngine.Scripting; @@ -14,6 +15,7 @@ namespace UnityEditor.Scripting.ScriptCompilation [RequiredByNativeCode(GenerateProxy = true)] [NativeHeader("Runtime/Scripting/ScriptingManagedProxySupport.h")] [NativeHeader("Runtime/ScriptingBackend/ScriptingNativeTypes.h")] + [DebuggerDisplay("{DirectoryPath}")] class AssetPathMetaData { public string DirectoryPath; diff --git a/Editor/Mono/Scripting/ScriptCompilation/CSharpNamespaceParser.cs b/Editor/Mono/Scripting/ScriptCompilation/CSharpNamespaceParser.cs index edd8731646..e32fe927a5 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/CSharpNamespaceParser.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/CSharpNamespaceParser.cs @@ -32,9 +32,9 @@ internal static class CSharpNamespaceParser static readonly Regex k_Strings = new Regex(@"""((\\[^\n]|[^""\n])*)""", RegexOptions.Compiled); static readonly Regex k_VerbatimStrings = new Regex(@"@(""[^""]*"")+", RegexOptions.Compiled); static readonly Regex k_NewlineRegex = new Regex("\r\n?", RegexOptions.Compiled); - static readonly Regex k_SingleQuote = new Regex(@"((?> namespaceScope { foreach (var tuple in namespaceScopeStack) { - if (tuple.Item1 && tuple.Item2 == stackCount) + if (tuple.Item1 && (stackCount == -1 || tuple.Item2 == stackCount)) return true; } return false; @@ -261,11 +261,11 @@ static string ReduceCodeAndCheckForNamespacesModification(string source, string foreach (var s in split) { - // Check for new namespace deeper than top level of directives + // Check for new namespace declarations, when we have are inside of multiple #ifs, + // and also have seen previous namespace declarations (likely namespace modification). if (k_Namespace.IsMatch(s)) { - if (stack.Count > 1) - namespaceModificationFound = true; + namespaceModificationFound |= (stack.Count > 1 && CheckForNamespaceModification(namespaceScopeStack, -1)); foundNamespace = true; } if (s.IndexOf("{", StringComparison.Ordinal) >= 0) @@ -291,7 +291,7 @@ static string ReduceCodeAndCheckForNamespacesModification(string source, string var directive = match.Groups[1].Value; if (directive == "else") { - namespaceModificationFound = CheckForNamespaceModification(namespaceScopeStack, stack.Count); + namespaceModificationFound |= CheckForNamespaceModification(namespaceScopeStack, stack.Count); var elseEmitting = stack.Peek().Item2; stack.Pop(); stack.Push(new Tuple(elseEmitting, false)); @@ -316,7 +316,7 @@ static string ReduceCodeAndCheckForNamespacesModification(string source, string } else if (directive == "elif") { - namespaceModificationFound = CheckForNamespaceModification(namespaceScopeStack, stack.Count); + namespaceModificationFound |= CheckForNamespaceModification(namespaceScopeStack, stack.Count); var evalResult = true; var elseEmitting = stack.Peek().Item2; stack.Pop(); diff --git a/Editor/Mono/Scripting/ScriptCompilation/CompilationPipeline.cs b/Editor/Mono/Scripting/ScriptCompilation/CompilationPipeline.cs index 440d3ed406..622938f475 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/CompilationPipeline.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/CompilationPipeline.cs @@ -284,8 +284,15 @@ public static string[] GetDefinesFromAssemblyName(string assemblyName) internal static string[] GetDefinesFromAssemblyName(EditorCompilation editorCompilation, string assemblyName) { - var assembly = GetAssemblies().FirstOrDefault(x => x.name == assemblyName); - return assembly?.defines; + try + { + var assembly = editorCompilation.GetCustomTargetAssemblyFromName(assemblyName); + return assembly?.Defines; + } + catch (ArgumentException) + { + return null; + } } public static string[] GetPrecompiledAssemblyNames() @@ -501,5 +508,15 @@ internal static string GetAssemblyDefinitionFilePathFromScriptPath(EditorCompila return null; } } + + public static void RequestScriptCompilation() + { + RequestScriptCompilation(EditorCompilationInterface.Instance); + } + + internal static void RequestScriptCompilation(EditorCompilation editorCompilation) + { + editorCompilation.DirtyAllScripts(); + } } } diff --git a/Editor/Mono/Scripting/ScriptCompilation/CompilationTask.cs b/Editor/Mono/Scripting/ScriptCompilation/CompilationTask.cs index 8468fe0497..1851f3337a 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/CompilationTask.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/CompilationTask.cs @@ -6,7 +6,9 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using UnityEditor.Scripting.Compilers; +using System.IO; namespace UnityEditor.Scripting.ScriptCompilation { @@ -14,7 +16,8 @@ namespace UnityEditor.Scripting.ScriptCompilation enum CompilationTaskOptions { None = 0, - StopOnFirstError = (1 << 0) + StopOnFirstError = (1 << 0), + RunPostProcessors = (1 << 1) } // CompilationTask represents one complete rebuild of all the ScriptAssembly's that are passed in the constructor. @@ -29,8 +32,13 @@ enum CompilionTaskState } HashSet pendingAssemblies; + Dictionary compiledAssemblies = new Dictionary(); Dictionary processedAssemblies = new Dictionary(); Dictionary compilerTasks = new Dictionary(); + List postProcessorTasks = new List(); + List pendingPostProcessorTasks = new List(); + + ScriptAssembly[] scriptAssemblies; string buildOutputDirectory; object context; int compilePhase = 0; @@ -38,11 +46,13 @@ enum CompilionTaskState CompilationTaskOptions compilationTaskOptions; int maxConcurrentCompilers; CompilionTaskState state = CompilionTaskState.Started; + IILPostProcessing ilPostProcessing; public event Action OnCompilationTaskStarted; public event Action OnCompilationTaskFinished; public event Action OnBeforeCompilationStarted; public event Action OnCompilationStarted; + public event Action OnPostProcessingStarted; public event Action> OnCompilationFinished; public bool Stopped { get; private set; } @@ -53,8 +63,10 @@ public CompilationTask(ScriptAssembly[] scriptAssemblies, object context, EditorScriptCompilationOptions options, CompilationTaskOptions compilationTaskOptions, - int maxConcurrentCompilers) + int maxConcurrentCompilers, + IILPostProcessing ilPostProcessing) { + this.scriptAssemblies = scriptAssemblies; pendingAssemblies = new HashSet(scriptAssemblies); CompileErrors = false; this.buildOutputDirectory = buildOutputDirectory; @@ -62,11 +74,20 @@ public CompilationTask(ScriptAssembly[] scriptAssemblies, this.options = options; this.compilationTaskOptions = compilationTaskOptions; this.maxConcurrentCompilers = maxConcurrentCompilers; + this.ilPostProcessing = ilPostProcessing; + } + + public ScriptAssembly[] ScriptAssemblies + { + get + { + return scriptAssemblies; + } } public bool IsCompiling { - get { return pendingAssemblies.Count > 0 || compilerTasks.Count > 0; } + get { return pendingAssemblies.Count > 0 || compilerTasks.Count > 0 || postProcessorTasks.Count > 0; } } public Dictionary CompilerMessages @@ -152,15 +173,21 @@ public bool Poll() var compiler = task.Value; var messages = compiler.GetCompilerMessages(); - - // Convert messages to list, OnCompilationFinished callbacks might add - // more messages var messagesList = messages.ToList(); - if (OnCompilationFinished != null) - OnCompilationFinished(assembly, messagesList); + compiledAssemblies.Add(assembly, messagesList.ToArray()); - processedAssemblies.Add(assembly, messagesList.ToArray()); + if (RunPostProcessors && !messagesList.Any(m => m.type == CompilerMessageType.Error)) + { + var postProcessorTask = new PostProcessorTask(assembly, messagesList, buildOutputDirectory, ilPostProcessing.PostProcess); + pendingPostProcessorTasks.Add(postProcessorTask); + } + else + { + // OnCompilationFinished callbacks might add more compiler messages + OnCompilationFinished?.Invoke(assembly, messagesList); + processedAssemblies.Add(assembly, messagesList.ToArray()); + } if (!CompileErrors) CompileErrors = messagesList.Any(m => m.type == CompilerMessageType.Error); @@ -169,6 +196,94 @@ public bool Poll() compiler.Dispose(); } + List startedPostProcessorTasks = null; + + // Check if any pending post processors can be run + foreach (var postProcessorTask in pendingPostProcessorTasks) + { + var assembly = postProcessorTask.Assembly; + + // We break out of this loop instead to continuing to ensure that + // OnCompilationFinished events are emitted in the same order as + // they finished compiling. + if (IsAnyProcessUsingAssembly(assembly)) + break; + + if (startedPostProcessorTasks == null) + { + startedPostProcessorTasks = new List(); + } + + startedPostProcessorTasks.Add(postProcessorTask); + + var assemblySourcePath = AssetPath.Combine(buildOutputDirectory, assembly.Filename); + var pdbSourcePath = AssetPath.Combine(buildOutputDirectory, assembly.PdbFilename); + + try + { + File.Copy(assemblySourcePath, assembly.FullPath, true); + File.Copy(pdbSourcePath, assembly.PdbFullPath, true); + + postProcessorTask.Poll(); + OnPostProcessingStarted?.Invoke(assembly); + + postProcessorTasks.Add(postProcessorTask); + } + catch (IOException e) + { + var messagesList = postProcessorTask.CompilerMessages; + + UnityEngine.Debug.LogError($"Fail to copy {assemblySourcePath} or {pdbSourcePath} to {AssetPath.GetDirectoryName(assembly.FullPath)} before post processing the assembly. Skipping post processing.\n{e}"); + // OnCompilationFinished callbacks might add more compiler messages + OnCompilationFinished?.Invoke(assembly, messagesList); + processedAssemblies.Add(assembly, messagesList.ToArray()); + } + } + + if (startedPostProcessorTasks != null) + foreach (var postProcessorTask in startedPostProcessorTasks) + { + pendingPostProcessorTasks.Remove(postProcessorTask); + } + + HashSet finishedPostProcessorTasks = null; + + foreach (var postProcessorTask in postProcessorTasks) + { + // We break out of this loop instead to continuing to ensure that + // OnCompilationFinished events are emitted in the same order as + // they finished compiling. + + if (!postProcessorTask.Poll()) + break; + + // Do not copy the post processed assembly in OnCompilationFinished + // if any of the running compilers have a reference to the assembly. + // As we might copy it while the compiler has the assembly open. + if (IsAnyProcessUsingAssembly(postProcessorTask.Assembly)) + break; + + var messagesList = postProcessorTask.CompilerMessages; + + // OnCompilationFinished callbacks might add more compiler messages + OnCompilationFinished?.Invoke(postProcessorTask.Assembly, messagesList); + processedAssemblies.Add(postProcessorTask.Assembly, messagesList.ToArray()); + + if (!CompileErrors) + CompileErrors = messagesList.Any(m => m.type == CompilerMessageType.Error); + + if (finishedPostProcessorTasks == null) + finishedPostProcessorTasks = new HashSet(); + + finishedPostProcessorTasks.Add(postProcessorTask); + } + + if (finishedPostProcessorTasks != null) + foreach (var finishedPostProcessorTask in finishedPostProcessorTasks) + { + postProcessorTasks.Remove(finishedPostProcessorTask); + } + // If StopOnFirstError is set, do not queue assemblies for compilation in case of compile errors. bool stopOnFirstError = (compilationTaskOptions & CompilationTaskOptions.StopOnFirstError) == CompilationTaskOptions.StopOnFirstError; @@ -176,28 +291,65 @@ public bool Poll() { pendingAssemblies.Clear(); - bool finishedRunningCompilerTasks = (compilerTasks.Count == 0); - - if (finishedRunningCompilerTasks) + if (FinishedCompilation) { HandleOnCompilationTaskFinished(); } - return finishedRunningCompilerTasks; + return FinishedCompilation; } // Queue pending assemblies for compilation if we have no running compilers or if compilers have finished. if (compilerTasks.Count == 0 || (finishedCompilerTasks != null && finishedCompilerTasks.Count > 0)) QueuePendingAssemblies(); - bool finishedCompilation = (pendingAssemblies.Count == 0 && compilerTasks.Count == 0); - - if (finishedCompilation) + if (FinishedCompilation) { HandleOnCompilationTaskFinished(); } - return finishedCompilation; + return FinishedCompilation; + } + + bool RunPostProcessors + { + get + { + return (compilationTaskOptions & CompilationTaskOptions.RunPostProcessors) > 0; + } + } + + bool FinishedCompilation + { + get + { + return pendingAssemblies.Count == 0 && compilerTasks.Count == 0 && postProcessorTasks.Count == 0; + } + } + + bool IsAnyProcessUsingAssembly(ScriptAssembly assembly) + { + if (AnyRunningCompilerHasReference(assembly)) + return true; + + if (ilPostProcessing != null && + ilPostProcessing.IsAnyRunningPostProcessorUsingAssembly(assembly)) + return true; + + return false; + } + + bool AnyRunningCompilerHasReference(ScriptAssembly assembly) + { + foreach (var compilerTask in compilerTasks) + { + var compilerAssembly = compilerTask.Key; + + if (compilerAssembly.ScriptAssemblyReferences.Contains(assembly)) + return true; + } + + return false; } void QueuePendingAssemblies() @@ -217,7 +369,7 @@ void QueuePendingAssemblies() { CompilerMessage[] messages; - if (!processedAssemblies.TryGetValue(reference, out messages)) + if (!compiledAssemblies.TryGetValue(reference, out messages)) { // If a reference is not compiling and not pending // also remove this assembly from pending. diff --git a/Editor/Mono/Scripting/ScriptCompilation/CustomScriptAssembly.cs b/Editor/Mono/Scripting/ScriptCompilation/CustomScriptAssembly.cs index 09b6224f83..f8303d4c22 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/CustomScriptAssembly.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/CustomScriptAssembly.cs @@ -4,13 +4,15 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using UnityEditor.Compilation; -using UnityEditor; +using UnityEditorInternal; using DiscoveredTargetInfo = UnityEditor.BuildTargetDiscovery.DiscoveredTargetInfo; namespace UnityEditor.Scripting.ScriptCompilation { + // https://docs.microsoft.com/en-us/cpp/windows/changing-a-symbol-or-symbol-name-id class SymbolNameRestrictions { private const int k_MaxLength = 247; @@ -22,18 +24,35 @@ public static bool IsValid(string name) return false; } - if (name.Length > k_MaxLength || - name.Contains(" ")) + if (name.Length > k_MaxLength) { return false; } - var firstChar = name[0]; - if (!Char.IsLetter(firstChar) && firstChar != '_') + // Invalid if the first character is a number. + if (char.IsNumber(name[0])) { return false; } + foreach (var chr in name) + { + // Skip if it's a letter. + if (char.IsLetter(chr)) + continue; + + // Skip if it's a number. + if (char.IsNumber(chr)) + continue; + + // Skip if it's an underscore. + if (chr == '_') + continue; + + // Invalid for unsupported characters. + return false; + } + return true; } } @@ -104,7 +123,7 @@ public void ValidateFields() (includePlatforms != null && includePlatforms.Length > 0)) throw new System.Exception("Both 'excludePlatforms' and 'includePlatforms' are set."); - if (autoReferenced && UnityCodeGenHelpers.IsCodeGen(name, includesExtension: false)) + if (autoReferenced && UnityCodeGenHelpers.IsCodeGen(name)) { throw new Exception($"Assembly '{name}' is a CodeGen assembly and cannot be Auto Referenced"); } @@ -197,11 +216,10 @@ public CustomScriptAssemblyPlatform(string name, string displayName, BuildTarget BuildTarget = buildTarget; } - public CustomScriptAssemblyPlatform(string name, BuildTarget buildTarget) : this(name, name, buildTarget) - { - } + public CustomScriptAssemblyPlatform(string name, BuildTarget buildTarget) : this(name, name, buildTarget) {} } + [DebuggerDisplay("{Name}")] class CustomScriptAssembly { public string FilePath { get; set; } @@ -210,8 +228,8 @@ class CustomScriptAssembly public string GUID { get; set; } public string[] References { get; set; } public string[] AdditionalPrefixes { get; set; } - public CustomScriptAssemblyPlatform[] IncludePlatforms { get; set; } - public CustomScriptAssemblyPlatform[] ExcludePlatforms { get; set; } + public CustomScriptAssemblyPlatform[] IncludePlatforms { get; set; } + public CustomScriptAssemblyPlatform[] ExcludePlatforms { get; set; } public AssetPathMetaData AssetPathMetaData { get; set; } public ScriptCompilerOptions CompilerOptions { get; set; } = new ScriptCompilerOptions(); @@ -227,7 +245,7 @@ public AssemblyFlags AssemblyFlags { get { - var assemblyFlags = AssemblyFlags.None; + var assemblyFlags = AssemblyFlags.UserAssembly; if (IncludePlatforms != null && IncludePlatforms.Length == 1 && IncludePlatforms[0].BuildTarget == BuildTarget.NoTarget) assemblyFlags |= AssemblyFlags.EditorOnly; @@ -258,6 +276,7 @@ static CustomScriptAssembly() { // When removing a platform from Platforms, please add it to DeprecatedPlatforms. DiscoveredTargetInfo[] buildTargetList = BuildTargetDiscovery.GetBuildTargetInfoList(); + // Need extra slot for Editor which is not included in the build target list Platforms = new CustomScriptAssemblyPlatform[buildTargetList.Length + 1]; Platforms[0] = new CustomScriptAssemblyPlatform("Editor", BuildTarget.NoTarget); @@ -306,7 +325,19 @@ public bool IsCompatibleWith(BuildTarget buildTarget, EditorScriptCompilationOpt } if (defines != null && defines.Length == 0) - throw new ArgumentException("defines cannot be empty", "defines"); + throw new ArgumentException("Defines cannot be empty", "defines"); + + // Log invalid define constraints + if (DefineConstraints != null) + { + for (var i = 0; i < DefineConstraints.Length; ++i) + { + if (!DefineConstraintsHelper.IsDefineConstraintValid(DefineConstraints[i])) + { + throw new AssemblyDefinitionException($"Invalid Define Constraint: \"{DefineConstraints[i]}\" at line {(i+1).ToString()}", FilePath); + } + } + } if (!DefineConstraintsHelper.IsDefineConstraintsCompatible(defines, DefineConstraints)) { @@ -338,7 +369,7 @@ public static CustomScriptAssembly Create(string name, string directory) var modifiedDirectory = AssetPath.ReplaceSeparators(directory); if (modifiedDirectory.Last() != AssetPath.Separator) - modifiedDirectory += AssetPath.Separator; + modifiedDirectory += AssetPath.Separator.ToString(); customScriptAssembly.Name = name; customScriptAssembly.FilePath = modifiedDirectory; diff --git a/Editor/Mono/Scripting/ScriptCompilation/DefineConstraintsHelper.cs b/Editor/Mono/Scripting/ScriptCompilation/DefineConstraintsHelper.cs index 5af84d5a28..a9c5f1a00b 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/DefineConstraintsHelper.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/DefineConstraintsHelper.cs @@ -5,15 +5,26 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; +using UnityEditorInternal; using UnityEngine.Scripting; namespace UnityEditor.Scripting.ScriptCompilation { - internal static class DefineConstraintsHelper + static class DefineConstraintsHelper { public const string Not = "!"; public const string Or = "||"; + public static readonly char[] k_ValidWhitespaces = { ' ', '\t' }; + + public enum DefineConstraintStatus + { + Compatible, + Incompatible, + Invalid, + } + [RequiredByNativeCode] public static bool IsDefineConstraintsCompatible(string[] defines, string[] defineConstraints) { @@ -23,35 +34,68 @@ public static bool IsDefineConstraintsCompatible(string[] defines, string[] defi } bool[] defineConstraintsValidity; - GetDefineConstraintsValidity(defines, defineConstraints, out defineConstraintsValidity); + GetDefineConstraintsCompatibility(defines, defineConstraints, out defineConstraintsValidity); return defineConstraintsValidity.All(c => c); } - static void GetDefineConstraintsValidity(string[] defines, string[] defineConstraints, out bool[] defineConstraintsValidity) + static void GetDefineConstraintsCompatibility(string[] defines, string[] defineConstraints, out bool[] defineConstraintsValidity) { defineConstraintsValidity = new bool[defineConstraints.Length]; for (int i = 0; i < defineConstraints.Length; ++i) { - defineConstraintsValidity[i] = IsDefineConstraintValid(defines, defineConstraints[i]); + defineConstraintsValidity[i] = GetDefineConstraintCompatibility(defines, defineConstraints[i]) == DefineConstraintStatus.Compatible; } } - internal static bool IsDefineConstraintValid(string[] defines, string defineConstraints) + internal static DefineConstraintStatus GetDefineConstraintCompatibility(string[] defines, string defineConstraints) { - var splitDefines = new HashSet(defineConstraints.Split(new[] { Or }, StringSplitOptions.RemoveEmptyEntries)); + // Split by "||" (OR) and keep it in the resulting array + var splitDefines = Regex.Split(defineConstraints, "(\\|\\|)"); - var notExpectedDefines = new HashSet(splitDefines.Where(x => x.StartsWith(Not)).Select(x => x.Substring(1))); - var expectedDefines = new HashSet(splitDefines.Where(x => !x.StartsWith(Not))); + // Trim what we consider valid space characters + for (var i = 0; i < splitDefines.Length; ++i) + { + splitDefines[i] = splitDefines[i].Trim(k_ValidWhitespaces); + } + + // Check for consecutive OR + for (var i = 0; i < splitDefines.Length; ++i) + { + if (splitDefines[i] == Or && (i < splitDefines.Length - 1 && splitDefines[i + 1] == Or)) + { + return DefineConstraintStatus.Invalid; + } + } + + var notExpectedDefines = new HashSet(splitDefines.Where(x => x.StartsWith(Not) && x != Or).Select(x => x.Substring(1))); + var expectedDefines = new HashSet(splitDefines.Where(x => !x.StartsWith(Not) && x != Or)); if (defines == null) { if (expectedDefines.Count > 0) { - return false; + return DefineConstraintStatus.Incompatible; + } + + return DefineConstraintStatus.Compatible; + } + + foreach (var define in expectedDefines) + { + if (!SymbolNameRestrictions.IsValid(define)) + { + return DefineConstraintStatus.Invalid; + } + } + + foreach (var define in notExpectedDefines) + { + if (!SymbolNameRestrictions.IsValid(define)) + { + return DefineConstraintStatus.Invalid; } - return true; } if (expectedDefines.Overlaps(notExpectedDefines)) @@ -61,17 +105,45 @@ internal static bool IsDefineConstraintValid(string[] defines, string defineCons notExpectedDefines.ExceptWith(complement); } + var expectedDefinesResult = expectedDefines.Any(defines.Contains) ? DefineConstraintStatus.Compatible : DefineConstraintStatus.Incompatible; + if (expectedDefines.Count > 0 && notExpectedDefines.Count == 0) + { + return expectedDefinesResult; + } + + var notExpectedDefinesResult = notExpectedDefines.Any(defines.Contains) ? DefineConstraintStatus.Incompatible : DefineConstraintStatus.Compatible; if (notExpectedDefines.Count > 0 && expectedDefines.Count == 0) { - return !notExpectedDefines.Any(defines.Contains); + return notExpectedDefinesResult; } - if (expectedDefines.Count > 0 && notExpectedDefines.Count == 0) + if (expectedDefinesResult == DefineConstraintStatus.Compatible || notExpectedDefinesResult == DefineConstraintStatus.Compatible) { - return expectedDefines.Any(defines.Contains); + return DefineConstraintStatus.Compatible; + } + + return DefineConstraintStatus.Incompatible; + } + + internal static bool IsDefineConstraintValid(string define) + { + if (define == null) + { + return false; + } + + // Split define by OR symbol + var splitDefines = define.Split(new[] { Or }, StringSplitOptions.RemoveEmptyEntries); + foreach (var d in splitDefines) + { + var finalDefine = (d.StartsWith(Not) ? d.Substring(1) : d).Trim(); + if (!SymbolNameRestrictions.IsValid(finalDefine)) + { + return false; + } } - return expectedDefines.Any(defines.Contains) || !notExpectedDefines.Any(defines.Contains); + return true; } } } diff --git a/Editor/Mono/Scripting/ScriptCompilation/DirtySource.cs b/Editor/Mono/Scripting/ScriptCompilation/DirtySource.cs new file mode 100644 index 0000000000..69d01b4854 --- /dev/null +++ b/Editor/Mono/Scripting/ScriptCompilation/DirtySource.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 System; +using UnityEngine.Bindings; + +namespace UnityEditor.Scripting.ScriptCompilation +{ + internal enum DirtySource + { + None = 0, + DirtyScript = 1, + DirtyAssembly = 2, + DirtyReference = 3, + } +} diff --git a/Editor/Mono/Scripting/ScriptCompilation/EditorBuildRules.cs b/Editor/Mono/Scripting/ScriptCompilation/EditorBuildRules.cs index f779f65259..f3f0c9fddb 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/EditorBuildRules.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/EditorBuildRules.cs @@ -272,14 +272,8 @@ public static Dictionary CreateTargetAssemblies(IEnumera ILookup filenameGroupedWithPrecompiledAssembly = userPrecompiledAssemblies.ToLookup(x => AssetPath.GetFileName(x.Path), x => x); foreach (IGrouping groupedPrecompiledAssemblies in filenameGroupedWithPrecompiledAssembly) { - if (groupedPrecompiledAssemblies.Count() > 1) - { - var pathsString = string.Join(", ", groupedPrecompiledAssemblies.Select(x => x.Path).ToArray()); - - throw new PrecompiledAssemblyException( - $"Multiple precompiled assemblies with the same name {groupedPrecompiledAssemblies.Key} included for the current platform. Only one assembly with the same name is allowed per platform. Assembly paths: {pathsString}"); - } - nameToPrecompiledAssemblies.Add(groupedPrecompiledAssemblies.Key, groupedPrecompiledAssemblies.Single()); + if (groupedPrecompiledAssemblies.Count() == 1) + nameToPrecompiledAssemblies.Add(groupedPrecompiledAssemblies.Key, groupedPrecompiledAssemblies.Single()); } // Setup references for TargetAssemblies @@ -336,7 +330,7 @@ public static ScriptAssembly[] GetAllScriptAssemblies(Dictionary if (allSourceFiles == null || allSourceFiles.Count() == 0) return new ScriptAssembly[0]; - var targetAssemblyFiles = new Dictionary>(); + var targetAssemblyFiles = new Dictionary(); foreach (var entry in allSourceFiles) { @@ -360,39 +354,44 @@ public static ScriptAssembly[] GetAllScriptAssemblies(Dictionary if (targetAssembly.Language == null && targetAssembly.Type == TargetAssemblyType.Custom) targetAssembly.Language = scriptLanguage; - HashSet assemblySourceFiles; + DirtyTargetAssembly dirtyTargetAssembly; - if (!targetAssemblyFiles.TryGetValue(targetAssembly, out assemblySourceFiles)) + if (!targetAssemblyFiles.TryGetValue(targetAssembly, out dirtyTargetAssembly)) { - assemblySourceFiles = new HashSet(); - targetAssemblyFiles[targetAssembly] = assemblySourceFiles; + dirtyTargetAssembly = new DirtyTargetAssembly(DirtySource.None); + targetAssemblyFiles[targetAssembly] = dirtyTargetAssembly; } - assemblySourceFiles.Add(AssetPath.Combine(projectDirectory, scriptFile)); + dirtyTargetAssembly.SourceFiles.Add(AssetPath.Combine(projectDirectory, scriptFile)); } - return ToScriptAssemblies(targetAssemblyFiles, settings, assemblies, runUpdaterAssemblies).ScriptAssemblies; + return ToScriptAssemblies(targetAssemblyFiles, settings, assemblies, runUpdaterAssemblies); } - public class ScriptAssembliesResult + internal class DirtyTargetAssembly { - public ScriptAssembliesResult(ScriptAssembly[] scriptAssemblies, bool pendingCodeGenAssembly) + public DirtyTargetAssembly(DirtySource dirtySource) { - ScriptAssemblies = scriptAssemblies; - PendingCodeGenAssembly = pendingCodeGenAssembly; + SourceFiles = new HashSet(); + DirtySource = dirtySource; } - public bool PendingCodeGenAssembly { get; private set; } - public ScriptAssembly[] ScriptAssemblies { get; private set; } + public HashSet SourceFiles { get; set; } + public DirtySource DirtySource { get; set; } } - public static ScriptAssembliesResult GenerateChangedScriptAssemblies(GenerateChangedScriptAssembliesArgs args) + public static ScriptAssembly[] GenerateChangedScriptAssemblies(GenerateChangedScriptAssembliesArgs args) { - var dirtyTargetAssemblies = new Dictionary>(); + var dirtyTargetAssemblies = new Dictionary(); // Add initial dirty target assemblies foreach (var dirtyTargetAssembly in args.DirtyTargetAssemblies) - dirtyTargetAssemblies[dirtyTargetAssembly] = new HashSet(); + { + if (!IsCompatibleWithPlatformAndDefines(dirtyTargetAssembly, args.Settings)) + continue; + + dirtyTargetAssemblies[dirtyTargetAssembly] = new DirtyTargetAssembly(DirtySource.DirtyAssembly); + } // Dirty custom script assemblies that have explicit references to // explicitly referenced dirty precompiled assemblies. @@ -407,7 +406,8 @@ public static ScriptAssembliesResult GenerateChangedScriptAssemblies(GenerateCha var customTargetAssembly = entry.Value; if (customTargetAssembly.PrecompiledReferences.Contains(dirtyPrecompiledAssembly)) { - dirtyTargetAssemblies[customTargetAssembly] = new HashSet(); + dirtyTargetAssemblies[customTargetAssembly] = new DirtyTargetAssembly(DirtySource.DirtyReference); + break; } } @@ -423,7 +423,7 @@ public static ScriptAssembliesResult GenerateChangedScriptAssemblies(GenerateCha foreach (var assemblyFilename in args.RunUpdaterAssemblies) { var targetAssembly = allTargetAssemblies.First(a => a.Filename == assemblyFilename); - dirtyTargetAssemblies[targetAssembly] = new HashSet(); + dirtyTargetAssemblies[targetAssembly] = new DirtyTargetAssembly(DirtySource.DirtyAssembly); } // Collect all dirty TargetAssemblies @@ -442,7 +442,7 @@ public static ScriptAssembliesResult GenerateChangedScriptAssemblies(GenerateCha if (!IsCompatibleWithPlatformAndDefines(targetAssembly, args.Settings)) continue; - HashSet assemblySourceFiles; + DirtyTargetAssembly dirtyTargetAssembly; var scriptExtension = ScriptCompilers.GetExtensionOfSourceFile(dirtySourceFile); SupportedLanguage scriptLanguage = null; @@ -459,16 +459,16 @@ public static ScriptAssembliesResult GenerateChangedScriptAssemblies(GenerateCha continue; } - if (!dirtyTargetAssemblies.TryGetValue(targetAssembly, out assemblySourceFiles)) + if (!dirtyTargetAssemblies.TryGetValue(targetAssembly, out dirtyTargetAssembly)) { - assemblySourceFiles = new HashSet(); - dirtyTargetAssemblies[targetAssembly] = assemblySourceFiles; + dirtyTargetAssembly = new DirtyTargetAssembly(DirtySource.DirtyScript); + dirtyTargetAssemblies[targetAssembly] = dirtyTargetAssembly; if (targetAssembly.Type == TargetAssemblyType.Custom) targetAssembly.Language = scriptLanguage; } - assemblySourceFiles.Add(AssetPath.Combine(args.ProjectDirectory, dirtySourceFile)); + dirtyTargetAssembly.SourceFiles.Add(AssetPath.Combine(args.ProjectDirectory, dirtySourceFile)); if (targetAssembly.Language == null && targetAssembly.Type == TargetAssemblyType.Custom) targetAssembly.Language = scriptLanguage; @@ -496,13 +496,13 @@ public static ScriptAssembliesResult GenerateChangedScriptAssemblies(GenerateCha continue; if (!dirtyTargetAssemblies.ContainsKey(assembly)) - dirtyTargetAssemblies[assembly] = new HashSet(); + dirtyTargetAssemblies[assembly] = new DirtyTargetAssembly(DirtySource.DirtyReference); } } // Return empty array in case of no dirty target assemblies if (dirtyTargetAssemblies.Count == 0) - return new ScriptAssembliesResult(new ScriptAssembly[0], false); + return new ScriptAssembly[0]; // Collect any TargetAssemblies that reference the dirty TargetAssemblies, as they will also be dirty. int dirtyAssemblyCount; @@ -523,7 +523,7 @@ public static ScriptAssembliesResult GenerateChangedScriptAssemblies(GenerateCha foreach (var reference in assembly.References) if (dirtyTargetAssemblies.ContainsKey(reference)) { - dirtyTargetAssemblies[assembly] = new HashSet(); + dirtyTargetAssemblies[assembly] = new DirtyTargetAssembly(DirtySource.DirtyReference); dirtyAssemblyCount++; break; } @@ -547,8 +547,6 @@ public static ScriptAssembliesResult GenerateChangedScriptAssemblies(GenerateCha if (!IsCompatibleWithPlatformAndDefines(targetAssembly, args.Settings)) continue; - HashSet assemblySourceFiles; - var scriptExtension = ScriptCompilers.GetExtensionOfSourceFile(sourceFile); var scriptLanguage = ScriptCompilers.GetLanguageFromExtension(scriptExtension); @@ -559,8 +557,9 @@ public static ScriptAssembliesResult GenerateChangedScriptAssemblies(GenerateCha if (scriptLanguage != targetAssembly.Language) args.NotCompiledTargetAssemblies.Add(targetAssembly); - if (dirtyTargetAssemblies.TryGetValue(targetAssembly, out assemblySourceFiles)) - assemblySourceFiles.Add(AssetPath.Combine(args.ProjectDirectory, sourceFile)); + DirtyTargetAssembly dirtyTargetAssembly; + if (dirtyTargetAssemblies.TryGetValue(targetAssembly, out dirtyTargetAssembly)) + dirtyTargetAssembly.SourceFiles.Add(AssetPath.Combine(args.ProjectDirectory, sourceFile)); } // Remove any target assemblies which have no source files associated with them. @@ -568,7 +567,7 @@ public static ScriptAssembliesResult GenerateChangedScriptAssemblies(GenerateCha foreach (var entry in dirtyTargetAssemblies) { - if (entry.Value.Count == 0 && entry.Key.Type == TargetAssemblyType.Custom) + if (entry.Value.SourceFiles.Count == 0 && entry.Key.Type == TargetAssemblyType.Custom) { noScriptsCustomTargetAssemblies.Add(entry.Key); } @@ -576,7 +575,7 @@ public static ScriptAssembliesResult GenerateChangedScriptAssemblies(GenerateCha args.NoScriptsCustomTargetAssemblies = noScriptsCustomTargetAssemblies.ToArray(); - dirtyTargetAssemblies = dirtyTargetAssemblies.Where(e => e.Value.Count > 0).ToDictionary(e => e.Key, e => e.Value); + dirtyTargetAssemblies = dirtyTargetAssemblies.Where(e => e.Value.SourceFiles.Count > 0).ToDictionary(e => e.Key, e => e.Value); // Remove any target assemblies which have been marked as do not compile. foreach (var removeAssembly in args.NotCompiledTargetAssemblies) @@ -587,9 +586,60 @@ public static ScriptAssembliesResult GenerateChangedScriptAssemblies(GenerateCha return scriptAssemblies; } - internal static ScriptAssembliesResult ToScriptAssemblies(IDictionary> targetAssemblies, ScriptAssemblySettings settings, + private static Dictionary ValidateAndGetNameToPrecompiledAssembly(PrecompiledAssembly[] precompiledAssemblies) + { + if (precompiledAssemblies == null) + { + return new Dictionary(0); + } + + Dictionary fileNameToUserPrecompiledAssemblies = new Dictionary(precompiledAssemblies.Length); + + var sameNamedPrecompiledAssemblies = new Dictionary>(precompiledAssemblies.Length); + for (int i = 0; i < precompiledAssemblies.Length; i++) + { + var precompiledAssembly = precompiledAssemblies[i]; + + var fileName = AssetPath.GetFileName(precompiledAssembly.Path); + if (!fileNameToUserPrecompiledAssemblies.ContainsKey(fileName)) + { + fileNameToUserPrecompiledAssemblies.Add(fileName, precompiledAssembly); + } + else + { + if (!sameNamedPrecompiledAssemblies.ContainsKey(fileName)) + { + sameNamedPrecompiledAssemblies.Add(fileName, new List + { + fileNameToUserPrecompiledAssemblies[fileName].Path + }); + } + sameNamedPrecompiledAssemblies[fileName].Add(precompiledAssembly.Path); + } + } + + foreach (var precompiledAssemblyNameToIndexes in sameNamedPrecompiledAssemblies) + { + string paths = string.Empty; + foreach (var precompiledPath in precompiledAssemblyNameToIndexes.Value) + { + paths += $"{Environment.NewLine}{precompiledPath}"; + } + + throw new PrecompiledAssemblyException( + $"Multiple precompiled assemblies with the same name {precompiledAssemblyNameToIndexes.Key} included or the current platform. Only one assembly with the same name is allowed per platform. Assembly paths: {paths}", paths); + } + + return fileNameToUserPrecompiledAssemblies; + } + + internal static ScriptAssembly[] ToScriptAssemblies(IDictionary targetAssemblies, ScriptAssemblySettings settings, CompilationAssemblies assemblies, HashSet runUpdaterAssemblies) { + // Use extra validation to identify multiple precompiled assemblies with the same name, and throw an exception + // This will cause script recompilation, preventing potential crashes when importing dependent assets + ValidateAndGetNameToPrecompiledAssembly(assemblies.PrecompiledAssemblies); + var scriptAssemblies = new ScriptAssembly[targetAssemblies.Count]; var targetToScriptAssembly = new Dictionary(); @@ -599,7 +649,7 @@ internal static ScriptAssembliesResult ToScriptAssemblies(IDictionary ScriptAssembly mapping for converting references @@ -612,6 +662,7 @@ internal static ScriptAssembliesResult ToScriptAssemblies(IDictionary stopWatchDict = new Dictionary(); bool areAllScriptsDirty; bool areAllPrecompiledAssembliesDirty; string projectDirectory = string.Empty; @@ -188,6 +187,8 @@ public override void PostprocessMessage(ref CompilerMessage message) public bool IsCodeGenAssemblyChanged { get; set; } + public ILPostProcessing ILPostProcessing = new ILPostProcessing(); + static EditorCompilation() {} public void Initialize() @@ -195,6 +196,8 @@ public void Initialize() // Initialize maxConcurrentCompilers if it hasn't been set already. if (maxConcurrentCompilers == 0) SetMaxConcurrentCompilers(UnityEngine.SystemInfo.processorCount); + + ILPostProcessing.ILPostProcessors = ILPostProcessing.FindAllPostProcessors(); } public void SetMaxConcurrentCompilers(int maxCompilers) @@ -222,6 +225,7 @@ internal void SetAssetPathsMetaData(AssetPathMetaData[] assetPathMetaDatas) .SelectMany(x => x.VersionMetaDatas ?? new AssetPathVersionMetaData[0]) .Distinct(assetPathVersionMetaDataComparer) .ToDictionary(x => x.Name, x => x.Version); + UpdateCustomTargetAssembliesAssetPathsMetaData(customScriptAssemblies, assetPathMetaDatas, forceUpdate: true); } internal AssetPathMetaData[] GetAssetPathsMetaData() @@ -277,13 +281,60 @@ public void DirtyAllScripts() areAllScriptsDirty = true; } - public void DirtyAllNonCodeGenAssemblies() + public class ILPostProcessCompiledTargetAssembly : ILPostProcessCompiledAssembly { - foreach (KeyValuePair customTargetAssembly in customTargetAssemblies) + public EditorBuildRules.TargetAssembly TargetAssembly { get; private set; } + + public ILPostProcessCompiledTargetAssembly(EditorBuildRules.TargetAssembly targetAssembly, string outputPath) + : base(targetAssembly, outputPath) { - if (!UnityCodeGenHelpers.IsCodeGen(customTargetAssembly.Key)) + TargetAssembly = targetAssembly; + } + } + + public void DirtyAllNonCodeGenAssemblies(EditorScriptCompilationOptions additionalOptions) + { + var ilPostProcessors = ILPostProcessing.ILPostProcessors; + + if (ilPostProcessors.Length == 0) + { + return; + } + + var scriptAssemblySettings = CreateEditorScriptAssemblySettings(EditorScriptCompilationOptions.BuildingForEditor | additionalOptions); + + var allTargetAssemblies = new Dictionary(customTargetAssemblies); + + foreach (var predefinedTargetAssembly in EditorBuildRules.GetPredefinedTargetAssemblies()) + { + allTargetAssemblies.Add(predefinedTargetAssembly.Filename, predefinedTargetAssembly); + } + + var ilCompiledAssemblies = new List(customScriptAssemblies.Length); + + foreach (var entry in allTargetAssemblies) + { + var targetAssembly = entry.Value; + + if (UnityCodeGenHelpers.IsCodeGen(targetAssembly.Filename)) + continue; + + if (dirtyTargetAssemblies.Contains(targetAssembly)) + continue; + + var compiledAssembly = new ILPostProcessCompiledTargetAssembly(targetAssembly, outputDirectory); + ilCompiledAssemblies.Add(compiledAssembly); + } + + foreach (var ilCompiledAssembly in ilCompiledAssemblies) + { + foreach (var ilPostProcessor in ilPostProcessors) { - dirtyTargetAssemblies.Add(customTargetAssembly.Value); + if (!ilPostProcessor.WillProcess(ilCompiledAssembly)) + continue; + + dirtyTargetAssemblies.Add(ilCompiledAssembly.TargetAssembly); + break; } } } @@ -566,6 +617,83 @@ public TargetAssemblyInfo[] GetAllCompiledAndResolvedCustomTargetAssemblies(Edit return targetAssemblies; } + public string[] GetCompiledAssemblyGraph(string assemblyName) + { + if (assemblyName == null) + throw new ArgumentNullException("assemblyName"); + + if (compilationTask == null) + throw new InvalidOperationException("Cannot call GetCompiledAssemblyGraph without having an active CompilationTask"); + + var compiledScriptAssemblies = compilationTask.ScriptAssemblies; + + assemblyName = AssetPath.GetAssemblyNameWithoutExtension(assemblyName); + ScriptAssembly scriptAssembly = compiledScriptAssemblies.SingleOrDefault(a => AssetPath.GetAssemblyNameWithoutExtension(a.Filename) == assemblyName); + + if (scriptAssembly == null) + throw new ArgumentException($"Could not find assembly name '{assemblyName}' in GetCompiledAssemblyGraph."); + + // Build a dictionary with the set of compiled referencing assemblies for each compiled assembly. + var referencingAssemblies = new Dictionary>(); + + foreach (var compiledScriptAssembly in compiledScriptAssemblies) + { + foreach (var referenceAssembly in compiledScriptAssembly.ScriptAssemblyReferences) + { + HashSet referencingScriptAssemblies; + + if (!referencingAssemblies.TryGetValue(referenceAssembly, out referencingScriptAssemblies)) + { + referencingScriptAssemblies = new HashSet(); + referencingAssemblies[referenceAssembly] = referencingScriptAssemblies; + } + + referencingScriptAssemblies.Add(compiledScriptAssembly); + } + } + + HashSet referencing; + + // If there are no referencing assemblies, just return the single assembly + if (!referencingAssemblies.TryGetValue(scriptAssembly, out referencing)) + { + return new string[] { scriptAssembly.Filename }; + } + + // Find all direct and indirect referencing assemblies, e.g. the + // entire graph of assemblies that would be recompiled if this assembly + // was recompiled. + HashSet result = new HashSet(); + + result.Add(scriptAssembly.Filename); + + List visit = new List(referencing); + + while (visit.Count > 0) + { + int lastIndex = visit.Count - 1; + var visitAssembly = visit[lastIndex]; + visit.RemoveAt(lastIndex); + + result.Add(visitAssembly.Filename); + + if (!referencingAssemblies.TryGetValue(visitAssembly, out referencing)) + { + continue; + } + + foreach (var referencingAssembly in referencing) + { + if (result.Contains(referencingAssembly.Filename)) + continue; + + visit.Add(referencingAssembly); + } + } + + return result.ToArray(); + } + static CustomScriptAssembly LoadCustomScriptAssemblyFromJsonPath(string path, string guid) { var json = Utility.ReadTextAsset(path); @@ -711,7 +839,7 @@ public static Exception[] UpdateCustomScriptAssemblies(CustomScriptAssembly[] cu AssetPathMetaData[] assetPathsMetaData) { var asmrefLookup = customScriptAssemblyReferences.ToLookup(x => x.Reference); - var exceptions = new List(); + // Add AdditionalPrefixes foreach (var assembly in customScriptAssemblies) @@ -722,49 +850,60 @@ public static Exception[] UpdateCustomScriptAssemblies(CustomScriptAssembly[] cu assembly.AdditionalPrefixes = foundAsmRefs.Any() ? foundAsmRefs.Select(ar => ar.PathPrefix).ToArray() : null; } - // Add AssetPathMetaData - if (assetPathsMetaData != null) + var exceptions = UpdateCustomTargetAssembliesAssetPathsMetaData(customScriptAssemblies, assetPathsMetaData); + return exceptions.ToArray(); + } + + static Exception[] UpdateCustomTargetAssembliesAssetPathsMetaData(CustomScriptAssembly[] customScriptAssemblies, + AssetPathMetaData[] assetPathsMetaData, bool forceUpdate = false) + { + if (assetPathsMetaData == null) + { + return new Exception[0]; + } + + var exceptions = new List(); + var assetMetaDataPaths = new string[assetPathsMetaData.Length]; + var lowerAssetMetaDataPaths = new string[assetPathsMetaData.Length]; + + for (int i = 0; i < assetPathsMetaData.Length; ++i) { - var assetMetaDataPaths = new string[assetPathsMetaData.Length]; - var lowerAssetMetaDataPaths = new string[assetPathsMetaData.Length]; + var assetPathMetaData = assetPathsMetaData[i]; + assetMetaDataPaths[i] = AssetPath.ReplaceSeparators(assetPathMetaData.DirectoryPath + AssetPath.Separator); + lowerAssetMetaDataPaths[i] = Utility.FastToLower(assetMetaDataPaths[i]); + } - for (int i = 0; i < assetPathsMetaData.Length; ++i) + foreach (var assembly in customScriptAssemblies) + { + if (assembly.AssetPathMetaData != null && !forceUpdate) { - var assetPathMetaData = assetPathsMetaData[i]; - assetMetaDataPaths[i] = AssetPath.ReplaceSeparators(assetPathMetaData.DirectoryPath + AssetPath.Separator); - lowerAssetMetaDataPaths[i] = Utility.FastToLower(assetMetaDataPaths[i]); + continue; } - foreach (var assembly in customScriptAssemblies) + try { - try + for (int i = 0; i < assetMetaDataPaths.Length; ++i) { - if (assembly.AssetPathMetaData == null) + var path = assetMetaDataPaths[i]; + var lowerPath = lowerAssetMetaDataPaths[i]; + + if (Utility.FastStartsWith(assembly.PathPrefix, path, lowerPath)) { - for (int i = 0; i < assetMetaDataPaths.Length; ++i) - { - var path = assetMetaDataPaths[i]; - var lowerPath = lowerAssetMetaDataPaths[i]; - - if (Utility.FastStartsWith(assembly.PathPrefix, path, lowerPath)) - { - assembly.AssetPathMetaData = assetPathsMetaData[i]; - break; - } - } + assembly.AssetPathMetaData = assetPathsMetaData[i]; + break; } } - catch (Exception e) - { - exceptions.Add(e); - } + } + catch (Exception e) + { + exceptions.Add(e); } } return exceptions.ToArray(); } - Exception[] UpdateCustomTargetAssemblies() + Exception[] UpdateCustomTargetAssemblies(bool forceUpdateAssetMetadata = false) { var exceptions = UpdateCustomScriptAssemblies(customScriptAssemblies, customScriptAssemblyReferences, m_AssetPathsMetaData); @@ -828,6 +967,12 @@ public void SkipCustomScriptAssemblyGraphValidation(bool skipChecks) skipCustomScriptAssemblyGraphValidation = skipChecks; } + public void ClearCustomScriptAssemblies() + { + customScriptAssemblies = null; + customScriptAssemblyReferences.Clear(); + } + public Exception[] SetAllCustomScriptAssemblyReferenceJsons(string[] paths) { return SetAllCustomScriptAssemblyReferenceJsonsContents(paths, null); @@ -939,7 +1084,7 @@ public Exception[] SetAllCustomScriptAssemblyJsonContents(string[] paths, string HashSet predefinedAssemblyNames = null; // To check if a path prefix is already being used we use a Dictionary where the key is the prefix and the value is the file path. - var prefixToFilePathLookup = customScriptAssemblyReferences.ToDictionary(x => x.PathPrefix, x => new List(){ x.FilePath }, StringComparer.OrdinalIgnoreCase); + var prefixToFilePathLookup = customScriptAssemblyReferences.ToDictionary(x => x.PathPrefix, x => new List() { x.FilePath }, StringComparer.OrdinalIgnoreCase); ClearCompilationSetupErrorFlags(CompilationSetupErrorFlags.loadError); @@ -1309,14 +1454,14 @@ public CustomScriptAssembly FindCustomScriptAssemblyFromAssemblyReference(string throw new InvalidOperationException($"Cannot find CustomScriptAssembly with reference '{reference}'"); } - public CompileStatus CompileScripts(EditorScriptCompilationOptions options, BuildTargetGroup platformGroup, BuildTarget platform) + public CompileStatus CompileScripts(EditorScriptCompilationOptions options, BuildTargetGroup platformGroup, BuildTarget platform, CompilationTaskOptions compilationTaskOptions = CompilationTaskOptions.StopOnFirstError) { var scriptAssemblySettings = CreateScriptAssemblySettings(platformGroup, platform, options); EditorBuildRules.TargetAssembly[] notCompiledTargetAssemblies = null; string[] notCompiledScripts = null; - var result = CompileScripts(scriptAssemblySettings, EditorTempPath, options, ref notCompiledTargetAssemblies, ref notCompiledScripts); + var result = CompileScripts(scriptAssemblySettings, EditorTempPath, options, compilationTaskOptions, ref notCompiledTargetAssemblies, ref notCompiledScripts); if (notCompiledTargetAssemblies != null) { @@ -1354,7 +1499,7 @@ private static EditorBuildRules.TargetAssembly[] GetPredefinedAssemblyReferences return targetAssembliesResult; } - internal CompileStatus CompileScripts(ScriptAssemblySettings scriptAssemblySettings, string tempBuildDirectory, EditorScriptCompilationOptions options, ref EditorBuildRules.TargetAssembly[] notCompiledTargetAssemblies, ref string[] notCompiledScripts) + internal CompileStatus CompileScripts(ScriptAssemblySettings scriptAssemblySettings, string tempBuildDirectory, EditorScriptCompilationOptions options, CompilationTaskOptions compilationTaskOptions, ref EditorBuildRules.TargetAssembly[] notCompiledTargetAssemblies, ref string[] notCompiledScripts) { DeleteUnusedAssemblies(scriptAssemblySettings); @@ -1386,8 +1531,7 @@ internal CompileStatus CompileScripts(ScriptAssemblySettings scriptAssemblySetti RunUpdaterAssemblies = runScriptUpdaterAssemblies }; - EditorBuildRules.ScriptAssembliesResult changedScriptAssemblies = EditorBuildRules.GenerateChangedScriptAssemblies(args); - ScriptAssembly[] scriptAssemblies = changedScriptAssemblies.ScriptAssemblies; + ScriptAssembly[] scriptAssemblies = EditorBuildRules.GenerateChangedScriptAssemblies(args); foreach (var customTargetAssembly in args.NoScriptsCustomTargetAssemblies) { @@ -1424,7 +1568,7 @@ internal CompileStatus CompileScripts(ScriptAssemblySettings scriptAssemblySetti if (!scriptAssemblies.Any()) return CompileStatus.Idle; - bool compiling = CompileScriptAssemblies(scriptAssemblies, scriptAssemblySettings, tempBuildDirectory, options, CompilationTaskOptions.StopOnFirstError, CompileScriptAssembliesOptions.none, changedScriptAssemblies.PendingCodeGenAssembly); + bool compiling = CompileScriptAssemblies(scriptAssemblies, scriptAssemblySettings, tempBuildDirectory, options, compilationTaskOptions, CompileScriptAssembliesOptions.none); return compiling ? CompileStatus.CompilationStarted : CompileStatus.Idle; } @@ -1440,16 +1584,14 @@ internal bool CompileCustomScriptAssemblies(ScriptAssemblySettings scriptAssembl DeleteUnusedAssemblies(); var scriptAssemblies = GetAllScriptAssembliesOfType(scriptAssemblySettings, EditorBuildRules.TargetAssemblyType.Custom); - bool hasPreprocessor = false; - foreach (var scriptAssembly in scriptAssemblies) - { - if (UnityCodeGenHelpers.IsCodeGen(scriptAssembly.Filename)) - { - hasPreprocessor = true; - } - } + return CompileScriptAssemblies(scriptAssemblies, scriptAssemblySettings, tempBuildDirectory, options, CompilationTaskOptions.None, CompileScriptAssembliesOptions.skipSetupChecks); + } - return CompileScriptAssemblies(scriptAssemblies, scriptAssemblySettings, tempBuildDirectory, options, CompilationTaskOptions.None, CompileScriptAssembliesOptions.skipSetupChecks, hasPreprocessor); + internal bool CompileCustomNonCodegenScriptAssemblies(EditorScriptCompilationOptions options, BuildTargetGroup platformGroup, BuildTarget platform) + { + DeleteUnusedAssemblies(); + DirtyAllNonCodeGenAssemblies(options); + return CompileScripts(options, platformGroup, platform, CompilationTaskOptions.None) == CompileStatus.CompilationStarted; } internal bool CompileScriptAssemblies(ScriptAssembly[] scriptAssemblies, @@ -1457,8 +1599,7 @@ internal bool CompileScriptAssemblies(ScriptAssembly[] scriptAssemblies, string tempBuildDirectory, EditorScriptCompilationOptions options, CompilationTaskOptions compilationTaskOptions, - CompileScriptAssembliesOptions compileScriptAssembliesOptions, - bool pendingCodeGenAssembly = false) + CompileScriptAssembliesOptions compileScriptAssembliesOptions) { StopAllCompilation(); @@ -1482,31 +1623,74 @@ internal bool CompileScriptAssemblies(ScriptAssembly[] scriptAssemblies, if (!Directory.Exists(tempBuildDirectory)) Directory.CreateDirectory(tempBuildDirectory); - var allTargetAssemblies = new Dictionary(customTargetAssemblies); - foreach (var predefinedTargetAssembly in EditorBuildRules.GetPredefinedTargetAssemblies()) + // CodeGen/ILPostProcessor + var scriptCodegenAssemblies = UnityCodeGenHelpers.ToScriptCodeGenAssemblies(scriptAssemblies); + var pendingCodeGenAssembly = scriptCodegenAssemblies.CodeGenAssemblies.Any(); + + // Do compile codegen assemblies that were scheduled for compilation + // because one of it's references changed. Only compile them when + // their source files are modified or the compilation is forced. + if (pendingCodeGenAssembly) { - allTargetAssemblies.Add(predefinedTargetAssembly.Filename, predefinedTargetAssembly); + var newScriptAssemblies = new List(scriptCodegenAssemblies.ScriptAssemblies); + + foreach (var codegenAssembly in scriptCodegenAssemblies.CodeGenAssemblies) + { + if (codegenAssembly.DirtySource == DirtySource.DirtyReference) + continue; + + newScriptAssemblies.Add(codegenAssembly); + } + + pendingCodeGenAssembly = newScriptAssemblies.Count > scriptCodegenAssemblies.ScriptAssemblies.Count; + scriptAssemblies = newScriptAssemblies.ToArray(); } - var findReferences = new FindReferences(allTargetAssemblies, scriptAssemblySettings); - var fullPathToTempOutputFolder = Path.GetFullPath(tempBuildDirectory); - ILPostProcessor[] ilPostProcessors = null; + // If we are not compiling any codegen assemblies and there are post processors, + // then run them after finishing compiling each assembly in + // MicrosoftCSharpCompilerWithPostProcessing.Poll() + if (!pendingCodeGenAssembly && ILPostProcessing.HasPostProcessors) + { + compilationTaskOptions |= CompilationTaskOptions.RunPostProcessors; + } // Compile to tempBuildDirectory - compilationTask = new CompilationTask(scriptAssemblies, tempBuildDirectory, "Editor Compilation", options, compilationTaskOptions, maxConcurrentCompilers); + compilationTask = new CompilationTask(scriptAssemblies, + tempBuildDirectory, + "Editor Compilation", + options, + compilationTaskOptions, + maxConcurrentCompilers, + ILPostProcessing); + compilationTask.OnCompilationTaskStarted += (context) => { + Console.WriteLine("- Starting script compilation"); + var stopwatch = new Stopwatch(); + stopwatch.Start(); + stopWatchDict[context] = stopwatch; + InvokeCompilationStarted(context); }; compilationTask.OnCompilationTaskFinished += (context) => { - if (!compilationTask.CompileErrors) + // Do not overwrite IsCodeGenAssemblyChanged if it was + // set to true during a previous failed compilation within + // the same domain. + if (IsCodeGenAssemblyChanged == false) { IsCodeGenAssemblyChanged = pendingCodeGenAssembly; } + InvokeCompilationFinished(context); + + var stopwatch = stopWatchDict[context]; + var elapsed = stopwatch.Elapsed; + stopWatchDict.Remove(context); + + Console.WriteLine($"- Finished script compilation in {elapsed.TotalSeconds:0.######} seconds"); }; compilationTask.OnBeforeCompilationStarted += (assembly, phase) => @@ -1516,9 +1700,12 @@ internal bool CompileScriptAssemblies(ScriptAssembly[] scriptAssemblies, compilationTask.OnCompilationStarted += (assembly, phase) => { + var stopwatch = new Stopwatch(); + stopwatch.Start(); var assemblyOutputPath = AssetPath.Combine(scriptAssemblySettings.OutputDirectory, assembly.Filename); Console.WriteLine("- Starting compile {0}", assemblyOutputPath); InvokeAssemblyCompilationStarted(assemblyOutputPath); + stopWatchDict[assemblyOutputPath] = stopwatch; }; compilationTask.OnCompilationFinished += (assembly, messages) => @@ -1528,46 +1715,7 @@ internal bool CompileScriptAssemblies(ScriptAssembly[] scriptAssemblies, var assemblyOutputPath = AssetPath.Combine(scriptAssemblySettings.OutputDirectory, assembly.Filename); var hasCompileError = messages.Any(m => m.type == CompilerMessageType.Error); - if (!pendingCodeGenAssembly && !hasCompileError) - { - if (ilPostProcessors == null) - { - ilPostProcessors = FindAllPostProcessors(); - } - - if (!UnityCodeGenHelpers.IsCodeGen(assembly.Filename)) - { - try - { - List diagnostics = RunILPostProcessors(ilPostProcessors, assembly, fullPathToTempOutputFolder, findReferences); - foreach (var message in diagnostics) - { - if (message.DiagnosticType == DiagnosticType.Error) - { - hasCompileError = true; - } - messages.Add(new CompilerMessage - { - assemblyName = message.File, - message = message.MessageData, - type = message.DiagnosticType == DiagnosticType.Error ? CompilerMessageType.Error : CompilerMessageType.Warning, - }); - } - } - catch (Exception exception) - { - messages.Add(new CompilerMessage - { - assemblyName = assembly.Filename, - message = $"Something went wrong while Post Processing the assembly ({assembly.Filename}) : {Environment.NewLine} {exception.Message} {Environment.NewLine}{exception.StackTrace}", - type = CompilerMessageType.Error, - }); - hasCompileError = true; - } - } - } - Console.WriteLine("- Finished compile {0}", assemblyOutputPath); changedAssemblies.Add(assembly.Filename); if (runScriptUpdaterAssemblies.Contains(assembly.Filename)) @@ -1591,73 +1739,24 @@ internal bool CompileScriptAssemblies(ScriptAssembly[] scriptAssemblies, } InvokeAssemblyCompilationFinished(assemblyOutputPath, messages); - }; - - compilationTask.Poll(); - return true; - } - static ILPostProcessor[] FindAllPostProcessors() - { - TypeCache.TypeCollection typesDerivedFrom = TypeCache.GetTypesDerivedFrom(); - ILPostProcessor[] ilPostProcessors = new ILPostProcessor[typesDerivedFrom.Count]; + Stopwatch stopwatch = null; - for (int i = 0; i < typesDerivedFrom.Count; i++) - { try { - ilPostProcessors[i] = (ILPostProcessor)Activator.CreateInstance(typesDerivedFrom[i]); - } - catch (Exception exception) - { - Console.WriteLine($"Could not create ILPostProcessor ({typesDerivedFrom[i].FullName}):{Environment.NewLine}{exception.StackTrace}"); - } - } - - return ilPostProcessors; - } - - static List RunILPostProcessors(ILPostProcessor[] ilPostProcessors, ScriptAssembly assembly, string outputTempPath, FindReferences findReferences) - { - var assemblyPath = Path.Combine(outputTempPath, assembly.Filename); - - var resultMessages = new List(); - if (!File.Exists(assemblyPath)) - { - resultMessages.Add(new DiagnosticMessage - { - File = assemblyPath, - MessageData = $"Could not find {assemblyPath} for post processing", - DiagnosticType = DiagnosticType.Error, - }); - } - - bool isILProcessed = false; - var ilPostProcessCompiledAssembly = new ILPostProcessCompiledAssembly(assembly, outputTempPath, findReferences); - - InMemoryAssembly postProcessedInMemoryAssembly = null; - foreach (var ilPostProcessor in ilPostProcessors) - { - Console.WriteLine($"IL PostProcessor {ilPostProcessor.GetType().Name} processing: {assembly.Filename}"); - var ilPostProcessResult = ilPostProcessor.Process(ilPostProcessCompiledAssembly); - postProcessedInMemoryAssembly = ilPostProcessResult?.InMemoryAssembly; - if (ilPostProcessResult?.InMemoryAssembly != null) - { - isILProcessed = true; - ilPostProcessCompiledAssembly.InMemoryAssembly = postProcessedInMemoryAssembly; + stopwatch = stopWatchDict[assemblyOutputPath]; + var elapsed = stopwatch.Elapsed; + Console.WriteLine($"- Finished compile {assemblyOutputPath} in {elapsed.TotalSeconds:0.######} seconds"); + stopWatchDict.Remove(assemblyOutputPath); } - - if (ilPostProcessResult?.Diagnostics != null) + catch (Exception) { - resultMessages.AddRange(ilPostProcessResult.Diagnostics); + Console.WriteLine("- Finished compile {0}", assemblyOutputPath); } - } - if (isILProcessed) - { - ilPostProcessCompiledAssembly.WriteAssembly(); - } + }; - return resultMessages; + compilationTask.Poll(); + return true; } static void RunScriptUpdater( @@ -1674,6 +1773,23 @@ static void RunScriptUpdater( ); } + public bool AreAllCodegenAssembliesCompiled(EditorScriptCompilationOptions options, BuildTarget buildTarget) + { + var assembliesWithScripts = GetTargetAssembliesWithScriptsHashSet(options); + + foreach (var targetAssembly in assembliesWithScripts) + { + if (!UnityCodeGenHelpers.IsCodeGen(targetAssembly.Filename) && + !UnityCodeGenHelpers.IsCodeGenTest(targetAssembly.Filename)) + continue; + + if (!AssetPath.Exists(targetAssembly.FullPath(outputDirectory))) + return false; + } + + return true; + } + void AddUnitySpecificErrorMessages(ScriptAssembly assembly, List messages) { var processors = new List() @@ -1775,6 +1891,12 @@ ScriptAssemblySettings CreateScriptAssemblySettings(BuildTargetGroup buildTarget predefinedAssembliesCompilerOptions.AllowUnsafeCode = true; predefinedAssembliesCompilerOptions.ApiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(buildTargetGroup); + ICompilationExtension compilationExtension = null; + if ((options & EditorScriptCompilationOptions.BuildingForEditor) == 0) + { + compilationExtension = ModuleManager.FindPlatformSupportModule(ModuleManager.GetTargetStringFromBuildTarget(buildTarget))?.CreateCompilationExtension(); + } + var settings = new ScriptAssemblySettings { BuildTarget = buildTarget, @@ -1782,6 +1904,7 @@ ScriptAssemblySettings CreateScriptAssemblySettings(BuildTargetGroup buildTarget OutputDirectory = GetCompileScriptsOutputDirectory(), CompilationOptions = options, PredefinedAssembliesCompilerOptions = predefinedAssembliesCompilerOptions, + CompilationExtension = compilationExtension }; return settings; @@ -2116,35 +2239,39 @@ private static void SetTargetAssemblyDefines(EditorBuildRules.TargetAssembly tar for (int i = 0; i < targetAssemblyVersionDefines.Count; i++) { - if (!assetPathVersionMetaDatas.ContainsKey(targetAssemblyVersionDefines[i].name)) + var targetAssemblyVersionDefine = targetAssemblyVersionDefines[i]; + if (!assetPathVersionMetaDatas.ContainsKey(targetAssemblyVersionDefine.name)) { continue; } - if (string.IsNullOrEmpty(targetAssemblyVersionDefines[i].expression)) + if (string.IsNullOrEmpty(targetAssemblyVersionDefine.expression)) { - var define = targetAssemblyVersionDefines[i].define; + var define = targetAssemblyVersionDefine.define; if (!string.IsNullOrEmpty(define)) { defines[populatedVersionDefinesCount] = define; - populatedVersionDefinesCount++; + ++populatedVersionDefinesCount; } continue; } - var versionDefineExpression = semVersionRangesFactory.GetExpression(targetAssemblyVersionDefines[i].expression); - var assetPathVersionMetaData = assetPathVersionMetaDatas[targetAssemblyVersionDefines[i].name]; - var semVersion = SemVersionParser.Parse(assetPathVersionMetaData); - - if (versionDefineExpression.IsValid(semVersion)) + try { - var define = targetAssemblyVersionDefines[i].define; - if (!string.IsNullOrEmpty(define)) + var versionDefineExpression = semVersionRangesFactory.GetExpression(targetAssemblyVersionDefine.expression); + var assetPathVersionMetaData = assetPathVersionMetaDatas[targetAssemblyVersionDefine.name]; + var semVersion = SemVersionParser.Parse(assetPathVersionMetaData); + if (versionDefineExpression.IsValid(semVersion)) { - defines[populatedVersionDefinesCount] = define; - populatedVersionDefinesCount++; + defines[populatedVersionDefinesCount] = targetAssemblyVersionDefine.define; + ++populatedVersionDefinesCount; } } + catch (Exception e) + { + var asset = AssetDatabase.LoadAssetAtPath(EditorCompilationInterface.Instance.FindCustomTargetAssemblyFromTargetAssembly(targetAssembly).FilePath); + UnityEngine.Debug.LogException(e, asset); + } } Array.Resize(ref defines, populatedVersionDefinesCount); diff --git a/Editor/Mono/Scripting/ScriptCompilation/EditorCompilationInterface.cs b/Editor/Mono/Scripting/ScriptCompilation/EditorCompilationInterface.cs index e43b43e120..0717a77598 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/EditorCompilationInterface.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/EditorCompilationInterface.cs @@ -64,7 +64,14 @@ static void LogException(Exception exception) var message = string.Format(exception.Message, filePath); var loadAssetAtPath = AssetDatabase.LoadAssetAtPath(filePath); - CompilationPipeline.LogEditorCompilationError(message, loadAssetAtPath.GetInstanceID()); + if (loadAssetAtPath != null) + { + CompilationPipeline.LogEditorCompilationError(message, loadAssetAtPath.GetInstanceID()); + } + else + { + UnityEngine.Debug.LogException(exception); + } } } else @@ -209,6 +216,12 @@ public static void SkipCustomScriptAssemblyGraphValidation(bool skipValidation) Instance.SkipCustomScriptAssemblyGraphValidation(skipValidation); } + [RequiredByNativeCode] + public static void ClearCustomScriptAssemblies() + { + Instance.ClearCustomScriptAssemblies(); + } + [RequiredByNativeCode] public static void RunScriptUpdaterOnAssembly(string assemblyFilename) { @@ -295,6 +308,12 @@ public static EditorCompilation.TargetAssemblyInfo[] GetAllCompiledAndResolvedCu return result; } + [RequiredByNativeCode] + public static string[] GetCompiledAssemblyGraph(string assemblyName) + { + return EmitExceptionAsError(() => Instance.GetCompiledAssemblyGraph(assemblyName), new string[0]); + } + [RequiredByNativeCode] public static EditorCompilation.TargetAssemblyInfo[] GetTargetAssembliesWithScripts() { @@ -327,6 +346,12 @@ public static bool CompileCustomScriptAssemblies(EditorScriptCompilationOptions return EmitExceptionAsError(() => Instance.CompileCustomScriptAssemblies(definesOptions, platformGroup, platform), false); } + [RequiredByNativeCode] + public static bool CompileCustomNonCodegenScriptAssemblies(EditorScriptCompilationOptions definesOptions, BuildTargetGroup platformGroup, BuildTarget platform) + { + return EmitExceptionAsError(() => Instance.CompileCustomNonCodegenScriptAssemblies(definesOptions, platformGroup, platform), false); + } + [RequiredByNativeCode] public static bool ShouldRecompileNonCodeGenAssembliesAfterReload() { @@ -336,7 +361,20 @@ public static bool ShouldRecompileNonCodeGenAssembliesAfterReload() [RequiredByNativeCode] public static void DirtyAllNonCodeGenAssemblies() { - EmitExceptionAsError(() => Instance.DirtyAllNonCodeGenAssemblies()); + var options = GetAdditionalEditorScriptCompilationOptions(); + EmitExceptionAsError(() => Instance.DirtyAllNonCodeGenAssemblies(options)); + } + + [RequiredByNativeCode] + public static bool HasILPostProcessors() + { + return EmitExceptionAsError(() => Instance.ILPostProcessing.HasPostProcessors, false); + } + + [RequiredByNativeCode] + public static bool AreAllCodegenAssembliesCompiled(EditorScriptCompilationOptions options, BuildTarget buildTarget) + { + return EmitExceptionAsError(() => Instance.AreAllCodegenAssembliesCompiled(options, buildTarget), false); } [RequiredByNativeCode] @@ -390,7 +428,16 @@ public static bool IsCompiling() [RequiredByNativeCode] public static EditorCompilation.CompileStatus TickCompilationPipeline(EditorScriptCompilationOptions options, BuildTargetGroup platformGroup, BuildTarget platform) { - return EmitExceptionAsError(() => Instance.TickCompilationPipeline(options, platformGroup, platform), EditorCompilation.CompileStatus.Idle); + try + { + return Instance.TickCompilationPipeline(options, platformGroup, platform); + } + catch (Exception e) + { + LogException(e); + ClearDirtyScripts(); + return EditorCompilation.CompileStatus.Idle; + } } [RequiredByNativeCode] diff --git a/Editor/Mono/Scripting/ScriptCompilation/EditorScriptCompilationOptions.cs b/Editor/Mono/Scripting/ScriptCompilation/EditorScriptCompilationOptions.cs index c2cc1d0136..4d1c02a96a 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/EditorScriptCompilationOptions.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/EditorScriptCompilationOptions.cs @@ -17,7 +17,7 @@ enum EditorScriptCompilationOptions BuildingForIl2Cpp = 1 << 3, BuildingWithAsserts = 1 << 4, BuildingIncludingTestAssemblies = 1 << 5, - BuildingPredefinedAssembliesAllowUnsafeCode = (1 << 6), - BuildingForHeadlessPlayer = 1 << 7 - }; + BuildingPredefinedAssembliesAllowUnsafeCode = 1 << 6, + BuildingForHeadlessPlayer = 1 << 7, + } } diff --git a/Editor/Mono/Scripting/ScriptCompilation/FindReferences.cs b/Editor/Mono/Scripting/ScriptCompilation/FindReferences.cs deleted file mode 100644 index 8e35ea3f5c..0000000000 --- a/Editor/Mono/Scripting/ScriptCompilation/FindReferences.cs +++ /dev/null @@ -1,137 +0,0 @@ -// Unity C# reference source -// Copyright (c) Unity Technologies. For terms of use, see -// https://unity3d.com/legal/licenses/Unity_Reference_Only_License - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Unity.CompilationPipeline.Common.ILPostProcessing; - -namespace UnityEditor.Scripting.ScriptCompilation -{ - [Flags] - internal enum FindReferencesQueryOptions - { - Direct = 1, - Indirect = 2, - Transitive = Indirect | Direct, - } - - internal class FindReferences - { - readonly Dictionary m_AllTargetAssemblies; - readonly Dictionary m_CompatibleTargetAssemblies = new Dictionary(); - readonly ScriptAssemblySettings m_AssemblySettings; - - readonly Dictionary> m_AssemblyNameReferences = - new Dictionary>(); - - public FindReferences(Dictionary targetAssemblies, - ScriptAssemblySettings assemblySettings) - { - m_AllTargetAssemblies = targetAssemblies; - m_AssemblySettings = assemblySettings; - } - - public HashSet Execute(string assembly, string[] searchReferences, - FindReferencesQueryOptions referencesOptions) - { - if (searchReferences.Length <= 0) - { - return new HashSet(); - } - - var searchQuery = new HashSet(searchReferences); - HashSet result = new HashSet(); - if ((referencesOptions & FindReferencesQueryOptions.Direct) == FindReferencesQueryOptions.Direct) - { - HashSet directResult = AllDirectReferences(m_AllTargetAssemblies[assembly]); - result.UnionWith(directResult); - result.IntersectWith(searchReferences); - } - - if ((referencesOptions & FindReferencesQueryOptions.Indirect) == FindReferencesQueryOptions.Indirect) - { - foreach (var reference in m_AllTargetAssemblies[assembly].References) - { - if (searchQuery.Count <= 0) - { - break; - } - result.UnionWith(FindReferencesRecursive(reference, searchQuery)); - } - } - - return result; - } - - private bool IsCompatibleCached(EditorBuildRules.TargetAssembly targetAssembly) - { - bool isCompatible; - if (m_CompatibleTargetAssemblies.TryGetValue(targetAssembly.Filename, out isCompatible)) - { - return isCompatible; - } - - isCompatible = targetAssembly.IsCompatibleFunc(m_AssemblySettings, targetAssembly.Defines ?? new string[0]); - m_CompatibleTargetAssemblies.Add(targetAssembly.Filename, isCompatible); - return isCompatible; - } - - private HashSet AllDirectReferences(EditorBuildRules.TargetAssembly targetAssembly) - { - HashSet references; - if (m_AssemblyNameReferences.TryGetValue(targetAssembly.Filename, out references)) - { - return references; - } - - references = new HashSet(); - foreach (var targetAssemblyReference in targetAssembly.References) - { - if (IsCompatibleCached(targetAssemblyReference)) - { - references.Add(targetAssemblyReference.Filename); - } - } - - foreach (var assemblyPrecompiledReference in targetAssembly.PrecompiledReferences) - { - var fileName = Path.GetFileName(assemblyPrecompiledReference.Path); - references.Add(fileName); - } - - m_AssemblyNameReferences.Add(targetAssembly.Filename, references); - return references; - } - - private List FindReferencesRecursive(EditorBuildRules.TargetAssembly targetAssembly, - HashSet searchFor) - { - var result = new List(searchFor.Count); - var allDirectReferences = AllDirectReferences(targetAssembly); - allDirectReferences.IntersectWith(searchFor); - result.AddRange(allDirectReferences); - - searchFor.ExceptWith(result); - if (!searchFor.Any()) - { - return result; - } - - foreach (var assemblyReference in targetAssembly.References) - { - if (!searchFor.Any()) - { - continue; - } - - var referenceResult = FindReferencesRecursive(assemblyReference, searchFor); - result.AddRange(referenceResult); - } - - return result; - } - } -} diff --git a/Editor/Mono/Scripting/ScriptCompilation/ILPostProcessCompiledAssembly.cs b/Editor/Mono/Scripting/ScriptCompilation/ILPostProcessCompiledAssembly.cs index 42370bb97c..333d233bb2 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/ILPostProcessCompiledAssembly.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/ILPostProcessCompiledAssembly.cs @@ -8,22 +8,37 @@ using System.Linq; using Unity.CompilationPipeline.Common.ILPostProcessing; using UnityEditor.Scripting.ScriptCompilation; +using UnityEngine.Profiling; internal class ILPostProcessCompiledAssembly : ICompiledAssembly { - readonly ScriptAssembly m_ScriptAssembly; + readonly string m_AssemblyFilename; readonly string m_OutputPath; - readonly FindReferences m_FindReferences; InMemoryAssembly m_InMemoryAssembly; - public ILPostProcessCompiledAssembly(ScriptAssembly scriptAssembly, string outputPath, FindReferences findReferences) + public ILPostProcessCompiledAssembly(ScriptAssembly scriptAssembly, string outputPath) { - m_ScriptAssembly = scriptAssembly; - Name = Path.GetFileNameWithoutExtension(scriptAssembly.Filename); - References = scriptAssembly.GetAllReferences().Select(Path.GetFileName).ToArray(); + m_AssemblyFilename = scriptAssembly.Filename; + Name = Path.GetFileNameWithoutExtension(m_AssemblyFilename); + References = scriptAssembly.GetAllReferences(); + Defines = scriptAssembly.Defines; + + m_OutputPath = outputPath; + } + + public ILPostProcessCompiledAssembly(EditorBuildRules.TargetAssembly targetAssembly, string outputPath) + { + m_AssemblyFilename = targetAssembly.Filename; + + Name = Path.GetFileNameWithoutExtension(m_AssemblyFilename); + + var precompiledAssemblyReferences = targetAssembly.PrecompiledReferences.Select(a => a.Path); + var targetAssemblyReferences = targetAssembly.References.Select(a => a.FullPath(outputPath)); + + References = precompiledAssemblyReferences.Concat(targetAssemblyReferences).ToArray(); + Defines = targetAssembly.Defines; m_OutputPath = outputPath; - m_FindReferences = findReferences; } private InMemoryAssembly CreateOrGetInMemoryAssembly() @@ -33,9 +48,9 @@ private InMemoryAssembly CreateOrGetInMemoryAssembly() return m_InMemoryAssembly; } - byte[] peData = File.ReadAllBytes(Path.Combine(m_OutputPath, m_ScriptAssembly.Filename)); + byte[] peData = File.ReadAllBytes(Path.Combine(m_OutputPath, m_AssemblyFilename)); - var pdbFileName = Path.GetFileNameWithoutExtension(m_ScriptAssembly.Filename) + ".pdb"; + var pdbFileName = Path.GetFileNameWithoutExtension(m_AssemblyFilename) + ".pdb"; byte[] pdbData = File.ReadAllBytes(Path.Combine(m_OutputPath, pdbFileName)); m_InMemoryAssembly = new InMemoryAssembly(peData, pdbData); @@ -50,40 +65,7 @@ public InMemoryAssembly InMemoryAssembly public string Name { get; set; } public string[] References { get; set; } - - public ReferenceQueryResult HasReferences(ReferenceQueryInput input) - { - if (input.References == null) - { - throw new ArgumentNullException(nameof(input.References)); - } - - HashSet result = m_FindReferences.Execute(m_ScriptAssembly.Filename, input.References, (FindReferencesQueryOptions)input.Options); - var found = new bool[input.References.Length]; - var isAllReferencesFound = result.Any(); - for (int i = 0; i < input.References.Length; i++) - { - found[i] = result.Contains(input.References[i]); - isAllReferencesFound &= found[i]; - } - - return new ReferenceQueryResult(found, isAllReferencesFound); - } - - public bool HasReference(string reference, ReferencesQueryOptions options = ReferencesQueryOptions.Direct) - { - if (string.IsNullOrEmpty(reference)) - { - throw new ArgumentException(nameof(reference)); - } - - var hasReferencesResult = HasReferences(new ReferenceQueryInput() - { - References = new string[] { reference }, - Options = options - }); - return hasReferencesResult.HasAllReferences && hasReferencesResult.HasReference.All(x => x); - } + public string[] Defines { get; private set; } public void WriteAssembly() { @@ -92,11 +74,15 @@ public void WriteAssembly() throw new ArgumentException("InMemoryAssembly has never been accessed or modified"); } - var assemblyPath = Path.Combine(m_OutputPath, m_ScriptAssembly.Filename); - var pdbFileName = Path.GetFileNameWithoutExtension(m_ScriptAssembly.Filename) + ".pdb"; + Profiler.BeginSample("ILPostProcessCompiledAssembly.WriteAssembly"); + + var assemblyPath = Path.Combine(m_OutputPath, m_AssemblyFilename); + var pdbFileName = Path.GetFileNameWithoutExtension(m_AssemblyFilename) + ".pdb"; var pdbPath = Path.Combine(m_OutputPath, pdbFileName); File.WriteAllBytes(assemblyPath, InMemoryAssembly.PeData); File.WriteAllBytes(pdbPath, InMemoryAssembly.PdbData); + + Profiler.EndSample(); } } diff --git a/Editor/Mono/Scripting/ScriptCompilation/ILPostProcessing.cs b/Editor/Mono/Scripting/ScriptCompilation/ILPostProcessing.cs new file mode 100644 index 0000000000..b82b8b2d9b --- /dev/null +++ b/Editor/Mono/Scripting/ScriptCompilation/ILPostProcessing.cs @@ -0,0 +1,251 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using Unity.CompilationPipeline.Common.Diagnostics; +using Unity.CompilationPipeline.Common.ILPostProcessing; +using UnityEditor.Scripting.Compilers; +using UnityEngine.Profiling; + +namespace UnityEditor.Scripting.ScriptCompilation +{ + internal struct ILPostProcessorData + { + public ILPostProcessor postProcessor; + public ScriptAssembly scriptAssembly; + } + + internal interface IILPostProcessing + { + List PostProcess(ScriptAssembly assembly, List messages, string outputTempPath); + bool IsAnyRunningPostProcessorUsingAssembly(ScriptAssembly assembly); + } + + class ConcurrentPostProcessors + { + object lockObject = new object(); + HashSet postProcessors = new HashSet(); + + public int Count + { + get + { + lock (lockObject) + { + return postProcessors.Count; + } + } + } + + public void Add(ILPostProcessorData postProcessor) + { + lock (lockObject) + { + postProcessors.Add(postProcessor); + } + } + + public void Remove(ILPostProcessorData postProcessor) + { + lock (lockObject) + { + postProcessors.Remove(postProcessor); + } + } + + public ILPostProcessorData[] ToArray() + { + lock (lockObject) + { + return postProcessors.ToArray(); + } + } + } + + internal class ILPostProcessing : IILPostProcessing + { + public ILPostProcessor[] ILPostProcessors { get; set; } + public ConcurrentPostProcessors RunningPostProcessors { get; } + + public ILPostProcessing() + { + RunningPostProcessors = new ConcurrentPostProcessors(); + } + + public bool HasPostProcessors + { + get + { + return ILPostProcessors != null && ILPostProcessors.Length > 0; + } + } + + public List PostProcess(ScriptAssembly assembly, List messages, string outputTempPath) + { + var hasCompileError = messages.Any(m => m.type == CompilerMessageType.Error); + + if (hasCompileError) + return messages; + + if (UnityCodeGenHelpers.IsCodeGen(assembly.Filename)) + return messages; + + if (!HasPostProcessors) + return messages; + + try + { + List diagnostics = RunILPostProcessors(assembly, outputTempPath); + foreach (var message in diagnostics) + { + if (message.DiagnosticType == DiagnosticType.Error) + { + hasCompileError = true; + } + messages.Add(new CompilerMessage + { + file = message.File, + column = message.Column, + line = message.Line, + message = message.MessageData, + type = message.DiagnosticType == DiagnosticType.Error ? CompilerMessageType.Error : CompilerMessageType.Warning, + }); + } + } + catch (Exception exception) + { + messages.Add(new CompilerMessage + { + assemblyName = assembly.Filename, + message = $"Something went wrong while Post Processing the assembly ({assembly.Filename}) : {Environment.NewLine} {exception.Message} {Environment.NewLine}{exception.StackTrace}", + type = CompilerMessageType.Error, + }); + } + + return messages; + } + + public bool IsAnyRunningPostProcessorUsingAssembly(ScriptAssembly assembly) + { + if (RunningPostProcessors == null || + RunningPostProcessors.Count == 0) + return false; + + var runningPostProcessorsArray = RunningPostProcessors.ToArray(); + + foreach (var postProcessor in runningPostProcessorsArray) + { + var postProcessorAssembly = postProcessor.scriptAssembly; + + if (postProcessorAssembly.ScriptAssemblyReferences.Contains(assembly)) + return true; + + if (postProcessorAssembly.References.Any(r => AssetPath.GetFileName(r) == assembly.Filename)) + return true; + } + + return false; + } + + public static ILPostProcessor[] FindAllPostProcessors() + { + TypeCache.TypeCollection typesDerivedFrom = TypeCache.GetTypesDerivedFrom(); + ILPostProcessor[] localILPostProcessors = new ILPostProcessor[typesDerivedFrom.Count]; + + for (int i = 0; i < typesDerivedFrom.Count; i++) + { + try + { + localILPostProcessors[i] = (ILPostProcessor)Activator.CreateInstance(typesDerivedFrom[i]); + } + catch (Exception exception) + { + Console.WriteLine($"Could not create ILPostProcessor ({typesDerivedFrom[i].FullName}):{Environment.NewLine}{exception.StackTrace}"); + } + } + + return localILPostProcessors; + } + + List RunILPostProcessors(ScriptAssembly assembly, string outputTempPath) + { + Profiler.BeginSample("CompilationPipeline.RunILPostProcessors"); + var assemblyPath = Path.Combine(outputTempPath, assembly.Filename); + + var resultMessages = new List(); + + if (!File.Exists(assemblyPath)) + { + resultMessages.Add(new DiagnosticMessage + { + File = assemblyPath, + MessageData = $"Could not find {assemblyPath} for post processing", + DiagnosticType = DiagnosticType.Error, + }); + } + + bool isILProcessed = false; + var ilPostProcessCompiledAssembly = new ILPostProcessCompiledAssembly(assembly, outputTempPath); + + InMemoryAssembly postProcessedInMemoryAssembly = null; + foreach (var ilPostProcessor in ILPostProcessors) + { + Profiler.BeginSample($"{ilPostProcessor.GetType().FullName}.Process({assembly.Filename})"); + + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + Console.WriteLine($" - Starting ILPostProcessor '{ilPostProcessor.GetType().FullName}' on {assembly.Filename}"); + + var ilPostProcessorInstance = ilPostProcessor.GetInstance(); + + var ilPostProcessorData = new ILPostProcessorData { postProcessor = ilPostProcessorInstance, scriptAssembly = assembly }; + RunningPostProcessors.Add(ilPostProcessorData); + + ILPostProcessResult ilPostProcessResult; + try + { + ilPostProcessResult = ilPostProcessorInstance.Process(ilPostProcessCompiledAssembly); + } + finally + { + RunningPostProcessors.Remove(ilPostProcessorData); + } + + stopwatch.Stop(); + + Profiler.EndSample(); + + var elapsed = stopwatch.Elapsed; + + Console.WriteLine($" - Finished ILPostProcessor '{ilPostProcessor.GetType().FullName}' on {assembly.Filename} in {elapsed.TotalSeconds:0.######} seconds"); + postProcessedInMemoryAssembly = ilPostProcessResult?.InMemoryAssembly; + + if (ilPostProcessResult?.InMemoryAssembly != null) + { + isILProcessed = true; + ilPostProcessCompiledAssembly.InMemoryAssembly = postProcessedInMemoryAssembly; + } + + if (ilPostProcessResult?.Diagnostics != null) + { + resultMessages.AddRange(ilPostProcessResult.Diagnostics); + } + } + + if (isILProcessed) + { + ilPostProcessCompiledAssembly.WriteAssembly(); + } + + Profiler.EndSample(); + + return resultMessages; + } + } +} diff --git a/Editor/Mono/Scripting/ScriptCompilation/PlatformSupportModuleHelpers.cs b/Editor/Mono/Scripting/ScriptCompilation/PlatformSupportModuleHelpers.cs new file mode 100644 index 0000000000..2f40d6ac32 --- /dev/null +++ b/Editor/Mono/Scripting/ScriptCompilation/PlatformSupportModuleHelpers.cs @@ -0,0 +1,38 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Linq; +using UnityEditor.Modules; + +namespace UnityEditor.Scripting.ScriptCompilation +{ + static class PlatformSupportModuleHelpers + { + public static void AddAdditionalPlatformSupportData(ICompilationExtension compilationExtension, ref ScriptAssembly scriptAssembly) + { + if (compilationExtension == null) + { + return; + } + + scriptAssembly.Defines = AddAdditionalToArray(scriptAssembly.Defines, compilationExtension.GetAdditionalDefines().ToArray()); + scriptAssembly.References = AddAdditionalToArray(scriptAssembly.References, compilationExtension.GetAdditionalAssemblyReferences() + .Concat(compilationExtension.GetWindowsMetadataReferences()).ToArray()); + scriptAssembly.Files = AddAdditionalToArray(scriptAssembly.Files, compilationExtension.GetAdditionalSourceFiles().ToArray()); + } + + private static string[] AddAdditionalToArray(string[] source, string[] extras) + { + if (extras == null) + { + return source; + } + var destinationArray = new string[source.Length + extras.Length]; + Array.Copy(source, destinationArray, source.Length); + Array.Copy(extras, 0, destinationArray, source.Length, extras.Length); + return destinationArray; + } + } +} diff --git a/Editor/Mono/Scripting/ScriptCompilation/PostProcessorTask.cs b/Editor/Mono/Scripting/ScriptCompilation/PostProcessorTask.cs new file mode 100644 index 0000000000..d312a6de61 --- /dev/null +++ b/Editor/Mono/Scripting/ScriptCompilation/PostProcessorTask.cs @@ -0,0 +1,46 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using UnityEditor.Scripting.Compilers; +using System.Threading; +using System.Collections.Generic; +using System; + +namespace UnityEditor.Scripting.ScriptCompilation +{ + class PostProcessorTask + { + public ScriptAssembly Assembly { get; private set; } + public List CompilerMessages { get; private set; } + string tempOutputDirectory; + Func, string, List> postProcessFunc; + Thread postProcessingThread; + + public PostProcessorTask(ScriptAssembly assembly, + List compilerMessages, + string tempOutputDirectory, + Func, string, List> postProcessFunc) + { + Assembly = assembly; + CompilerMessages = compilerMessages; + this.tempOutputDirectory = tempOutputDirectory; + this.postProcessFunc = postProcessFunc; + } + + public bool Poll() + { + if (postProcessingThread == null) + { + postProcessingThread = new Thread(() => + { + CompilerMessages = postProcessFunc(Assembly, CompilerMessages, tempOutputDirectory); + }); + + postProcessingThread.Start(); + } + + return !postProcessingThread.IsAlive; + } + } +} diff --git a/Editor/Mono/Scripting/ScriptCompilation/ScriptAssembly.cs b/Editor/Mono/Scripting/ScriptCompilation/ScriptAssembly.cs index d660bac2c6..bfc881e2d9 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/ScriptAssembly.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/ScriptAssembly.cs @@ -8,6 +8,7 @@ using System.Linq; using UnityEditor.Scripting.Compilers; using UnityEditor.Compilation; +using UnityEditor.Modules; namespace UnityEditor.Scripting.ScriptCompilation { @@ -21,6 +22,7 @@ class ScriptAssemblySettings public ScriptCompilerOptions PredefinedAssembliesCompilerOptions { get; set; } public string[] ExtraGeneralDefines { get; set; } + public ICompilationExtension CompilationExtension { get; set; } public ScriptAssemblySettings() @@ -50,6 +52,14 @@ class ScriptAssembly public BuildTarget BuildTarget { get; set; } public SupportedLanguage Language { get; set; } public string Filename { get; set; } + + public string PdbFilename + { + get + { + return $"{AssetPath.GetAssemblyNameWithoutExtension(Filename)}.pdb"; + } + } public string OutputDirectory { get; set; } /// @@ -66,8 +76,15 @@ class ScriptAssembly public bool CallOnBeforeCompilationStarted { get; set; } public ScriptCompilerOptions CompilerOptions { get; set; } public string GeneratedResponseFile { get; set; } + public DirtySource DirtySource { get; set; } + + public ScriptAssembly() + { + DirtySource = DirtySource.None; + } public string FullPath { get { return AssetPath.Combine(OutputDirectory, Filename); } } + public string PdbFullPath { get { return AssetPath.Combine(OutputDirectory, PdbFilename); } } public string[] GetAllReferences() { diff --git a/Editor/Mono/Scripting/ScriptCompilation/SemVersion.cs b/Editor/Mono/Scripting/ScriptCompilation/SemVersion.cs index 2e02640757..c8332c2b62 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/SemVersion.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/SemVersion.cs @@ -129,7 +129,10 @@ private static int CompareExtension(string current, string other, bool lower = f if (currentPartIsNumber && otherPartIsNumber) { result = currentNumber.CompareTo(otherNumber); - if (result != 0) return currentPart.CompareTo(otherNumber); + if (result != 0) + { + return result; + } } else { @@ -145,10 +148,11 @@ private static int CompareExtension(string current, string other, bool lower = f result = string.CompareOrdinal(currentPart, otherPart); if (result != 0) + { return result; + } } } - return currentParts.Length.CompareTo(otherParts.Length); } diff --git a/Editor/Mono/Scripting/ScriptCompilation/SemVersionRangesFactory.cs b/Editor/Mono/Scripting/ScriptCompilation/SemVersionRangesFactory.cs index c0bf19defb..94ec2fc804 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/SemVersionRangesFactory.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/SemVersionRangesFactory.cs @@ -6,9 +6,9 @@ namespace UnityEditor.Scripting.ScriptCompilation { - internal class SemVersionRangesFactory + class SemVersionRangesFactory { - private SemVersionRanges m_SemVersionRanges; + SemVersionRanges m_SemVersionRanges; public SemVersionRangesFactory() { diff --git a/Editor/Mono/Scripting/ScriptCompilation/UnityCodeGenHelpers.cs b/Editor/Mono/Scripting/ScriptCompilation/UnityCodeGenHelpers.cs index 14033e5ba0..1e1d49dc33 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/UnityCodeGenHelpers.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/UnityCodeGenHelpers.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Collections.Generic; using System.IO; using System.Linq; using UnityEditor.Scripting.ScriptCompilation; @@ -14,7 +15,10 @@ namespace UnityEditor.Scripting.ScriptCompilation { internal static class UnityCodeGenHelpers { + const string k_CompilerSuffix = ".Compiler"; const string k_CodeGenSuffix = ".CodeGen"; + const string k_CompilerTestsSuffix = ".Compiler.Tests"; + const string k_CodeGenTestsSuffix = ".CodeGen.Tests"; const string k_CodeGenPrefix = "Unity."; const string k_UnityEngineModules = "UnityEngine"; @@ -23,34 +27,69 @@ internal static class UnityCodeGenHelpers const string k_UnityEditorModules = "UnityEditor"; const string k_UnityEditorModulesLower = "unityeditor"; - public static bool IsCodeGen(string assemblyName, bool includesExtension = true) + public struct ScriptCodeGenAssemblies { - var name = (includesExtension ? Path.GetFileNameWithoutExtension(assemblyName) : assemblyName); - var isCodeGen = name.StartsWith(k_CodeGenPrefix) && name.EndsWith(k_CodeGenSuffix, StringComparison.OrdinalIgnoreCase); - return isCodeGen; + public List ScriptAssemblies; + public List CodeGenAssemblies; + } + + public static bool IsCodeGen(string assemblyName) + { + var name = AssetPath.GetAssemblyNameWithoutExtension(assemblyName); + + if (name.StartsWith(k_CodeGenPrefix, StringComparison.OrdinalIgnoreCase) && name.EndsWith(k_CompilerSuffix, StringComparison.OrdinalIgnoreCase)) + return true; + + if (name.StartsWith(k_CodeGenPrefix, StringComparison.OrdinalIgnoreCase) && name.EndsWith(k_CodeGenSuffix, StringComparison.OrdinalIgnoreCase)) + return true; + + return false; + } + + public static bool IsCodeGenTest(string assemblyName) + { + var name = AssetPath.GetAssemblyNameWithoutExtension(assemblyName); + + if (name.StartsWith(k_CodeGenPrefix, StringComparison.OrdinalIgnoreCase) && name.EndsWith(k_CompilerTestsSuffix, StringComparison.OrdinalIgnoreCase)) + return true; + + if (name.StartsWith(k_CodeGenPrefix, StringComparison.OrdinalIgnoreCase) && name.EndsWith(k_CodeGenTestsSuffix, StringComparison.OrdinalIgnoreCase)) + return true; + + return false; } public static void UpdateCodeGenScriptAssembly(ref ScriptAssembly scriptAssembly) { - scriptAssembly.ScriptAssemblyReferences = new ScriptAssembly[0]; + int referencesLength = scriptAssembly.References.Length; + var newReferences = new string[referencesLength + 1]; + Array.Copy(scriptAssembly.References, newReferences, referencesLength); + newReferences[referencesLength] = AssetPath.Combine(EditorApplication.applicationContentsPath, "Managed", "Unity.CompilationPipeline.Common.dll"); + scriptAssembly.References = newReferences; + } + + public static ScriptCodeGenAssemblies ToScriptCodeGenAssemblies(ScriptAssembly[] scriptAssemblies) + { + var result = new ScriptCodeGenAssemblies(); - int newReferenceCount = 0; - var references = new string[scriptAssembly.References.Length]; + result.ScriptAssemblies = new List(scriptAssemblies.Length); + result.CodeGenAssemblies = new List(scriptAssemblies.Length); - foreach (var reference in scriptAssembly.References) + foreach (var scriptAssembly in scriptAssemblies) { - var name = AssetPath.GetFileName(reference); - if (!Utility.FastStartsWith(name, k_UnityEngineModules, k_UnityEngineModulesLower) - && !Utility.FastStartsWith(name, k_UnityEditorModules, k_UnityEditorModulesLower)) + bool isCodeGen = IsCodeGen(scriptAssembly.Filename); + + if (isCodeGen) + { + result.CodeGenAssemblies.Add(scriptAssembly); + } + else { - references[newReferenceCount] = reference; - newReferenceCount++; + result.ScriptAssemblies.Add(scriptAssembly); } } - var result = new string[newReferenceCount + 1]; - Array.Copy(references, result, newReferenceCount); - result[newReferenceCount] = AssetPath.Combine(EditorApplication.applicationContentsPath, "Managed", "Unity.CompilationPipeline.Common.dll"); - scriptAssembly.References = result; + + return result; } } } diff --git a/Editor/Mono/Scripting/ScriptCompilation/Utility.cs b/Editor/Mono/Scripting/ScriptCompilation/Utility.cs index e9aecdbd87..b57b537784 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/Utility.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/Utility.cs @@ -129,7 +129,7 @@ public static string FileNameWithoutExtension(string path) if (indexOfDot == -1) { - indexOfDot = path.Length - 1; + indexOfDot = path.Length; } return path.Substring(indexOfSlash, indexOfDot - indexOfSlash); diff --git a/Editor/Mono/SearchUtility.cs b/Editor/Mono/SearchUtility.cs index 9b36997ade..149ef0ac18 100644 --- a/Editor/Mono/SearchUtility.cs +++ b/Editor/Mono/SearchUtility.cs @@ -187,14 +187,18 @@ internal static bool CheckForKeyWords(string searchString, SearchFilter filter, if (count < 0 || quote2 == -1) count = searchString.Length - startIndex; - // Strip filepath from quotes - assetPath = "Assets/" + searchString.Substring(startIndex, count); + // Strip filepath from quotes, don't prefix with Assets/, we need to support Packages/ (https://fogbugz.unity3d.com/f/cases/1161019/) + assetPath = searchString.Substring(startIndex, count); } else - // Otherwise use string from colon to end - assetPath = "Assets/" + searchString.Substring(firstColon + 1); + // Otherwise use string from colon to end, don't prefix with Assets/, we need to support Packages/ (https://fogbugz.unity3d.com/f/cases/1161019/) + assetPath = searchString.Substring(firstColon + 1); Object obj = AssetDatabase.LoadMainAssetAtPath(assetPath); + if (obj == null) + // Backward compatibility, in case Assets/ was strip from path + obj = AssetDatabase.LoadMainAssetAtPath("Assets/" + assetPath); + if (obj != null) instanceID = obj.GetInstanceID(); //else diff --git a/Editor/Mono/SerializedProperty.bindings.cs b/Editor/Mono/SerializedProperty.bindings.cs index 4bbb965dac..2bb59db58c 100644 --- a/Editor/Mono/SerializedProperty.bindings.cs +++ b/Editor/Mono/SerializedProperty.bindings.cs @@ -9,6 +9,8 @@ using UnityEngine.Bindings; using UnityObject = UnityEngine.Object; +using System.Reflection; +using System.Text.RegularExpressions; namespace UnityEditor { @@ -74,6 +76,9 @@ public enum SerializedPropertyType // Bounds with int property. BoundsInt = 23, + + // Managed reference property. + ManagedReference = 24, } [NativeHeader("Editor/Src/Utility/SerializedProperty.h")] @@ -492,12 +497,12 @@ internal int hashCodeForPropertyPathWithoutArrayIndex get { Verify(); - return GetHasCodeForPropertyPathWithoutArrayIndexInternal(); + return GetHashCodeForPropertyPathWithoutArrayIndexInternal(); } } - [NativeName("GetHasCodeForPropertyPathWithoutArrayIndex")] - private extern int GetHasCodeForPropertyPathWithoutArrayIndexInternal(); + [NativeName("GetHashCodeForPropertyPathWithoutArrayIndex")] + private extern int GetHashCodeForPropertyPathWithoutArrayIndexInternal(); // Is this property editable? (RO) public bool editable @@ -611,6 +616,43 @@ public bool isInstantiatedPrefab [NativeName("GetIsInstantiatedPrefab")] private extern bool GetIsInstantiatedPrefabInternal(); + /// + /// A property can reference any element in the parent SerializedObject. + /// In the context of polymorphic serialization, those elements might be dynamic instances + /// not statically discoverable from the class type. + /// We need to take a very specific code path when we try to get the type of a field + /// inside such a dynamic instance through a SerializedProperty. + /// + /// @see UnityEditor.ScriptAttributeUtility.GetFieldInfoAndStaticTypeFromProperty + /// + internal bool isReferencingAManagedReferenceField + { + get + { + Verify(VerifyFlags.IteratorNotAtEnd); + return IsReferencingAManagedReferenceFieldInternal(); + } + } + + // Useful in the same context as 'isReferencingAManagedReferenceField'. + [NativeName("IsReferencingAManagedReferenceField")] + private extern bool IsReferencingAManagedReferenceFieldInternal(); + + /// + /// Returns the FQN in the format " " for the current dynamic managed reference. + /// + /// + // Useful in the same context as 'isReferencingAManagedReferenceField'. + [NativeName("GetFullyQualifiedTypenameForCurrentTypeTree")] + internal extern string GetFullyQualifiedTypenameForCurrentTypeTreeInternal(); + + /// + /// Returns the path of the current field on the dynamic reference class. + /// + // Useful in the same context as 'isReferencingAManagedReferenceField'. + [NativeName("GetPropertyPathInCurrentManagedTypeTree")] + internal extern string GetPropertyPathInCurrentManagedTypeTreeInternal(); + // Is property's value different from the prefab it belongs to? public bool prefabOverride { @@ -863,6 +905,96 @@ public UnityObject objectReferenceValue } } + // Value of an object reference property. + public object managedReferenceValue + { + set + { + if (propertyType != SerializedPropertyType.ManagedReference) + { + throw new System.InvalidOperationException( + $"Attempting to set the managed reference value on a SerializedProperty that is set to a '{this.type}'"); + } + + // Make sure that the underlying base type is compatible with the current object + Type type; + var fieldInfo = UnityEditor.ScriptAttributeUtility.GetFieldInfoAndStaticTypeFromProperty(this, out type); + var propertyBaseType = type; + + if (value != null) + { + var valueType = value.GetType(); + if (valueType == typeof(UnityObject) || valueType.IsSubclassOf(typeof(UnityObject))) + { + throw new System.InvalidOperationException( + $"Cannot assign an object deriving from UnityEngine.Object to a managed reference. This is not supported."); + } + else if (!propertyBaseType.IsAssignableFrom(valueType)) + { + throw new System.InvalidOperationException( + $"Cannot assign an object of type '{valueType.Name}' to a managed reference with a base type of '{propertyBaseType.Name}': types are not compatible"); + } + } + + Verify(VerifyFlags.IteratorNotAtEnd); + SetManagedReferenceValueInternal(value); + } + // No getter for managed reference since it adds a few notions that might be hard to reason about from the user's perspective. + // A serializedobject is a serialized version of a UnityObject. A serialized property is a view on that serialized object. + // All operations and all that a serialized knows about are around that serialzied view of the managed world. + // Managed references in the context of SO/SP does not have the same semantics as C# references. All the references are + // serialized properly but the original C# reference is lost in the process and cannot be retrieved unless e.g. cached locally + // on the C# side (i.e. in this file). BUT this would not world properly or involve tricky reconstruction with domain reloads + // after which all those C# references would have to be reconstructed etc. + // So for now we dont allow getting a managed reference. + + // If we ever wanted to add one though, the approach would be to add an explicit setting that takes an reference ID and an object instance as + // input, this would allow serialized properties to edit relations between managed references. We would also need a method to get + // an instance from a managed reference id along with a nice safe API around it. + } + + // Dynamic type for the current managed reference. + public string managedReferenceFullTypename + { + get + { + if (propertyType != SerializedPropertyType.ManagedReference) + { + throw new System.InvalidOperationException( + $"Attempting to get the managed reference full typename on a SerializedProperty that is set to a '{this.type}'"); + } + if (serializedObject.targetObject == null) + { + return null; + } + return GetManagedReferenceFullTypeNameInternal(); + } + } + + // Static type for the current managed reference. + public string managedReferenceFieldTypename + { + get + { + if (propertyType != SerializedPropertyType.ManagedReference) + { + throw new System.InvalidOperationException( + $"Attempting to get the managed reference full typename on a SerializedProperty that is set to a '{this.type}'"); + } + + Type type; + var fieldInfo = UnityEditor.ScriptAttributeUtility.GetFieldInfoAndStaticTypeFromProperty(this, out type); + + return $"{type.Assembly.GetName().Name} {type.FullName.Replace("+", "/")}"; + } + } + + [NativeName("GetManagedReferenceFullTypeName")] + private extern string GetManagedReferenceFullTypeNameInternal(); + + [NativeName("SetManagedReferenceValue")] + private extern void SetManagedReferenceValueInternal(object value); + [NativeName("GetPPtrValue")] private extern UnityObject GetPPtrValueInternal(); diff --git a/Editor/Mono/Settings/Providers/AssetSettingsProvider.cs b/Editor/Mono/Settings/Providers/AssetSettingsProvider.cs index 681fd5ce1c..900f5d4c9c 100644 --- a/Editor/Mono/Settings/Providers/AssetSettingsProvider.cs +++ b/Editor/Mono/Settings/Providers/AssetSettingsProvider.cs @@ -128,7 +128,13 @@ public override void OnTitleBarGUI() EditorGUIUtility.DrawEditorHeaderItems(rect, tagrObjects); var settingsRect = GUILayoutUtility.GetRect(btnWidth, btnHeight); settingsRect.y = rect.y; - if (GUI.Button(settingsRect, EditorGUI.GUIContents.titleSettingsIcon, Styles.settingsStyle)) + + // Settings; process event even for disabled UI + var wasEnabled = GUI.enabled; + GUI.enabled = true; + var showMenu = GUI.Button(settingsRect, EditorGUI.GUIContents.titleSettingsIcon, Styles.settingsStyle); + GUI.enabled = wasEnabled; + if (showMenu) { EditorUtility.DisplayObjectContextMenu(settingsRect, tagrObjects, 0); } diff --git a/Editor/Mono/Settings/SettingsProvider.cs b/Editor/Mono/Settings/SettingsProvider.cs index 8a41709f10..45da0667df 100644 --- a/Editor/Mono/Settings/SettingsProvider.cs +++ b/Editor/Mono/Settings/SettingsProvider.cs @@ -124,7 +124,7 @@ public virtual void OnInspectorUpdate() public void Repaint() { - settingsWindow.Repaint(); + settingsWindow?.Repaint(); } public void PopulateSearchKeywordsFromGUIContentProperties() diff --git a/Editor/Mono/Settings/SettingsService.cs b/Editor/Mono/Settings/SettingsService.cs index af49bc0371..958acd9a7b 100644 --- a/Editor/Mono/Settings/SettingsService.cs +++ b/Editor/Mono/Settings/SettingsService.cs @@ -32,8 +32,11 @@ public static void NotifySettingsProviderChanged() const string k_ProjectSettings = "Edit/Project Settings"; static SettingsService() { - EditorApplication.update -= CheckProjectSettings; - EditorApplication.update += CheckProjectSettings; + if (Unity.MPE.ProcessService.level == Unity.MPE.ProcessLevel.UMP_MASTER) + { + EditorApplication.update -= CheckProjectSettings; + EditorApplication.update += CheckProjectSettings; + } } internal static event Action settingsProviderChanged; diff --git a/Editor/Mono/SettingsWindow/GraphicsSettingsEditors.cs b/Editor/Mono/SettingsWindow/GraphicsSettingsEditors.cs index 1e01c07c16..d026ddc39e 100644 --- a/Editor/Mono/SettingsWindow/GraphicsSettingsEditors.cs +++ b/Editor/Mono/SettingsWindow/GraphicsSettingsEditors.cs @@ -339,10 +339,11 @@ internal class TierSettingsEditor : Editor internal void OnFieldLabelsGUI(bool vertical) { + bool usingSRP = GraphicsSettings.currentRenderPipeline != null; + if (!vertical) EditorGUILayout.LabelField(Styles.standardShaderSettings, EditorStyles.boldLabel); - bool usingSRP = GraphicsSettings.currentRenderPipeline != null; if (!usingSRP) { EditorGUILayout.LabelField(Styles.standardShaderQuality); @@ -366,12 +367,7 @@ internal void OnFieldLabelsGUI(bool vertical) EditorGUILayout.LabelField(Styles.cascadedShadowMaps); EditorGUILayout.LabelField(Styles.prefer32BitShadowMaps); EditorGUILayout.LabelField(Styles.useHDR); - } - - EditorGUILayout.LabelField(Styles.hdrMode); - - if (!usingSRP) - { + EditorGUILayout.LabelField(Styles.hdrMode); EditorGUILayout.LabelField(Styles.renderingPath); } @@ -457,12 +453,9 @@ internal void OnTierGUI(BuildTargetGroup platform, GraphicsTier tier, bool verti ts.cascadedShadowMaps = EditorGUILayout.Toggle(ts.cascadedShadowMaps); ts.prefer32BitShadowMaps = EditorGUILayout.Toggle(ts.prefer32BitShadowMaps); ts.hdr = EditorGUILayout.Toggle(ts.hdr); - } - - ts.hdrMode = HDRModePopup(ts.hdrMode); - - if (!usingSRP) + ts.hdrMode = HDRModePopup(ts.hdrMode); ts.renderingPath = RenderingPathPopup(ts.renderingPath); + } if (SupportedRenderingFeatures.IsLightmapBakeTypeSupported(LightmapBakeType.Realtime)) ts.realtimeGICPUUsage = RealtimeGICPUUsagePopup(ts.realtimeGICPUUsage); @@ -525,10 +518,11 @@ internal void OnGuiVertical(BuildTargetGroup platform) EditorGUIUtility.labelWidth = 80; EditorGUILayout.LabelField(Styles.tierName[(int)tier], EditorStyles.boldLabel); GUILayout.FlexibleSpace(); - EditorGUIUtility.labelWidth = 75; + EditorGUIUtility.labelWidth = 80; autoSettings = EditorGUILayout.Toggle(Styles.autoSettings, autoSettings); GUILayout.EndHorizontal(); } + if (EditorGUI.EndChangeCheck()) { EditorGraphicsSettings.RegisterUndo(); diff --git a/Editor/Mono/ShaderUtil.bindings.cs b/Editor/Mono/ShaderUtil.bindings.cs index c89ec5d4cc..49bed02398 100644 --- a/Editor/Mono/ShaderUtil.bindings.cs +++ b/Editor/Mono/ShaderUtil.bindings.cs @@ -10,7 +10,6 @@ using UnityEngine.Rendering; using UnityEngine.Scripting; using ShaderPlatform = UnityEngine.Rendering.GraphicsDeviceType; -using TextureDimension = UnityEngine.Rendering.TextureDimension; using UnityEngine.Experimental.Rendering; namespace UnityEditor @@ -103,15 +102,6 @@ internal struct ShaderVariantEntriesData [NativeHeader("Runtime/Shaders/GpuPrograms/GpuProgramManager.h")] public sealed partial class ShaderUtil { - public enum ShaderPropertyType - { - Color, - Vector, - Float, - Range, - TexEnv, - }; - extern internal static int GetAvailableShaderCompilerPlatforms(); extern internal static bool HasSurfaceShaders([NotNull] Shader s); @@ -124,8 +114,6 @@ public enum ShaderPropertyType extern internal static int GetRenderQueue([NotNull] Shader s); extern internal static bool HasTangentChannel([NotNull] Shader s); - extern public static int GetPropertyCount([NotNull] Shader s); - extern internal static void FetchCachedMessages([NotNull] Shader s); extern public static int GetShaderMessageCount([NotNull] Shader s); extern public static ShaderMessage[] GetShaderMessages([NotNull] Shader s); @@ -144,76 +132,6 @@ public enum ShaderPropertyType extern public static string GetCallableShaderName([NotNull] RayTracingShader s, int shaderIndex); extern public static int GetCallableShaderParamSize([NotNull] RayTracingShader s, int shaderIndex); - private static void CheckPropertyIndex(Shader s, int idx) - { - if (idx < 0 || idx >= GetPropertyCount(s)) - throw new ArgumentException("Passed property index is out of range."); - } - - [FreeFunction] extern private static int FindShaderPropertyIndex([NotNull] Shader s, string name); - - - [NativeName("GetPropertyName")] extern private static string GetPropertyNameImpl([NotNull] Shader s, int propertyIdx); - public static string GetPropertyName(Shader s, int propertyIdx) - { - CheckPropertyIndex(s, propertyIdx); - return GetPropertyNameImpl(s, propertyIdx); - } - - [NativeName("GetPropertyType")] extern private static ShaderPropertyType GetPropertyTypeImpl([NotNull] Shader s, int propertyIdx); - public static ShaderPropertyType GetPropertyType(Shader s, int propertyIdx) - { - CheckPropertyIndex(s, propertyIdx); - return GetPropertyTypeImpl(s, propertyIdx); - } - - [NativeName("GetPropertyDescription")] extern private static string GetPropertyDescriptionImpl([NotNull] Shader s, int propertyIdx); - public static string GetPropertyDescription(Shader s, int propertyIdx) - { - CheckPropertyIndex(s, propertyIdx); - return GetPropertyDescriptionImpl(s, propertyIdx); - } - - [NativeName("GetShaderPropertyAttributes")] extern private static string[] GetShaderPropertyAttributesImpl([NotNull] Shader s, int propertyIdx); - internal static string[] GetShaderPropertyAttributes(Shader s, string name) - { - int idx = FindShaderPropertyIndex(s, name); - if (idx < 0) return null; - - string[] ret = GetShaderPropertyAttributesImpl(s, idx); - return ret.Length > 0 ? ret : null; - } - - [NativeName("GetRangeLimits")] extern private static float GetRangeLimitsImpl([NotNull] Shader s, int propertyIdx, int defminmax); - public static float GetRangeLimits(Shader s, int propertyIdx, int defminmax) - { - CheckPropertyIndex(s, propertyIdx); - if (defminmax < 0 || defminmax > 2) - throw new ArgumentException("defminmax should be one of 0,1,2."); - return GetRangeLimitsImpl(s, propertyIdx, defminmax); - } - - [NativeName("GetTexDim")] extern private static TextureDimension GetTexDimImpl([NotNull] Shader s, int propertyIdx); - public static TextureDimension GetTexDim(Shader s, int propertyIdx) - { - CheckPropertyIndex(s, propertyIdx); - return GetTexDimImpl(s, propertyIdx); - } - - [NativeName("IsShaderPropertyHidden")] extern private static bool IsShaderPropertyHiddenImpl([NotNull] Shader s, int propertyIdx); - public static bool IsShaderPropertyHidden(Shader s, int propertyIdx) - { - CheckPropertyIndex(s, propertyIdx); - return IsShaderPropertyHiddenImpl(s, propertyIdx); - } - - [NativeName("IsShaderPropertyNonModifiableTexureProperty")] extern private static bool IsShaderPropertyNonModifiableTexurePropertyImpl([NotNull] Shader s, int propertyIdx); - public static bool IsShaderPropertyNonModifiableTexureProperty(Shader s, int propertyIdx) - { - CheckPropertyIndex(s, propertyIdx); - return IsShaderPropertyNonModifiableTexurePropertyImpl(s, propertyIdx); - } - extern static public void ClearCachedData([NotNull] Shader s); extern internal static int GetTextureBindingIndex(Shader s, int texturePropertyID); @@ -240,7 +158,7 @@ public static bool IsShaderPropertyNonModifiableTexureProperty(Shader s, int pro extern public static bool hardwareSupportsRectRenderTexture { get; } extern internal static bool hardwareSupportsFullNPOT { get; } - + extern internal static void RequestLoadRenderDoc(); extern internal static void RecreateGfxDevice(); extern internal static void RecreateSkinnedMeshResources(); extern internal static void ReloadAllShaders(); diff --git a/Editor/Mono/ShaderUtil.bindings.deprecated.cs b/Editor/Mono/ShaderUtil.bindings.deprecated.cs index e942dacc56..28b0d03b14 100644 --- a/Editor/Mono/ShaderUtil.bindings.deprecated.cs +++ b/Editor/Mono/ShaderUtil.bindings.deprecated.cs @@ -5,6 +5,8 @@ using System; using UnityEngine; using UnityEngine.Bindings; +using ShaderPropertyFlags = UnityEngine.Rendering.ShaderPropertyFlags; +using TextureDimension = UnityEngine.Rendering.TextureDimension; namespace UnityEditor { @@ -24,5 +26,86 @@ public enum ShaderPropertyTexDim TexDimCUBE = 4, TexDimAny = 6, }; + + // We can't deprecate them yet (Sept 2019): Some integration tests expect no compile warning/error from built-in packages but TextMeshPro uses + // these APIs and emits deprecation warnings. We'll land the new APIs without deprecation first and then see if we can upgrade TextMeshPro afterwards. + + //[Obsolete("Use UnityEngine.ShaderPropertyType instead.", false)] + public enum ShaderPropertyType + { + Color, + Vector, + Float, + Range, + TexEnv, + }; + + //[Obsolete("Use Shader.GetPropertyCount instead.", false)] + public static int GetPropertyCount(Shader s) + { + if (s == null) + throw new ArgumentNullException("s"); + return s.GetPropertyCount(); + } + + //[Obsolete("Use Shader.GetPropertyName instead.", false)] + public static string GetPropertyName(Shader s, int propertyIdx) + { + if (s == null) + throw new ArgumentNullException("s"); + return s.GetPropertyName(propertyIdx); + } + + //[Obsolete("Use Shader.GetPropertyType instead.", false)] + public static ShaderPropertyType GetPropertyType(Shader s, int propertyIdx) + { + if (s == null) + throw new ArgumentNullException("s"); + return (ShaderPropertyType)s.GetPropertyType(propertyIdx); + } + + //[Obsolete("Use Shader.GetPropertyDescription instead.", false)] + public static string GetPropertyDescription(Shader s, int propertyIdx) + { + if (s == null) + throw new ArgumentNullException("s"); + return s.GetPropertyDescription(propertyIdx); + } + + //[Obsolete("Use Shader.GetPropertyRangeLimits and Shader.GetDefaultValue instead.", false)] + public static float GetRangeLimits(Shader s, int propertyIdx, int defminmax) + { + if (s == null) + throw new ArgumentNullException("s"); + else if (defminmax < 0 || defminmax > 2) + throw new ArgumentException("defminmax should be one of 0,1,2."); + return defminmax > 0 + ? s.GetPropertyRangeLimits(propertyIdx)[defminmax - 1] + : s.GetPropertyDefaultFloatValue(propertyIdx); + } + + //[Obsolete("Use Shader.GetPropertyTextureDimension instead.", false)] + public static TextureDimension GetTexDim(Shader s, int propertyIdx) + { + if (s == null) + throw new ArgumentNullException("s"); + return s.GetPropertyTextureDimension(propertyIdx); + } + + //[Obsolete("Use Shader.GetPropertyFlags and test against ShaderPropertyFlags.HideInInspector instead.", false)] + public static bool IsShaderPropertyHidden(Shader s, int propertyIdx) + { + if (s == null) + throw new ArgumentNullException("s"); + return (s.GetPropertyFlags(propertyIdx) & ShaderPropertyFlags.HideInInspector) != 0; + } + + //[Obsolete("Use Shader.GetPropertyFlags and test against ShaderPropertyFlags.NonModifiableTextureData instead.", false)] + public static bool IsShaderPropertyNonModifiableTexureProperty(Shader s, int propertyIdx) + { + if (s == null) + throw new ArgumentNullException("s"); + return (s.GetPropertyFlags(propertyIdx) & ShaderPropertyFlags.NonModifiableTextureData) != 0; + } } } diff --git a/Editor/Mono/SpritesEditor.bindings.cs b/Editor/Mono/SpritesEditor.bindings.cs index 23dafb5893..b5d94f35d2 100644 --- a/Editor/Mono/SpritesEditor.bindings.cs +++ b/Editor/Mono/SpritesEditor.bindings.cs @@ -59,7 +59,12 @@ namespace UnityEditorInternal public sealed class InternalSpriteUtility { extern public static Rect[] GenerateAutomaticSpriteRectangles([NotNull] Texture2D texture, int minRectSize, int extrudeSize); - extern public static Rect[] GenerateGridSpriteRectangles([NotNull] Texture2D texture, Vector2 offset, Vector2 size, Vector2 padding); + extern public static Rect[] GenerateGridSpriteRectangles([NotNull] Texture2D texture, Vector2 offset, Vector2 size, Vector2 padding, bool keepEmptyRects); + + public static Rect[] GenerateGridSpriteRectangles(Texture2D texture, Vector2 offset, Vector2 size, Vector2 padding) + { + return GenerateGridSpriteRectangles(texture, offset, size, padding, false); + } } [StaticAccessor("SpriteUtilityBindings", StaticAccessorType.DoubleColon)] diff --git a/Editor/Mono/StaticOcclusionCulling.bindings.cs b/Editor/Mono/StaticOcclusionCulling.bindings.cs index 6daa1d2a1b..2c17065e9b 100644 --- a/Editor/Mono/StaticOcclusionCulling.bindings.cs +++ b/Editor/Mono/StaticOcclusionCulling.bindings.cs @@ -23,6 +23,9 @@ public static class StaticOcclusionCulling [NativeName("GenerateTomeInBackground")] public static extern bool GenerateInBackground(); + [NativeName("RemoveTempFolder")] + public static extern void RemoveCacheFolder(); + // Used to invalidate preVisualistion debug data. internal static extern void InvalidatePrevisualisationData(); diff --git a/Editor/Mono/SyncProject.cs b/Editor/Mono/SyncProject.cs index 7a7a2438e6..f6674474bb 100644 --- a/Editor/Mono/SyncProject.cs +++ b/Editor/Mono/SyncProject.cs @@ -12,6 +12,7 @@ using UnityEngine; using UnityEngine.Scripting; using UnityEditorInternal; +using Unity.CodeEditor; namespace UnityEditor { @@ -42,10 +43,14 @@ public VisualStudioPath(string path, string edition = "") [InitializeOnLoad] internal partial class SyncVS : AssetPostprocessor { + static bool s_Enabled = false; static bool s_AlreadySyncedThisDomainReload; static SyncVS() { + s_Enabled = Unity.MPE.ProcessService.level == Unity.MPE.ProcessLevel.UMP_MASTER; + if (!s_Enabled) + return; Synchronizer = new SolutionSynchronizer(Directory.GetParent(Application.dataPath).FullName, new SolutionSynchronizationSettings()); try { @@ -56,16 +61,6 @@ static SyncVS() Console.WriteLine("Error detecting Visual Studio installations: {0}{1}{2}", ex.Message, Environment.NewLine, ex.StackTrace); InstalledVisualStudios = new Dictionary(); } - - SetVisualStudioAsEditorIfNoEditorWasSet(); - } - - private static void SetVisualStudioAsEditorIfNoEditorWasSet() - { - var externalEditor = EditorPrefs.GetString("kScriptsDefaultApp"); - var bestVisualStudio = FindBestVisualStudio(); - if (externalEditor == "" && bestVisualStudio != null) - EditorPrefs.SetString("kScriptsDefaultApp", bestVisualStudio); } public static string FindBestVisualStudio() @@ -83,7 +78,7 @@ public override int VisualStudioVersion { get { - var vs = ScriptEditorUtility.GetExternalScriptEditor(); + var vs = CodeEditor.CurrentEditorInstallation; if (InstalledVisualStudios.ContainsKey(UnityEditor.VisualStudioVersion.VisualStudio2008) && (vs != String.Empty) && PathsAreEquivalent(InstalledVisualStudios[UnityEditor.VisualStudioVersion.VisualStudio2008].Last().Path, vs)) @@ -160,7 +155,11 @@ public void OnActiveBuildTargetChanged(BuildTarget oldTarget, BuildTarget newTar [RequiredByNativeCode] public static void SyncVisualStudioProjectIfItAlreadyExists() { - if (Synchronizer.SolutionExists()) + if (!s_Enabled) + return; + + #pragma warning disable 618 + if (Synchronizer.SolutionExists() && ScriptEditorUtility.GetScriptEditorFromPath(CodeEditor.CurrentEditorInstallation) != ScriptEditorUtility.ScriptEditor.Other) { Synchronizer.Sync(); } @@ -174,6 +173,9 @@ public static void PostprocessSyncProject( string[] movedAssets, string[] movedFromAssetPaths) { + if (!s_Enabled) + return; + Synchronizer.SyncIfNeeded(addedAssets.Union(deletedAssets.Union(movedAssets.Union(movedFromAssetPaths))), importedAssets); } diff --git a/Editor/Mono/UIElements/Controls/BasePopupField.cs b/Editor/Mono/UIElements/Controls/BasePopupField.cs index db5f5a4383..eb65012957 100644 --- a/Editor/Mono/UIElements/Controls/BasePopupField.cs +++ b/Editor/Mono/UIElements/Controls/BasePopupField.cs @@ -20,6 +20,8 @@ public abstract class BasePopupField : BaseField m_Choices; TextElement m_TextElement; + VisualElement m_ArrowElement; + protected TextElement textElement { get { return m_TextElement; } @@ -61,6 +63,7 @@ public string text public new static readonly string ussClassName = "unity-base-popup-field"; public static readonly string textUssClassName = ussClassName + "__text"; + public static readonly string arrowUssClassName = ussClassName + "__arrow"; public new static readonly string labelUssClassName = ussClassName + "__label"; public new static readonly string inputUssClassName = ussClassName + "__input"; @@ -81,6 +84,12 @@ internal BasePopupField(string label) m_TextElement.AddToClassList(textUssClassName); visualInput.AddToClassList(inputUssClassName); visualInput.Add(m_TextElement); + + m_ArrowElement = new VisualElement(); + m_ArrowElement.AddToClassList(arrowUssClassName); + m_ArrowElement.pickingMode = PickingMode.Ignore; + visualInput.Add(m_ArrowElement); + choices = new List(); } diff --git a/Editor/Mono/UIElements/Controls/BindingExtensions.cs b/Editor/Mono/UIElements/Controls/BindingExtensions.cs index 9821996507..9a7831657a 100644 --- a/Editor/Mono/UIElements/Controls/BindingExtensions.cs +++ b/Editor/Mono/UIElements/Controls/BindingExtensions.cs @@ -105,9 +105,10 @@ public static void Unbind(this VisualElement element) throw new ArgumentNullException(nameof(element)); } - RemoveBinding(element); + RemoveBinding(element as IBindable); - for (int i = 0; i < element.hierarchy.childCount; ++i) + var childCount = element.hierarchy.childCount; + for (int i = 0; i < childCount; ++i) { Unbind(element.hierarchy[i]); } @@ -115,7 +116,14 @@ public static void Unbind(this VisualElement element) public static SerializedProperty BindProperty(this IBindable field, SerializedObject obj) { - return BindPropertyWithParent(field, new SerializedObjectUpdateWrapper(obj), null); + var property = obj?.FindProperty(field.bindingPath); + + if (property != null) + { + Bind(field as VisualElement, new SerializedObjectUpdateWrapper(obj), null); + } + + return property; } public static void BindProperty(this IBindable field, SerializedProperty property) @@ -125,7 +133,8 @@ public static void BindProperty(this IBindable field, SerializedProperty propert throw new ArgumentNullException(nameof(property)); } - DoBindProperty(field, new SerializedObjectUpdateWrapper(property.serializedObject), property); + field.bindingPath = property.propertyPath; + Bind(field as VisualElement, new SerializedObjectUpdateWrapper(property.serializedObject), null); } private static void DoBindProperty(IBindable field, SerializedObjectUpdateWrapper obj, SerializedProperty property) @@ -133,22 +142,21 @@ private static void DoBindProperty(IBindable field, SerializedObjectUpdateWrappe var fieldElement = field as VisualElement; if (property == null || fieldElement == null) { - // Object is null or property was not found, we have to make sure we delete any previous binding - RemoveBinding(fieldElement); + // Object is null or property was not found, we have nothing to do here return; } + //we first remove any binding that were already present + RemoveBinding(field); + // This covers the case where a field is being manually bound to a property field.bindingPath = property.propertyPath; - if (property != null && fieldElement != null) + using (var evt = SerializedPropertyBindEvent.GetPooled(property)) { - using (var evt = SerializedPropertyBindEvent.GetPooled(property)) + if (SendBindingEvent(evt, fieldElement)) { - if (SendBindingEvent(evt, fieldElement)) - { - return; - } + return; } } @@ -179,7 +187,8 @@ internal static void Bind(VisualElement element, SerializedObjectUpdateWrapper o } } - for (int i = 0; i < element.hierarchy.childCount; ++i) + var childCount = element.hierarchy.childCount; + for (int i = 0; i < childCount; ++i) { Bind(element.hierarchy[i], objWrapper, parentProperty); } @@ -206,18 +215,15 @@ private static SerializedProperty BindPropertyWithParent(IBindable field, Serial return evt.isPropagationStopped; } - private static void RemoveBinding(VisualElement element) + private static void RemoveBinding(IBindable bindable) { - var bindable = element as IBindable; - if (element == null || !bindable.IsBound()) + if (bindable == null || !bindable.IsBound()) { return; } - if (bindable != null) - { - bindable.binding?.Release(); - bindable.binding = null; - } + + bindable.binding?.Release(); + bindable.binding = null; } /// Property getters @@ -587,7 +593,7 @@ public void UpdateRevision() public bool IsValid() { - if (obj == null) + if (obj == null || obj.m_NativeObjectPtr == IntPtr.Zero) return false; return obj.isValid; @@ -597,7 +603,7 @@ public void UpdateIfNecessary() { if (!wasUpdated) { - if (obj.isValid) + if (IsValid()) { obj.UpdateIfRequiredOrScript(); obj.UpdateExpandedState(); @@ -916,7 +922,7 @@ protected void UpdateFieldIsAttached() else { //we're not dealing with VisualElement - if (!isFieldAttached) + if (m_Field != null && !isFieldAttached) { isFieldAttached = true; ResetCachedValues(); @@ -1082,6 +1088,7 @@ public override void Release() propGetValue = null; propSetValue = null; propCompareValues = null; + ResetCachedValues(); isReleased = true; } } @@ -1125,7 +1132,6 @@ private void SetBinding(INotifyValueChanged c, public override void Release() { base.Release(); - lastFieldValue = default(TValue); s_Pool.Release(this); } @@ -1201,6 +1207,7 @@ private void SetBinding(BaseField c, SerializedObjectUpdateWrapper objWrap } lastEnumValue = enumValueAsInt; + c.value = value; // Make sure to write this property only after setting a first value into the field // This avoid any null checks in regular update methods @@ -1250,18 +1257,30 @@ protected override void SyncPropertyToField(BaseField c, SerializedPropert protected override void UpdateLastFieldValue() { + if (field == null || managedType == null) + { + lastEnumValue = 0; + return; + } + var enumData = EnumDataUtility.GetCachedEnumData(managedType); + Enum fieldValue = field?.value; + if (enumData.flags) - lastEnumValue = EnumDataUtility.EnumFlagsToInt(enumData, field.value); + lastEnumValue = EnumDataUtility.EnumFlagsToInt(enumData, fieldValue); else { - int valueIndex = Array.IndexOf(enumData.values, field.value); + int valueIndex = Array.IndexOf(enumData.values, fieldValue); if (valueIndex != -1) lastEnumValue = enumData.flagValues[valueIndex]; else - Debug.LogWarning("Error: invalid enum value " + field.value + " for type " + managedType); + { + lastEnumValue = 0; + if (field != null) + Debug.LogWarning("Error: invalid enum value " + fieldValue + " for type " + managedType); + } } } @@ -1295,6 +1314,8 @@ public override void Release() field = null; managedType = null; isReleased = true; + + ResetCachedValues(); s_Pool.Release(this); } } @@ -1356,7 +1377,14 @@ protected override void SyncPropertyToField(PopupField c, SerializedProp protected override void UpdateLastFieldValue() { - lastFieldValueIndex = field.index; + if (field == null) + { + lastFieldValueIndex = Int32.MinValue; + } + else + { + lastFieldValueIndex = field.index; + } } protected override bool SyncFieldValueToProperty() @@ -1393,11 +1421,14 @@ public override void Release() FieldBinding = null; } + boundObject = null; boundProperty = null; field = null; lastFieldValueIndex = -1; isReleased = true; + + ResetCachedValues(); s_Pool.Release(this); } } @@ -1436,14 +1467,18 @@ private void SetBinding(INotifyValueChanged c, this.propSetValue = setValue; this.propCompareValues = compareValues; this.field = c; - this.lastFieldValue = default(TValue); - Update(); + + // In this subclass implementation the lastFieldValue is in fact the propertyValue assigned to the field. + // this is made to compare TValues instead of strings + UpdateLastFieldValue(); + AssignValueToField(lastFieldValue); } protected override void UpdateLastFieldValue() { if (field == null) { + lastFieldValue = default(TValue); return; } @@ -1453,7 +1488,6 @@ protected override void UpdateLastFieldValue() public override void Release() { base.Release(); - lastFieldValue = default(TValue); s_Pool.Release(this); } diff --git a/Editor/Mono/UIElements/Controls/EnumField.cs b/Editor/Mono/UIElements/Controls/EnumField.cs index 08117f7730..7cc30ac81c 100644 --- a/Editor/Mono/UIElements/Controls/EnumField.cs +++ b/Editor/Mono/UIElements/Controls/EnumField.cs @@ -36,6 +36,7 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext private Type m_EnumType; private TextElement m_TextElement; + private VisualElement m_ArrowElement; private EnumData m_EnumData; public string text @@ -49,6 +50,11 @@ private void Initialize(Enum defaultValue) m_TextElement.AddToClassList(textUssClassName); m_TextElement.pickingMode = PickingMode.Ignore; visualInput.Add(m_TextElement); + + m_ArrowElement = new VisualElement(); + m_ArrowElement.AddToClassList(arrowUssClassName); + m_ArrowElement.pickingMode = PickingMode.Ignore; + visualInput.Add(m_ArrowElement); if (defaultValue != null) { Init(defaultValue); @@ -57,6 +63,7 @@ private void Initialize(Enum defaultValue) public new static readonly string ussClassName = "unity-enum-field"; public static readonly string textUssClassName = ussClassName + "__text"; + public static readonly string arrowUssClassName = ussClassName + "__arrow"; public new static readonly string labelUssClassName = ussClassName + "__label"; public new static readonly string inputUssClassName = ussClassName + "__input"; diff --git a/Editor/Mono/UIElements/Controls/ListViewBindings.cs b/Editor/Mono/UIElements/Controls/ListViewBindings.cs index 56706556bd..005f432291 100644 --- a/Editor/Mono/UIElements/Controls/ListViewBindings.cs +++ b/Editor/Mono/UIElements/Controls/ListViewBindings.cs @@ -43,12 +43,12 @@ protected void SetBinding(ListView listView, if (listView.makeItem == null) { - listView.makeItem = MakeListViewItem; + listView.makeItem = () => MakeListViewItem(); } if (listView.bindItem == null) { - listView.bindItem = BindListViewItem; + listView.bindItem = (v, i) => BindListViewItem(v, i); } listView.itemsSource = m_DataList; diff --git a/Editor/Mono/UIElements/Controls/PopupField.cs b/Editor/Mono/UIElements/Controls/PopupField.cs index 3d31175b02..548b56162a 100644 --- a/Editor/Mono/UIElements/Controls/PopupField.cs +++ b/Editor/Mono/UIElements/Controls/PopupField.cs @@ -89,6 +89,10 @@ public int index public new static readonly string labelUssClassName = ussClassName + "__label"; public new static readonly string inputUssClassName = ussClassName + "__input"; + public PopupField() + : this(null) + {} + public PopupField(string label = null) : base(label) { diff --git a/Editor/Mono/UIElements/Controls/TextValueField.cs b/Editor/Mono/UIElements/Controls/TextValueField.cs index 66dd03064b..ecb43ef377 100644 --- a/Editor/Mono/UIElements/Controls/TextValueField.cs +++ b/Editor/Mono/UIElements/Controls/TextValueField.cs @@ -81,7 +81,7 @@ protected void AddLabelDragger() public override void SetValueWithoutNotify(TValueType newValue) { base.SetValueWithoutNotify(newValue); - if (!isDelayed && textValueInput.m_UpdateTextFromValue) + if (textValueInput.m_UpdateTextFromValue) { // Value is the same but the text might not be in sync // In the case of an expression like 2+2, the text might not be equal to the result diff --git a/Editor/Mono/UIElements/EditorCursor.cs b/Editor/Mono/UIElements/EditorCursor.cs index 6e7d425223..55683e233f 100644 --- a/Editor/Mono/UIElements/EditorCursor.cs +++ b/Editor/Mono/UIElements/EditorCursor.cs @@ -24,7 +24,17 @@ public void SetCursor(Cursor cursor) } else { - EditorGUIUtility.SetCurrentViewCursor(null, Vector2.zero, (MouseCursor)cursor.defaultCursorId); + var mouseCursor = (MouseCursor)cursor.defaultCursorId; + if (mouseCursor == MouseCursor.Arrow) + { + // If it's the default cursor reset the cursor state + // so that editor cursor rects can be processed + EditorGUIUtility.ClearCurrentViewCursor(); + } + else + { + EditorGUIUtility.SetCurrentViewCursor(null, Vector2.zero, mouseCursor); + } } } diff --git a/Editor/Mono/UIElements/Toolbar/ToolbarMenu.cs b/Editor/Mono/UIElements/Toolbar/ToolbarMenu.cs index 9fa6c30276..b6390b5323 100644 --- a/Editor/Mono/UIElements/Toolbar/ToolbarMenu.cs +++ b/Editor/Mono/UIElements/Toolbar/ToolbarMenu.cs @@ -21,19 +21,40 @@ public enum Variant Clickable clickable; public DropdownMenu menu { get; } + public override string text + { + get { return base.text; } + set { m_TextElement.text = value; base.text = value; } + } public new static readonly string ussClassName = "unity-toolbar-menu"; public static readonly string popupVariantUssClassName = ussClassName + "--popup"; + public static readonly string textUssClassName = ussClassName + "__text"; + public static readonly string arrowUssClassName = ussClassName + "__arrow"; + + private TextElement m_TextElement; + private VisualElement m_ArrowElement; public ToolbarMenu() { Toolbar.SetToolbarStyleSheet(this); + generateVisualContent = null; clickable = new Clickable(this.ShowMenu); this.AddManipulator(clickable); menu = new DropdownMenu(); AddToClassList(ussClassName); + + m_TextElement = new TextElement(); + m_TextElement.AddToClassList(textUssClassName); + m_TextElement.pickingMode = PickingMode.Ignore; + Add(m_TextElement); + + m_ArrowElement = new VisualElement(); + m_ArrowElement.AddToClassList(arrowUssClassName); + m_ArrowElement.pickingMode = PickingMode.Ignore; + Add(m_ArrowElement); } Variant m_Variant; diff --git a/Editor/Mono/UIElements/UIElementsEditorUtility.cs b/Editor/Mono/UIElements/UIElementsEditorUtility.cs index bf891502f1..dc2165bf98 100644 --- a/Editor/Mono/UIElements/UIElementsEditorUtility.cs +++ b/Editor/Mono/UIElements/UIElementsEditorUtility.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.Globalization; using System.Linq; using UnityEditor.Experimental; using UnityEditor.StyleSheets; @@ -29,12 +30,12 @@ internal static class UIElementsEditorUtility internal static readonly StyleSheet s_DefaultCommonDarkStyleSheet; internal static readonly StyleSheet s_DefaultCommonLightStyleSheet; - internal static string GetStyleSheetPathForCurrentFont(string sheetPath) + internal static string GetStyleSheetPathForFont(string sheetPath, string fontName) { // Load the stylesheet of the current font if (LocalizationDatabase.currentEditorLanguage == SystemLanguage.English) { - return sheetPath.Replace(".uss", "_" + EditorResources.GetCurrentFont().ToLower() + ".uss"); + return sheetPath.Replace(".uss", "_" + fontName.ToLowerInvariant() + ".uss"); } else { @@ -42,13 +43,23 @@ internal static string GetStyleSheetPathForCurrentFont(string sheetPath) } } + internal static string GetStyleSheetPathForCurrentFont(string sheetPath) + { + return GetStyleSheetPathForFont(sheetPath, EditorResources.currentFontName); + } + + internal static StyleSheet LoadSKinnedStyleSheetForFont(int skin, string fontName) + { + return EditorGUIUtility.Load(GetStyleSheetPathForFont(skin == EditorResources.darkSkinIndex ? s_DefaultCommonDarkStyleSheetPath : s_DefaultCommonLightStyleSheetPath, fontName)) as StyleSheet; + } + static UIElementsEditorUtility() { // Load the stylesheet of the current font - s_DefaultCommonDarkStyleSheet = EditorGUIUtility.Load(GetStyleSheetPathForCurrentFont(s_DefaultCommonDarkStyleSheetPath)) as StyleSheet; + s_DefaultCommonDarkStyleSheet = LoadSKinnedStyleSheetForFont(EditorResources.darkSkinIndex, EditorResources.currentFontName); s_DefaultCommonDarkStyleSheet.isUnityStyleSheet = true; - s_DefaultCommonLightStyleSheet = EditorGUIUtility.Load(GetStyleSheetPathForCurrentFont(s_DefaultCommonLightStyleSheetPath)) as StyleSheet; + s_DefaultCommonLightStyleSheet = LoadSKinnedStyleSheetForFont(EditorResources.normalSkinIndex, EditorResources.currentFontName); s_DefaultCommonLightStyleSheet.isUnityStyleSheet = true; } diff --git a/Editor/Mono/UIElements/VisualSplitter.cs b/Editor/Mono/UIElements/VisualSplitter.cs index 31159b11b3..9d4b52a8be 100644 --- a/Editor/Mono/UIElements/VisualSplitter.cs +++ b/Editor/Mono/UIElements/VisualSplitter.cs @@ -155,7 +155,8 @@ public VisualSplitter() public List GetAffectedVisualElements() { List elements = VisualElementListPool.Get(); - for (int i = 0; i < hierarchy.childCount; ++i) + var count = hierarchy.childCount; + for (int i = 0; i < count; ++i) { VisualElement element = hierarchy[i]; if (element.resolvedStyle.position == Position.Relative) @@ -172,7 +173,8 @@ protected override void ImmediateRepaint() void UpdateCursorRects() { - for (int i = 0; i < hierarchy.childCount - 1; ++i) + var count = hierarchy.childCount; + for (int i = 0; i < count - 1; ++i) { VisualElement visualElement = hierarchy[i]; bool isVertical = resolvedStyle.flexDirection == FlexDirection.Column || resolvedStyle.flexDirection == FlexDirection.ColumnReverse; diff --git a/Editor/Mono/Unsupported.bindings.cs b/Editor/Mono/Unsupported.bindings.cs index b34d05aa61..6cc97d9e2b 100644 --- a/Editor/Mono/Unsupported.bindings.cs +++ b/Editor/Mono/Unsupported.bindings.cs @@ -78,6 +78,20 @@ public static bool IsDeveloperBuild() [FreeFunction("GetScreenManager().SetAllowCursorLock")] internal static extern void SetAllowCursorLock(bool allow, DisallowCursorLockReasons reasons); + // Remove in 2021.0 + [Obsolete("This method has been marked obsolete, use SetOverrideLightingSettings instead (UnityUpgradable) -> SetOverrideLightingSettings(*)", true)] + public static bool SetOverrideRenderSettings(Scene scene) + { + return SetOverrideLightingSettings(scene); + } + + // Remove in 2021.0 + [Obsolete("This method has been marked obsolete, use RestoreOverrideLightingSettings instead (UnityUpgradable) -> RestoreOverrideLightingSettings(*)", true)] + public static void RestoreOverrideRenderSettings() + { + RestoreOverrideLightingSettings(); + } + public static bool SetOverrideLightingSettings(Scene scene) { return SetOverrideLightingSettingsInternal(scene.handle); diff --git a/Editor/Mono/Utils/IconUtility.bindings.cs b/Editor/Mono/Utils/IconUtility.bindings.cs index ef5a803cca..ba3d53e2f3 100644 --- a/Editor/Mono/Utils/IconUtility.bindings.cs +++ b/Editor/Mono/Utils/IconUtility.bindings.cs @@ -11,7 +11,7 @@ namespace UnityEditor.Utils static class IconUtility { [FreeFunction] - extern public static void AddIconToWindowsExecutable(string path); + extern public static bool AddIconToWindowsExecutable(string path); [FreeFunction] extern public static bool SaveIcoForPlatform(string path, BuildTargetGroup buildTargetGroup, Vector2Int[] iconSizes); diff --git a/Editor/Mono/Utils/PerformanceChecks.cs b/Editor/Mono/Utils/PerformanceChecks.cs index 04dd425e9f..f18b9c4bd1 100644 --- a/Editor/Mono/Utils/PerformanceChecks.cs +++ b/Editor/Mono/Utils/PerformanceChecks.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using UnityEngine; +using UnityEngine.Rendering; using System; using BuildTargetDiscovery = UnityEditor.BuildTargetDiscovery; using TargetAttributes = UnityEditor.BuildTargetDiscovery.TargetAttributes; @@ -77,13 +78,13 @@ public static string CheckMaterial(Material mat, BuildTarget buildTarget) bool isColor = false; Shader shader = mat.shader; - int count = ShaderUtil.GetPropertyCount(shader); + int count = shader.GetPropertyCount(); for (int i = 0; i < count; i++) { - if (ShaderUtil.GetPropertyName(shader, i) == "_Emission") + if (shader.GetPropertyName(i) == "_Emission") { - isColor = (ShaderUtil.GetPropertyType(shader, i) == ShaderUtil.ShaderPropertyType.Color); + isColor = shader.GetPropertyType(i) == ShaderPropertyType.Color; break; } } diff --git a/Editor/Mono/Utils/StateCache.cs b/Editor/Mono/Utils/StateCache.cs index e24a23ac21..9413efbd96 100644 --- a/Editor/Mono/Utils/StateCache.cs +++ b/Editor/Mono/Utils/StateCache.cs @@ -66,7 +66,7 @@ public void SetState(string key, T obj) m_Cache[key] = obj; } - public T GetState(string key) + public T GetState(string key, T defaultValue = default(T)) { if (string.IsNullOrEmpty(key)) throw new ArgumentException("key cannot be null or empty string", key); @@ -86,7 +86,7 @@ public T GetState(string key) catch (Exception e) { Debug.LogError(string.Format("Error loading file {0}. Error: {1}", filePath, e.ToString())); - return default(T); + return defaultValue; } try @@ -97,14 +97,14 @@ public T GetState(string key) { Debug.LogError(string.Format("Invalid file content for {0}. Removing file. Error: {1}", filePath, exception.ToString())); RemoveState(key); - return default(T); + return defaultValue; } m_Cache[key] = obj; return obj; } - return default(T); + return defaultValue; } public void RemoveState(string key) diff --git a/Editor/Mono/VersionControl/Common/VCAsset.cs b/Editor/Mono/VersionControl/Common/VCAsset.cs index 0d51ccc8ca..7178813d11 100644 --- a/Editor/Mono/VersionControl/Common/VCAsset.cs +++ b/Editor/Mono/VersionControl/Common/VCAsset.cs @@ -138,6 +138,9 @@ internal static string AllStateToString(States state) if (IsState(state, States.ReadOnly)) sb.Append("ReadOnly, "); + if (IsState(state, States.Unversioned)) + sb.Append("Unversioned, "); + // remove trailing ", " if had any if (sb.Length > 2) sb.Remove(sb.Length - 2, 2); diff --git a/Editor/Mono/VersionControl/Common/VCAssetModificationHooks.cs b/Editor/Mono/VersionControl/Common/VCAssetModificationHooks.cs index 9bcd90bb04..04b4a8c929 100644 --- a/Editor/Mono/VersionControl/Common/VCAssetModificationHooks.cs +++ b/Editor/Mono/VersionControl/Common/VCAssetModificationHooks.cs @@ -48,14 +48,24 @@ static Asset GetStatusForceUpdate(string fromPath) { var task = Provider.Status(fromPath); task.Wait(); - return task.assetList.Count > 0 ? task.assetList[0] : null; + var taskResultList = task.assetList; + return taskResultList.Count > 0 ? taskResultList[0] : null; } static AssetList GetStatusForceUpdate(List fromPaths) { var task = Provider.Status(fromPaths.ToArray()); task.Wait(); - return task.success ? task.assetList : null; + if (!task.success) + return null; + + // Status task might return more items in the list (e.g. meta files), + // and return them out of order too. Make sure to return proper sized + // and in-order list back. + var taskResultList = task.assetList; + var result = new AssetList {Capacity = fromPaths.Count}; + result.AddRange(fromPaths.Select(path => taskResultList.SingleOrDefault(a => a.path == path))); + return result; } // Handle asset moving @@ -64,6 +74,11 @@ public static AssetMoveResult OnWillMoveAsset(string from, string to) if (!Provider.enabled || EditorUserSettings.WorkOffline) return AssetMoveResult.DidNotMove; + if (!Provider.PathIsVersioned(from)) + return AssetMoveResult.DidNotMove; + if (!Provider.PathIsVersioned(to)) + return AssetMoveResult.DidNotMove; + if (InternalEditorUtility.isHumanControllingUs && Directory.Exists(from) && !EditorUtility.DisplayDialog("Confirm version control operation", L10n.Tr($"You are about to move or rename a folder that is under version control.\n\nFrom:\t{from}\nTo:\t{to}\n\nAre you sure you want to perform this action?"), "Yes", "No")) { return AssetMoveResult.FailedMove; @@ -109,6 +124,9 @@ public static AssetDeleteResult OnWillDeleteAsset(string assetPath, RemoveAssetO if (!Provider.enabled || EditorUserSettings.WorkOffline) return AssetDeleteResult.DidNotDelete; + if (!Provider.PathIsVersioned(assetPath)) + return AssetDeleteResult.DidNotDelete; + Task task = Provider.Delete(assetPath); task.SetCompletionAction(CompletionAction.UpdatePendingWindow); task.Wait(); diff --git a/Editor/Mono/VersionControl/Common/VCProvider.cs b/Editor/Mono/VersionControl/Common/VCProvider.cs index 76266a378d..d10184ff67 100644 --- a/Editor/Mono/VersionControl/Common/VCProvider.cs +++ b/Editor/Mono/VersionControl/Common/VCProvider.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.Collections.Generic; +using System.Linq; using UnityEngine; namespace UnityEditor.VersionControl @@ -184,24 +184,42 @@ static private Task CheckCallbackAndCheckout(AssetList assets, CheckoutMode mode return Internal_Checkout(consolidatedAssetList.ToArray(), mode, changeset); } - static public Task Checkout(AssetList assets, CheckoutMode mode, ChangeSet changeset = null) + static public Task Checkout(AssetList assets, CheckoutMode mode) + { + return Checkout(assets, mode, null); + } + + static public Task Checkout(AssetList assets, CheckoutMode mode, ChangeSet changeset) { return CheckCallbackAndCheckout(assets, mode, changeset); } - static public Task Checkout(string[] assets, CheckoutMode mode, ChangeSet changeset = null) + static public Task Checkout(string[] assets, CheckoutMode mode) + { + return Checkout(assets, mode, null); + } + + static public Task Checkout(string[] assets, CheckoutMode mode, ChangeSet changeset) { var assetList = new AssetList(); foreach (var path in assets) { var asset = GetAssetByPath(path); - assetList.Add(asset); + if (asset == null) + { + asset = new Asset(path); + } } return CheckCallbackAndCheckout(assetList, mode, changeset); } - static public Task Checkout(Object[] assets, CheckoutMode mode, ChangeSet changeset = null) + static public Task Checkout(Object[] assets, CheckoutMode mode) + { + return Checkout(assets, mode, null); + } + + static public Task Checkout(Object[] assets, CheckoutMode mode, ChangeSet changeset) { var assetList = new AssetList(); foreach (var o in assets) @@ -222,7 +240,12 @@ static public bool CheckoutIsValid(Asset asset, CheckoutMode mode) return Internal_CheckoutIsValid(new Asset[] { asset }, mode); } - static public Task Checkout(Asset asset, CheckoutMode mode, ChangeSet changeset = null) + static public Task Checkout(Asset asset, CheckoutMode mode) + { + return Checkout(asset, mode, null); + } + + static public Task Checkout(Asset asset, CheckoutMode mode, ChangeSet changeset) { var assetList = new AssetList(); assetList.Add(asset); @@ -230,15 +253,22 @@ static public Task Checkout(Asset asset, CheckoutMode mode, ChangeSet changeset return CheckCallbackAndCheckout(assetList, mode, changeset); } - static public Task Checkout(string asset, CheckoutMode mode, ChangeSet changeset = null) + static public Task Checkout(string asset, CheckoutMode mode) { - var assetList = new AssetList(); - assetList.Add(GetAssetByPath(asset)); + return Checkout(asset, mode, null); + } - return CheckCallbackAndCheckout(assetList, mode, changeset); + static public Task Checkout(string asset, CheckoutMode mode, ChangeSet changeset) + { + return Checkout(new string[] { asset }, mode, changeset); } - static public Task Checkout(UnityEngine.Object asset, CheckoutMode mode, ChangeSet changeset = null) + static public Task Checkout(UnityEngine.Object asset, CheckoutMode mode) + { + return Checkout(asset, mode, null); + } + + static public Task Checkout(UnityEngine.Object asset, CheckoutMode mode, ChangeSet changeset) { var path = AssetDatabase.GetAssetPath(asset); var vcasset = GetAssetByPath(path); @@ -249,34 +279,30 @@ static public Task Checkout(UnityEngine.Object asset, CheckoutMode mode, ChangeS return CheckCallbackAndCheckout(assetList, mode, changeset); } - //*Undocumented : intention is to make this public - static internal bool PromptAndCheckoutIfNeeded(string[] assets, string promptIfCheckoutIsNeeded, ChangeSet changeset = null) + internal static bool HandlePreCheckoutCallback(ref string[] paths, ref ChangeSet changeSet) { + if (preCheckoutCallback == null) + return true; + var assetList = new AssetList(); - foreach (var path in assets) - { - var asset = GetAssetByPath(path); - assetList.Add(asset); - } + assetList.AddRange(paths.Select(GetAssetByPath)); - if (preCheckoutCallback != null) + try { - var changesetID = changeset == null ? ChangeSet.defaultID : changeset.id; - string changesetDescription = null; - if (preCheckoutCallback(assetList, ref changesetID, ref changesetDescription) == true) + var id = changeSet == null ? ChangeSet.defaultID : changeSet.id; + string desc = null; + if (preCheckoutCallback(assetList, ref id, ref desc)) { - var changesetTask = CheckAndCreateUserSuppliedChangeSet(changesetID, changesetDescription, ref changeset); - if (changesetTask != null) + var task = CheckAndCreateUserSuppliedChangeSet(id, desc, ref changeSet); + if (task != null) { - Debug.LogError("Tried to create/rename remote ChangeSet to match the one specified in user-supplied callback but failed with error code: " + changesetTask.resultCode.ToString()); + Debug.LogError( + "Tried to create/rename remote ChangeSet to match the one specified in user-supplied callback but failed with error code: " + + task.resultCode); return false; } - var newAssets = new List(); - foreach (var asset in assetList) - { - newAssets.Add(asset.path); - } - assets = newAssets.ToArray(); + + paths = assetList.Select(asset => asset.path).ToArray(); } else { @@ -284,8 +310,13 @@ static internal bool PromptAndCheckoutIfNeeded(string[] assets, string promptIfC return false; } } + catch (System.Exception ex) + { + Debug.LogWarning("User-created pre-checkout callback has thrown an exception: " + ex.Message); + return false; + } - return Internal_PromptAndCheckoutIfNeeded(assets, promptIfCheckoutIsNeeded, changeset); + return true; } static public Task Delete(string assetProjectPath) diff --git a/Editor/Mono/VersionControl/UI/VCListControl.cs b/Editor/Mono/VersionControl/UI/VCListControl.cs index a84b24137b..580765ef02 100644 --- a/Editor/Mono/VersionControl/UI/VCListControl.cs +++ b/Editor/Mono/VersionControl/UI/VCListControl.cs @@ -620,7 +620,7 @@ private void DrawItems(Rect area, bool focus) bool isSelected = readOnly ? false : IsSelected(it); if (it.Parent != null && it.Parent.Parent != null && it.Parent.Parent.Parent == null) - x -= 16; + x += 5; DrawItem(it, area, x, y, focus, isSelected); y += c_lineHeight; @@ -651,6 +651,13 @@ void HandleKeyInput(Event e) if (selectList.Count == 0) return; + // Selected list items values can be null if using during list update + foreach (KeyValuePair item in selectList) + { + if (item.Value == null) + return; + } + // Arrow key up if (e.keyCode == KeyCode.UpArrow || e.keyCode == KeyCode.DownArrow) { diff --git a/Editor/Mono/VersionControl/UI/VCMenuPending.cs b/Editor/Mono/VersionControl/UI/VCMenuPending.cs index f324d0a51a..3b839ea279 100644 --- a/Editor/Mono/VersionControl/UI/VCMenuPending.cs +++ b/Editor/Mono/VersionControl/UI/VCMenuPending.cs @@ -37,7 +37,7 @@ static void Revert(int userData) //[MenuItem ("CONTEXT/Pending/Revert Unchanged", true, 201)] static bool RevertUnchangedTest(int userData) { - return Provider.RevertIsValid(ListControl.FromID(userData).SelectedAssets, RevertMode.Normal); + return Provider.RevertIsValid(ListControl.FromID(userData).SelectedAssets, RevertMode.Unchanged); } //[MenuItem ("CONTEXT/Pending/Revert Unchanged", false, 201)] diff --git a/Editor/Mono/VersionControl/UI/VCMenuProject.cs b/Editor/Mono/VersionControl/UI/VCMenuProject.cs index 5bd45a8543..4273f9ebda 100644 --- a/Editor/Mono/VersionControl/UI/VCMenuProject.cs +++ b/Editor/Mono/VersionControl/UI/VCMenuProject.cs @@ -95,20 +95,6 @@ static void CheckOutMeta(MenuCommand cmd) Provider.Checkout(list, CheckoutMode.Meta); } - // Called from native class VCSAssetMenuHandler as "Assets/Version Control/Check Out (Other)/Both" menu handler - static bool CheckOutBothTest(MenuCommand cmd) - { - AssetList selected = Provider.GetAssetListFromSelection(); - return Provider.enabled && Provider.CheckoutIsValid(selected, CheckoutMode.Both); - } - - // Called from native class VCSAssetMenuHandler as "Assets/Version Control/Check Out (Other)/Both" menu handler - static void CheckOutBoth(MenuCommand cmd) - { - AssetList list = Provider.GetAssetListFromSelection(); - Provider.Checkout(list, CheckoutMode.Both); - } - // Called from native class VCSAssetMenuHandler as "Assets/Version Control/Mark Add" menu handler static bool MarkAddTest(MenuCommand cmd) { @@ -153,7 +139,7 @@ static void Revert(ShortcutArguments args) static bool RevertUnchangedTest(MenuCommand cmd) { AssetList selected = Provider.GetAssetListFromSelection(); - return Provider.enabled && Provider.RevertIsValid(selected, RevertMode.Normal); + return Provider.enabled && Provider.RevertIsValid(selected, RevertMode.Unchanged); } // Called from native class VCSAssetMenuHandler as "Assets/Version Control/Revert Unchanged" menu handler diff --git a/Editor/Mono/VersionControl/UI/VCWindowChange.cs b/Editor/Mono/VersionControl/UI/VCWindowChange.cs index d5a379ffa7..dca17215fe 100644 --- a/Editor/Mono/VersionControl/UI/VCWindowChange.cs +++ b/Editor/Mono/VersionControl/UI/VCWindowChange.cs @@ -2,9 +2,12 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System.Collections.Generic; using UnityEngine; using UnityEditorInternal.VersionControl; using UnityEditor.ShortcutManagement; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; namespace UnityEditor.VersionControl { @@ -438,6 +441,9 @@ void Save(bool submit) return; } + bool cancelSubmit = HandleActiveScenes(); + if (cancelSubmit) + return; UnityEditor.AssetDatabase.SaveAssets(); // Submit the change list. Last parameter is "save only" so invert the submit flag @@ -455,5 +461,44 @@ static void SubmitChangeset(ShortcutArguments args) if (window != null) window.Save(true); } + + private bool HandleActiveScenes() + { + List scenes = new List(); + for (int i = 0; i < SceneManager.sceneCount; i++) + { + Scene scene = SceneManager.GetSceneAt(i); + Asset asset = Provider.GetAssetByPath(scene.path); + if (asset != null && asset.IsUnderVersionControl && scene.isDirty) + { + scenes.Add(scene); + } + } + + if (scenes.Count > 0) + { + List filteredScenes = new List(); + foreach (var asset in assetList) + { + foreach (var scene in scenes) + { + if (asset.path.Equals(scene.path)) + { + filteredScenes.Add(scene); + break; + } + } + } + + if (filteredScenes.Count > 0) + { + if (!EditorSceneManager.SaveModifiedScenesIfUserWantsTo(filteredScenes.ToArray())) + { + return true; + } + } + } + return false; + } } } diff --git a/Editor/Mono/VersionControl/UI/VCWindowPending.cs b/Editor/Mono/VersionControl/UI/VCWindowPending.cs index 30483d9609..458cfe7160 100644 --- a/Editor/Mono/VersionControl/UI/VCWindowPending.cs +++ b/Editor/Mono/VersionControl/UI/VCWindowPending.cs @@ -2,6 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; using UnityEditor.ShortcutManagement; using UnityEngine; using UnityEditorInternal.VersionControl; @@ -38,13 +39,17 @@ internal class Styles bool m_ShowIncoming = false; const float k_ResizerHeight = 17f; const float k_MinIncomingAreaHeight = 50f; - const float k_BottomBarHeight = 17f; + const float k_BottomBarHeight = 21f; float s_ToolbarButtonsWidth = 0f; float s_SettingsButtonWidth = 0f; float s_DeleteChangesetsButtonWidth = 0f; static GUIContent[] sStatusWheel; + DateTime lastRefresh = new DateTime(0); + private int refreshInterval = 1000; // this is in MS + private bool scheduleRefresh = false; + // Workaround to reload vcs info upon domain reload. TODO: Fix VersionControl.ListControl to get rid of this static bool s_DidReload = false; // defaults to false after domain reload @@ -174,14 +179,21 @@ void UpdateWindow() return; } - if (Provider.onlineState == OnlineState.Online) + if (TimeElapsed() > refreshInterval) { - Task changesTask = Provider.ChangeSets(); - changesTask.SetCompletionAction(CompletionAction.OnChangeSetsPendingWindow); + if (Provider.onlineState == OnlineState.Online) + { + Task changesTask = Provider.ChangeSets(); + changesTask.SetCompletionAction(CompletionAction.OnChangeSetsPendingWindow); + + Task incomingTask = Provider.Incoming(); + incomingTask.SetCompletionAction(CompletionAction.OnIncomingPendingWindow); + } - Task incomingTask = Provider.Incoming(); - incomingTask.SetCompletionAction(CompletionAction.OnIncomingPendingWindow); + lastRefresh = DateTime.Now; } + else + scheduleRefresh = true; } void OnGotLatest(Task t) @@ -455,11 +467,10 @@ void OnGUI() GUIUtility.ExitGUI(); } - Color origColor = GUI.color; - GUI.color = new Color(1, 1, 1, 1 * .5f); bool refreshButtonClicked = GUILayout.Button(refreshIcon, EditorStyles.toolbarButton); - refresh = refresh || refreshButtonClicked; - GUI.color = origColor; + refresh = refresh || refreshButtonClicked || scheduleRefresh; + + bool repaint = false; if (refresh) { @@ -468,6 +479,9 @@ void OnGUI() Provider.InvalidateCache(); Provider.UpdateSettings(); } + + repaint = true; + scheduleRefresh = false; UpdateWindow(); } @@ -475,8 +489,6 @@ void OnGUI() Rect rect = new Rect(0, toolBarHeight, position.width, position.height - toolBarHeight - k_BottomBarHeight); - bool repaint = false; - GUILayout.EndHorizontal(); if (EditorUserSettings.WorkOffline) @@ -680,5 +692,11 @@ void CreateStaticResources() changeIcon.name = "ChangeIcon"; } } + + double TimeElapsed() + { + var elapsed = DateTime.Now - lastRefresh; + return elapsed.TotalMilliseconds; + } } } diff --git a/Editor/Mono/VersionControl/VCAsset.bindings.cs b/Editor/Mono/VersionControl/VCAsset.bindings.cs index c63914edac..77f497f9a6 100644 --- a/Editor/Mono/VersionControl/VCAsset.bindings.cs +++ b/Editor/Mono/VersionControl/VCAsset.bindings.cs @@ -39,7 +39,8 @@ public enum States ReadOnly = 16384, MetaFile = 32768, MovedLocal = 65536, - MovedRemote = 131072 + MovedRemote = 131072, + Unversioned = 262144 } public Asset(string clientPath) diff --git a/Editor/Mono/VersionControl/VCProvider.bindings.cs b/Editor/Mono/VersionControl/VCProvider.bindings.cs index 554aa6efa6..87b358c72b 100644 --- a/Editor/Mono/VersionControl/VCProvider.bindings.cs +++ b/Editor/Mono/VersionControl/VCProvider.bindings.cs @@ -2,6 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System.Collections.Generic; using UnityEngine; using UnityEngine.Bindings; using UnityEngine.Scripting; @@ -143,14 +144,8 @@ internal static extern CustomCommand[] customCommands [FreeFunction("VersionControlBindings::VCProvider::Internal_Checkout")] private static extern Task Internal_Checkout(Asset[] assets, CheckoutMode mode, ChangeSet changeset); - [FreeFunction("VersionControlBindings::VCProvider::Internal_CheckoutStrings")] - private static extern Task Internal_CheckoutStrings(string[] assets, CheckoutMode mode); - - [FreeFunction("VersionControlBindings::VCProvider::Internal_PromptAndCheckoutIfNeeded")] - private static extern bool Internal_PromptAndCheckoutIfNeeded(string[] assets, string promptIfCheckoutIsNeeded, ChangeSet changeset); - [FreeFunction("VersionControlBindings::VCProvider::MakeEditable")] - static internal extern bool MakeEditable(string[] assets, [Out] string[] editableAssets); + internal static extern bool MakeEditableImpl(string[] assets, string prompt, ChangeSet changeSet, object outNotEditablePathsList); [NativeThrows] [FreeFunction("VersionControlBindings::VCProvider::Internal_Delete")] diff --git a/Editor/Mono/VisualStudioIntegration/FileIO.cs b/Editor/Mono/VisualStudioIntegration/FileIO.cs new file mode 100644 index 0000000000..d85c827264 --- /dev/null +++ b/Editor/Mono/VisualStudioIntegration/FileIO.cs @@ -0,0 +1,42 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.IO; +using System.Text; + +namespace UnityEditor.VisualStudioIntegration +{ + interface IFileIO + { + bool Exists(string fileName); + + string ReadAllText(string fileName); + void WriteAllText(string fileName, string content); + + void CreateDirectory(string pathName); + } + + class FileIOProvider : IFileIO + { + public bool Exists(string fileName) + { + return File.Exists(fileName); + } + + public string ReadAllText(string fileName) + { + return File.ReadAllText(fileName); + } + + public void WriteAllText(string fileName, string content) + { + File.WriteAllText(fileName, content, Encoding.UTF8); + } + + public void CreateDirectory(string pathName) + { + Directory.CreateDirectory(pathName); + } + } +} diff --git a/Editor/Mono/VisualStudioIntegration/ProjectGenerationFlag.cs b/Editor/Mono/VisualStudioIntegration/ProjectGenerationFlag.cs new file mode 100644 index 0000000000..2b8d567c8b --- /dev/null +++ b/Editor/Mono/VisualStudioIntegration/ProjectGenerationFlag.cs @@ -0,0 +1,22 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; + +namespace UnityEditor.VisualStudioIntegration +{ + [Flags] + internal enum ProjectGenerationFlag + { + None = 0, + Embedded = 1, + Local = 2, + Registry = 4, + Git = 8, + BuiltIn = 16, + Unknown = 32, + PlayerAssemblies = 64, + LocalTarBall = 128, + } +} diff --git a/Editor/Mono/VisualStudioIntegration/SolutionSynchronizer.cs b/Editor/Mono/VisualStudioIntegration/SolutionSynchronizer.cs index 14ccd0bb9a..fe677f2a66 100644 --- a/Editor/Mono/VisualStudioIntegration/SolutionSynchronizer.cs +++ b/Editor/Mono/VisualStudioIntegration/SolutionSynchronizer.cs @@ -7,20 +7,15 @@ using System.IO; using System.Linq; using System.Security; -using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; -using UnityEditor.Scripting; +using UnityEditor.Compilation; +using UnityEditor.PackageManager; using UnityEditor.Scripting.ScriptCompilation; using UnityEditor.Utils; using UnityEditorInternal; -using UnityEditor.Scripting.Compilers; -using UnityEngine.Profiling; - -using UnityEditor.Compilation; -using UnityEditor.Modules; using UnityEngine; -using UnityEditor.PackageManager; +using UnityEngine.Profiling; namespace UnityEditor.VisualStudioIntegration { @@ -34,39 +29,122 @@ enum ScriptingLanguage interface IAssemblyNameProvider { + string[] ProjectSupportedExtensions { get; } + ProjectGenerationFlag ProjectGenerationFlag { get; } string GetAssemblyNameFromScriptPath(string path); - IEnumerable GetAllScriptAssemblies(Func shouldFileBePartOfSolution, string projectDirectory); + IEnumerable GetAssemblies(Func shouldFileBePartOfSolution); IEnumerable GetAllAssetPaths(); + UnityEditor.PackageManager.PackageInfo FindForAssetPath(string assetPath); + ResponseFileData ParseResponseFile(string responseFilePath, string projectDirectory, string[] systemReferenceDirectories); + bool IsInternalizedPackagePath(string path); + bool HasFlag(ProjectGenerationFlag flag); + void ToggleProjectGeneration(ProjectGenerationFlag preference); } class AssemblyNameProvider : IAssemblyNameProvider { + ProjectGenerationFlag m_ProjectGenerationFlag = (ProjectGenerationFlag)EditorPrefs.GetInt("unity_project_generation_flag", 0); + + public string[] ProjectSupportedExtensions => EditorSettings.projectGenerationUserExtensions; + + public ProjectGenerationFlag ProjectGenerationFlag + { + get { return m_ProjectGenerationFlag; } + private set + { + EditorPrefs.SetInt("unity_project_generation_flag", (int)value); + m_ProjectGenerationFlag = value; + } + } + public string GetAssemblyNameFromScriptPath(string path) { return CompilationPipeline.GetAssemblyNameFromScriptPath(path); } - public IEnumerable GetAllScriptAssemblies(Func shouldFileBePartOfSolution, string projectDirectory) + public IEnumerable GetAssemblies(Func shouldFileBePartOfSolution) { - return EditorCompilationInterface.Instance.GetAllScriptAssemblies(EditorScriptCompilationOptions.BuildingForEditor | EditorCompilationInterface.GetAdditionalEditorScriptCompilationOptions(), null) - .Where(i => 0 < i.Files.Length && i.Files.Any(shouldFileBePartOfSolution)) - .Select(x => x.ToMonoIsland(EditorScriptCompilationOptions.BuildingForEditor, string.Empty, projectDirectory)).ToList(); + return CompilationPipeline.GetAssemblies() + .Where(i => 0 < i.sourceFiles.Length && i.sourceFiles.Any(shouldFileBePartOfSolution)); } public IEnumerable GetAllAssetPaths() { return AssetDatabase.GetAllAssetPaths(); } - } - class SolutionSynchronizer - { - enum Mode + public ResponseFileData ParseResponseFile(string responseFilePath, string projectDirectory, string[] systemReferenceDirectories) + { + return CompilationPipeline.ParseResponseFile( + responseFilePath, + projectDirectory, + systemReferenceDirectories + ); + } + + public bool HasFlag(ProjectGenerationFlag flag) + { + return (this.ProjectGenerationFlag & flag) == flag; + } + + public void ToggleProjectGeneration(ProjectGenerationFlag preference) { - UnityScriptAsUnityProj, - UnityScriptAsPrecompiledAssembly + if (HasFlag(preference)) + { + ProjectGenerationFlag ^= preference; + } + else + { + ProjectGenerationFlag |= preference; + } + } + + public UnityEditor.PackageManager.PackageInfo FindForAssetPath(string assetPath) + { + return UnityEditor.PackageManager.PackageInfo.FindForAssetPath(assetPath); + } + + public bool IsInternalizedPackagePath(string path) + { + if (string.IsNullOrEmpty(path)) + { + return true; + } + var packageInfo = FindForAssetPath(path); + if (packageInfo == null) + { + return false; + } + var packageSource = packageInfo.source; + switch (packageSource) + { + case PackageSource.Embedded: + return !HasFlag(ProjectGenerationFlag.Embedded); + case PackageSource.Registry: + return !HasFlag(ProjectGenerationFlag.Registry); + case PackageSource.BuiltIn: + return !HasFlag(ProjectGenerationFlag.BuiltIn); + case PackageSource.Unknown: + return !HasFlag(ProjectGenerationFlag.Unknown); + case PackageSource.Local: + return !HasFlag(ProjectGenerationFlag.Local); + case PackageSource.Git: + return !HasFlag(ProjectGenerationFlag.Git); + case PackageSource.LocalTarball: + return !HasFlag(ProjectGenerationFlag.LocalTarBall); + } + + return true; + } + + public void ResetProjectGenerationFlag() + { + ProjectGenerationFlag = ProjectGenerationFlag.None; } + } + class SolutionSynchronizer + { public static readonly ISolutionSynchronizationSettings DefaultSynchronizationSettings = new DefaultSolutionSynchronizationSettings(); @@ -109,18 +187,21 @@ enum Mode private readonly string _projectDirectory; private readonly ISolutionSynchronizationSettings _settings; private readonly string _projectName; - readonly IAssemblyNameProvider m_assemblyNameProvider; bool m_ShouldGenerateAll; + readonly IFileIO m_FileIO; + + internal IAssemblyNameProvider AssemblyNameProvider { get; } - public SolutionSynchronizer(string projectDirectory, ISolutionSynchronizationSettings settings, IAssemblyNameProvider assemblyNameProvider) + public SolutionSynchronizer(string projectDirectory, ISolutionSynchronizationSettings settings, IAssemblyNameProvider assemblyNameProvider, IFileIO fileIO) { _projectDirectory = projectDirectory.ConvertSeparatorsToUnity(); _settings = settings; _projectName = Path.GetFileName(_projectDirectory); - m_assemblyNameProvider = assemblyNameProvider; + AssemblyNameProvider = assemblyNameProvider; + m_FileIO = fileIO; } - public SolutionSynchronizer(string projectDirectory, ISolutionSynchronizationSettings settings) : this(projectDirectory, settings, new AssemblyNameProvider()) + public SolutionSynchronizer(string projectDirectory, ISolutionSynchronizationSettings settings) : this(projectDirectory, settings, new AssemblyNameProvider(), new FileIOProvider()) { } @@ -130,7 +211,7 @@ public SolutionSynchronizer(string projectDirectory) : this(projectDirectory, De private void SetupProjectSupportedExtensions() { - ProjectSupportedExtensions = EditorSettings.projectGenerationUserExtensions; + ProjectSupportedExtensions = AssemblyNameProvider.ProjectSupportedExtensions; } public bool ShouldFileBePartOfSolution(string file) @@ -138,7 +219,7 @@ public bool ShouldFileBePartOfSolution(string file) string extension = Path.GetExtension(file); // Exclude files coming from packages except if they are internalized. - if (!m_ShouldGenerateAll && IsNonInternalizedPackagePath(file)) + if (AssemblyNameProvider.IsInternalizedPackagePath(file)) { return false; } @@ -147,7 +228,7 @@ public bool ShouldFileBePartOfSolution(string file) if (extension == ".dll") return true; - if (file.ToLower().EndsWith(".asmdef")) + if (file.ToLowerInvariant().EndsWith(".asmdef", StringComparison.OrdinalIgnoreCase)) return true; return IsSupportedExtension(extension); @@ -163,9 +244,9 @@ private bool IsSupportedExtension(string extension) return false; } - private static ScriptingLanguage ScriptingLanguageFor(MonoIsland island) + private static ScriptingLanguage ScriptingLanguageFor(Assembly assembly) { - return ScriptingLanguageFor(island.GetExtensionOfSourceFiles()); + return ScriptingLanguageFor(GetExtensionOfSourceFiles(assembly.sourceFiles)); } private static ScriptingLanguage ScriptingLanguageFor(string extension) @@ -177,23 +258,35 @@ private static ScriptingLanguage ScriptingLanguageFor(string extension) return ScriptingLanguage.None; } - public bool ProjectExists(MonoIsland island) + static string GetExtensionOfSourceFiles(string[] files) + { + return files.Length > 0 ? GetExtensionOfSourceFile(files[0]) : "NA"; + } + + static string GetExtensionOfSourceFile(string file) + { + var ext = Path.GetExtension(file).ToLower(); + ext = ext.Substring(1); //strip dot + return ext; + } + + public bool ProjectExists(Assembly assembly) { - return File.Exists(ProjectFile(island)); + return m_FileIO.Exists(ProjectFile(assembly)); } public bool SolutionExists() { - return File.Exists(SolutionFile()); + return m_FileIO.Exists(SolutionFile()); } - private static void DumpIsland(MonoIsland island) + private static void DumpAssembly(Assembly assembly) { - Console.WriteLine("{0} ({1})", island._output, island._api_compatibility_level); + Console.WriteLine("{0} ({1})", assembly.outputPath, assembly.compilerOptions.ApiCompatibilityLevel); Console.WriteLine("Files: "); - Console.WriteLine(string.Join("\n", island._files)); + Console.WriteLine(string.Join("\n", assembly.sourceFiles)); Console.WriteLine("References: "); - Console.WriteLine(string.Join("\n", island._references)); + Console.WriteLine(string.Join("\n", assembly.allReferences)); Console.WriteLine(""); } @@ -260,9 +353,9 @@ internal void GenerateAndWriteSolutionAndProjects(ScriptEditorUtility.ScriptEdit Profiler.BeginSample("GenerateAndWriteSolutionAndProjects"); Profiler.BeginSample("SolutionSynchronizer.GetIslands"); - // Only synchronize islands that have associated source files and ones that we actually want in the project. + // Only synchronize assemblies that have associated source files and ones that we actually want in the project. // This also filters out DLLs coming from .asmdef files in packages. - IEnumerable islands = m_assemblyNameProvider.GetAllScriptAssemblies(ShouldFileBePartOfSolution, _projectDirectory); + IEnumerable assemblies = AssemblyNameProvider.GetAssemblies(ShouldFileBePartOfSolution); Profiler.EndSample(); @@ -270,29 +363,30 @@ internal void GenerateAndWriteSolutionAndProjects(ScriptEditorUtility.ScriptEdit var allAssetProjectParts = GenerateAllAssetProjectParts(); Profiler.EndSample(); - var monoIslands = islands.ToList(); + var assemblyList = assemblies.ToList(); Profiler.BeginSample("SyncSolution"); - SyncSolution(monoIslands.ToList()); + SyncSolution(assemblyList); Profiler.EndSample(); - var allProjectIslands = RelevantIslandsForMode(monoIslands, ModeForCurrentExternalEditor()).ToList(); + var allProjectAssemblies = RelevantAssemblies(assemblyList).ToList(); + var assemblyNames = new HashSet(allProjectAssemblies.Select(assembly => Path.GetFileName(assembly.outputPath))); - foreach (MonoIsland island in allProjectIslands) + foreach (Assembly assembly in allProjectAssemblies) { Profiler.BeginSample("SyncProject"); - SyncProject(island, allAssetProjectParts, ParseResponseFileData(island), allProjectIslands); + SyncProject(assembly, allAssetProjectParts, ParseResponseFileData(assembly), assemblyNames); Profiler.EndSample(); } Profiler.EndSample(); } - IEnumerable ParseResponseFileData(MonoIsland island) + List ParseResponseFileData(Assembly assembly) { - var systemReferenceDirectories = MonoLibraryHelpers.GetSystemReferenceDirectories(island._api_compatibility_level); + var systemReferenceDirectories = MonoLibraryHelpers.GetSystemReferenceDirectories(assembly.compilerOptions.ApiCompatibilityLevel); - Dictionary responseFilesData = island._responseFiles.ToDictionary(x => x, x => ScriptCompilerBase.ParseResponseFileFromFile( + Dictionary responseFilesData = assembly.compilerOptions.ResponseFiles.ToDictionary(x => x, x => AssemblyNameProvider.ParseResponseFile( x, _projectDirectory, systemReferenceDirectories @@ -310,27 +404,26 @@ IEnumerable ParseResponseFileData(MonoIsland island) } } - return responseFilesData.Select(x => x.Value); + return responseFilesData.Select(x => x.Value).ToList(); } Dictionary GenerateAllAssetProjectParts() { Dictionary stringBuilders = new Dictionary(); - foreach (string asset in m_assemblyNameProvider.GetAllAssetPaths()) + foreach (string asset in AssemblyNameProvider.GetAllAssetPaths()) { // Exclude files coming from packages except if they are internalized. - if (!m_ShouldGenerateAll && IsNonInternalizedPackagePath(asset)) + if (AssemblyNameProvider.IsInternalizedPackagePath(asset)) { continue; } + string extension = Path.GetExtension(asset); if (IsSupportedExtension(extension) && ScriptingLanguage.None == ScriptingLanguageFor(extension)) { // Find assembly the asset belongs to by adding script extension and using compilation pipeline. - var assemblyName = m_assemblyNameProvider.GetAssemblyNameFromScriptPath(asset + ".cs"); - assemblyName = assemblyName ?? m_assemblyNameProvider.GetAssemblyNameFromScriptPath(asset + ".js"); - assemblyName = assemblyName ?? m_assemblyNameProvider.GetAssemblyNameFromScriptPath(asset + ".boo"); + var assemblyName = AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset); if (string.IsNullOrEmpty(assemblyName)) { @@ -359,26 +452,16 @@ Dictionary GenerateAllAssetProjectParts() return result; } - bool IsNonInternalizedPackagePath(string file) - { - if (UnityEditor.PackageManager.Folders.IsPackagedAssetPath(file)) - { - bool rootFolder, readOnly; - bool validPath = AssetDatabase.GetAssetFolderInfo(file, out rootFolder, out readOnly); - return (!validPath || readOnly); - } - return false; - } - - void SyncProject(MonoIsland island, + void SyncProject( + Assembly assembly, Dictionary allAssetsProjectParts, - IEnumerable responseFilesData, - List allProjectIslands) + List responseFilesData, + HashSet assemblyNames) { - SyncProjectFileIfNotChanged(ProjectFile(island), ProjectText(island, ModeForCurrentExternalEditor(), allAssetsProjectParts, responseFilesData, allProjectIslands)); + SyncProjectFileIfNotChanged(ProjectFile(assembly), ProjectText(assembly, allAssetsProjectParts, responseFilesData, assemblyNames)); } - static void SyncProjectFileIfNotChanged(string path, string newContents) + void SyncProjectFileIfNotChanged(string path, string newContents) { if (Path.GetExtension(path) == ".csproj") { @@ -388,7 +471,7 @@ static void SyncProjectFileIfNotChanged(string path, string newContents) SyncFileIfNotChanged(path, newContents); } - static void SyncSolutionFileIfNotChanged(string path, string newContents) + void SyncSolutionFileIfNotChanged(string path, string newContents) { newContents = AssetPostprocessingInternal.CallOnGeneratedSlnSolution(path, newContents); @@ -442,11 +525,11 @@ static void LogDifference(string path, string currentContents, string newContent while (currentLine != null && newLine != null); } - private static void SyncFileIfNotChanged(string filename, string newContents) + private void SyncFileIfNotChanged(string filename, string newContents) { - if (File.Exists(filename)) + if (m_FileIO.Exists(filename)) { - var currentContents = File.ReadAllText(filename); + var currentContents = m_FileIO.ReadAllText(filename); if (currentContents == newContents) { @@ -464,7 +547,7 @@ private static void SyncFileIfNotChanged(string filename, string newContents) } } - File.WriteAllText(filename, newContents, Encoding.UTF8); + m_FileIO.WriteAllText(filename, newContents); } public static readonly Regex scriptReferenceExpression = new Regex( @@ -478,19 +561,17 @@ static bool IsAdditionalInternalAssemblyReference(bool isBuildingEditorProject, return false; } - string ProjectText(MonoIsland island, - Mode mode, + string ProjectText( + Assembly assembly, Dictionary allAssetsProjectParts, - IEnumerable responseFilesData, - List allProjectIslands) + List responseFilesData, + HashSet assemblyNames) { - var projectBuilder = new StringBuilder(ProjectHeader(island, responseFilesData)); + var projectBuilder = new StringBuilder(); + projectBuilder.Append(ProjectHeader(assembly, responseFilesData)); var references = new List(); - var projectReferences = new List(); - Match match; - bool isBuildingEditorProject = island._editor; - foreach (string file in island._files) + foreach (string file in assembly.sourceFiles) { if (!ShouldFileBePartOfSolution(file)) continue; @@ -499,8 +580,7 @@ string ProjectText(MonoIsland island, var fullFile = EscapedRelativePathFor(file); if (".dll" != extension) { - var tagName = "Compile"; - projectBuilder.Append(" <").Append(tagName).Append(" Include=\"").Append(fullFile).Append("\" />").Append(WindowsNewline); + projectBuilder.Append(" ").Append(WindowsNewline); } else { @@ -508,85 +588,42 @@ string ProjectText(MonoIsland island, } } - string additionalAssetsForProject; - var assemblyName = Utility.FileNameWithoutExtension(island._output); - // Append additional non-script files that should be included in project generation. - if (allAssetsProjectParts.TryGetValue(assemblyName, out additionalAssetsForProject)) + string additionalAssetsForProject; + if (allAssetsProjectParts.TryGetValue(assembly.name, out additionalAssetsForProject)) projectBuilder.Append(additionalAssetsForProject); - var allAdditionalReferenceFilenames = new List(); - var islandRefs = references.Union(island._references); + var responseRefs = responseFilesData.SelectMany(x => x.FullPathReferences.Select(r => r)); + var internalAssemblyReferences = assembly.assemblyReferences + .Where(i => !i.sourceFiles.Any(ShouldFileBePartOfSolution)).Select(i => i.outputPath); + var allReferences = + assembly.compiledAssemblyReferences + .Union(responseRefs) + .Union(references) + .Union(internalAssemblyReferences); - foreach (string reference in islandRefs) + foreach (var reference in allReferences) { - if (reference.EndsWith("/UnityEditor.dll", StringComparison.Ordinal) - || reference.EndsWith("/UnityEngine.dll", StringComparison.Ordinal) - || reference.EndsWith("\\UnityEditor.dll", StringComparison.Ordinal) - || reference.EndsWith("\\UnityEngine.dll", StringComparison.Ordinal)) - continue; - - match = scriptReferenceExpression.Match(reference); - if (match.Success) - { - var language = ScriptCompilers.GetLanguageFromExtension(island.GetExtensionOfSourceFiles()); - var targetLanguage = (ScriptingLanguage)Enum.Parse(typeof(ScriptingLanguage), language.GetLanguageName(), true); - if (mode == Mode.UnityScriptAsUnityProj || ScriptingLanguage.CSharp == targetLanguage) - { - // Add a reference to a project except if it's a reference to a script assembly - // that we are not generating a project for. This will be the case for assemblies - // coming from .assembly.json files in non-internalized packages. - var dllName = match.Groups["dllname"].Value; - if (allProjectIslands.Any(i => Path.GetFileName(i._output) == dllName)) - { - projectReferences.Add(match); - continue; - } - } - } - string fullReference = Path.IsPathRooted(reference) ? reference : Path.Combine(_projectDirectory, reference); - if (!AssemblyHelper.IsManagedAssembly(fullReference)) - continue; - if (AssemblyHelper.IsInternalAssembly(fullReference)) - { - if (!IsAdditionalInternalAssemblyReference(isBuildingEditorProject, fullReference)) - continue; - var referenceName = Path.GetFileName(fullReference); - if (allAdditionalReferenceFilenames.Contains(referenceName)) - continue; - allAdditionalReferenceFilenames.Add(referenceName); - } - AppendReference(fullReference, projectBuilder); } - var responseRefs = responseFilesData.SelectMany(x => x.FullPathReferences); - foreach (var reference in responseRefs) - { - AppendReference(reference, projectBuilder); - } - - if (0 < projectReferences.Count) + if (0 < assembly.assemblyReferences.Length) { - string referencedProject; - projectBuilder.AppendLine(" "); - projectBuilder.AppendLine(" "); - foreach (Match reference in projectReferences) + projectBuilder.Append(" ").Append(WindowsNewline); + projectBuilder.Append(" ").Append(WindowsNewline); + foreach (Assembly reference in assembly.assemblyReferences.Where(i => i.sourceFiles.Any(ShouldFileBePartOfSolution))) { - var targetAssembly = EditorCompilationInterface.Instance.GetTargetAssemblyDetails(reference.Groups["dllname"].Value); - ScriptingLanguage targetLanguage = ScriptingLanguage.None; - if (targetAssembly != null) - targetLanguage = (ScriptingLanguage)Enum.Parse(typeof(ScriptingLanguage), targetAssembly.Language.GetLanguageName(), true); - referencedProject = reference.Groups["project"].Value; - projectBuilder.Append(" ").Append(WindowsNewline); - projectBuilder.Append(" {").Append(ProjectGuid(Path.Combine("Temp", reference.Groups["project"].Value + ".dll"))).Append("}").Append(WindowsNewline); - projectBuilder.Append(" ").Append(referencedProject).Append("").Append(WindowsNewline); - projectBuilder.AppendLine(" "); + var referencedProject = reference.outputPath; + + projectBuilder.Append(" ").Append(WindowsNewline); + projectBuilder.Append(" {").Append(ProjectGuid(reference.name)).Append("}").Append(WindowsNewline); + projectBuilder.Append(" ").Append(reference.name).Append("").Append(WindowsNewline); + projectBuilder.Append(" ").Append(WindowsNewline); } } - projectBuilder.Append(ProjectFooter(island)); + projectBuilder.Append(ProjectFooter(assembly)); return projectBuilder.ToString(); } @@ -601,10 +638,10 @@ static void AppendReference(string fullReference, StringBuilder projectBuilder) projectBuilder.Append(" ").Append(WindowsNewline); } - public string ProjectFile(MonoIsland island) + public string ProjectFile(Assembly assembly) { - ScriptingLanguage language = ScriptingLanguageFor(island); - return Path.Combine(_projectDirectory, string.Format("{0}{1}", Utility.FileNameWithoutExtension(island._output), ProjectExtensions[language])); + ScriptingLanguage language = ScriptingLanguageFor(assembly); + return Path.Combine(_projectDirectory, string.Format("{0}{1}", assembly.name, ProjectExtensions[language])); } internal string SolutionFile() @@ -612,7 +649,7 @@ internal string SolutionFile() return Path.Combine(_projectDirectory, string.Format("{0}.sln", _projectName)); } - private string ProjectHeader(MonoIsland island, + private string ProjectHeader(Assembly assembly, IEnumerable responseFilesData) { string targetframeworkversion = "v3.5"; @@ -622,9 +659,9 @@ private string ProjectHeader(MonoIsland island, string baseDirectory = "."; string cscToolPath = "$(CscToolPath)"; string cscToolExe = "$(CscToolExe)"; - ScriptingLanguage language = ScriptingLanguageFor(island); + ScriptingLanguage language = ScriptingLanguageFor(assembly); - if (PlayerSettingsEditor.IsLatestApiCompatibility(island._api_compatibility_level)) + if (PlayerSettingsEditor.IsLatestApiCompatibility(assembly.compilerOptions.ApiCompatibilityLevel)) { targetframeworkversion = "v4.7.1"; targetLanguageVersion = "latest"; @@ -645,17 +682,17 @@ private string ProjectHeader(MonoIsland island, var arguments = new object[] { - toolsversion, productversion, ProjectGuid(island._output), + toolsversion, productversion, ProjectGuid(assembly.name), _settings.EngineAssemblyPath, _settings.EditorAssemblyPath, - string.Join(";", new[] { "DEBUG", "TRACE"}.Concat(island._defines).Concat(responseFilesData.SelectMany(x => x.Defines)).Distinct().ToArray()), + string.Join(";", new[] { "DEBUG", "TRACE"}.Concat(assembly.defines).Concat(responseFilesData.SelectMany(x => x.Defines)).Distinct().ToArray()), MSBuildNamespaceUri, - Utility.FileNameWithoutExtension(island._output), + assembly.name, EditorSettings.projectGenerationRootNamespace, targetframeworkversion, targetLanguageVersion, baseDirectory, - island._allowUnsafeCode | responseFilesData.Any(x => x.Unsafe), + assembly.compilerOptions.AllowUnsafeCode | responseFilesData.Any(x => x.Unsafe), cscToolPath, cscToolExe, }; @@ -670,24 +707,12 @@ private string ProjectHeader(MonoIsland island, } } - private void SyncSolution(IEnumerable islands) - { - SyncSolutionFileIfNotChanged(SolutionFile(), SolutionText(islands, ModeForCurrentExternalEditor())); - } - - private static Mode ModeForCurrentExternalEditor() + private void SyncSolution(IEnumerable assemblies) { - #pragma warning disable 618 - var scriptEditor = ScriptEditorUtility.GetScriptEditorFromPreferences(); - - if (scriptEditor == ScriptEditorUtility.ScriptEditor.VisualStudio || - scriptEditor == ScriptEditorUtility.ScriptEditor.VisualStudioExpress) - return Mode.UnityScriptAsPrecompiledAssembly; - - return EditorPrefs.GetBool("kExternalEditorSupportsUnityProj", false) ? Mode.UnityScriptAsUnityProj : Mode.UnityScriptAsPrecompiledAssembly; + SyncSolutionFileIfNotChanged(SolutionFile(), SolutionText(assemblies)); } - private string SolutionText(IEnumerable islands, Mode mode) + private string SolutionText(IEnumerable assemblies) { var fileversion = "11.00"; var vsversion = "2010"; @@ -696,27 +721,26 @@ private string SolutionText(IEnumerable islands, Mode mode) fileversion = "10.00"; vsversion = "2008"; } - var relevantIslands = RelevantIslandsForMode(islands, mode); - string projectEntries = GetProjectEntries(relevantIslands); - string projectConfigurations = string.Join(WindowsNewline, relevantIslands.Select(i => GetProjectActiveConfigurations(ProjectGuid(i._output))).ToArray()); + var relevantAssemblies = RelevantAssemblies(assemblies); + string projectEntries = GetProjectEntries(relevantAssemblies); + string projectConfigurations = string.Join(WindowsNewline, relevantAssemblies.Select(i => GetProjectActiveConfigurations(ProjectGuid(i.name))).ToArray()); return string.Format(_settings.SolutionTemplate, fileversion, vsversion, projectEntries, projectConfigurations); } - private static IEnumerable RelevantIslandsForMode(IEnumerable islands, Mode mode) + static IEnumerable RelevantAssemblies(IEnumerable assemblies) { - IEnumerable relevantIslands = islands.Where(i => (mode == Mode.UnityScriptAsUnityProj || ScriptingLanguage.CSharp == ScriptingLanguageFor(i))); - return relevantIslands; + return assemblies.Where(i => ScriptingLanguage.CSharp == ScriptingLanguageFor(i)); } /// /// Get a Project("{guid}") = "MyProject", "MyProject.unityproj", "{projectguid}" /// entry for each relevant language /// - internal string GetProjectEntries(IEnumerable islands) + internal string GetProjectEntries(IEnumerable assemblies) { - var projectEntries = islands.Select(i => string.Format( + var projectEntries = assemblies.Select(i => string.Format( DefaultSynchronizationSettings.SolutionProjectEntryTemplate, - SolutionGuid(i), Utility.FileNameWithoutExtension(i._output), Path.GetFileName(ProjectFile(i)), ProjectGuid(i._output) + SolutionGuid(i), i.name, Path.GetFileName(ProjectFile(i)), ProjectGuid(i.name) )); return string.Join(WindowsNewline, projectEntries.ToArray()); @@ -732,34 +756,51 @@ private string GetProjectActiveConfigurations(string projectGuid) projectGuid); } - private string EscapedRelativePathFor(string file) + string EscapedRelativePathFor(string file) { - var projectDir = _projectDirectory.ConvertSeparatorsToWindows(); - file = file.ConvertSeparatorsToWindows(); - var path = Paths.SkipPathPrefix(file, projectDir); - if (PackageManager.Folders.IsPackagedAssetPath(path.ConvertSeparatorsToUnity())) + var projectDir = _projectDirectory.Replace('/', '\\'); + file = file.Replace('/', '\\'); + var path = SkipPathPrefix(file, projectDir); + + var packageInfo = AssemblyNameProvider.FindForAssetPath(path.Replace('\\', '/')); + if (packageInfo != null) { // We have to normalize the path, because the PackageManagerRemapper assumes // dir seperators will be os specific. - var absolutePath = Path.GetFullPath(path.NormalizePath()).ConvertSeparatorsToWindows(); - path = Paths.SkipPathPrefix(absolutePath, projectDir); + var absolutePath = Path.GetFullPath(NormalizePath(path)).Replace('/', '\\'); + path = SkipPathPrefix(absolutePath, projectDir); } + return SecurityElement.Escape(path); } - string ProjectGuid(string assembly) + static string SkipPathPrefix(string path, string prefix) + { + if (path.StartsWith($@"{prefix}\")) + return path.Substring(prefix.Length + 1); + return path; + } + + static string NormalizePath(string path) + { + if (Path.DirectorySeparatorChar == '\\') + return path.Replace('/', Path.DirectorySeparatorChar); + return path.Replace('\\', Path.DirectorySeparatorChar); + } + + string ProjectGuid(string assemblyName) { - return SolutionGuidGenerator.GuidForProject(_projectName + Utility.FileNameWithoutExtension(assembly)); + return SolutionGuidGenerator.GuidForProject(_projectName + assemblyName); } - string SolutionGuid(MonoIsland island) + string SolutionGuid(Assembly assembly) { - return SolutionGuidGenerator.GuidForSolution(_projectName, island.GetExtensionOfSourceFiles()); + return SolutionGuidGenerator.GuidForSolution(_projectName, GetExtensionOfSourceFiles(assembly.sourceFiles)); } - string ProjectFooter(MonoIsland island) + string ProjectFooter(Assembly assembly) { - return _settings.GetProjectFooterTemplate(ScriptingLanguageFor(island)); + return _settings.GetProjectFooterTemplate(ScriptingLanguageFor(assembly)); } [Obsolete("Use AssemblyHelper.IsManagedAssembly")] diff --git a/Editor/Mono/VisualStudioIntegration/UnityVSSupport.cs b/Editor/Mono/VisualStudioIntegration/UnityVSSupport.cs index 394ea49a36..7c08b48b3d 100644 --- a/Editor/Mono/VisualStudioIntegration/UnityVSSupport.cs +++ b/Editor/Mono/VisualStudioIntegration/UnityVSSupport.cs @@ -336,7 +336,7 @@ public static void ScriptEditorChanged(string editorPath) // We reload the domain because selecting a different editor requires loading a different UnityVS Initialize(editorPath); - InternalEditorUtility.RequestScriptReload(); + EditorUtility.RequestScriptReload(); } public static string GetAboutWindowLabel() diff --git a/External/MonoBleedingEdge/builds/monodistribution/lib/mono/4.5/Facades/netstandard.dll b/External/MonoBleedingEdge/builds/monodistribution/lib/mono/4.5/Facades/netstandard.dll index 73897a6699..9350e84c4a 100644 Binary files a/External/MonoBleedingEdge/builds/monodistribution/lib/mono/4.5/Facades/netstandard.dll and b/External/MonoBleedingEdge/builds/monodistribution/lib/mono/4.5/Facades/netstandard.dll differ diff --git a/External/Unity.Compiler.Client/Unity.CompilationPipeline.Common.dll b/External/Unity.Compiler.Client/Unity.CompilationPipeline.Common.dll index b90e62f25e..0fbf2ac501 100644 Binary files a/External/Unity.Compiler.Client/Unity.CompilationPipeline.Common.dll and b/External/Unity.Compiler.Client/Unity.CompilationPipeline.Common.dll differ diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000000..2303b574a4 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1 @@ +For terms of use, see https://unity3d.com/legal/licenses/Unity_Reference_Only_License diff --git a/Modules/Accessibility/VisionUtility.cs b/Modules/Accessibility/VisionUtility.cs index dcebf5e125..7df416dc0b 100644 --- a/Modules/Accessibility/VisionUtility.cs +++ b/Modules/Accessibility/VisionUtility.cs @@ -6,7 +6,9 @@ using System.Linq; using UnityEngine; using UnityEngine.Scripting; +using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("Acessibility.VisionUtility.Tests")] namespace UnityEngine.Accessibility { [UsedByNativeCode] @@ -14,30 +16,39 @@ public static class VisionUtility { // http://www.somersault1824.com/wp-content/uploads/2015/02/color-blindness-palette.png // colors are ordered across rows to maximize contrast with small palettes - private static readonly Color[] s_ColorBlindSafePalette = new[] + static readonly Color[] s_ColorBlindSafePalette = new Color[] { - (Color) new Color32(0, 0, 0, 255), - (Color) new Color32(73, 0, 146, 255), -// (Color) new Color32(146, 0, 0, 255), // doesn't look good in editor + new Color32(0, 0, 0, 255), // Not used by profiler because too dark + new Color32(73, 0, 146, 255), // Not used by profiler because too dark +// new Color32(146, 0, 0, 255), // doesn't look good in editor - (Color) new Color32(7, 71, 81, 255), -// (Color) new Color32(0, 109, 219, 255), // tritanopia ambiguous 1 -// (Color) new Color32(146, 73, 0, 255), // doesn't look good in editor + new Color32(7, 71, 81, 255), // Not used by profiler because too dark +// new Color32(0, 109, 219, 255), // tritanopia ambiguous 1 +// new Color32(146, 73, 0, 255), // doesn't look good in editor - (Color) new Color32(0, 146, 146, 255), // tritanopia ambiguous 1 - (Color) new Color32(182, 109, 255, 255), -// (Color) new Color32(219, 109, 0, 255), // tritanopia ambiguous 2; also doesn't look good in editor + new Color32(0, 146, 146, 255), // "Rendering" // tritanopia ambiguous 1 + new Color32(182, 109, 255, 255), // "Scripts", "Managed Jobs", "Burst Jobs" +// new Color32(219, 209, 0, 255), // tritanopia ambiguous 2; also doesn't look good in editor - (Color) new Color32(255, 109, 182, 255), // tritanopia ambiguous 2 - (Color) new Color32(109, 182, 255, 255), - (Color) new Color32(36, 255, 36, 255), + new Color32(255, 109, 182, 255), // "Physics" // tritanopia ambiguous 2 + new Color32(109, 182, 255, 255), // "Animnation" + new Color32(36, 255, 36, 255), // "GC" - (Color) new Color32(255, 182, 219, 255), - (Color) new Color32(182, 219, 255, 255), - (Color) new Color32(255, 255, 109, 255), + new Color32(255, 182, 219, 255), // "VSync" + new Color32(182, 219, 255, 255), // "GI" + new Color32(255, 255, 109, 255), // UI: "UI Layout", "UI Render" + // Bugfix additions to address https://fogbugz.unity3d.com/f/cases/1101387/ + new Color32(30, 92, 92, 255), // Profiler - CPU Usage Chart: "Other" category + new Color32(74, 154, 87, 255), // Profiler - GI Chart: "Blocked Command Write" - CPU Timeline: "Audio" + new Color32(113, 66, 183, 255), // CPU Timeline: "Audio Job" + new Color32(162, 66, 183, 255), // CPU Timeline: "Audio Update Job" + new Color32(178, 92, 25, 255), // CPU Timeline: "Memory Allocation" + new Color32(100, 100, 100, 255), // CPU Timeline: "Internal" + new Color32(80, 203, 181, 255), // CPU Timeline: "Build Interface" + new Color32(82, 205, 242, 255), // CPU Timeline: "Input" }; - private static readonly float[] s_ColorBlindSafePaletteLuminanceValues = + static readonly float[] s_ColorBlindSafePaletteLuminanceValues = s_ColorBlindSafePalette.Select(c => ComputePerceivedLuminance(c)).ToArray(); // https://en.wikipedia.org/wiki/Relative_luminance @@ -47,27 +58,75 @@ internal static float ComputePerceivedLuminance(Color color) return Mathf.LinearToGammaSpace(0.2126f * color.r + 0.7152f * color.g + 0.0722f * color.b); } + internal static void GetLuminanceValuesForPalette(Color[] palette, ref float[] outLuminanceValues) + { + Debug.Assert(palette != null && outLuminanceValues != null, "Passed in arrays can't be null."); + Debug.Assert(palette.Length == outLuminanceValues.Length, "Passed in arrays need to be of the same length."); + for (int i = 0; i < palette.Length; i++) + { + outLuminanceValues[i] = ComputePerceivedLuminance(palette[i]); + } + } + public static int GetColorBlindSafePalette(Color[] palette, float minimumLuminance, float maximumLuminance) + { + if (palette == null) + throw new ArgumentNullException("palette"); + unsafe + { + fixed(Color * p = palette) + { + return GetColorBlindSafePaletteInternal(p, palette.Length, minimumLuminance, maximumLuminance, useColor32: false); + } + } + } + + internal static int GetColorBlindSafePalette(Color32[] palette, float minimumLuminance, float maximumLuminance) + { + if (palette == null) + throw new ArgumentNullException("palette"); + unsafe + { + fixed(Color32 * p = palette) + { + return GetColorBlindSafePaletteInternal(p, palette.Length, minimumLuminance, maximumLuminance, useColor32: true); + } + } + } + + // MethodImplOptions.AggressiveInlining = 256 + [MethodImpl(256)] + unsafe static int GetColorBlindSafePaletteInternal(void* palette, int paletteLength, float minimumLuminance, float maximumLuminance, bool useColor32) { if (palette == null) throw new ArgumentNullException("palette"); - Color[] acceptableColors = Enumerable.Range(0, s_ColorBlindSafePalette.Length).Where( + var acceptableColors = Enumerable.Range(0, s_ColorBlindSafePalette.Length).Where( i => s_ColorBlindSafePaletteLuminanceValues[i] >= minimumLuminance && s_ColorBlindSafePaletteLuminanceValues[i] <= maximumLuminance ).Select(i => s_ColorBlindSafePalette[i] ).ToArray(); - int numUniqueColors = Mathf.Min(palette.Length, acceptableColors.Length); + int numUniqueColors = Mathf.Min(paletteLength, acceptableColors.Length); if (numUniqueColors > 0) { - for (int i = 0, count = palette.Length; i < count; ++i) - palette[i] = acceptableColors[i % numUniqueColors]; + for (int i = 0, count = paletteLength; i < count; ++i) + { + if (useColor32) + ((Color32*)palette)[i] = (Color32)acceptableColors[i % numUniqueColors]; + else + ((Color*)palette)[i] = (Color)acceptableColors[i % numUniqueColors]; + } } else { - for (int i = 0, count = palette.Length; i < count; ++i) - palette[i] = default(Color); + for (int i = 0, count = paletteLength; i < count; ++i) + { + if (useColor32) + ((Color32*)palette)[i] = default(Color32); + else + ((Color*)palette)[i] = default(Color); + } } return numUniqueColors; diff --git a/Modules/AndroidJNI/AndroidJava.cs b/Modules/AndroidJNI/AndroidJava.cs index 8690e5c33c..a0d9fdd78e 100644 --- a/Modules/AndroidJNI/AndroidJava.cs +++ b/Modules/AndroidJNI/AndroidJava.cs @@ -832,13 +832,28 @@ private static IntPtr GetStaticMethodID(string clazz, string methodName, string } } + private static IntPtr GetMethodID(string clazz, string methodName, string signature) + { + IntPtr jclass = AndroidJNISafe.FindClass(clazz); + try + { + return AndroidJNISafe.GetMethodID(jclass, methodName, signature); + } + finally + { + AndroidJNISafe.DeleteLocalRef(jclass); + } + } + private const string RELECTION_HELPER_CLASS_NAME = "com/unity3d/player/ReflectionHelper"; private static readonly GlobalJavaObjectRef s_ReflectionHelperClass = new GlobalJavaObjectRef(AndroidJNISafe.FindClass(RELECTION_HELPER_CLASS_NAME)); private static readonly IntPtr s_ReflectionHelperGetConstructorID = GetStaticMethodID(RELECTION_HELPER_CLASS_NAME, "getConstructorID", "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Constructor;"); private static readonly IntPtr s_ReflectionHelperGetMethodID = GetStaticMethodID(RELECTION_HELPER_CLASS_NAME, "getMethodID", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/reflect/Method;"); private static readonly IntPtr s_ReflectionHelperGetFieldID = GetStaticMethodID(RELECTION_HELPER_CLASS_NAME, "getFieldID", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/reflect/Field;"); + private static readonly IntPtr s_ReflectionHelperGetFieldSignature = GetStaticMethodID(RELECTION_HELPER_CLASS_NAME, "getFieldSignature", "(Ljava/lang/reflect/Field;)Ljava/lang/String;"); private static readonly IntPtr s_ReflectionHelperNewProxyInstance = GetStaticMethodID(RELECTION_HELPER_CLASS_NAME, "newProxyInstance", "(JLjava/lang/Class;)Ljava/lang/Object;"); private static readonly IntPtr s_ReflectionHelperSetNativeExceptionOnProxy = GetStaticMethodID(RELECTION_HELPER_CLASS_NAME, "setNativeExceptionOnProxy", "(Ljava/lang/Object;JZ)V"); + private static readonly IntPtr s_FieldGetDeclaringClass = GetMethodID("java/lang/reflect/Field", "getDeclaringClass", "()Ljava/lang/Class;"); public static IntPtr GetConstructorMember(IntPtr jclass, string signature) { @@ -891,6 +906,18 @@ public static IntPtr GetFieldMember(IntPtr jclass, string fieldName, string sign } } + public static IntPtr GetFieldClass(IntPtr field) + { + return AndroidJNISafe.CallObjectMethod(field, s_FieldGetDeclaringClass, null); + } + + public static string GetFieldSignature(IntPtr field) + { + jvalue[] jniArgs = new jvalue[1]; + jniArgs[0].l = field; + return AndroidJNISafe.CallStaticStringMethod(s_ReflectionHelperClass, s_ReflectionHelperGetFieldSignature, jniArgs); + } + public static IntPtr NewProxyInstance(IntPtr delegateHandle, IntPtr interfaze) { jvalue[] jniArgs = new jvalue[2]; @@ -1396,24 +1423,37 @@ private static IntPtr GetMethodIDFallback(IntPtr jclass, string methodName, stri public static IntPtr GetFieldID(IntPtr jclass, string fieldName, string signature, bool isStatic) { - IntPtr field = IntPtr.Zero; + IntPtr memberID = IntPtr.Zero; + Exception reflectEx = null; + AndroidJNI.PushLocalFrame(10); try { - field = AndroidReflection.GetFieldMember(jclass, fieldName, signature, isStatic); - return AndroidJNISafe.FromReflectedField(field); + IntPtr field = AndroidReflection.GetFieldMember(jclass, fieldName, signature, isStatic); + if (!isStatic) + jclass = AndroidReflection.GetFieldClass(field); + signature = AndroidReflection.GetFieldSignature(field); } catch (Exception e) { - IntPtr memberID = isStatic + reflectEx = e; + } + + try + { + memberID = isStatic ? AndroidJNISafe.GetStaticFieldID(jclass, fieldName, signature) : AndroidJNISafe.GetFieldID(jclass, fieldName, signature); - if (memberID != IntPtr.Zero) - return memberID; - throw e; + if (memberID == IntPtr.Zero) + { + if (reflectEx != null) + throw reflectEx; + throw new Exception(string.Format("Field {0} or type signature {1} not found", fieldName, signature)); + } + return memberID; } finally { - AndroidJNISafe.DeleteLocalRef(field); + AndroidJNI.PopLocalFrame(IntPtr.Zero); } } diff --git a/Modules/Animation/ScriptBindings/AnimationClip.bindings.cs b/Modules/Animation/ScriptBindings/AnimationClip.bindings.cs index 08a9f68d5f..e0dc4483f2 100644 --- a/Modules/Animation/ScriptBindings/AnimationClip.bindings.cs +++ b/Modules/Animation/ScriptBindings/AnimationClip.bindings.cs @@ -49,7 +49,7 @@ public void SampleAnimation(GameObject go, float time) public extern float frameRate { get; set; } [FreeFunction("AnimationClipBindings::Internal_SetCurve", HasExplicitThis = true)] - public extern void SetCurve([NotNull] string relativePath, Type type, [NotNull] string propertyName, AnimationCurve curve); + public extern void SetCurve([NotNull] string relativePath, [NotNull] Type type, [NotNull] string propertyName, AnimationCurve curve); //*undocumented* public extern void EnsureQuaternionContinuity(); diff --git a/Modules/AssetDatabase/Editor/ScriptBindings/AssetDatabase.bindings.cs b/Modules/AssetDatabase/Editor/ScriptBindings/AssetDatabase.bindings.cs index 6740f25314..90c81a6971 100644 --- a/Modules/AssetDatabase/Editor/ScriptBindings/AssetDatabase.bindings.cs +++ b/Modules/AssetDatabase/Editor/ScriptBindings/AssetDatabase.bindings.cs @@ -58,6 +58,8 @@ internal enum ImportPackageOptions [NativeHeader("Modules/AssetDatabase/Editor/Public/AssetDatabaseUtility.h")] [NativeHeader("Modules/AssetDatabase/Editor/ScriptBindings/AssetDatabase.bindings.h")] [NativeHeader("Editor/Src/PackageUtility.h")] + [NativeHeader("Editor/Src/VersionControl/VC_bindings.h")] + [NativeHeader("Editor/Src/Application/ApplicationFunctions.h")] [StaticAccessor("AssetDatabaseBindings", StaticAccessorType.DoubleColon)] public partial class AssetDatabase { @@ -65,6 +67,10 @@ public partial class AssetDatabase extern internal static void RegisterAssetFolder(string path, bool immutable, string guid); extern internal static void UnregisterAssetFolder(string path); + // used by integration tests + extern internal static void RegisterRedirectedAssetFolder(string mountPoint, string folder, string physicalPath, bool immutable, string guid); + extern internal static void UnregisterRedirectedAssetFolder(string mountPoint, string folder); + // returns true if the folder is known by the asset database // rootFolder is true if the path is a registered root folder // immutable is true when the root of the path was registered with the immutable flag (e.g. shared package) @@ -292,72 +298,66 @@ public static void ExportPackage(string assetPathName, string fileName, ExportPa extern internal static string GetUniquePathNameAtSelectedPath(string fileName); - [System.Obsolete("AssetDatabase.IsOpenForEdit without StatusQueryOptions has been deprecated. Use the version with StatusQueryOptions instead. This will always request the cached status (StatusQueryOptions.UseCachedIfPossible)")] - public static bool IsOpenForEdit(UnityEngine.Object assetObject) + [uei.ExcludeFromDocs] public static bool IsOpenForEdit(UnityEngine.Object assetObject) { return IsOpenForEdit(assetObject, StatusQueryOptions.UseCachedIfPossible); } - public static bool IsOpenForEdit(UnityEngine.Object assetObject, StatusQueryOptions StatusQueryOptions) + public static bool IsOpenForEdit(UnityEngine.Object assetObject, [uei.DefaultValue("StatusQueryOptions.UseCachedIfPossible")] StatusQueryOptions statusOptions) { string assetPath = GetAssetOrScenePath(assetObject); - return IsOpenForEdit(assetPath, StatusQueryOptions); + return IsOpenForEdit(assetPath, statusOptions); } - [System.Obsolete("AssetDatabase.IsOpenForEdit without StatusQueryOptions has been deprecated. Use the version with StatusQueryOptions instead. This will always request the cached status (StatusQueryOptions.UseCachedIfPossible)")] - public static bool IsOpenForEdit(string assetOrMetaFilePath) + [uei.ExcludeFromDocs] public static bool IsOpenForEdit(string assetOrMetaFilePath) { return IsOpenForEdit(assetOrMetaFilePath, StatusQueryOptions.UseCachedIfPossible); } - public static bool IsOpenForEdit(string assetOrMetaFilePath, StatusQueryOptions StatusQueryOptions) + public static bool IsOpenForEdit(string assetOrMetaFilePath, [uei.DefaultValue("StatusQueryOptions.UseCachedIfPossible")] StatusQueryOptions statusOptions) { string message; - return IsOpenForEdit(assetOrMetaFilePath, out message, StatusQueryOptions); + return IsOpenForEdit(assetOrMetaFilePath, out message, statusOptions); } - [System.Obsolete("AssetDatabase.IsOpenForEdit without StatusQueryOptions has been deprecated. Use the version with StatusQueryOptions instead. This will always request the cached status (StatusQueryOptions.UseCachedIfPossible)")] - public static bool IsOpenForEdit(UnityEngine.Object assetObject, out string message) + [uei.ExcludeFromDocs] public static bool IsOpenForEdit(UnityEngine.Object assetObject, out string message) { return IsOpenForEdit(assetObject, out message, StatusQueryOptions.UseCachedIfPossible); } - public static bool IsOpenForEdit(UnityEngine.Object assetObject, out string message, StatusQueryOptions statusOptions) + public static bool IsOpenForEdit(UnityEngine.Object assetObject, out string message, [uei.DefaultValue("StatusQueryOptions.UseCachedIfPossible")] StatusQueryOptions statusOptions) { string assetPath = GetAssetOrScenePath(assetObject); return IsOpenForEdit(assetPath, out message, statusOptions); } - [System.Obsolete("AssetDatabase.IsOpenForEdit without StatusQueryOptions has been deprecated. Use the version with StatusQueryOptions instead. This will always request the cached status (StatusQueryOptions.UseCachedIfPossible)")] - public static bool IsOpenForEdit(string assetOrMetaFilePath, out string message) + [uei.ExcludeFromDocs] public static bool IsOpenForEdit(string assetOrMetaFilePath, out string message) { return IsOpenForEdit(assetOrMetaFilePath, out message, StatusQueryOptions.UseCachedIfPossible); } - public static bool IsOpenForEdit(string assetOrMetaFilePath, out string message, StatusQueryOptions statusOptions) + public static bool IsOpenForEdit(string assetOrMetaFilePath, out string message, [uei.DefaultValue("StatusQueryOptions.UseCachedIfPossible")] StatusQueryOptions statusOptions) { return AssetModificationProcessorInternal.IsOpenForEdit(assetOrMetaFilePath, out message, statusOptions); } - [System.Obsolete("AssetDatabase.IsMetaFileOpenForEdit without StatusQueryOptions has been deprecated. Use the version with StatusQueryOptions instead. This will always request the cached status (StatusQueryOptions.UseCachedIfPossible)")] - public static bool IsMetaFileOpenForEdit(UnityEngine.Object assetObject) + [uei.ExcludeFromDocs] public static bool IsMetaFileOpenForEdit(UnityEngine.Object assetObject) { return IsMetaFileOpenForEdit(assetObject, StatusQueryOptions.UseCachedIfPossible); } - public static bool IsMetaFileOpenForEdit(UnityEngine.Object assetObject, StatusQueryOptions statusOptions) + public static bool IsMetaFileOpenForEdit(UnityEngine.Object assetObject, [uei.DefaultValue("StatusQueryOptions.UseCachedIfPossible")] StatusQueryOptions statusOptions) { string message; return IsMetaFileOpenForEdit(assetObject, out message, statusOptions); } - [System.Obsolete("AssetDatabase.IsMetaFileOpenForEdit without StatusQueryOptions has been deprecated. Use the version with StatusQueryOptions instead. This will always request the cached status (StatusQueryOptions.UseCachedIfPossible)")] - public static bool IsMetaFileOpenForEdit(UnityEngine.Object assetObject, out string message) + [uei.ExcludeFromDocs] public static bool IsMetaFileOpenForEdit(UnityEngine.Object assetObject, out string message) { return IsMetaFileOpenForEdit(assetObject, out message, StatusQueryOptions.UseCachedIfPossible); } - public static bool IsMetaFileOpenForEdit(UnityEngine.Object assetObject, out string message, StatusQueryOptions statusOptions) + public static bool IsMetaFileOpenForEdit(UnityEngine.Object assetObject, out string message, [uei.DefaultValue("StatusQueryOptions.UseCachedIfPossible")] StatusQueryOptions statusOptions) { string assetPath = GetAssetOrScenePath(assetObject); string metaPath = AssetDatabase.GetTextMetaFilePathFromAssetPath(assetPath); @@ -385,8 +385,6 @@ internal extern static string assetFolderGUID extern internal static bool IsV1Enabled(); [FreeFunction("AssetDatabase::IsV2Enabled")] extern internal static bool IsV2Enabled(); - [FreeFunction("AssetDatabase::IsOnDemandModeEnabled")] - extern internal static bool IsOnDemandModeEnabled(); [FreeFunction("AssetDatabase::CloseCachedFiles")] extern internal static void CloseCachedFiles(); @@ -503,6 +501,12 @@ internal static bool ImportPackageImmediately(string packagePath) { return ImportPackage(packagePath, ImportPackageOptions.NoGUI); } + + [FreeFunction("ApplicationDisallowAutoRefresh")] + public static extern void DisallowAutoRefresh(); + + [FreeFunction("ApplicationAllowAutoRefresh")] + public static extern void AllowAutoRefresh(); } } diff --git a/Modules/AssetDatabase/Editor/ScriptBindings/AssetDatabaseExperimental.bindings.cs b/Modules/AssetDatabase/Editor/ScriptBindings/AssetDatabaseExperimental.bindings.cs index b7a42b491d..45feb71087 100644 --- a/Modules/AssetDatabase/Editor/ScriptBindings/AssetDatabaseExperimental.bindings.cs +++ b/Modules/AssetDatabase/Editor/ScriptBindings/AssetDatabaseExperimental.bindings.cs @@ -3,6 +3,8 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Runtime.InteropServices; + using System.Collections.Generic; using System.Linq; using UnityEngine; @@ -12,11 +14,43 @@ namespace UnityEditor.Experimental { + public enum OnDemandState + { + Unavailable = 0, + Processing = 1, + Downloading = 2, + Available = 3, + Failed = 4 + } + + [NativeHeader("Modules/AssetDatabase/Editor/Public/AssetDatabaseTypes.h")] + [RequiredByNativeCode] + [StructLayout(LayoutKind.Sequential)] + public struct OnDemandProgress + { + public OnDemandState state; + public float progress; + } + [NativeHeader("Modules/AssetDatabase/Editor/V2/AssetDatabaseCounters.h")] [NativeHeader("Modules/AssetDatabase/Editor/V2/V1Compatibility.h")] [NativeHeader("Modules/AssetDatabase/Editor/Public/AssetDatabaseExperimental.h")] public sealed partial class AssetDatabaseExperimental { + public enum OnDemandMode + { + Off = 0, + Lazy = 1, + Background = 2 + } + + public enum ImportSyncMode + { + Block = 0, + Queue = 1, + Poll = 2 + } + public struct AssetDatabaseCounters { public struct Counter @@ -67,6 +101,11 @@ public void ResetDeltas() public extern static AssetDatabaseCounters counters { get; } + [FreeFunction()] + public extern static bool CanConnectToCacheServer(string ip, UInt16 port); + [FreeFunction()] + public extern static void RefreshConnectionToCacheServer(); + [FreeFunction("IsConnectedToCacheServerV2")] public extern static bool IsConnectedToCacheServer(); [FreeFunction("ReconnectToCacheServerV2")] @@ -75,9 +114,8 @@ public void ResetDeltas() public extern static string GetCacheServerAddress(); [FreeFunction()] public extern static UInt16 GetCacheServerPort(); - - public extern static bool onlyUploadToCacheServer { get; set; } - public extern static int minReliabilityIndex { get; set; } + [FreeFunction()] + public extern static void RefreshCacheServerNamespacePrefix(); [FreeFunction("CacheServerCountersResetDeltas")] private extern static void CacheServerCountersResetDeltas(); @@ -85,16 +123,27 @@ public void ResetDeltas() [FreeFunction("ImportCountersResetDeltas")] private extern static void ImportCountersResetDeltas(); - private extern static Hash128 GetArtifactHash_Internal_Guid_SelectImporter(string guid); - private extern static Hash128 GetArtifactHash_Internal_Guid(string guid, Type importerType); + public extern static OnDemandMode ActiveOnDemandMode + { + [FreeFunction("GetOnDemandModeV2")] get; + [FreeFunction("SetOnDemandModeV2")] set; + } + [NativeHeader("Modules/AssetDatabase/Editor/V2/Virtualization/Virtualization.h")] + internal extern static bool VirtualizationEnabled + { + [FreeFunction("Virtualization_IsEnabled")] get; + } + private extern static Hash128 GetArtifactHash_Internal_Guid_SelectImporter(string guid, ImportSyncMode mode); + private extern static Hash128 GetArtifactHash_Internal_Guid(string guid, Type importerType, ImportSyncMode mode); + private extern static Hash128[] GetArtifactHashes_Internal_Guids_SelectImporter(string[] guid, ImportSyncMode mode); - [uei.ExcludeFromDocs] public static Hash128 GetArtifactHash(string guid) { return GetArtifactHash(guid, null); } - public static Hash128 GetArtifactHash(string guid, [uei.DefaultValue("null")] Type importerType) + [uei.ExcludeFromDocs] public static Hash128 GetArtifactHash(string guid, ImportSyncMode mode = ImportSyncMode.Block) { return GetArtifactHash(guid, null, mode); } + public static Hash128 GetArtifactHash(string guid, [uei.DefaultValue("null")] Type importerType, ImportSyncMode mode = ImportSyncMode.Block) { if (importerType == null) - return GetArtifactHash_Internal_Guid_SelectImporter(guid); + return GetArtifactHash_Internal_Guid_SelectImporter(guid, mode); else - return GetArtifactHash_Internal_Guid(guid, importerType); + return GetArtifactHash_Internal_Guid(guid, importerType, mode); } public static bool GetArtifactPaths(Hash128 hash, out string[] paths) @@ -105,8 +154,26 @@ public static bool GetArtifactPaths(Hash128 hash, out string[] paths) return success; } + public static Hash128[] GetArtifactHashes(string[] guids, ImportSyncMode mode = ImportSyncMode.Block) + { + return GetArtifactHashes_Internal_Guids_SelectImporter(guids, mode); + } + extern private static string[] GetArtifactPathsImpl(Hash128 hash, out bool success); + private extern static OnDemandProgress GetOnDemandArtifactProgress_Internal_SelectImporter(string guid); + private extern static OnDemandProgress GetOnDemandArtifactProgress_Internal(string guid, Type importerType); + + public static OnDemandProgress GetOnDemandArtifactProgress(string guid) + { + return GetOnDemandArtifactProgress_Internal_SelectImporter(guid); + } + + public static OnDemandProgress GetOnDemandArtifactProgress(string guid, Type importerType) + { + return GetOnDemandArtifactProgress_Internal(guid, importerType); + } + [RequiredByNativeCode] static string[] OnSourceAssetsModified(string[] changedAssets, string[] addedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { @@ -127,5 +194,14 @@ static string[] OnSourceAssetsModified(string[] changedAssets, string[] addedAss return assetsReportedChanged.ToArray(); } + + [FreeFunction("AssetDatabaseExperimental::RegisterCustomDependency")] + public extern static void RegisterCustomDependency(string dependency, Hash128 hashOfValue); + + [FreeFunction("AssetDatabaseExperimental::UnregisterCustomDependencyPrefixFilter")] + public extern static UInt32 UnregisterCustomDependencyPrefixFilter(string prefixFilter); + + [FreeFunction("AssetDatabase::IsAssetImportProcess")] + public extern static bool IsAssetImportWorkerProcess(); } } diff --git a/Modules/AssetPipelineEditor/AssetPostprocessors/FBXMaterialDescriptionPreprocessor.cs b/Modules/AssetPipelineEditor/AssetPostprocessors/FBXMaterialDescriptionPreprocessor.cs index 9c178de944..db6e540376 100644 --- a/Modules/AssetPipelineEditor/AssetPostprocessors/FBXMaterialDescriptionPreprocessor.cs +++ b/Modules/AssetPipelineEditor/AssetPostprocessors/FBXMaterialDescriptionPreprocessor.cs @@ -23,8 +23,8 @@ public override int GetPostprocessOrder() public void OnPreprocessMaterialDescription(MaterialDescription description, Material material, AnimationClip[] clips) { - var lowerCasePath = Path.GetExtension(assetPath).ToLower(); - if (lowerCasePath == ".fbx") + var lowerCaseExtension = Path.GetExtension(assetPath).ToLower(); + if (lowerCaseExtension == ".fbx" || lowerCaseExtension == ".obj" || lowerCaseExtension == ".blend" || lowerCaseExtension == ".mb" || lowerCaseExtension == ".ma" || lowerCaseExtension == ".max" || lowerCaseExtension == ".dae") { if (IsAutodeskInteractiveMaterial(description)) CreateFromAutodeskInteractiveMaterial(description, material, clips); @@ -76,7 +76,7 @@ void CreateFrom3DsMaxArnoldStandardSurfaceMaterial(MaterialDescription descripti var shader = Shader.Find("Autodesk Interactive"); if (shader == null) { - context.LogImportError("ThreeDSMaterialDescriptionPreprocessor cannot find a shader named 'Autodesk Interactive'."); + context.LogImportError("FBXMaterialDescriptionPreprocessor cannot find a shader named 'Autodesk Interactive'."); return; } material.shader = shader; @@ -172,7 +172,7 @@ void CreateFromMayaArnoldStandardSurfaceMaterial(MaterialDescription description var shader = Shader.Find("Autodesk Interactive"); if (shader == null) { - context.LogImportError("ThreeDSMaterialDescriptionPreprocessor cannot find a shader named 'Autodesk Interactive'."); + context.LogImportError("FBXMaterialDescriptionPreprocessor cannot find a shader named 'Autodesk Interactive'."); return; } material.shader = shader; @@ -342,7 +342,7 @@ void CreateFrom3DsMaxPhysicalMaterial(MaterialDescription description, Material var shader = Shader.Find("Autodesk Interactive"); if (shader == null) { - context.LogImportError("ThreeDSMaterialDescriptionPreprocessor cannot find a shader named 'Autodesk Interactive'."); + context.LogImportError("FBXMaterialDescriptionPreprocessor cannot find a shader named 'Autodesk Interactive'."); return; } material.shader = shader; @@ -423,7 +423,7 @@ void CreateFromAutodeskInteractiveMaterial(MaterialDescription description, Mate var shader = Shader.Find("Autodesk Interactive"); if (shader == null) { - context.LogImportError("ThreeDSMaterialDescriptionPreprocessor cannot find a shader named 'Autodesk Interactive'."); + context.LogImportError("FBXMaterialDescriptionPreprocessor cannot find a shader named 'Autodesk Interactive'."); return; } material.shader = shader; @@ -729,7 +729,7 @@ void CreateFromStandardMaterial(MaterialDescription description, Material materi var shader = Shader.Find("Standard"); if (shader == null) { - context.LogImportError("ThreeDSMaterialDescriptionPreprocessor cannot find a shader named 'Standard'."); + context.LogImportError("FBXMaterialDescriptionPreprocessor cannot find a shader named 'Standard'."); return; } material.shader = shader; diff --git a/Modules/AssetPipelineEditor/AssetPostprocessors/ModelImporterPostProcessor.cs b/Modules/AssetPipelineEditor/AssetPostprocessors/ModelImporterPostProcessor.cs index 9f65f30847..e3932dc7d3 100644 --- a/Modules/AssetPipelineEditor/AssetPostprocessors/ModelImporterPostProcessor.cs +++ b/Modules/AssetPipelineEditor/AssetPostprocessors/ModelImporterPostProcessor.cs @@ -17,7 +17,7 @@ internal class ModelImporterPostProcessor : AssetPostprocessor static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromPath) { - if (AssetDatabase.IsOnDemandModeEnabled()) + if (UnityEditor.Experimental.AssetDatabaseExperimental.ActiveOnDemandMode != UnityEditor.Experimental.AssetDatabaseExperimental.OnDemandMode.Off || UnityEditor.Experimental.AssetDatabaseExperimental.VirtualizationEnabled) { // This PostProcessAllAssets will forcefully import everything in on-demand mode // We need to find a better way of filtering on asset type without forcing an import diff --git a/Modules/AssetPipelineEditor/AssetPostprocessors/SketchupMaterialDescriptionPreprocessor.cs b/Modules/AssetPipelineEditor/AssetPostprocessors/SketchupMaterialDescriptionPreprocessor.cs index 11bba41f57..855e2cb7cf 100644 --- a/Modules/AssetPipelineEditor/AssetPostprocessors/SketchupMaterialDescriptionPreprocessor.cs +++ b/Modules/AssetPipelineEditor/AssetPostprocessors/SketchupMaterialDescriptionPreprocessor.cs @@ -33,7 +33,7 @@ void CreateFromSketchupMaterial(MaterialDescription description, Material materi var shader = Shader.Find("Standard"); if (shader == null) { - context.LogImportError("ThreeDSMaterialDescriptionPreprocessor cannot find a shader named 'Standard'."); + context.LogImportError("SketchupMaterialDescriptionPreprocessor cannot find a shader named 'Standard'."); return; } material.shader = shader; diff --git a/Modules/AssetPipelineEditor/ImportSettings/AssetImporterEditor.cs b/Modules/AssetPipelineEditor/ImportSettings/AssetImporterEditor.cs index 206cd87dcd..eac8f4f2b6 100644 --- a/Modules/AssetPipelineEditor/ImportSettings/AssetImporterEditor.cs +++ b/Modules/AssetPipelineEditor/ImportSettings/AssetImporterEditor.cs @@ -554,7 +554,6 @@ bool CheckForApplyOnClose(bool isQuitting = false) case 0: Apply(); // we need to call Apply before re-importing in case the user overriden it. ImportAssets(assetPaths.ToArray()); - ResetValues(); break; case 1: return false; diff --git a/Modules/AssetPipelineEditor/ImportSettings/AudioImporterInspector.cs b/Modules/AssetPipelineEditor/ImportSettings/AudioImporterInspector.cs index a7bfb948e5..1fea136470 100644 --- a/Modules/AssetPipelineEditor/ImportSettings/AudioImporterInspector.cs +++ b/Modules/AssetPipelineEditor/ImportSettings/AudioImporterInspector.cs @@ -350,7 +350,7 @@ private void OnSampleSettingGUI(BuildTargetGroup platform, SerializedProperty au EditorGUI.showMixedValue = property.hasMultipleDifferentValues; using (var changed = new EditorGUI.ChangeCheckScope()) { - var newValue = EditorGUILayout.IntSlider(propertyScope.content, (int)Mathf.Clamp(property.floatValue * 100.0f, 1.0f, 100.0f), 1, 100); + var newValue = EditorGUILayout.IntSlider(propertyScope.content, (int)Mathf.Clamp(property.floatValue * 100.0f + 0.5f, 1.0f, 100.0f), 1, 100); if (changed.changed) { property.floatValue = 0.01f * newValue; diff --git a/Modules/AssetPipelineEditor/ImportSettings/ModelImporterClipEditor.cs b/Modules/AssetPipelineEditor/ImportSettings/ModelImporterClipEditor.cs index b1eff1cb36..ce216d20b7 100644 --- a/Modules/AssetPipelineEditor/ImportSettings/ModelImporterClipEditor.cs +++ b/Modules/AssetPipelineEditor/ImportSettings/ModelImporterClipEditor.cs @@ -3,10 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; -using System.IO; using UnityEngine; -using UnityEditor; -using UnityEditor.Animations; using UnityEditorInternal; using System.Collections.Generic; using UnityEditor.Experimental.AssetImporters; @@ -21,17 +18,7 @@ internal class ModelImporterClipEditor : BaseAssetImporterTabUI AnimationClipEditor m_AnimationClipEditor; ModelImporter singleImporter { get { return targets[0] as ModelImporter; } } - public int m_SelectedClipIndexDoNotUseDirectly = -1; - public int selectedClipIndex - { - get { return m_SelectedClipIndexDoNotUseDirectly; } - set - { - m_SelectedClipIndexDoNotUseDirectly = value; - if (m_ClipList != null) - m_ClipList.index = value; - } - } + internal const string ActiveClipIndex = "ModelImporterClipEditor.ActiveClipIndex"; public string selectedClipName { @@ -42,39 +29,98 @@ public string selectedClipName } } + private class ClipInformation + { + SerializedProperty prop; + + public ClipInformation(SerializedProperty clipProperty) + { + prop = clipProperty; + animationClipProperty = clipProperty; + } + + public SerializedProperty animationClipProperty { get; } + AnimationClipInfoProperties m_Property; + public AnimationClipInfoProperties property => m_Property ?? (m_Property = new AnimationClipInfoProperties(animationClipProperty)); + + public string name + { + get { return prop.FindPropertyRelative("name").stringValue; } + } + + public string firstFrame + { + get { return prop.FindPropertyRelative("firstFrame").floatValue.ToString("0.0", CultureInfo.InvariantCulture.NumberFormat); } + } + + public string lastFrame + { + get { return prop.FindPropertyRelative("lastFrame").floatValue.ToString("0.0", CultureInfo.InvariantCulture.NumberFormat); } + } + } + SerializedObject m_DefaultClipsSerializedObject = null; +#pragma warning disable 0649 + [CacheProperty] SerializedProperty m_AnimationType; + [CacheProperty] SerializedProperty m_ImportAnimation; + [CacheProperty] SerializedProperty m_ClipAnimations; + [CacheProperty] SerializedProperty m_BakeSimulation; + [CacheProperty] SerializedProperty m_ResampleCurves; + [CacheProperty] SerializedProperty m_AnimationCompression; + [CacheProperty] SerializedProperty m_AnimationRotationError; + [CacheProperty] SerializedProperty m_AnimationPositionError; + [CacheProperty] SerializedProperty m_AnimationScaleError; + [CacheProperty] SerializedProperty m_AnimationWrapMode; + [CacheProperty] SerializedProperty m_LegacyGenerateAnimations; + [CacheProperty] SerializedProperty m_ImportAnimatedCustomProperties; + [CacheProperty] SerializedProperty m_ImportConstraints; + [CacheProperty] SerializedProperty m_MotionNodeName; +#pragma warning restore 0649 + public int motionNodeIndex { get; set; } public int pivotNodeIndex { get; set; } +#pragma warning disable 0649 + [CacheProperty] private SerializedProperty m_RigImportErrors; + [CacheProperty] private SerializedProperty m_RigImportWarnings; + [CacheProperty] private SerializedProperty m_AnimationImportErrors; + [CacheProperty] private SerializedProperty m_AnimationImportWarnings; + [CacheProperty] private SerializedProperty m_AnimationRetargetingWarnings; + [CacheProperty] private SerializedProperty m_AnimationDoRetargetingWarnings; +#pragma warning restore 0649 - GUIContent[] m_MotionNodeList; - static private bool motionNodeFoldout = false; + string m_Errors; + string m_Warnings; + string m_RigWarnings; + string m_RetargetWarnings; - private static bool importMessageFoldout = false; + GUIContent[] m_MotionNodeList; + private static bool s_MotionNodeFoldout = false; + private static bool s_ImportMessageFoldout = false; ReorderableList m_ClipList; @@ -173,81 +219,57 @@ public Styles() public ModelImporterClipEditor(AssetImporterEditor panelContainer) : base(panelContainer) - {} + { + //Generate new Clip List + m_ClipList = new ReorderableList(new List(), typeof(string), false, true, true, true); + m_ClipList.onAddCallback = AddClipInList; + m_ClipList.onSelectCallback = SelectClipInList; + m_ClipList.onRemoveCallback = RemoveClipInList; + m_ClipList.drawElementCallback = DrawClipElement; + m_ClipList.drawHeaderCallback = DrawClipHeader; + m_ClipList.elementHeight = EditorGUI.kSingleLineHeight; + } + internal override void OnEnable() { - m_ClipAnimations = serializedObject.FindProperty("m_ClipAnimations"); + Editor.AssignCachedProperties(this, serializedObject.GetIterator()); - m_AnimationType = serializedObject.FindProperty("m_AnimationType"); - m_LegacyGenerateAnimations = serializedObject.FindProperty("m_LegacyGenerateAnimations"); - - // Animation - m_ImportAnimation = serializedObject.FindProperty("m_ImportAnimation"); - m_BakeSimulation = serializedObject.FindProperty("m_BakeSimulation"); - m_ResampleCurves = serializedObject.FindProperty("m_ResampleCurves"); - m_AnimationCompression = serializedObject.FindProperty("m_AnimationCompression"); - m_AnimationRotationError = serializedObject.FindProperty("m_AnimationRotationError"); - m_AnimationPositionError = serializedObject.FindProperty("m_AnimationPositionError"); - m_AnimationScaleError = serializedObject.FindProperty("m_AnimationScaleError"); - m_AnimationWrapMode = serializedObject.FindProperty("m_AnimationWrapMode"); - m_ImportAnimatedCustomProperties = serializedObject.FindProperty("m_ImportAnimatedCustomProperties"); - m_ImportConstraints = serializedObject.FindProperty("m_ImportConstraints"); - - m_RigImportErrors = serializedObject.FindProperty("m_RigImportErrors"); - m_RigImportWarnings = serializedObject.FindProperty("m_RigImportWarnings"); - m_AnimationImportErrors = serializedObject.FindProperty("m_AnimationImportErrors"); - m_AnimationImportWarnings = serializedObject.FindProperty("m_AnimationImportWarnings"); - m_AnimationRetargetingWarnings = serializedObject.FindProperty("m_AnimationRetargetingWarnings"); - m_AnimationDoRetargetingWarnings = serializedObject.FindProperty("m_AnimationDoRetargetingWarnings"); + // caching errors values now as they can't change until next re-import that will triggers a new OnEnable + m_Errors = m_AnimationImportErrors.stringValue; + m_Warnings = m_AnimationImportWarnings.stringValue; + m_RigWarnings = m_RigImportWarnings.stringValue; + m_RetargetWarnings = m_AnimationRetargetingWarnings.stringValue; + + RegisterListeners(); if (serializedObject.isEditingMultipleObjects) return; - // Find all serialized property before calling SetupDefaultClips - if (m_ClipAnimations.arraySize == 0) - SetupDefaultClips(); - - - selectedClipIndex = EditorPrefs.GetInt("ModelImporterClipEditor.ActiveClipIndex", 0); - ValidateClipSelectionIndex(); - EditorPrefs.SetInt("ModelImporterClipEditor.ActiveClipIndex", selectedClipIndex); - - if (m_AnimationClipEditor != null && selectedClipIndex >= 0) - SyncClipEditor(); - - // Automatically select the first clip - if (m_ClipAnimations.arraySize != 0) - SelectClip(selectedClipIndex); + //Sometimes we dont want to start at the 0th index, this is where we're editing a clip - see + m_ClipList.index = EditorPrefs.GetInt(ActiveClipIndex, 0); + EditorPrefs.SetInt(ActiveClipIndex, m_ClipList.index); + //Reset the Model Importer to its serialized copy + DeserializeClips(); string[] transformPaths = singleImporter.transformPaths; m_MotionNodeList = new GUIContent[transformPaths.Length + 1]; m_MotionNodeList[0] = EditorGUIUtility.TrTextContent(""); + if (m_MotionNodeList.Length > 1) + m_MotionNodeList[1] = EditorGUIUtility.TrTextContent(""); - for (int i = 0; i < transformPaths.Length; i++) - { - if (i == 0) - { - m_MotionNodeList[1] = EditorGUIUtility.TrTextContent(""); - } - else - { - m_MotionNodeList[i + 1] = new GUIContent(transformPaths[i]); - } - } + for (int i = 1; i < transformPaths.Length; i++) + m_MotionNodeList[i + 1] = new GUIContent(transformPaths[i]); - m_MotionNodeName = serializedObject.FindProperty("m_MotionNodeName"); motionNodeIndex = ArrayUtility.FindIndex(m_MotionNodeList, delegate(GUIContent content) { return content.text == m_MotionNodeName.stringValue; }); motionNodeIndex = motionNodeIndex < 1 ? 0 : motionNodeIndex; } - void SyncClipEditor() + void SyncClipEditor(AnimationClipInfoProperties info) { if (m_AnimationClipEditor == null || m_MaskInspector == null) return; - AnimationClipInfoProperties info = GetAnimationClipInfoAtIndex(selectedClipIndex); - // It mandatory to set clip info into mask inspector first, this will update m_Mask. m_MaskInspector.clipInfo = info; @@ -301,17 +323,10 @@ private void TransferDefaultClipsToCustomClips() m_DefaultClipsSerializedObject = null; PatchDefaultClipTakeNamesToSplitClipNames(); + UpdateList(); - SyncClipEditor(); - } - - private void ValidateClipSelectionIndex() - { - // selected clip index can be invalid if array was changed and then reverted. - if (selectedClipIndex > m_ClipAnimations.arraySize - 1) - { - selectedClipIndex = 0; - } + if (m_ClipList.index >= 0) + SyncClipEditor(((ClipInformation)m_ClipList.list[m_ClipList.index]).property); } internal override void OnDestroy() @@ -321,6 +336,7 @@ internal override void OnDestroy() internal override void OnDisable() { + UnregisterListeners(); DestroyEditorsAndData(); base.OnDisable(); @@ -329,36 +345,23 @@ internal override void OnDisable() internal override void ResetValues() { base.ResetValues(); - m_ClipAnimations = serializedObject.FindProperty("m_ClipAnimations"); - m_AnimationType = serializedObject.FindProperty("m_AnimationType"); - m_DefaultClipsSerializedObject = null; - if (m_ClipAnimations.arraySize == 0) - SetupDefaultClips(); - - ValidateClipSelectionIndex(); - UpdateList(); - SelectClip(selectedClipIndex); + DeserializeClips(); } void AnimationClipGUI() { - string errors = m_AnimationImportErrors.stringValue; - string warnings = m_AnimationImportWarnings.stringValue; - string rigWarnings = m_RigImportWarnings.stringValue; - string retargetWarnings = m_AnimationRetargetingWarnings.stringValue; - - if (errors.Length > 0) + if (m_Errors.Length > 0) { EditorGUILayout.HelpBox(styles.ErrorsFoundWhileImportingThisAnimation); } else { - if (rigWarnings.Length > 0) + if (m_RigWarnings.Length > 0) { EditorGUILayout.HelpBox(styles.WarningsFoundWhileImportingRig); } - if (warnings.Length > 0) + if (m_Warnings.Length > 0) { EditorGUILayout.HelpBox(styles.WarningsFoundWhileImportingThisAnimation); } @@ -384,23 +387,23 @@ void AnimationClipGUI() RootMotionNodeSettings(); - importMessageFoldout = EditorGUILayout.Foldout(importMessageFoldout, styles.ImportMessages, true); + s_ImportMessageFoldout = EditorGUILayout.Foldout(s_ImportMessageFoldout, styles.ImportMessages, true); - if (importMessageFoldout) + if (s_ImportMessageFoldout) { - if (errors.Length > 0) - EditorGUILayout.HelpBox(L10n.Tr(errors), MessageType.Error); - if (warnings.Length > 0) - EditorGUILayout.HelpBox(L10n.Tr(warnings), MessageType.Warning); + if (m_Errors.Length > 0) + EditorGUILayout.HelpBox(L10n.Tr(m_Errors), MessageType.Error); + if (m_Warnings.Length > 0) + EditorGUILayout.HelpBox(L10n.Tr(m_Warnings), MessageType.Warning); if (animationType == ModelImporterAnimationType.Human) { EditorGUILayout.PropertyField(m_AnimationDoRetargetingWarnings, styles.GenerateRetargetingWarnings); if (m_AnimationDoRetargetingWarnings.boolValue) { - if (retargetWarnings.Length > 0) + if (m_RetargetWarnings.Length > 0) { - EditorGUILayout.HelpBox(L10n.Tr(retargetWarnings), MessageType.Info); + EditorGUILayout.HelpBox(L10n.Tr(m_RetargetWarnings), MessageType.Info); } } else @@ -417,12 +420,17 @@ public override void OnInspectorGUI() styles = new Styles(); EditorGUILayout.PropertyField(m_ImportConstraints, styles.ImportConstraints); - EditorGUILayout.PropertyField(m_ImportAnimation, styles.ImportAnimations); + + using (var check = new EditorGUI.ChangeCheckScope()) + { + EditorGUILayout.PropertyField(m_ImportAnimation, styles.ImportAnimations); + if (check.changed) + DeserializeClips(); + } if (m_ImportAnimation.boolValue && !m_ImportAnimation.hasMultipleDifferentValues) { bool hasNoValidAnimationData = targets.Length == 1 && singleImporter.importedTakeInfos.Length == 0 && singleImporter.animationType != ModelImporterAnimationType.None; - if (IsDeprecatedMultiAnimationRootImport()) EditorGUILayout.HelpBox(styles.AnimationDataWas); else if (hasNoValidAnimationData) @@ -451,12 +459,17 @@ public override void OnInspectorGUI() EditorGUILayout.HelpBox(styles.TheRigsOfTheSelectedModelsAre); else { - if (m_ImportAnimation.boolValue && !m_ImportAnimation.hasMultipleDifferentValues) - AnimationClipGUI(); + AnimationClipGUI(); } } } + internal override void PostApply() + { + base.PostApply(); + DeserializeClips(); + } + void AnimationSettings() { EditorGUILayout.Space(); @@ -514,9 +527,9 @@ void RootMotionNodeSettings() { if (animationType == ModelImporterAnimationType.Human || animationType == ModelImporterAnimationType.Generic) { - motionNodeFoldout = EditorGUILayout.Foldout(motionNodeFoldout, styles.MotionSetting, true); + s_MotionNodeFoldout = EditorGUILayout.Foldout(s_MotionNodeFoldout, styles.MotionSetting, true); - if (motionNodeFoldout) + if (s_MotionNodeFoldout) { EditorGUI.BeginChangeCheck(); motionNodeIndex = EditorGUILayout.Popup(styles.MotionNode, motionNodeIndex, m_MotionNodeList); @@ -567,31 +580,31 @@ void SelectClip(int selected) DestroyEditorsAndData(); - selectedClipIndex = selected; - if (selectedClipIndex < 0 || selectedClipIndex >= m_ClipAnimations.arraySize) - { - selectedClipIndex = -1; + m_ClipList.index = selected; + if (m_ClipList.index < 0) return; - } - AnimationClipInfoProperties info = GetAnimationClipInfoAtIndex(selected); + AnimationClipInfoProperties info = ((ClipInformation)m_ClipList.list[m_ClipList.index]).property; AnimationClip clip = singleImporter.GetPreviewAnimationClipForTake(info.takeName); if (clip != null) { m_AnimationClipEditor = (AnimationClipEditor)Editor.CreateEditor(clip, typeof(AnimationClipEditor)); InitMask(info); - SyncClipEditor(); + SyncClipEditor(info); } } void UpdateList() { - if (m_ClipList == null) - return; - List clipInfos = new List(); + List clipInfos = new List(); + var prop = m_ClipAnimations.FindPropertyRelative("Array.size"); for (int i = 0; i < m_ClipAnimations.arraySize; i++) - clipInfos.Add(GetAnimationClipInfoAtIndex(i)); + { + prop.Next(false); + clipInfos.Add(new ClipInformation(prop.Copy())); + } m_ClipList.list = clipInfos; + m_ClipList.index = Mathf.Clamp(m_ClipList.index, -1, m_ClipAnimations.arraySize - 1); } void AddClipInList(ReorderableList list) @@ -601,9 +614,9 @@ void AddClipInList(ReorderableList list) int takeIndex = 0; - if (0 < selectedClipIndex && selectedClipIndex < m_ClipAnimations.arraySize) + if (0 < m_ClipList.index && m_ClipList.index < m_ClipAnimations.arraySize) { - AnimationClipInfoProperties info = GetAnimationClipInfoAtIndex(selectedClipIndex); + AnimationClipInfoProperties info = ((ClipInformation)m_ClipList.list[m_ClipList.index]).property; for (int i = 0; i < singleImporter.importedTakeInfos.Length; i++) { if (singleImporter.importedTakeInfos[i].name == info.takeName) @@ -615,7 +628,6 @@ void AddClipInList(ReorderableList list) } AddClip(singleImporter.importedTakeInfos[takeIndex]); - UpdateList(); SelectClip(list.list.Count - 1); } @@ -624,7 +636,6 @@ void RemoveClipInList(ReorderableList list) TransferDefaultClipsToCustomClips(); RemoveClip(list.index); - UpdateList(); SelectClip(Mathf.Min(list.index, list.count - 1)); } @@ -637,14 +648,14 @@ void SelectClipInList(ReorderableList list) private void DrawClipElement(Rect rect, int index, bool selected, bool focused) { - AnimationClipInfoProperties info = m_ClipList.list[index] as AnimationClipInfoProperties; + ClipInformation info = (ClipInformation)m_ClipList.list[index]; rect.xMax -= kFrameColumnWidth * 2; GUI.Label(rect, info.name, EditorStyles.label); rect.x = rect.xMax; rect.width = kFrameColumnWidth; - GUI.Label(rect, info.firstFrame.ToString("0.0", CultureInfo.InvariantCulture.NumberFormat), styles.numberStyle); + GUI.Label(rect, info.firstFrame, styles.numberStyle); rect.x = rect.xMax; - GUI.Label(rect, info.lastFrame.ToString("0.0", CultureInfo.InvariantCulture.NumberFormat), styles.numberStyle); + GUI.Label(rect, info.lastFrame, styles.numberStyle); } private void DrawClipHeader(Rect rect) @@ -660,18 +671,6 @@ private void DrawClipHeader(Rect rect) void AnimationSplitTable() { - if (m_ClipList == null) - { - m_ClipList = new ReorderableList(new List(), typeof(string), false, true, true, true); - m_ClipList.onAddCallback = AddClipInList; - m_ClipList.onSelectCallback = SelectClipInList; - m_ClipList.onRemoveCallback = RemoveClipInList; - m_ClipList.drawElementCallback = DrawClipElement; - m_ClipList.drawHeaderCallback = DrawClipHeader; - m_ClipList.elementHeight = 16; - UpdateList(); - m_ClipList.index = selectedClipIndex; - } m_ClipList.DoLayoutList(); EditorGUI.BeginChangeCheck(); @@ -682,14 +681,14 @@ void AnimationSplitTable() if (clip == null) return; - if (m_AnimationClipEditor != null && selectedClipIndex != -1) + if (m_AnimationClipEditor != null) { GUILayout.Space(5); AnimationClip actualClip = m_AnimationClipEditor.target as AnimationClip; if (!actualClip.legacy) - GetSelectedClipInfo().AssignToPreviewClip(actualClip); + clip.AssignToPreviewClip(actualClip); TakeInfo[] importedTakeInfos = singleImporter.importedTakeInfos; string[] takeNames = new string[importedTakeInfos.Length]; @@ -728,7 +727,7 @@ void AnimationSplitTable() clip.name = MakeUniqueClipName(takeNames[newTakeIndex]); SetupTakeNameAndFrames(clip, importedTakeInfos[newTakeIndex]); GUIUtility.keyboardControl = 0; - SelectClip(selectedClipIndex); + SelectClip(m_ClipList.index); // actualClip has been changed by SelectClip actualClip = m_AnimationClipEditor.target as AnimationClip; @@ -737,17 +736,17 @@ void AnimationSplitTable() m_AnimationClipEditor.OnInspectorGUI(); - AvatarMaskSettings(GetSelectedClipInfo()); + AvatarMaskSettings(clip); if (!actualClip.legacy) - GetSelectedClipInfo().ExtractFromPreviewClip(actualClip); - } - } + clip.ExtractFromPreviewClip(actualClip); - if (EditorGUI.EndChangeCheck() || m_AnimationClipEditor.needsToGenerateClipInfo) - { - TransferDefaultClipsToCustomClips(); - m_AnimationClipEditor.needsToGenerateClipInfo = false; + if (EditorGUI.EndChangeCheck() || m_AnimationClipEditor.needsToGenerateClipInfo) + { + TransferDefaultClipsToCustomClips(); + m_AnimationClipEditor.needsToGenerateClipInfo = false; + } + } } } @@ -772,21 +771,13 @@ bool IsDeprecatedMultiAnimationRootImport() public override void OnInteractivePreviewGUI(Rect r, GUIStyle background) { - if (m_AnimationClipEditor) - m_AnimationClipEditor.OnInteractivePreviewGUI(r, background); - } - - AnimationClipInfoProperties GetAnimationClipInfoAtIndex(int index) - { - return new AnimationClipInfoProperties(m_ClipAnimations.GetArrayElementAtIndex(index)); + m_AnimationClipEditor.OnInteractivePreviewGUI(r, background); } AnimationClipInfoProperties GetSelectedClipInfo() { - if (selectedClipIndex >= 0 && selectedClipIndex < m_ClipAnimations.arraySize) - return GetAnimationClipInfoAtIndex(selectedClipIndex); - else - return null; + //If it doesn't have a selected clip. return null - there is nothing to select! + return m_ClipList.index >= 0 && m_ClipList.index < m_ClipList.count ? ((ClipInformation)m_ClipList.list[m_ClipList.index]).property : null; } /// @@ -834,7 +825,7 @@ string FindNextAvailableName(string baseName) string[] allClipNames = new string[m_ClipAnimations.arraySize]; for (int i = 0; i < m_ClipAnimations.arraySize; ++i) { - AnimationClipInfoProperties clip = GetAnimationClipInfoAtIndex(i); + AnimationClipInfoProperties clip = ((ClipInformation)m_ClipList.list[i]).property; allClipNames[i] = clip.name; } Array.Sort(allClipNames, StringComparer.InvariantCulture); @@ -869,6 +860,7 @@ void RemoveClip(int index) SetupDefaultClips(); m_ImportAnimation.boolValue = false; } + UpdateList(); } void SetupTakeNameAndFrames(AnimationClipInfoProperties info, TakeInfo takeInfo) @@ -883,7 +875,8 @@ void AddClip(TakeInfo takeInfo) string uniqueName = MakeUniqueClipName(takeInfo.defaultClipName); m_ClipAnimations.InsertArrayElementAtIndex(m_ClipAnimations.arraySize); - AnimationClipInfoProperties info = GetAnimationClipInfoAtIndex(m_ClipAnimations.arraySize - 1); + var property = m_ClipAnimations.GetArrayElementAtIndex(m_ClipAnimations.arraySize - 1); + AnimationClipInfoProperties info = new AnimationClipInfoProperties(property); info.name = uniqueName; SetupTakeNameAndFrames(info, takeInfo); @@ -907,9 +900,9 @@ void AddClip(TakeInfo takeInfo) SetBodyMaskDefaultValues(info); - info.ClearEvents(); info.ClearCurves(); + UpdateList(); } private AvatarMask m_Mask = null; @@ -930,7 +923,14 @@ private void AvatarMaskSettings(AnimationClipInfoProperties clipInfo) m_MaskFoldout = EditorGUILayout.Foldout(m_MaskFoldout, styles.Mask, true); GUI.changed = wasChanged; - if (clipInfo.maskType == ClipAnimationMaskType.CreateFromThisModel && !m_MaskInspector.IsMaskUpToDate() && !m_MaskInspector.IsMaskEmpty()) + var maskType = clipInfo.maskType; + bool upToDate = true; + if (maskType == ClipAnimationMaskType.CreateFromThisModel || maskType == ClipAnimationMaskType.CopyFromOther) + { + upToDate = m_MaskInspector.IsMaskUpToDate(); + } + + if (maskType == ClipAnimationMaskType.CreateFromThisModel && !upToDate && !m_MaskInspector.IsMaskEmpty()) { GUILayout.BeginHorizontal(EditorStyles.helpBox); GUILayout.Label(styles.MaskHasAPath, @@ -947,7 +947,7 @@ private void AvatarMaskSettings(AnimationClipInfoProperties clipInfo) GUILayout.EndVertical(); GUILayout.EndHorizontal(); } - else if (clipInfo.maskType == ClipAnimationMaskType.CopyFromOther && clipInfo.MaskNeedsUpdating()) + else if (maskType == ClipAnimationMaskType.CopyFromOther && clipInfo.MaskNeedsUpdating()) { GUILayout.BeginHorizontal(EditorStyles.helpBox); GUILayout.Label(styles.SourceMaskHasChanged, @@ -962,7 +962,7 @@ private void AvatarMaskSettings(AnimationClipInfoProperties clipInfo) GUILayout.EndVertical(); GUILayout.EndHorizontal(); } - else if (clipInfo.maskType == ClipAnimationMaskType.CopyFromOther && !m_MaskInspector.IsMaskUpToDate()) + else if (maskType == ClipAnimationMaskType.CopyFromOther && !upToDate) { GUILayout.BeginHorizontal(EditorStyles.helpBox); GUILayout.Label(styles.SourceMaskHasAPath, @@ -1018,5 +1018,46 @@ private void SetBodyMaskDefaultValues(AnimationClipInfoProperties clipInfo) bodyMask.GetArrayElementAtIndex(i).intValue = 1; } } + + void RegisterListeners() + { + //Ensures that the ClipList and the Serialized copy of the clip remain in sync when an Undo/Redo is performed. + if (!serializedObject.isEditingMultipleObjects) + Undo.undoRedoPerformed += HandleUndo; + } + + void UnregisterListeners() + { + //Ensures that the ClipList and the Serialized copy of the clip remain in sync when an Undo/Redo is performed. + if (!serializedObject.isEditingMultipleObjects) + Undo.undoRedoPerformed -= HandleUndo; + } + + void HandleUndo() + { + //Update animations serialization in-case something has changed + m_ClipAnimations.serializedObject.UpdateIfRequiredOrScript(); + + //Reset the cache to the serialized values + DeserializeClips(); + } + + void DeserializeClips() + { + //Clear the clip editors + DestroyEditorsAndData(); + + //Reload the clips + m_ClipAnimations = serializedObject.FindProperty("m_ClipAnimations"); + m_AnimationType = serializedObject.FindProperty("m_AnimationType"); + m_DefaultClipsSerializedObject = null; + if (m_ClipAnimations.arraySize == 0) + SetupDefaultClips(); + UpdateList(); + + //Set the active clip within a valid range, -1 ONLY if there are no possible clips to select. + int selectedClip = m_ClipList.count > 0 ? Mathf.Clamp(m_ClipList.index, 0, m_ClipList.count) : -1; + SelectClip(selectedClip); + } } } diff --git a/Modules/AssetPipelineEditor/ImportSettings/ModelImporterMaterialEditor.cs b/Modules/AssetPipelineEditor/ImportSettings/ModelImporterMaterialEditor.cs index c35651de02..1449169806 100644 --- a/Modules/AssetPipelineEditor/ImportSettings/ModelImporterMaterialEditor.cs +++ b/Modules/AssetPipelineEditor/ImportSettings/ModelImporterMaterialEditor.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using UnityEditor.Experimental.AssetImporters; using UnityEngine; using Object = UnityEngine.Object; @@ -32,12 +33,12 @@ internal class ModelImporterMaterialEditor : BaseAssetImporterTabUI SerializedProperty m_MaterialImportMode; - private bool m_HasEmbeddedMaterials = false; + private bool m_CanExtractEmbeddedMaterials = false; class ExternalObjectCache { - public Material material = null; public int propertyIdx = 0; + public SerializedProperty property; } class MaterialCache @@ -129,32 +130,28 @@ private void UpdateShowAllMaterialNameOptions() #pragma warning restore 618 } - private bool HasEmbeddedMaterials() + bool CanExtractEmbeddedMaterials() { if (m_Materials.arraySize == 0) return false; - // if the m_ExternalObjecs map has any unapplied changes, keep the state of the button as is + // If the m_ExternalObjects map has any un-applied changes, keep the state of the button as is if (m_ExternalObjects.serializedObject.hasModifiedProperties) - return m_HasEmbeddedMaterials; + return m_CanExtractEmbeddedMaterials; - m_HasEmbeddedMaterials = true; + //Are there any materials that haven't been extracted? foreach (var t in m_ExternalObjects.serializedObject.targetObjects) { var importer = t as ModelImporter; var externalObjectMap = importer.GetExternalObjectMap(); var materialsList = importer.sourceMaterials; - int remappedMaterialCount = 0; - foreach (var entry in externalObjectMap) - { - if (entry.Key.type == typeof(Material) && Array.Exists(materialsList, x => x.name == entry.Key.name)) - ++remappedMaterialCount; - } - - m_HasEmbeddedMaterials = m_HasEmbeddedMaterials && remappedMaterialCount != materialsList.Length; + int mappedMaterialCount = externalObjectMap.Count(x => x.Key.type == typeof(Material) && x.Value != null && Array.Exists(materialsList, y => y.name == x.Key.name)); + if (mappedMaterialCount != materialsList.Length) + return m_CanExtractEmbeddedMaterials = true; } - return m_HasEmbeddedMaterials; + + return m_CanExtractEmbeddedMaterials = false; } internal override void OnEnable() @@ -213,9 +210,9 @@ private void BuildExternalObjectsCache() var pair = m_ExternalObjects.GetArrayElementAtIndex(externalObjectIdx); var cachedObject = new ExternalObjectCache(); - var materialProp = pair.FindPropertyRelative("second"); - cachedObject.material = materialProp != null ? materialProp.objectReferenceValue as Material : null; + cachedObject.property = pair.FindPropertyRelative("second"); cachedObject.propertyIdx = externalObjectIdx; + var externalName = pair.FindPropertyRelative("first.name").stringValue; var externalType = pair.FindPropertyRelative("first.type").stringValue; @@ -324,7 +321,7 @@ private bool ExtractMaterialsGUI() using (new EditorGUILayout.HorizontalScope()) { EditorGUILayout.PrefixLabel(Styles.Materials); - using (new EditorGUI.DisabledScope(!HasEmbeddedMaterials())) + using (new EditorGUI.DisabledScope(!CanExtractEmbeddedMaterials())) { if (GUILayout.Button(Styles.ExtractEmbeddedMaterials)) { @@ -337,13 +334,22 @@ private bool ExtractMaterialsGUI() // cancel the extraction if the user did not select a folder return false; } + destinationPath = FileUtil.GetProjectRelativePath(destinationPath); + //Where all the required embedded materials are not in the asset database, we need to reimport them + if (!AllEmbeddedMaterialsAreImported()) + { + if (EditorUtility.DisplayDialog(L10n.Tr("Are you sure you want to re-extract the Materials?"), L10n.Tr("In order to re-extract the Materials we'll need to reimport the mesh, this might take a while. Do you want to continue?"), L10n.Tr("Yes"), L10n.Tr("No"))) + ReimportEmbeddedMaterials(); + else + return false; + } + try { // batch the extraction of the textures AssetDatabase.StartAssetEditing(); - PrefabUtility.ExtractMaterialsFromAsset(targets, destinationPath); } finally @@ -360,9 +366,50 @@ private bool ExtractMaterialsGUI() return false; } + public bool AllEmbeddedMaterialsAreImported() + { + foreach (ModelImporter modelImporter in m_ExternalObjects.serializedObject.targetObjects) + { + //Find the names of embedded materials - the source materials that are not re-mapped in the externalObjectsCache + IEnumerable namesOfEmbeddedMaterials = modelImporter.sourceMaterials + .Where(x => !m_ExternalObjectsCache.Any(y => y.Key.Item1 == x.name && y.Value.property != null && y.Value.property.objectReferenceValue != null)) + .Select(x => x.name); + + //Find the names of embedded materials in the AssetDatabase + IEnumerable namesOfMaterialsInAssetDatabase = AssetDatabase.LoadAllAssetsAtPath(modelImporter.assetPath) + .Where(x => x.GetType() == typeof(Material)) + .Select(x => x.name); + + //Are there any embedded materials that *arent* in the AssetDatabase? + if (namesOfEmbeddedMaterials.Except(namesOfMaterialsInAssetDatabase).Any()) + return false; + } + + return true; + } + + public void ReimportEmbeddedMaterials() + { + //Select any material properties which are marked as "missing" + int[] missingMaterialIndexes = m_ExternalObjectsCache.Values.Select((extObj, index) => new { extObj, index }) + .Where(x => x.extObj.property != null && x.extObj.property.objectReferenceValue == null && x.extObj.property.objectReferenceInstanceIDValue != 0) + .Select(x => x.index) + .ToArray(); + + //Remove missing materials + for (int i = missingMaterialIndexes.Length - 1; i >= 0; i--) + m_ExternalObjects.DeleteArrayElementAtIndex(missingMaterialIndexes[i]); + + serializedObject.ApplyModifiedProperties(); + + //Force a reimport - any materials marked "None" (including former missing Materials), will now be assigned an embedded material + AssetImporter assetImporter = (AssetImporter)target; + AssetDatabase.ImportAsset(assetImporter.assetPath, ImportAssetOptions.ForceUpdate); + } + private bool MaterialRemapOptions() { - m_ShowMaterialRemapOptions = EditorGUILayout.Foldout(m_ShowMaterialRemapOptions, Styles.RemapOptions); + m_ShowMaterialRemapOptions = EditorGUILayout.Foldout(m_ShowMaterialRemapOptions, Styles.RemapOptions, true); if (m_ShowMaterialRemapOptions) { EditorGUI.indentLevel++; @@ -444,7 +491,7 @@ void DoMaterialsGUI() { if (m_MaterialImportMode.intValue != (int)ModelImporterMaterialImportMode.None) { - if (m_MaterialImportMode.intValue == (int)ModelImporterMaterialImportMode.LegacyImport) + if (m_MaterialImportMode.intValue == (int)ModelImporterMaterialImportMode.ImportStandard) { EditorGUILayout.PropertyField(m_UseSRGBMaterialColor, Styles.SRGBMaterialColor); } @@ -478,7 +525,7 @@ void DoMaterialsGUI() Styles.ExternalMaterialSearchHelp[m_MaterialSearch.intValue].text + "\n" + Styles.ExternalMaterialHelpEnd.text; } - else if (m_Materials.arraySize > 0 && HasEmbeddedMaterials()) + else if (m_Materials.arraySize > 0 && CanExtractEmbeddedMaterials()) { // we're generating materials inside the prefab materialHelp = Styles.InternalMaterialHelp.text; @@ -537,54 +584,100 @@ internal override void ResetValues() void DoMaterialRemapList() { + // OnEnabled is not called consistently when the asset gets reimported, we need to rebuild the cache here if it's outdated. + if (m_ExternalObjects.arraySize != m_ExternalObjectsCache.Count()) + ResetValues(); // The list of material names is immutable, whereas the map of external objects can change based on user actions. - // For each material name, map the external object associated with it. - // The complexity comes from the fact that we may not have an external object in the map, so we can't make a property out of it + // For each material name, map the external object associated with it where one exists. for (int materialIdx = 0; materialIdx < m_MaterialsCache.Count; ++materialIdx) { var mat = m_MaterialsCache[materialIdx]; ExternalObjectCache cachedExternalObject; + bool hasMatchingCachedObject = m_ExternalObjectsCache.TryGetValue(new Tuple(mat.name, mat.type), out cachedExternalObject) && cachedExternalObject != null; - GUIContent nameLabel = EditorGUIUtility.TextContent(mat.name); - nameLabel.tooltip = mat.name; + if (hasMatchingCachedObject) + { + //The material already has a serialized property, so use it! + MaterialPropertyGUI(mat, cachedExternalObject); + } + else + { + //The material doesn't have a serialized property, so it's going to have to draw the GUI a different way! + MaterialPropertyGUI(mat); + } + } + } - Material material = m_ExternalObjectsCache.TryGetValue(new Tuple(mat.name, mat.type), out cachedExternalObject) ? cachedExternalObject.material : null; + void MaterialPropertyGUI(MaterialCache materialCache, ExternalObjectCache externalObjectCache) + { + GUIContent nameLabel = EditorGUIUtility.TextContent(materialCache.name); + nameLabel.tooltip = materialCache.name; - EditorGUI.BeginChangeCheck(); - material = EditorGUILayout.ObjectField(nameLabel, material, typeof(Material), false) as Material; - if (EditorGUI.EndChangeCheck()) - { - if (cachedExternalObject != null) - { - if (material == null) - { - m_ExternalObjects.DeleteArrayElementAtIndex(cachedExternalObject.propertyIdx); - } - else - { - var pair = m_ExternalObjects.GetArrayElementAtIndex(cachedExternalObject.propertyIdx); - pair.FindPropertyRelative("second").objectReferenceValue = material; - } - } - else if (material != null) - { - m_ExternalObjects.arraySize++; - var pair = m_ExternalObjects.GetArrayElementAtIndex(m_ExternalObjects.arraySize - 1); - pair.FindPropertyRelative("first.name").stringValue = mat.name; - pair.FindPropertyRelative("first.type").stringValue = mat.type; - pair.FindPropertyRelative("first.assembly").stringValue = mat.assembly; - pair.FindPropertyRelative("second").objectReferenceValue = material; - // ExternalObjects is serialized as a map, so items are reordered when deserializing. - // We need to update the serializedObject to trigger the reordering before rebuilding the cache. - serializedObject.ApplyModifiedProperties(); - serializedObject.Update(); - } + EditorGUI.BeginChangeCheck(); - BuildExternalObjectsCache(); - break; + SerializedProperty property = externalObjectCache.property; + EditorGUILayout.ObjectField(property, typeof(Material), nameLabel); + Material material = property.objectReferenceValue as Material; + if (EditorGUI.EndChangeCheck()) + { + if (material == null) + m_ExternalObjects.DeleteArrayElementAtIndex(externalObjectCache.propertyIdx); + else + { + var pair = m_ExternalObjects.GetArrayElementAtIndex(externalObjectCache.propertyIdx); + pair.FindPropertyRelative("second").objectReferenceValue = material; } + + BuildExternalObjectsCache(); + } + } + + void MaterialPropertyGUI(MaterialCache materialCache) + { + GUIContent nameLabel = EditorGUIUtility.TextContent(materialCache.name); + nameLabel.tooltip = materialCache.name; + + EditorGUI.BeginChangeCheck(); + Material material = ObjectFieldWithPPtrHashID(nameLabel, null, typeof(Material), false) as Material; + if (EditorGUI.EndChangeCheck() && material != null) + { + m_ExternalObjects.arraySize++; + var pair = m_ExternalObjects.GetArrayElementAtIndex(m_ExternalObjects.arraySize - 1); + pair.FindPropertyRelative("first.name").stringValue = materialCache.name; + pair.FindPropertyRelative("first.type").stringValue = materialCache.type; + pair.FindPropertyRelative("first.assembly").stringValue = materialCache.assembly; + pair.FindPropertyRelative("second").objectReferenceValue = material; + + // ExternalObjects is serialized as a map, so items are reordered when deserializing. + // We need to update the serializedObject to trigger the reordering before rebuilding the cache. + serializedObject.ApplyModifiedProperties(); + serializedObject.Update(); + + BuildExternalObjectsCache(); } } + + private static readonly int s_PPtrHash = "s_PPtrHash".GetHashCode(); + + // Taken from EditorGUI in order to work arround the issue of ObjectField using different ControlIDs when a Serialized property is passed as argument. + private static Object ObjectFieldWithPPtrHashID(GUIContent label, Object obj, Type objType, bool allowSceneObjects, params GUILayoutOption[] options) + { + var height = EditorGUIUtility.HasObjectThumbnail(objType) ? EditorGUI.kObjectFieldThumbnailHeight : EditorGUI.kSingleLineHeight; + Rect position = EditorGUILayout.GetControlRect(true, height, options); + + int id = GUIUtility.GetControlID(s_PPtrHash, FocusType.Keyboard, position); + position = EditorGUI.PrefixLabel(position, id, label); + + if (EditorGUIUtility.HasObjectThumbnail(objType) && position.height > EditorGUI.kSingleLineHeight) + { + // Make object field with thumbnail quadratic and align to the right + float size = Mathf.Min(position.width, position.height); + position.height = size; + position.xMin = position.xMax - size; + } + + return EditorGUI.DoObjectField(position, position, id, obj, objType, null, null, allowSceneObjects); + } } } diff --git a/Modules/AssetPipelineEditor/ImportSettings/ModelImporterModelEditor.cs b/Modules/AssetPipelineEditor/ImportSettings/ModelImporterModelEditor.cs index a25492512e..b8b26a21f6 100644 --- a/Modules/AssetPipelineEditor/ImportSettings/ModelImporterModelEditor.cs +++ b/Modules/AssetPipelineEditor/ImportSettings/ModelImporterModelEditor.cs @@ -10,49 +10,83 @@ namespace UnityEditor { internal class ModelImporterModelEditor : BaseAssetImporterTabUI { +#pragma warning disable 0649 // Scene + [CacheProperty] SerializedProperty m_GlobalScale; + [CacheProperty] SerializedProperty m_UseFileScale; + [CacheProperty] SerializedProperty m_FileScale; + [CacheProperty] SerializedProperty m_FileScaleUnit; + [CacheProperty] SerializedProperty m_FileScaleFactor; + [CacheProperty] SerializedProperty m_ImportBlendShapes; + [CacheProperty] SerializedProperty m_ImportVisibility; + [CacheProperty] protected SerializedProperty m_ImportCameras; + [CacheProperty] SerializedProperty m_ImportLights; // Meshes + [CacheProperty] SerializedProperty m_MeshCompression; + [CacheProperty] SerializedProperty m_IsReadable; + [CacheProperty("meshOptimizationFlags")] SerializedProperty m_MeshOptimizationFlags; // Geometry + [CacheProperty("keepQuads")] SerializedProperty m_KeepQuads; + [CacheProperty("weldVertices")] SerializedProperty m_WeldVertices; + [CacheProperty("indexFormat")] protected SerializedProperty m_IndexFormat; + [CacheProperty("swapUVChannels")] SerializedProperty m_SwapUVChannels; + [CacheProperty("generateSecondaryUV")] SerializedProperty m_GenerateSecondaryUV; bool m_SecondaryUVAdvancedOptions = false; + [CacheProperty("secondaryUVAngleDistortion")] SerializedProperty m_SecondaryUVAngleDistortion; + [CacheProperty("secondaryUVAreaDistortion")] SerializedProperty m_SecondaryUVAreaDistortion; + [CacheProperty("secondaryUVHardAngle")] SerializedProperty m_SecondaryUVHardAngle; + [CacheProperty("secondaryUVPackMargin")] SerializedProperty m_SecondaryUVPackMargin; + [CacheProperty("normalImportMode")] protected SerializedProperty m_NormalImportMode; + [CacheProperty("normalCalculationMode")] protected SerializedProperty m_NormalCalculationMode; + [CacheProperty("blendShapeNormalImportMode")] SerializedProperty m_BlendShapeNormalCalculationMode; + [CacheProperty("legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes")] SerializedProperty m_LegacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes; + [CacheProperty("normalSmoothingSource")] SerializedProperty m_NormalSmoothingSource; + [CacheProperty("normalSmoothAngle")] protected SerializedProperty m_NormalSmoothAngle; + [CacheProperty("tangentImportMode")] protected SerializedProperty m_TangentImportMode; // Prefab + [CacheProperty] SerializedProperty m_PreserveHierarchy; + [CacheProperty] SerializedProperty m_SortHierarchyByName; + [CacheProperty] SerializedProperty m_AddColliders; +#pragma warning restore 0649 + public ModelImporterModelEditor(AssetImporterEditor panelContainer) : base(panelContainer) @@ -61,38 +95,7 @@ public ModelImporterModelEditor(AssetImporterEditor panelContainer) internal override void OnEnable() { - m_GlobalScale = serializedObject.FindProperty("m_GlobalScale"); - m_UseFileScale = serializedObject.FindProperty("m_UseFileScale"); - m_FileScale = serializedObject.FindProperty("m_FileScale"); - m_FileScaleUnit = serializedObject.FindProperty("m_FileScaleUnit"); - m_FileScaleFactor = serializedObject.FindProperty("m_FileScaleFactor"); - m_MeshCompression = serializedObject.FindProperty("m_MeshCompression"); - m_ImportBlendShapes = serializedObject.FindProperty("m_ImportBlendShapes"); - m_ImportCameras = serializedObject.FindProperty("m_ImportCameras"); - m_ImportLights = serializedObject.FindProperty("m_ImportLights"); - m_AddColliders = serializedObject.FindProperty("m_AddColliders"); - m_SwapUVChannels = serializedObject.FindProperty("swapUVChannels"); - m_GenerateSecondaryUV = serializedObject.FindProperty("generateSecondaryUV"); - m_SecondaryUVAngleDistortion = serializedObject.FindProperty("secondaryUVAngleDistortion"); - m_SecondaryUVAreaDistortion = serializedObject.FindProperty("secondaryUVAreaDistortion"); - m_SecondaryUVHardAngle = serializedObject.FindProperty("secondaryUVHardAngle"); - m_SecondaryUVPackMargin = serializedObject.FindProperty("secondaryUVPackMargin"); - m_NormalSmoothAngle = serializedObject.FindProperty("normalSmoothAngle"); - m_NormalImportMode = serializedObject.FindProperty("normalImportMode"); - m_NormalCalculationMode = serializedObject.FindProperty("normalCalculationMode"); - m_LegacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes = serializedObject.FindProperty("legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes"); - m_NormalSmoothingSource = serializedObject.FindProperty("normalSmoothingSource"); - m_BlendShapeNormalCalculationMode = serializedObject.FindProperty("blendShapeNormalImportMode"); - m_TangentImportMode = serializedObject.FindProperty("tangentImportMode"); - m_MeshOptimizationFlags = serializedObject.FindProperty("meshOptimizationFlags"); - m_IsReadable = serializedObject.FindProperty("m_IsReadable"); - - m_KeepQuads = serializedObject.FindProperty("keepQuads"); - m_IndexFormat = serializedObject.FindProperty("indexFormat"); - m_WeldVertices = serializedObject.FindProperty("weldVertices"); - m_ImportVisibility = serializedObject.FindProperty("m_ImportVisibility"); - m_PreserveHierarchy = serializedObject.FindProperty("m_PreserveHierarchy"); - m_SortHierarchyByName = serializedObject.FindProperty("m_SortHierarchyByName"); + Editor.AssignCachedProperties(this, serializedObject.GetIterator()); } protected static class Styles @@ -148,7 +151,7 @@ public override void OnInspectorGUI() protected void MeshesGUI() { - GUILayout.Label(Styles.Meshes, EditorStyles.boldLabel); + EditorGUILayout.LabelField(Styles.Meshes, EditorStyles.boldLabel); using (var horizontal = new EditorGUILayout.HorizontalScope()) { using (var prop = new EditorGUI.PropertyScope(horizontal.rect, Styles.MeshCompressionLabel, m_MeshCompression)) diff --git a/Modules/AssetPipelineEditor/ImportSettings/ModelImporterRigEditor.cs b/Modules/AssetPipelineEditor/ImportSettings/ModelImporterRigEditor.cs index e19b56dc76..1082adddc9 100644 --- a/Modules/AssetPipelineEditor/ImportSettings/ModelImporterRigEditor.cs +++ b/Modules/AssetPipelineEditor/ImportSettings/ModelImporterRigEditor.cs @@ -19,26 +19,44 @@ internal class ModelImporterRigEditor : BaseAssetImporterTabUI Avatar m_Avatar; +#pragma warning disable 0649 + [CacheProperty] SerializedProperty m_AnimationType; + [CacheProperty] SerializedProperty m_AvatarSetup; + [CacheProperty("m_LastHumanDescriptionAvatarSource")] SerializedProperty m_AvatarSource; + [CacheProperty] SerializedProperty m_LegacyGenerateAnimations; + [CacheProperty] SerializedProperty m_AnimationCompression; + [CacheProperty("skinWeightsMode")] SerializedProperty m_SkinWeightsMode; + [CacheProperty("maxBonesPerVertex")] SerializedProperty m_MaxBonesPerVertex; + [CacheProperty("minBoneWeight")] SerializedProperty m_MinBoneWeight; + [CacheProperty] SerializedProperty m_OptimizeGameObjects; + [CacheProperty("m_HumanDescription.m_RootMotionBoneName")] SerializedProperty m_RootMotionBoneName; + [CacheProperty("m_HasExtraRoot")] SerializedProperty m_SrcHasExtraRoot; + [CacheProperty("m_HumanDescription.m_HasExtraRoot")] SerializedProperty m_DstHasExtraRoot; + [CacheProperty] SerializedProperty m_RigImportErrors; + [CacheProperty] SerializedProperty m_RigImportWarnings; + [CacheProperty("m_HumanDescription.m_Human")] SerializedProperty m_HumanBoneArray; + [CacheProperty("m_HumanDescription.m_Skeleton")] SerializedProperty m_Skeleton; +#pragma warning restore 0649 private static bool importMessageFoldout = false; @@ -55,8 +73,6 @@ private ModelImporterAnimationType animationType bool m_AvatarCopyIsUpToDate; private bool m_CanMultiEditTransformList; - public int rootIndex { get; set; } - bool m_IsBiped = false; List m_BipedMappingReport = null; @@ -118,12 +134,7 @@ public ModelImporterRigEditor(AssetImporterEditor panelContainer) internal override void OnEnable() { - m_AnimationType = serializedObject.FindProperty("m_AnimationType"); - m_AvatarSetup = serializedObject.FindProperty("m_AvatarSetup"); - m_AvatarSource = serializedObject.FindProperty("m_LastHumanDescriptionAvatarSource"); - - // Generic bone setup - m_RootMotionBoneName = serializedObject.FindProperty("m_HumanDescription.m_RootMotionBoneName"); + Editor.AssignCachedProperties(this, serializedObject.GetIterator()); m_ExposeTransformEditor = new ExposeTransformEditor(); @@ -135,28 +146,6 @@ internal override void OnEnable() if (m_RootMotionBoneList.Length > 0) m_RootMotionBoneList[0] = EditorGUIUtility.TrTextContent("None"); - rootIndex = ArrayUtility.FindIndex(m_RootMotionBoneList, delegate(GUIContent content) { return FileUtil.GetLastPathNameComponent(content.text) == m_RootMotionBoneName.stringValue; }); - rootIndex = rootIndex < 1 ? 0 : rootIndex; - - m_SrcHasExtraRoot = serializedObject.FindProperty("m_HasExtraRoot"); - m_DstHasExtraRoot = serializedObject.FindProperty("m_HumanDescription.m_HasExtraRoot"); - - // Animation - m_LegacyGenerateAnimations = serializedObject.FindProperty("m_LegacyGenerateAnimations"); - m_AnimationCompression = serializedObject.FindProperty("m_AnimationCompression"); - - m_SkinWeightsMode = serializedObject.FindProperty("skinWeightsMode"); - m_MaxBonesPerVertex = serializedObject.FindProperty("maxBonesPerVertex"); - m_MinBoneWeight = serializedObject.FindProperty("minBoneWeight"); - - m_OptimizeGameObjects = serializedObject.FindProperty("m_OptimizeGameObjects"); - - m_RigImportErrors = serializedObject.FindProperty("m_RigImportErrors"); - m_RigImportWarnings = serializedObject.FindProperty("m_RigImportWarnings"); - - m_HumanBoneArray = serializedObject.FindProperty("m_HumanDescription.m_Human"); - m_Skeleton = serializedObject.FindProperty("m_HumanDescription.m_Skeleton"); - m_ExposeTransformEditor.OnEnable(singleImporter.transformPaths, serializedObject); m_CanMultiEditTransformList = CanMultiEditTransformList(); @@ -267,7 +256,6 @@ void GenericGUI() if (m_AvatarSetup.intValue == (int)ModelImporterAvatarSetup.CreateFromThisModel) { // Do not allow multi edit of root node if all rigs doesn't match - EditorGUI.BeginChangeCheck(); using (new EditorGUI.DisabledScope(!m_CanMultiEditTransformList)) { if (assetTarget == null) @@ -276,20 +264,22 @@ void GenericGUI() EditorGUILayout.TextField(Styles.RootNode, m_RootMotionBoneName.stringValue); } else - rootIndex = EditorGUILayout.Popup(Styles.RootNode, rootIndex, m_RootMotionBoneList); - } - if (EditorGUI.EndChangeCheck()) - { - if (assetTarget != null) { - if (rootIndex > 0 && rootIndex < m_RootMotionBoneList.Length) - { - m_RootMotionBoneName.stringValue = - FileUtil.GetLastPathNameComponent(m_RootMotionBoneList[rootIndex].text); - } - else + EditorGUI.BeginChangeCheck(); + var currentIndex = ArrayUtility.FindIndex(m_RootMotionBoneList, content => FileUtil.GetLastPathNameComponent(content.text) == m_RootMotionBoneName.stringValue); + currentIndex = currentIndex < 1 ? 0 : currentIndex; + currentIndex = EditorGUILayout.Popup(Styles.RootNode, currentIndex, m_RootMotionBoneList); + if (EditorGUI.EndChangeCheck()) { - m_RootMotionBoneName.stringValue = ""; + if (currentIndex > 0 && currentIndex < m_RootMotionBoneList.Length) + { + m_RootMotionBoneName.stringValue = + FileUtil.GetLastPathNameComponent(m_RootMotionBoneList[currentIndex].text); + } + else + { + m_RootMotionBoneName.stringValue = ""; + } } } } @@ -569,7 +559,7 @@ public override void OnInspectorGUI() if (m_SkinWeightsMode.intValue == (int)ModelImporterSkinWeights.Custom) { - EditorGUILayout.IntSlider(m_MaxBonesPerVertex, 1, 32, Styles.MaxBonesPerVertex); + EditorGUILayout.IntSlider(m_MaxBonesPerVertex, 1, 255, Styles.MaxBonesPerVertex); EditorGUILayout.Slider(m_MinBoneWeight, 0.001f, 0.5f, Styles.MinBoneWeight); } } diff --git a/Modules/AssetPipelineEditor/ImportSettings/PluginImporterInspector.cs b/Modules/AssetPipelineEditor/ImportSettings/PluginImporterInspector.cs index dc63be90a9..79a50e009c 100644 --- a/Modules/AssetPipelineEditor/ImportSettings/PluginImporterInspector.cs +++ b/Modules/AssetPipelineEditor/ImportSettings/PluginImporterInspector.cs @@ -423,6 +423,8 @@ protected override void Apply() serializedObject.Update(); base.Apply(); + + m_HasModified = false; } protected override void Awake() diff --git a/Modules/AssetPipelineEditor/Public/ModelImporting/ModelImporter.bindings.cs b/Modules/AssetPipelineEditor/Public/ModelImporting/ModelImporter.bindings.cs index 24159f47ac..b7e61ebd34 100644 --- a/Modules/AssetPipelineEditor/Public/ModelImporting/ModelImporter.bindings.cs +++ b/Modules/AssetPipelineEditor/Public/ModelImporting/ModelImporter.bindings.cs @@ -233,11 +233,16 @@ public enum ModelImporterMaterialImportMode { [Tooltip("Do not import materials")] None = 0, - [InspectorName("Import (Legacy)")] - [Tooltip("Use the legacy Material import method.")] - LegacyImport = 1, - [InspectorName("Import (Experimental)")] + [InspectorName("Standard")] + [Tooltip("Use the standard Material import method.")] + ImportStandard = 1, + [InspectorName("Import via MaterialDescription (Experimental)")] [Tooltip("Use AssetPostprocessor.OnPreprocessMaterialDescription")] + ImportViaMaterialDescription = 2, + + [System.Obsolete("Use ImportStandard (UnityUpgradable) -> ImportStandard")] + LegacyImport = 1, + [System.Obsolete("Use ImportViaMaterialDescription (UnityUpgradable) -> ImportViaMaterialDescription")] Import = 2 }; @@ -701,8 +706,8 @@ public int maxBonesPerVertex set { - if (value < 1 || value > 32) - throw new ArgumentOutOfRangeException(nameof(maxBonesPerVertex), value, "Value must be in the range 1 - 32."); + if (value < 1 || value > 255) + throw new ArgumentOutOfRangeException(nameof(maxBonesPerVertex), value, "Value must be in the range 1 - 255."); if (skinWeights != ModelImporterSkinWeights.Custom) Debug.LogWarning("ModelImporter.maxBonesPerVertex is ignored unless ModelImporter.skinWeights is set to ModelImporterSkinWeights.Custom."); SetMaxBonesPerVertex(value); @@ -1053,5 +1058,11 @@ public extern ModelImporterMaterialImportMode materialImportMode get; set; } + + public extern bool autoGenerateAvatarMappingIfUnspecified + { + get; + set; + } } } diff --git a/Modules/AssetPipelineEditor/Public/ScriptedImporter.cs b/Modules/AssetPipelineEditor/Public/ScriptedImporter.cs index 99f43a338f..2db71ff800 100644 --- a/Modules/AssetPipelineEditor/Public/ScriptedImporter.cs +++ b/Modules/AssetPipelineEditor/Public/ScriptedImporter.cs @@ -76,7 +76,7 @@ internal static void RegisterScriptedImporters() // Register the importer foreach (var ext in handledExts) - AssetImporter.RegisterImporter(importerType, attribute.version, attribute.importQueuePriority, ext.Key, supportsImportDependencyHinting, attribute.AutoSelect); + AssetImporter.RegisterImporter(importerType, attribute.version, attribute.importQueuePriority, ext.Key, supportsImportDependencyHinting, attribute.AutoSelect, attribute.AllowCaching); } } @@ -113,6 +113,8 @@ public class ScriptedImporterAttribute : Attribute public bool AutoSelect = true; + public bool AllowCaching = false; + public ScriptedImporterAttribute(int version, string[] exts) { Init(version, exts, 0); diff --git a/Modules/Audio/Public/ScriptBindings/Audio.bindings.cs b/Modules/Audio/Public/ScriptBindings/Audio.bindings.cs index 52b3b783d0..4d320886fb 100644 --- a/Modules/Audio/Public/ScriptBindings/Audio.bindings.cs +++ b/Modules/Audio/Public/ScriptBindings/Audio.bindings.cs @@ -600,23 +600,23 @@ static public void GetSpectrumData(float[] samples, int channel, FFTWindow windo [StaticAccessor("AudioSourceBindings", StaticAccessorType.DoubleColon)] public sealed partial class AudioSource : AudioBehaviour { - extern static private float GetPitch(AudioSource source); - extern static private void SetPitch(AudioSource source, float pitch); + extern static private float GetPitch([NotNull] AudioSource source); + extern static private void SetPitch([NotNull] AudioSource source, float pitch); - extern static private void PlayHelper(AudioSource source, UInt64 delay); + extern static private void PlayHelper([NotNull] AudioSource source, UInt64 delay); extern private void Play(double delay); - extern static private void PlayOneShotHelper(AudioSource source, AudioClip clip, float volumeScale); + extern static private void PlayOneShotHelper([NotNull] AudioSource source, AudioClip clip, float volumeScale); extern private void Stop(bool stopOneShots); [NativeThrows] - extern static private void SetCustomCurveHelper(AudioSource source, AudioSourceCurveType type, AnimationCurve curve); - extern static private AnimationCurve GetCustomCurveHelper(AudioSource source, AudioSourceCurveType type); + extern static private void SetCustomCurveHelper([NotNull] AudioSource source, AudioSourceCurveType type, AnimationCurve curve); + extern static private AnimationCurve GetCustomCurveHelper([NotNull] AudioSource source, AudioSourceCurveType type); - extern static private void GetOutputDataHelper(AudioSource source, [Out] float[] samples, int channel); + extern static private void GetOutputDataHelper([NotNull] AudioSource source, [Out] float[] samples, int channel); [NativeThrows] - extern static private void GetSpectrumDataHelper(AudioSource source, [Out] float[] samples, int channel, FFTWindow window); + extern static private void GetSpectrumDataHelper([NotNull] AudioSource source, [Out] float[] samples, int channel, FFTWindow window); // The volume of the audio source (0.0 to 1.0) extern public float volume { get; set; } @@ -649,41 +649,84 @@ extern public int timeSamples extern public AudioMixerGroup outputAudioMixerGroup { get; set; } - [NativeConditional("PS4_PAD_SPEAKER")] - [NativeMethod(Name = "AudioSourceBindings::PlayOnDualShock4", HasExplicitThis = true)] + [NativeConditional("PLATFORM_SUPPORTS_GAMEPAD_AUDIO")] + [NativeMethod(Name = "AudioSourceBindings::PlayOnDualShock4", HasExplicitThis = true, ThrowsException = true)] + [Obsolete("Use PlayOnGamepad instead")] extern public bool PlayOnDualShock4(Int32 userId); - [NativeConditional("PS4_PAD_SPEAKER")] - [NativeMethod(Name = "AudioSourceBindings::DisableDualShock4Output", HasExplicitThis = true)] + [NativeConditional("PLATFORM_SUPPORTS_GAMEPAD_AUDIO")] + [NativeMethod(Name = "AudioSourceBindings::SetDualShock4SpeakerMixLevel", HasExplicitThis = true, ThrowsException = true)] + [Obsolete("Use SetGamepadSpeakerMixLevel instead")] + extern public bool SetDualShock4PadSpeakerMixLevel(Int32 userId, Int32 mixLevel); + + [NativeConditional("PLATFORM_SUPPORTS_GAMEPAD_AUDIO")] + [NativeMethod(Name = "AudioSourceBindings::SetDualShock4SpeakerMixLevelDefault", HasExplicitThis = true, ThrowsException = true)] + [Obsolete("Use SetGamepadSpeakerMixLevelDefault instead")] + extern public bool SetDualShock4PadSpeakerMixLevelDefault(Int32 userId); + + [NativeConditional("PLATFORM_SUPPORTS_GAMEPAD_AUDIO")] + [NativeMethod(Name = "AudioSourceBindings::SetDualShock4SpeakerRestrictedAudio", HasExplicitThis = true, ThrowsException = true)] + [Obsolete("Use SetgamepadSpeakerRestrictedAudio instead")] + extern public bool SetDualShock4PadSpeakerRestrictedAudio(Int32 userId, bool restricted); + + [NativeConditional("PLATFORM_SUPPORTS_GAMEPAD_AUDIO")] + [NativeMethod(Name = "AudioSourceBindings::PlayOnGamepad", HasExplicitThis = true, ThrowsException = true)] + [Obsolete("Use PlayOnGamepad instead")] + extern public bool PlayOnDualShock4PadIndex(Int32 slot); + + [NativeConditional("PLATFORM_SUPPORTS_GAMEPAD_AUDIO")] + [NativeMethod(Name = "AudioSourceBindings::DisableGamepadOutput", HasExplicitThis = true)] + [Obsolete("Use DisableGamepadOutput instead")] extern public bool DisableDualShock4Output(); - [NativeConditional("PS4_PAD_SPEAKER")] - [NativeMethod(Name = "AudioSourceBindings::PlayOnDualShock4PadIndex", HasExplicitThis = true)] - extern public bool PlayOnDualShock4PadIndex(Int32 padIndex); + [NativeConditional("PLATFORM_SUPPORTS_GAMEPAD_AUDIO")] + [NativeMethod(Name = "AudioSourceBindings::SetGamepadSpeakerMixLevel", HasExplicitThis = true, ThrowsException = true)] + [Obsolete("Use SetGamepadSpeakerMixLevel instead")] + extern public bool SetDualShock4PadSpeakerMixLevelPadIndex(Int32 slot, Int32 mixLevel); - [NativeConditional("PS4_PAD_SPEAKER")] - [NativeName("SetPS4DualShock4PadSpeakerMixLevel")] - extern public bool SetDualShock4PadSpeakerMixLevel(Int32 userId, Int32 mixLevel); + [NativeConditional("PLATFORM_SUPPORTS_GAMEPAD_AUDIO")] + [NativeMethod(Name = "AudioSourceBindings::SetGamepadSpeakerMixLevelDefault", HasExplicitThis = true, ThrowsException = true)] + [Obsolete("Use SetGamepadSpeakerMixLevelDefault instead")] + extern public bool SetDualShock4PadSpeakerMixLevelDefaultPadIndex(Int32 slot); - [NativeConditional("PS4_PAD_SPEAKER")] - [NativeName("SetPS4DualShock4PadSpeakerMixLevelPadIndex")] - extern public bool SetDualShock4PadSpeakerMixLevelPadIndex(Int32 padIndex, Int32 mixLevel); + [NativeConditional("PLATFORM_SUPPORTS_GAMEPAD_AUDIO")] + [NativeMethod(Name = "AudioSourceBindings::SetGamepadSpeakerRestrictedAudio", HasExplicitThis = true, ThrowsException = true)] + [Obsolete("Use SetGamepadSpeakerRestrictedAudio instead")] + extern public bool SetDualShock4PadSpeakerRestrictedAudioPadIndex(Int32 slot, bool restricted); - [NativeConditional("PS4_PAD_SPEAKER")] - [NativeName("SetPS4DualShock4PadSpeakerMixLevelDefault")] - extern public bool SetDualShock4PadSpeakerMixLevelDefault(Int32 userId); + [NativeConditional("PLATFORM_SUPPORTS_GAMEPAD_AUDIO")] + [NativeMethod(Name = "AudioSourceBindings::PlayOnGamepad", HasExplicitThis = true, ThrowsException = true)] + extern public bool PlayOnGamepad(Int32 slot); - [NativeConditional("PS4_PAD_SPEAKER")] - [NativeName("SetPS4DualShock4PadSpeakerMixLevelDefaultPadIndex")] - extern public bool SetDualShock4PadSpeakerMixLevelDefaultPadIndex(Int32 padIndex); + [NativeConditional("PLATFORM_SUPPORTS_GAMEPAD_AUDIO")] + [NativeMethod(Name = "AudioSourceBindings::DisableGamepadOutput", HasExplicitThis = true)] + extern public bool DisableGamepadOutput(); + + [NativeConditional("PLATFORM_SUPPORTS_GAMEPAD_AUDIO")] + [NativeMethod(Name = "AudioSourceBindings::SetGamepadSpeakerMixLevel", HasExplicitThis = true, ThrowsException = true)] + extern public bool SetGamepadSpeakerMixLevel(Int32 slot, Int32 mixLevel); + + [NativeConditional("PLATFORM_SUPPORTS_GAMEPAD_AUDIO")] + [NativeMethod(Name = "AudioSourceBindings::SetGamepadSpeakerMixLevelDefault", HasExplicitThis = true, ThrowsException = true)] + extern public bool SetGamepadSpeakerMixLevelDefault(Int32 slot); + + [NativeConditional("PLATFORM_SUPPORTS_GAMEPAD_AUDIO")] + [NativeMethod(Name = "AudioSourceBindings::SetGamepadSpeakerRestrictedAudio", HasExplicitThis = true, ThrowsException = true)] + extern public bool SetGamepadSpeakerRestrictedAudio(Int32 slot, bool restricted); + + public enum GamepadSpeakerOutputType + { + Speaker = 0, + Vibration = 1, + } + + [NativeConditional("PLATFORM_SUPPORTS_GAMEPAD_AUDIO")] + [NativeMethod(Name = "GamepadSpeakerSupportsOutputType", HasExplicitThis = false)] + extern static public bool GamepadSpeakerSupportsOutputType(GamepadSpeakerOutputType outputType); - [NativeConditional("PS4_PAD_SPEAKER")] - [NativeName("SetPS4DualShock4PadSpeakerRestrictedAudio")] - extern public bool SetDualShock4PadSpeakerRestrictedAudio(Int32 userId, bool restricted); - [NativeConditional("PS4_PAD_SPEAKER")] - [NativeName("SetPS4DualShock4PadSpeakerRestrictedAudioPadIndex")] - extern public bool SetDualShock4PadSpeakerRestrictedAudioPadIndex(Int32 padIndex, bool restricted); + [NativeConditional("PLATFORM_SUPPORTS_GAMEPAD_AUDIO")] + extern public GamepadSpeakerOutputType gamepadSpeakerOutputType { get; set; } // Plays the ::ref::clip with a certain delay (the optional delay argument is deprecated since 4.1a3) and the functionality has been replaced by PlayDelayed. [ExcludeFromDocs] @@ -1115,13 +1158,13 @@ public float roomRolloffFactor extern public float density { get; set; } // Reference high frequency in Hz. Ranges from 20.0 to 20000.0. Default is 5000.0. - public float hfReference { get; set; } + extern public float hfReference { get; set; } // Room effect low-frequency level in mB. Ranges from -10000.0 to 0.0. Default is 0.0. extern public float roomLF { get; set; } // Reference low-frequency in Hz. Ranges from 20.0 to 1000.0. Default is 250.0. - public float lfReference { get; set; } + extern public float lfReference { get; set; } } // Use this class to record to an [[AudioClip|audio clip]] using a connected microphone. diff --git a/Modules/BuildPipeline/Editor/Managed/ContentBuildInterface.bindings.cs b/Modules/BuildPipeline/Editor/Managed/ContentBuildInterface.bindings.cs index d86456989c..58351cd922 100644 --- a/Modules/BuildPipeline/Editor/Managed/ContentBuildInterface.bindings.cs +++ b/Modules/BuildPipeline/Editor/Managed/ContentBuildInterface.bindings.cs @@ -3,10 +3,12 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; -using System.IO; using UnityEditor.Build.Player; using UnityEngine.Bindings; +using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("Unity.ScriptableBuildPipeline.Editor")] + namespace UnityEditor.Build.Content { [NativeHeader("Modules/BuildPipeline/Editor/Public/ContentBuildTypes.h")] @@ -37,6 +39,8 @@ public static SceneDependencyInfo CalculatePlayerDependenciesForScene(string sce public static extern ObjectIdentifier[] GetPlayerObjectIdentifiersInAsset(GUID asset, BuildTarget target); + internal static extern ObjectIdentifier[] GetPlayerObjectIdentifiersInSerializedFile(string filePath, BuildTarget target); + public static extern ObjectIdentifier[] GetPlayerDependenciesForObject(ObjectIdentifier objectID, BuildTarget target, TypeDB typeDB); public static extern ObjectIdentifier[] GetPlayerDependenciesForObjects(ObjectIdentifier[] objectIDs, BuildTarget target, TypeDB typeDB); diff --git a/Modules/BuildPipeline/Editor/Managed/ResourceFile.cs b/Modules/BuildPipeline/Editor/Managed/ResourceFile.cs index abfa4a04cc..a4d54370ce 100644 --- a/Modules/BuildPipeline/Editor/Managed/ResourceFile.cs +++ b/Modules/BuildPipeline/Editor/Managed/ResourceFile.cs @@ -16,14 +16,14 @@ public struct ResourceFile { [NativeName("fileName")] internal string m_FileName; - public string fileName { get { return m_FileName; } } + public string fileName { get { return m_FileName; } set { m_FileName = value; } } [NativeName("fileAlias")] internal string m_FileAlias; - public string fileAlias { get { return m_FileAlias; } } + public string fileAlias { get { return m_FileAlias; } set { m_FileAlias = value; } } [NativeName("serializedFile")] internal bool m_SerializedFile; - public bool serializedFile { get { return m_SerializedFile; } } + public bool serializedFile { get { return m_SerializedFile; } set { m_SerializedFile = value; } } } } diff --git a/Modules/GraphViewEditor/EdgeControl.cs b/Modules/GraphViewEditor/EdgeControl.cs index ff11e04a71..dd8613e620 100644 --- a/Modules/GraphViewEditor/EdgeControl.cs +++ b/Modules/GraphViewEditor/EdgeControl.cs @@ -849,7 +849,7 @@ void DrawEdge(MeshGenerationContext mgc) float halfWidth = edgeWidth * 0.5f; float currentLength = 0; - float flags = (float)VertexFlags.LastType; + Color32 flags = new Color32(0, 0, 0, (byte)VertexFlags.LastType); Vector2 unitPreviousSegment = Vector2.zero; for (int i = 0; i < cpt; ++i) @@ -883,8 +883,8 @@ void DrawEdge(MeshGenerationContext mgc) Vector2 uv = new Vector2(dir.y * halfWidth, -dir.x * halfWidth); // Normal scaled by half width Color32 tint = Color.LerpUnclamped(outColor, inColor, currentLength / polyLineLength); - md.SetNextVertex(new Vertex() { position = new Vector3(pos.x, pos.y, 1), uv = uv, tint = tint, flags = flags }); - md.SetNextVertex(new Vertex() { position = new Vector3(pos.x, pos.y, -1), uv = uv, tint = tint, flags = flags }); + md.SetNextVertex(new Vertex() { position = new Vector3(pos.x, pos.y, 1), uv = uv, tint = tint, idsFlags = flags }); + md.SetNextVertex(new Vertex() { position = new Vector3(pos.x, pos.y, -1), uv = uv, tint = tint, idsFlags = flags }); if (i < cpt - 2) { @@ -904,13 +904,13 @@ void DrawEdge(MeshGenerationContext mgc) if ((i & 0x01) == 0) { md.SetNextIndex((UInt16)i); - md.SetNextIndex((UInt16)(i + 1)); md.SetNextIndex((UInt16)(i + 2)); + md.SetNextIndex((UInt16)(i + 1)); } else { - md.SetNextIndex((UInt16)(i + 1)); md.SetNextIndex((UInt16)i); + md.SetNextIndex((UInt16)(i + 1)); md.SetNextIndex((UInt16)(i + 2)); } } diff --git a/Modules/GraphViewEditor/Elements/MiniMap.cs b/Modules/GraphViewEditor/Elements/MiniMap.cs index 15d74fc1fa..b91eaa044c 100644 --- a/Modules/GraphViewEditor/Elements/MiniMap.cs +++ b/Modules/GraphViewEditor/Elements/MiniMap.cs @@ -213,8 +213,11 @@ void CalculateRects(VisualElement container) // Bring back viewport coordinates to (0,0), scale 1:1 m_ViewportRect.x += containerInvTranslation.x; m_ViewportRect.y += containerInvTranslation.y; - m_ViewportRect.x += graphView.worldBound.x * containerInvScale.x; - m_ViewportRect.y += graphView.worldBound.y * containerInvScale.y; + + var graphViewWB = graphView.worldBound; + + m_ViewportRect.x += graphViewWB.x * containerInvScale.x; + m_ViewportRect.y += graphViewWB.y * containerInvScale.y; m_ViewportRect.width *= containerInvScale.x; m_ViewportRect.height *= containerInvScale.y; diff --git a/Modules/GraphViewEditor/Elements/Scope.cs b/Modules/GraphViewEditor/Elements/Scope.cs index 1c6831edf8..bca117b3d8 100644 --- a/Modules/GraphViewEditor/Elements/Scope.cs +++ b/Modules/GraphViewEditor/Elements/Scope.cs @@ -248,7 +248,8 @@ void MarkChildrenDirtyRepaint(VisualElement parent) { parent.MarkDirtyRepaint(); - for (int i = 0; i < parent.hierarchy.childCount; ++i) + var childCount = parent.hierarchy.childCount; + for (int i = 0; i < childCount; ++i) { MarkChildrenDirtyRepaint(parent.hierarchy[i]); } @@ -266,13 +267,14 @@ void MarkBoundingBoxesDirty(VisualElement ve) MarkChildrenBoundingBoxesDirty(ve); } - void MarkChildrenBoundingBoxesDirty(VisualElement parent) + void MarkChildrenBoundingBoxesDirty(VisualElement element) { - parent.isBoundingBoxDirty = true; + element.isBoundingBoxDirty = true; - for (int i = 0; i < parent.hierarchy.childCount; ++i) + var count = element.hierarchy.childCount; + for (int i = 0; i < count; ++i) { - MarkChildrenBoundingBoxesDirty(parent.hierarchy[i]); + MarkChildrenBoundingBoxesDirty(element.hierarchy[i]); } } diff --git a/Modules/GraphViewEditor/Views/GraphView.cs b/Modules/GraphViewEditor/Views/GraphView.cs index dd158284be..56a637b861 100644 --- a/Modules/GraphViewEditor/Views/GraphView.cs +++ b/Modules/GraphViewEditor/Views/GraphView.cs @@ -283,9 +283,9 @@ protected GraphView() Font graphViewFont = EditorGUIUtility.LoadRequired("GraphView/DummyFont(LucidaGrande).ttf") as Font; if (Application.platform == RuntimePlatform.WindowsEditor) - graphViewFont.fontNames = new string[] { "Segoe UI", "Helvetica Neue", "Helvetica", "Arial", "Verdana" }; + graphViewFont.fontNames = new string[] { "Verdana" }; else if (Application.platform == RuntimePlatform.OSXEditor) - graphViewFont.fontNames = new string[] { "Helvetica Neue", "Lucida Grande" }; + graphViewFont.fontNames = new string[] { "Lucida Grande" }; m_FontsOverridden = true; } diff --git a/Modules/IMGUI/DrawStates.cs b/Modules/IMGUI/DrawStates.cs index 52d523241e..e25764035d 100644 --- a/Modules/IMGUI/DrawStates.cs +++ b/Modules/IMGUI/DrawStates.cs @@ -22,10 +22,8 @@ public DrawStates(int controlId, bool isHover, bool isActive, bool on, bool hasK hasTextInput = false; drawSelectionAsComposition = false; - cursorFirst = -1; - cursorLast = -1; - cursorColor = Color.red; - selectionColor = Color.red; + cursorFirst = cursorLast = -1; + selectionColor = cursorColor = Color.red; } public DrawStates(int controlId, bool isHover, bool isActive, bool on, bool hasKeyboardFocus, diff --git a/Modules/IMGUI/Event.bindings.cs b/Modules/IMGUI/Event.bindings.cs index 1711302709..98069dbbc0 100644 --- a/Modules/IMGUI/Event.bindings.cs +++ b/Modules/IMGUI/Event.bindings.cs @@ -55,7 +55,7 @@ public extern string commandName FreeFunction("GUIEvent::CopyFromPtr", IsThreadSafe = true, HasExplicitThis = true)] internal extern void CopyFromPtr(IntPtr ptr); - public static extern bool PopEvent(Event outEvent); + public static extern bool PopEvent([NotNull] Event outEvent); public static extern int GetEventCount(); private static extern void Internal_SetNativeEvent(IntPtr ptr); diff --git a/Modules/IMGUI/GUI.cs b/Modules/IMGUI/GUI.cs index 541c7001fe..3dac2fe99d 100644 --- a/Modules/IMGUI/GUI.cs +++ b/Modules/IMGUI/GUI.cs @@ -532,7 +532,7 @@ public static string PasswordField(Rect position, string password, char maskChar bool oldGUIChanged = GUI.changed; GUI.changed = false; - if (TouchScreenKeyboard.isSupported) + if (TouchScreenKeyboard.isSupported && !TouchScreenKeyboard.isInPlaceEditingAllowed) DoTextField(position, GUIUtility.GetControlID(FocusType.Keyboard), t, false, maxLength, style, password, maskChar); else DoTextField(position, GUIUtility.GetControlID(FocusType.Keyboard, position), t, false, maxLength, style); @@ -607,7 +607,7 @@ internal static void DoTextField(Rect position, int id, GUIContent content, bool editor.controlID = id; editor.DetectFocusChange(); - if (TouchScreenKeyboard.isSupported) + if (TouchScreenKeyboard.isSupported && !TouchScreenKeyboard.isInPlaceEditingAllowed) { HandleTextFieldEventForTouchscreen(position, id, content, multiline, maxLength, style, secureText, maskChar, editor); } @@ -1743,23 +1743,29 @@ public abstract class Scope : IDisposable { bool m_Disposed; - protected abstract void CloseScope(); + internal virtual void Dispose(bool disposing) + { + if (m_Disposed) + return; + if (disposing && !GUIUtility.guiIsExiting) + CloseScope(); + m_Disposed = true; + } + ~Scope() { - if (!m_Disposed) - // Can warn again because we have the ExitingGUI hint - Debug.LogError("Scope was not disposed! You should use the 'using' keyword or manually call Dispose."); - // ...but can't actually close scope because we can't do gui stuff from finalizer thread :-| + if (!m_Disposed && !GUIUtility.guiIsExiting) + Console.WriteLine($"{GetType().Name} was not disposed! You should use the 'using' keyword or manually call Dispose."); + Dispose(false); } public void Dispose() { - if (m_Disposed) - return; - m_Disposed = true; - if (!GUIUtility.guiIsExiting) - CloseScope(); + Dispose(true); + GC.SuppressFinalize(this); } + + protected abstract void CloseScope(); } public class GroupScope : Scope diff --git a/Modules/IMGUI/GUILayoutUtility.cs b/Modules/IMGUI/GUILayoutUtility.cs index f3cdee8a2f..e879344a3b 100644 --- a/Modules/IMGUI/GUILayoutUtility.cs +++ b/Modules/IMGUI/GUILayoutUtility.cs @@ -291,7 +291,7 @@ static void LayoutSingleGroup(GUILayoutGroup i) static GUILayoutGroup CreateGUILayoutGroupInstanceOfType(Type LayoutType) { if (!typeof(GUILayoutGroup).IsAssignableFrom(LayoutType)) - throw new ArgumentException("LayoutType needs to be of type GUILayoutGroup"); + throw new ArgumentException("LayoutType needs to be of type GUILayoutGroup", nameof(LayoutType)); return (GUILayoutGroup)Activator.CreateInstance(LayoutType); } @@ -312,7 +312,7 @@ internal static GUILayoutGroup BeginLayoutGroup(GUIStyle style, GUILayoutOption[ default: g = current.topLevel.GetNext() as GUILayoutGroup; if (g == null) - throw new ArgumentException("GUILayout: Mismatched LayoutGroup." + Event.current.type); + throw new ExitGUIException("GUILayout: Mismatched LayoutGroup." + Event.current.type); g.ResetCursor(); GUIDebugger.LogLayoutGroupEntry(g.rect, g.marginLeft, g.marginRight, g.marginTop, g.marginBottom, g.style, g.isVertical); break; @@ -358,7 +358,7 @@ internal static GUILayoutGroup BeginLayoutArea(GUIStyle style, Type layoutType) default: g = current.windows.GetNext() as GUILayoutGroup; if (g == null) - throw new ArgumentException("GUILayout: Mismatched LayoutGroup." + Event.current.type); + throw new ExitGUIException("GUILayout: Mismatched LayoutGroup." + Event.current.type); g.ResetCursor(); GUIDebugger.LogLayoutGroupEntry(g.rect, g.marginLeft, g.marginRight, g.marginTop, g.marginBottom, g.style, g.isVertical); break; diff --git a/Modules/IMGUI/GUIStyle.cs b/Modules/IMGUI/GUIStyle.cs index 2a3c86689b..3869ebc7ba 100644 --- a/Modules/IMGUI/GUIStyle.cs +++ b/Modules/IMGUI/GUIStyle.cs @@ -273,7 +273,7 @@ public void Draw(Rect position, GUIContent content, int controlID, bool on) public void Draw(Rect position, GUIContent content, int controlID, bool on, bool hover) { - Draw(position, content, controlID, hover, false, on, controlID == GUIUtility.keyboardControl); + Draw(position, content, controlID, hover, GUIUtility.hotControl == controlID, on, GUIUtility.HasKeyFocus(controlID)); } private void Draw(Rect position, GUIContent content, int controlId, bool isHover, bool isActive, bool on, bool hasKeyboardFocus) diff --git a/Modules/IMGUI/GUIUtility.cs b/Modules/IMGUI/GUIUtility.cs index 08268a5516..124d9bd742 100644 --- a/Modules/IMGUI/GUIUtility.cs +++ b/Modules/IMGUI/GUIUtility.cs @@ -22,6 +22,17 @@ namespace UnityEngine // *undocumented* public sealed class ExitGUIException : Exception { + public ExitGUIException() + { + GUIUtility.guiIsExiting = true; + } + + internal ExitGUIException(string message) + : base(message) + { + GUIUtility.guiIsExiting = true; + Console.WriteLine(message); + } } // Utility class for making new GUI controls. @@ -120,12 +131,17 @@ public static int keyboardControl } } + internal static Func s_HasCurrentWindowKeyFocusFunc; + + internal static bool HasKeyFocus(int controlID) + { + return controlID == GUIUtility.keyboardControl && + (s_HasCurrentWindowKeyFocusFunc != null ? s_HasCurrentWindowKeyFocusFunc() : true); + } + //*undocumented* public static void ExitGUI() { - // Hint for scope helpers - guiIsExiting = true; - // We have to always throw the ExitGUIException otherwise the exiting out of recursive on GUI will not work. throw new ExitGUIException(); } diff --git a/Modules/IMGUI/TextEditor.cs b/Modules/IMGUI/TextEditor.cs index 92650522ff..47f91f7e0e 100644 --- a/Modules/IMGUI/TextEditor.cs +++ b/Modules/IMGUI/TextEditor.cs @@ -56,7 +56,7 @@ public Rect position // Reset the scrollOffset to force its recomputation. scrollOffset = Vector2.zero; - m_Position = value; + m_Position = GUIUtility.AlignRectToDevice(value); UpdateScrollOffset(); } @@ -676,13 +676,15 @@ public void DblClickSnap(DblClickSnapping snapping) int GetGraphicalLineStart(int p) { Vector2 point = style.GetCursorPixelPosition(localPosition, m_Content, p); - point.x = 0; + point.y += 1.0f / GUIUtility.pixelsPerPoint; // we make sure no floating point errors can make us land on another line + point.x = localPosition.x; return style.GetCursorStringIndex(localPosition, m_Content, point); } int GetGraphicalLineEnd(int p) { Vector2 point = style.GetCursorPixelPosition(localPosition, m_Content, p); + point.y += 1.0f / GUIUtility.pixelsPerPoint; // we make sure no floating point errors can make us land on another line point.x += 5000; return style.GetCursorStringIndex(localPosition, m_Content, point); } diff --git a/Modules/PackageManager/Editor/Managed/PackageInfo.cs b/Modules/PackageManager/Editor/Managed/PackageInfo.cs index 85431e2687..2e83a8d7ef 100644 --- a/Modules/PackageManager/Editor/Managed/PackageInfo.cs +++ b/Modules/PackageManager/Editor/Managed/PackageInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Runtime.InteropServices; using UnityEngine; @@ -179,107 +180,54 @@ public static PackageInfo FindForAssembly(Assembly assembly) throw new ArgumentNullException("assembly"); string fullPath = assembly.Location; - string relativePath = GetRelativePathForAssemblyFilePath(fullPath); - if (!String.IsNullOrEmpty(relativePath)) - return FindForAssetPath(relativePath); - if (relativePath == null) - return null; - - // Path is outside the project dir - or possibly inside the project dir but local (e.g. in LocalPackages in the test project) - // Might be in the global package cache, might be a built-in engine module, etc. Do a scan through all packages for one that owns - // this directory. - foreach (var package in GetAll()) - { - if (fullPath.StartsWith(package.resolvedPath + System.IO.Path.DirectorySeparatorChar)) - return package; - } - - return null; - } - - private static string GetPathForPackageAssemblyName(string assemblyPath) - { - if (assemblyPath == null) - throw new ArgumentNullException("assemblyName"); - if (assemblyPath == string.Empty) - throw new ArgumentException("Assembly path cannot be empty.", "assemblyPath"); + // See if there is an asmdef file for this assembly - use it if so + var asmdefPath = CompilationPipeline.GetAssemblyDefinitionFilePathFromAssemblyName(fullPath); + if (!String.IsNullOrEmpty(asmdefPath)) + return FindForAssetPath(asmdefPath); - var assemblyName = FileUtil.UnityGetFileNameWithoutExtension(assemblyPath); - - var assets = AssetDatabase.FindAssets("a:packages " + assemblyName); - foreach (var guid in assets) + // No asmdef - this is a precompiled DLL. + // Do a scan through all packages for one that owns the directory in which it is. + foreach (var package in GetAll()) { - var path = AssetDatabase.GUIDToAssetPath(guid); - if (path.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) - return path; + if (fullPath.StartsWith(package.resolvedPath + Path.DirectorySeparatorChar)) + return package; } return null; } - private static string GetRelativePathForAssemblyFilePath(string fullPath) - { - if (fullPath == null) - throw new ArgumentNullException("fullPath"); - - if (fullPath.StartsWith(Environment.CurrentDirectory + System.IO.Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)) - { - // Path is inside the project dir - var relativePath = fullPath.Substring(Environment.CurrentDirectory.Length + 1).Replace('\\', '/'); - - // See if there is an asmdef file for this assembly - use it if so - var asmdefPath = CompilationPipeline.GetAssemblyDefinitionFilePathFromAssemblyName(fullPath); - if (asmdefPath != null) - relativePath = asmdefPath; - - // See if this is a prebuilt package assembly - use it if so - var packagePath = GetPathForPackageAssemblyName(relativePath); - if (packagePath != null) - relativePath = packagePath; - - // If we don't have a valid path, or it's inside the Assets folder, it's not part of a package - if (string.IsNullOrEmpty(relativePath) || relativePath.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase)) - return null; - - if (relativePath.StartsWith(Folders.GetPackagesPath() + "/", StringComparison.OrdinalIgnoreCase)) - return relativePath; - } - return String.Empty; - } - internal static List GetForAssemblyFilePaths(List assemblyPaths) { - // We will first get all the relative paths from assembly paths - Dictionary matchingRelativePaths = new Dictionary(); + // We will first get all the relative asmdef paths from assembly paths + var pathsToProcess = new HashSet(); foreach (string assemblyPath in assemblyPaths) { - string relativePath = GetRelativePathForAssemblyFilePath(assemblyPath); - if (relativePath != null) - matchingRelativePaths.Add(assemblyPath, relativePath); + // See if there is an asmdef file for this assembly - use it if so + var asmdefPath = CompilationPipeline.GetAssemblyDefinitionFilePathFromAssemblyName(assemblyPath); + pathsToProcess.Add(String.IsNullOrEmpty(asmdefPath) ? assemblyPath : asmdefPath); } - // We will loop thru all the packages and see if they match the relative paths + // We will loop through all the packages and see if they match the relative or absolute paths of asmdefs or assemblies List matchingPackages = new List(); foreach (var package in GetAll()) { - foreach (var item in matchingRelativePaths) + foreach (var path in pathsToProcess) { bool found; - string relativePath = item.Value; - if (!String.IsNullOrEmpty(relativePath)) - found = (relativePath == package.assetPath || relativePath.StartsWith(package.assetPath + '/')); + if (Path.IsPathRooted(path)) + found = path.StartsWith(package.resolvedPath + Path.DirectorySeparatorChar); else - found = item.Key.StartsWith(package.resolvedPath + System.IO.Path.DirectorySeparatorChar); + found = path.StartsWith(package.assetPath + '/'); if (found) { matchingPackages.Add(package); - matchingRelativePaths.Remove(item.Key); + pathsToProcess.Remove(path); break; } } - if (matchingRelativePaths.Count == 0) + if (pathsToProcess.Count == 0) break; } return matchingPackages; diff --git a/Modules/PackageManager/Editor/Managed/PackageManifestImporterEditor.cs b/Modules/PackageManager/Editor/Managed/PackageManifestImporterEditor.cs index 0b3e6d740d..f9cd04f298 100644 --- a/Modules/PackageManager/Editor/Managed/PackageManifestImporterEditor.cs +++ b/Modules/PackageManager/Editor/Managed/PackageManifestImporterEditor.cs @@ -18,7 +18,7 @@ namespace UnityEditor.PackageManager [CanEditMultipleObjects] internal class PackageManifestImporterEditor : AssetImporterEditor { - private enum PackageVisibility + enum PackageVisibility { DependsOnType, AlwaysHidden, @@ -53,20 +53,20 @@ private static List MajorUnityVersions private static readonly List MinorUnityVersions = new List { "1", "2", "3", "4" }; [Serializable] - private class AdvancedSettings + class AdvancedSettings { public PackageVisibility visibility; } [Serializable] - private class PackageDependency + class PackageDependency { public string packageName; public string version; } [Serializable] - private class PackageUnityVersion + class PackageUnityVersion { public bool isEnable; public string major; @@ -75,7 +75,7 @@ private class PackageUnityVersion } [Serializable] - private class PackageInformation + class PackageInformation { public string packageName; public string displayName; @@ -87,7 +87,7 @@ private class PackageInformation } [Serializable] - private class PackageManifestState : ScriptableObject + class PackageManifestState : ScriptableObject { public bool isValidFile; public PackageInformation info; diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreCache.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreCache.cs index f8531ddd65..1ac349eeb0 100644 --- a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreCache.cs +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreCache.cs @@ -17,14 +17,10 @@ internal sealed class AssetStoreCache public static IAssetStoreCache instance => s_Instance ?? AssetStoreCacheInternal.instance; [Serializable] - [FilePathAttribute("Asset Store/Cache/AssetStore.cache", FilePathAttribute.Location.AppDataFolder)] internal class AssetStoreCacheInternal : ScriptableSingleton, IAssetStoreCache, ISerializationCallbackReceiver { private Dictionary m_ProductETags = new Dictionary(); - [NonSerialized] - internal bool m_IsModified; - [SerializeField] private long[] m_SerializedIds = new long[0]; @@ -40,18 +36,7 @@ public void OnBeforeSerialize() public void OnAfterDeserialize() { for (var i = 0; i < m_SerializedIds.Length; i++) - { m_ProductETags[m_SerializedIds[i]] = m_SerializedETags[i]; - } - } - - private void OnDisable() - { - if (m_IsModified) - { - Save(true); - m_IsModified = false; - } } public string GetLastETag(long productId) @@ -61,12 +46,7 @@ public string GetLastETag(long productId) public void SetLastETag(long productId, string etag) { - var lastEtag = GetLastETag(productId); - if (etag != lastEtag) - { - m_ProductETags[productId] = etag; - m_IsModified = true; - } + m_ProductETags[productId] = etag; } public Texture2D LoadImage(long productId, string url) diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreClient.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreClient.cs index 03faf25f5c..2c29691c49 100644 --- a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreClient.cs +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreClient.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using UnityEngine; -using AssetStorePackageInfo = UnityEditor.PackageInfo; namespace UnityEditor.PackageManager.UI.AssetStore { @@ -21,6 +20,7 @@ internal class AssetStoreClientInternal : ScriptableSingleton> onPackagesChanged = delegate {}; + public event Action onPackageVersionUpdated = delegate {}; public event Action onDownloadProgress = delegate {}; public event Action onListOperationStart = delegate {}; @@ -35,57 +35,34 @@ internal class AssetStoreClientInternal : ScriptableSingleton m_Downloads = new Dictionary(); - private Dictionary m_UpdateDetails = new Dictionary(); + private Dictionary m_FetchedInfos = new Dictionary(); - private HashSet m_PackageDetailsFetched = new HashSet(); - - [SerializeField] - private string[] m_SerializedUpdateDetailKeys = new string[0]; - - [SerializeField] - private PackageState[] m_SerializedUpdateDetailValues = new PackageState[0]; + private Dictionary m_LocalInfos = new Dictionary(); [SerializeField] private DownloadProgress[] m_SerializedDownloads = new DownloadProgress[0]; [SerializeField] - private long[] m_SerializedPackageDetailsFetched; + private FetchedInfo[] m_SerializedFetchedInfos = new FetchedInfo[0]; [SerializeField] - private bool m_SetupDone; + private LocalInfo[] m_SerializedLocalInfos = new LocalInfo[0]; + + [NonSerialized] + private bool m_EventsRegistered; public void OnAfterDeserialize() { - m_Downloads.Clear(); - foreach (var p in m_SerializedDownloads) - { - m_Downloads[p.packageId] = p; - } - - m_UpdateDetails.Clear(); - for (var i = 0; i < m_SerializedUpdateDetailKeys.Length; i++) - { - m_UpdateDetails[m_SerializedUpdateDetailKeys[i]] = m_SerializedUpdateDetailValues[i]; - } - - m_PackageDetailsFetched = new HashSet(m_SerializedPackageDetailsFetched); + m_Downloads = m_SerializedDownloads.ToDictionary(d => d.packageId, d => d); + m_FetchedInfos = m_SerializedFetchedInfos.ToDictionary(info => info.id, info => info); + m_LocalInfos = m_SerializedLocalInfos.ToDictionary(info => info.id, info => info); } public void OnBeforeSerialize() { m_SerializedDownloads = m_Downloads.Values.ToArray(); - - m_SerializedUpdateDetailKeys = new string[m_UpdateDetails.Count]; - m_SerializedUpdateDetailValues = new PackageState[m_UpdateDetails.Count]; - var i = 0; - foreach (var kp in m_UpdateDetails) - { - m_SerializedUpdateDetailKeys[i] = kp.Key; - m_SerializedUpdateDetailValues[i] = kp.Value; - i++; - } - - m_SerializedPackageDetailsFetched = m_PackageDetailsFetched.ToArray(); + m_SerializedFetchedInfos = m_FetchedInfos.Values.ToArray(); + m_SerializedLocalInfos = m_LocalInfos.Values.ToArray(); } public void Fetch(long productId) @@ -96,25 +73,19 @@ public void Fetch(long productId) return; } + RefreshLocalInfos(); + var id = productId.ToString(); - var localPackages = GetLocalPackages(); - if (localPackages.ContainsKey(id)) - RefreshProductUpdateDetails(new Dictionary { { id, localPackages[id] } }, () => { FetchInternal(localPackages, productId); }); - else - FetchInternal(localPackages, productId); - } + var localInfo = m_LocalInfos.Get(id); + if (localInfo?.updateInfoFetched == false) + RefreshProductUpdateDetails(new[] { localInfo }); - private void FetchInternal(IDictionary localPackages, long productID) - { // create a placeholder before fetching data from the cloud for the first time - if (!m_PackageDetailsFetched.Contains(productID)) - { - onPackagesChanged?.Invoke(new[] { new PlaceholderPackage(productID.ToString(), PackageTag.AssetStore) }); - } - - FetchDetailsInternal(new[] { productID }, localPackages); + if (!m_FetchedInfos.ContainsKey(productId.ToString())) + onPackagesChanged?.Invoke(new[] { new PlaceholderPackage(productId.ToString(), PackageType.AssetStore) }); - onProductFetched?.Invoke(productID); + FetchDetails(new[] { productId }); + onProductFetched?.Invoke(productId); } public void List(int offset, int limit, string searchText = "", bool fetchDetails = true) @@ -127,15 +98,11 @@ public void List(int offset, int limit, string searchText = "", bool fetchDetail onListOperationStart?.Invoke(); - var localPackages = GetLocalPackages(); + RefreshLocalInfos(); + if (offset == 0) - RefreshProductUpdateDetails(localPackages, () => { ListInternal(localPackages, offset, limit, searchText, fetchDetails); }); - else - ListInternal(localPackages, offset, limit, searchText, fetchDetails); - } + RefreshProductUpdateDetails(); - private void ListInternal(IDictionary localPackages, int offset, int limit, string searchText, bool fetchDetails) - { AssetStoreRestAPI.instance.GetProductIDList(offset, limit, searchText, productList => { if (!productList.isValid) @@ -161,11 +128,11 @@ private void ListInternal(IDictionary localPackag var placeholderPackages = new List(); - foreach (var product in productList.list) + foreach (var productId in productList.list) { // create a placeholder before fetching data from the cloud for the first time - if (!m_PackageDetailsFetched.Contains(product)) - placeholderPackages.Add(new PlaceholderPackage(product.ToString(), PackageTag.AssetStore)); + if (!m_FetchedInfos.ContainsKey(productId.ToString())) + placeholderPackages.Add(new PlaceholderPackage(productId.ToString(), PackageType.AssetStore, PackageTag.None, PackageProgress.Refreshing)); } if (placeholderPackages.Any()) @@ -174,16 +141,11 @@ private void ListInternal(IDictionary localPackag onListOperationFinish?.Invoke(); if (fetchDetails) - FetchDetailsInternal(productList.list, localPackages); + FetchDetails(productList.list); }); } public void FetchDetails(IEnumerable packageIds) - { - FetchDetailsInternal(packageIds, GetLocalPackages()); - } - - private void FetchDetailsInternal(IEnumerable packageIds, IDictionary localPackages) { var countProduct = packageIds.Count(); if (countProduct == 0) @@ -195,54 +157,31 @@ private void FetchDetailsInternal(IEnumerable packageIds, IDictionary { - AssetStorePackage package; - object error; - if (!productDetail.TryGetValue("errorMessage", out error)) + AssetStorePackage package = null; + var error = productDetail.GetString("errorMessage"); + if (string.IsNullOrEmpty(error)) { - AssetStorePackageInfo localPackage; - if (localPackages.TryGetValue(id.ToString(), out localPackage)) - { - productDetail["localPath"] = localPackage.packagePath; - } + var fetchedInfo = FetchedInfo.ParseFetchedInfo(id.ToString(), productDetail); + if (fetchedInfo == null) + package = new AssetStorePackage(id.ToString(), new Error(NativeErrorCode.Unknown, "Error parsing product details.")); else { - productDetail["localPath"] = string.Empty; - } - - package = new AssetStorePackage(id.ToString(), productDetail); - if (m_UpdateDetails.ContainsKey(package.uniqueId)) - { - package.SetState(m_UpdateDetails[package.uniqueId]); - } - - if (package.state == PackageState.Outdated && !string.IsNullOrEmpty(localPackage.packagePath)) - { - package.m_FetchedVersion.localPath = string.Empty; - - try - { - var info = new AssetStorePackageVersion.SpecificVersionInfo(); - var item = Json.Deserialize(localPackage.jsonInfo) as Dictionary; - info.versionId = item["version_id"] as string; - info.versionString = item["version"] as string; - info.publishedDate = item["pubdate"] as string; - info.supportedVersion = item["unity_version"] as string; - - var installedVersion = new AssetStorePackageVersion(id.ToString(), productDetail, info); - installedVersion.localPath = localPackage.packagePath; - - package.AddVersion(installedVersion); - } - catch (Exception) + var oldFetchedInfo = m_FetchedInfos.Get(fetchedInfo.id); + if (oldFetchedInfo == null || oldFetchedInfo.versionId != fetchedInfo.versionId || oldFetchedInfo.versionString != fetchedInfo.versionString) { + if (string.IsNullOrEmpty(fetchedInfo.packageName)) + package = new AssetStorePackage(fetchedInfo, m_LocalInfos.Get(fetchedInfo.id)); + else + UpmClient.instance.FetchForProduct(fetchedInfo.id, fetchedInfo.packageName); + m_FetchedInfos[fetchedInfo.id] = fetchedInfo; } } - m_PackageDetailsFetched.Add(id); } else - package = new AssetStorePackage(id.ToString(), new Error(NativeErrorCode.Unknown, error as string)); + package = new AssetStorePackage(id.ToString(), new Error(NativeErrorCode.Unknown, error)); - onPackagesChanged?.Invoke(new[] { package }); + if (package != null) + onPackagesChanged?.Invoke(new[] { package }); countProduct--; if (countProduct == 0) @@ -251,178 +190,12 @@ private void FetchDetailsInternal(IEnumerable packageIds, IDictionary packages) - { - if (packages == null || !packages.Any() || !ApplicationUtil.instance.isUserLoggedIn) - return; - - var localInfos = new Dictionary(); - var localPackages = AssetStoreUtils.instance.GetLocalPackageList(); - foreach (var p in localPackages) - { - if (!string.IsNullOrEmpty(p.jsonInfo)) - { - var item = Json.Deserialize(p.jsonInfo) as Dictionary; - if (item != null && item.ContainsKey("id") && item["id"] is string) - { - localInfos[(string)item["id"]] = new AssetStorePackageVersion.SpecificVersionInfo - { - packagePath = p.packagePath, - versionString = item.ContainsKey("version") && item["version"] is string? (string)item["version"] : string.Empty, - versionId = item.ContainsKey("version_id") && item["version_id"] is string? (string)item["version_id"] : string.Empty, - publishedDate = item.ContainsKey("pubdate") && item["pubdate"] is string? (string)item["pubdate"] : string.Empty, - supportedVersion = item.ContainsKey("unity_version") && item["unity_version"] is string? (string)item["unity_version"] : string.Empty - }; - } - } - } - - var updatedPackages = new List(); - foreach (var package in packages) - { - var assetStorePackage = package as AssetStorePackage; - if (assetStorePackage == null) - continue; - - AssetStorePackageVersion.SpecificVersionInfo localInfo; - localInfos.TryGetValue(assetStorePackage.uniqueId, out localInfo); - - var packageChanged = false; - if (localInfo == null) - { - if (assetStorePackage.installedVersion != null) - { - assetStorePackage.m_FetchedVersion.localPath = string.Empty; - if (assetStorePackage.m_FetchedVersion != assetStorePackage.m_LocalVersion) - { - assetStorePackage.RemoveVersion(assetStorePackage.m_LocalVersion); - } - - assetStorePackage.SetState(PackageState.UpToDate); - packageChanged = true; - } - } - else if (assetStorePackage.installedVersion == null || localInfo.versionString != assetStorePackage.installedVersion.versionString) - { - if (assetStorePackage.m_FetchedVersion.versionString == localInfo.versionString) - { - assetStorePackage.m_FetchedVersion.localPath = localInfo.packagePath; - if (assetStorePackage.m_LocalVersion != assetStorePackage.m_FetchedVersion) - { - assetStorePackage.RemoveVersion(assetStorePackage.m_LocalVersion); - } - - assetStorePackage.SetState(PackageState.UpToDate); - } - else if (assetStorePackage.m_LocalVersion.versionString == localInfo.versionString) - { - assetStorePackage.m_FetchedVersion.localPath = string.Empty; - assetStorePackage.m_LocalVersion.localPath = localInfo.packagePath; - assetStorePackage.SetState(PackageState.Outdated); - } - else - { - assetStorePackage.m_FetchedVersion.localPath = string.Empty; - assetStorePackage.AddVersion(new AssetStorePackageVersion(assetStorePackage.m_FetchedVersion, localInfo)); - assetStorePackage.m_LocalVersion.localPath = localInfo.packagePath; - assetStorePackage.SetState(PackageState.Outdated); - } - - packageChanged = true; - } - - if (packageChanged) - { - if (m_UpdateDetails.ContainsKey(assetStorePackage.uniqueId)) - m_UpdateDetails[assetStorePackage.uniqueId] = assetStorePackage.state; - - updatedPackages.Add(package); - } - } - - if (updatedPackages.Any()) - onPackagesChanged?.Invoke(updatedPackages); - } - - public void Refresh(IPackage package) + public void RefreshLocal() { if (!ApplicationUtil.instance.isUserLoggedIn) return; - var assetStorePackage = package as AssetStorePackage; - if (assetStorePackage == null) - return; - - AssetStorePackageVersion.SpecificVersionInfo localInfo = null; - var localPackage = AssetStoreUtils.instance.GetLocalPackageList().FirstOrDefault(p => - { - if (!string.IsNullOrEmpty(p.jsonInfo)) - { - var item = Json.Deserialize(p.jsonInfo) as Dictionary; - if (item != null && item.ContainsKey("id") && item["id"] is string && package.uniqueId == (string)item["id"]) - { - localInfo = new AssetStorePackageVersion.SpecificVersionInfo - { - versionString = item.ContainsKey("version") && item["version"] is string? (string)item["version"] : string.Empty, - versionId = item.ContainsKey("version_id") && item["version_id"] is string? (string)item["version_id"] : string.Empty, - publishedDate = item.ContainsKey("pubdate") && item["pubdate"] is string? (string)item["pubdate"] : string.Empty, - supportedVersion = item.ContainsKey("unity_version") && item["unity_version"] is string? (string)item["unity_version"] : string.Empty - }; - return true; - } - } - return false; - }); - - var packageChanged = false; - if (localInfo == null) - { - if (assetStorePackage.installedVersion != null) - { - assetStorePackage.m_FetchedVersion.localPath = string.Empty; - if (assetStorePackage.m_FetchedVersion != assetStorePackage.m_LocalVersion) - { - assetStorePackage.RemoveVersion(assetStorePackage.m_LocalVersion); - } - - assetStorePackage.SetState(PackageState.UpToDate); - packageChanged = true; - } - } - else if (assetStorePackage.installedVersion == null || localInfo.versionString != assetStorePackage.installedVersion.versionString) - { - if (assetStorePackage.m_FetchedVersion.versionString == localInfo.versionString) - { - assetStorePackage.m_FetchedVersion.localPath = localPackage.packagePath; - if (assetStorePackage.m_LocalVersion != assetStorePackage.m_FetchedVersion) - { - assetStorePackage.RemoveVersion(assetStorePackage.m_LocalVersion); - } - assetStorePackage.SetState(PackageState.UpToDate); - } - else if (assetStorePackage.m_LocalVersion.versionString == localInfo.versionString) - { - assetStorePackage.m_FetchedVersion.localPath = string.Empty; - assetStorePackage.m_LocalVersion.localPath = localPackage.packagePath; - assetStorePackage.SetState(PackageState.Outdated); - } - else - { - assetStorePackage.m_FetchedVersion.localPath = string.Empty; - assetStorePackage.AddVersion(new AssetStorePackageVersion(assetStorePackage.m_FetchedVersion, localInfo)); - assetStorePackage.m_LocalVersion.localPath = localPackage.packagePath; - assetStorePackage.SetState(PackageState.Outdated); - } - - packageChanged = true; - } - - if (packageChanged) - { - if (m_UpdateDetails.ContainsKey(assetStorePackage.uniqueId)) - m_UpdateDetails[assetStorePackage.uniqueId] = assetStorePackage.state; - onPackagesChanged?.Invoke(new[] { package }); - } + RefreshLocalInfos(); } public bool IsAnyDownloadInProgress() @@ -540,31 +313,76 @@ public void OnDownloadProgress(string packageId, string message, ulong bytes, ul onDownloadProgress?.Invoke(progress); } - public void Setup() + private void OnProductPackageChanged(string productId, IPackage package) + { + var fetchedInfo = m_FetchedInfos.Get(productId); + if (fetchedInfo != null) + { + var assetStorePackage = new AssetStorePackage(fetchedInfo, package as UpmPackage); + onPackagesChanged?.Invoke(new[] { assetStorePackage }); + } + } + + private void OnProductPackageVersionUpdated(string productId, IPackageVersion version) { - System.Diagnostics.Debug.Assert(!m_SetupDone); - m_SetupDone = true; + var upmVersion = version as UpmPackageVersion; + var fetchedInfo = m_FetchedInfos.Get(productId); + if (upmVersion != null && fetchedInfo != null) + upmVersion.UpdateFetchedInfo(fetchedInfo); + onPackageVersionUpdated?.Invoke(productId, version); + } + + private void OnProductPackageFetchError(string productId, Error error) + { + var fetchedInfo = m_FetchedInfos.Get(productId); + if (fetchedInfo != null) + { + var assetStorePackage = new AssetStorePackage(fetchedInfo); + var assetStorePackageVersion = assetStorePackage.versionList.primary as AssetStorePackageVersion; + assetStorePackageVersion.SetUpmPackageFetchError(error); + onPackagesChanged?.Invoke(new[] { assetStorePackage }); + } + } + + public void RegisterEvents() + { + if (m_EventsRegistered) + return; + + m_EventsRegistered = true; ApplicationUtil.instance.onUserLoginStateChange += OnUserLoginStateChange; if (ApplicationUtil.instance.isUserLoggedIn) { AssetStoreUtils.instance.RegisterDownloadDelegate(this); } + + UpmClient.instance.onProductPackageChanged += OnProductPackageChanged; + UpmClient.instance.onProductPackageVersionUpdated += OnProductPackageVersionUpdated; + UpmClient.instance.onProductPackageFetchError += OnProductPackageFetchError; } - public void Clear() + public void UnregisterEvents() { - System.Diagnostics.Debug.Assert(m_SetupDone); - m_SetupDone = false; + if (!m_EventsRegistered) + return; + + m_EventsRegistered = false; AssetStoreUtils.instance.UnRegisterDownloadDelegate(this); ApplicationUtil.instance.onUserLoginStateChange -= OnUserLoginStateChange; + UpmClient.instance.onProductPackageChanged -= OnProductPackageChanged; + UpmClient.instance.onProductPackageVersionUpdated -= OnProductPackageVersionUpdated; + UpmClient.instance.onProductPackageFetchError -= OnProductPackageFetchError; } - public void Reset() + public void ClearCache() { - m_UpdateDetails.Clear(); - m_PackageDetailsFetched.Clear(); + m_LocalInfos.Clear(); + m_FetchedInfos.Clear(); + + m_SerializedLocalInfos = new LocalInfo[0]; + m_SerializedFetchedInfos = new FetchedInfo[0]; } private void OnUserLoginStateChange(bool loggedIn) @@ -573,6 +391,8 @@ private void OnUserLoginStateChange(bool loggedIn) { AssetStoreUtils.instance.UnRegisterDownloadDelegate(this); AbortAllDownloads(); + ClearCache(); + UpmClient.instance.ClearProductCache(); } else { @@ -590,50 +410,89 @@ public void AbortAllDownloads() AssetStoreDownloadOperation.instance.AbortDownloadPackageAsync(download); } - private void RefreshProductUpdateDetails(IDictionary localPackages, Action doneCallbackAction) + public void RefreshProductUpdateDetails(IEnumerable localInfos = null) { - var needsUpdateDetail = localPackages.Where(kp => m_UpdateDetails[kp.Key] == PackageState.UpToDate); - if (!needsUpdateDetail.Any()) - { - doneCallbackAction?.Invoke(); - } - else + localInfos = localInfos ?? m_LocalInfos.Values.Where(info => !info.updateInfoFetched); + if (!localInfos.Any()) + return; + + AssetStoreRestAPI.instance.GetProductUpdateDetail(localInfos, updateDetails => { - var list = needsUpdateDetail.Select(kp => kp.Value).ToList(); - AssetStoreRestAPI.instance.GetProductUpdateDetail(list, updateDetails => + if (updateDetails.ContainsKey("errorMessage")) + return; + + var results = updateDetails.GetList>("results"); + if (results == null) + return; + + foreach (var updateDetail in results) { - object error; - if (!updateDetails.TryGetValue("errorMessage", out error)) + var id = updateDetail.GetString("id"); + var localInfo = m_LocalInfos.Get(id); + if (localInfo != null) { - var results = updateDetails["results"] as List; - foreach (var item in results) + localInfo.updateInfoFetched = true; + var newValue = updateDetail.Get("can_update", 0L) != 0L; + if (localInfo.canUpdate != newValue) { - var updateDetail = item as IDictionary; - var canUpdate = (updateDetail["can_update"] is long? (long)updateDetail["can_update"] : 0) != 0; - m_UpdateDetails[updateDetail["id"] as string] = canUpdate ? PackageState.Outdated : PackageState.UpToDate; + localInfo.canUpdate = newValue; + OnLocalInfoChanged(localInfo); } } - - doneCallbackAction?.Invoke(); - }); - } + } + }); } - private IDictionary GetLocalPackages() + private void RefreshLocalInfos() { - var localPackages = new Dictionary(); - foreach (var package in AssetStoreUtils.instance.GetLocalPackageList()) + var infos = AssetStoreUtils.instance.GetLocalPackageList(); + var oldLocalInfos = m_LocalInfos; + m_LocalInfos = new Dictionary(); + foreach (var info in infos) { - var item = Json.Deserialize(package.jsonInfo) as Dictionary; - if (item != null && item.ContainsKey("id") && item["id"] is string) + var parsedInfo = LocalInfo.ParseLocalInfo(info); + var id = parsedInfo?.id; + if (string.IsNullOrEmpty(id)) + continue; + + var oldInfo = oldLocalInfos.Get(id); + if (oldInfo != null) { - var packageId = (string)item["id"]; - localPackages[packageId] = package; - if (!m_UpdateDetails.ContainsKey(packageId)) - m_UpdateDetails[packageId] = PackageState.UpToDate; + oldLocalInfos.Remove(oldInfo.id); + + if (oldInfo.versionId == parsedInfo.versionId && + oldInfo.versionString == parsedInfo.versionString && + oldInfo.packagePath == parsedInfo.packagePath) + { + m_LocalInfos[id] = oldInfo; + continue; + } } + + m_LocalInfos[id] = parsedInfo; + OnLocalInfoChanged(parsedInfo); } - return localPackages; + + foreach (var info in oldLocalInfos.Values) + OnLocalInfoRemoved(info); + } + + private void OnLocalInfoChanged(LocalInfo localInfo) + { + var fetchedInfo = m_FetchedInfos.Get(localInfo.id); + if (fetchedInfo == null) + return; + var package = new AssetStorePackage(fetchedInfo, localInfo); + onPackagesChanged?.Invoke(new[] { package }); + } + + private void OnLocalInfoRemoved(LocalInfo localInfo) + { + var fetchedInfo = m_FetchedInfos.Get(localInfo.id); + if (fetchedInfo == null) + return; + var package = new AssetStorePackage(fetchedInfo, (LocalInfo)null); + onPackagesChanged?.Invoke(new[] { package }); } } } diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreFetchedInfo.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreFetchedInfo.cs new file mode 100644 index 0000000000..7cc1d375c7 --- /dev/null +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreFetchedInfo.cs @@ -0,0 +1,220 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace UnityEditor.PackageManager.UI.AssetStore +{ + [Serializable] + internal class FetchedInfo + { + public string id; + public string packageName; + public string description; + public string author; + public string publisherId; + public string category; + public string versionString; + public string versionId; + public string publishedDate; + public string displayName; + public string state; + + public List supportedVersions; + public List images; + public List links; + public List sizeInfos; + + public static FetchedInfo ParseFetchedInfo(string productId, IDictionary productDetail) + { + if (string.IsNullOrEmpty(productId) || productDetail == null || !productDetail.Any()) + return null; + return new FetchedInfo(productId, productDetail); + } + + private FetchedInfo(string productId, IDictionary productDetail) + { + id = productId; + description = CleanUpHtml(productDetail.GetString("description")) ?? string.Empty; + + var publisher = productDetail.GetDictionary("productPublisher"); + if (publisher != null) + { + if (publisher.GetString("url") == "http://unity3d.com") + author = "Unity Technologies Inc."; + else + author = publisher.GetString("name") ?? L10n.Tr("Unknown publisher"); + publisherId = publisher.GetString("externalRef") ?? string.Empty; + } + else + { + author = string.Empty; + publisherId = string.Empty; + } + + packageName = productDetail.GetString("packageName") ?? string.Empty; + category = productDetail.GetDictionary("category")?.GetString("name") ?? string.Empty; + + var versionInfo = productDetail.GetDictionary("version"); + if (versionInfo != null) + { + versionString = versionInfo.GetString("name"); + versionId = versionInfo.GetString("id"); + publishedDate = versionInfo.GetString("publishedDate"); + } + + displayName = productDetail.GetString("displayName"); + + supportedVersions = productDetail.GetList("supportedUnityVersions")?.ToList(); + + state = productDetail.GetString("state"); + + images = GetImagesFromProductDetails(productDetail); + links = GetLinksFromProductDetails(productDetail); + sizeInfos = GetSizeInfoFromProductDetails(productDetail); + } + + private static string CleanUpHtml(string source) + { + if (string.IsNullOrEmpty(source)) + return source; + + source = source.Replace("
", "\n"); + + var array = new char[source.Length]; + var arrayIndex = 0; + var inside = false; + + foreach (var c in source.ToCharArray()) + { + if (c == '<') + inside = true; + else if (c == '>') + inside = false; + else + { + if (!inside) + array[arrayIndex++] = c; + } + } + + var text = new string(array, 0, arrayIndex); + text = Regex.Replace(text, @"&#x?\d+;", ""); + text = text.Replace(" ", " "); + text = text.Replace("<", "<"); + text = text.Replace(">", ">"); + text = text.Replace("&", "&"); + text = text.Replace(""", "\""); + text = text.Replace("'", "'"); + text = Regex.Replace(text, @"[\n\r]+", "\n"); + text = text.Trim(' ', '\r', '\n', '\t'); + + return text; + } + + private static List GetImagesFromProductDetails(IDictionary productDetail) + { + var result = new List(); + var mainImageThumbnailUrl = productDetail.GetDictionary("mainImage")?.GetString("url"); + if (!string.IsNullOrEmpty(mainImageThumbnailUrl)) + { + mainImageThumbnailUrl = mainImageThumbnailUrl.Replace("//d2ujflorbtfzji.cloudfront.net/", "//assetstorev1-prd-cdn.unity3d.com/"); + result.Add(new PackageImage + { + type = PackageImage.ImageType.Main, + thumbnailUrl = "http:" + mainImageThumbnailUrl, + url = string.Empty + }); + } + + var images = productDetail.GetList>("images") ?? Enumerable.Empty>(); + foreach (var image in images) + { + var type = image?.GetString("type"); + if (string.IsNullOrEmpty(type)) + continue; + + var imageType = PackageImage.ImageType.Screenshot; + var thumbnailUrl = image.GetString("thumbnailUrl"); + thumbnailUrl = thumbnailUrl.Replace("//d2ujflorbtfzji.cloudfront.net/", "//assetstorev1-prd-cdn.unity3d.com/"); + + if (type == "sketchfab") + imageType = PackageImage.ImageType.Sketchfab; + else if (type == "youtube") + imageType = PackageImage.ImageType.Youtube; + + var imageUrl = image.GetString("imageUrl"); + if (imageType == PackageImage.ImageType.Screenshot) + imageUrl = "http:" + imageUrl; + + result.Add(new PackageImage + { + type = imageType, + thumbnailUrl = "http:" + thumbnailUrl, + url = imageUrl + }); + } + return result; + } + + private static List GetLinksFromProductDetails(IDictionary productDetail) + { + var result = new List(); + + var slug = productDetail.GetString("slug") ?? productDetail.GetString("id"); + result.Add(GetPackageLink("View in the Asset Store", $"/packages/p/{slug}")); + + var publisher = productDetail.GetDictionary("productPublisher"); + if (publisher != null) + { + var url = publisher.GetString("url"); + if (!string.IsNullOrEmpty(url) && Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute)) + result.Add(GetPackageLink("Publisher Website", url)); + + var supportUrl = publisher.GetString("supportUrl"); + if (!string.IsNullOrEmpty(supportUrl) && Uri.IsWellFormedUriString(supportUrl, UriKind.RelativeOrAbsolute)) + result.Add(GetPackageLink("Publisher Support", supportUrl)); + } + return result; + } + + private static List GetSizeInfoFromProductDetails(IDictionary productDetail) + { + var result = new List(); + var uploads = productDetail.GetDictionary("uploads"); + if (uploads != null) + { + foreach (var key in uploads.Keys) + { + var simpleVersion = Regex.Replace(key, @"(?\d+)\.(?\d+).(?\d+)[abfp].+", "${major}.${minor}.${patch}"); + SemVersion version; + if (SemVersion.TryParse(simpleVersion.Trim(), out version)) + { + var info = uploads.GetDictionary(key); + var assetCount = info.GetString("assetCount") ?? string.Empty; + var downloadSize = info.GetString("downloadSize") ?? string.Empty; + + result.Add(new PackageSizeInfo + { + supportedUnityVersion = version, + assetCount = string.IsNullOrEmpty(assetCount) ? 0 : ulong.Parse(assetCount), + downloadSize = string.IsNullOrEmpty(downloadSize) ? 0 : ulong.Parse(downloadSize) + }); + } + } + } + return result; + } + + private static PackageLink GetPackageLink(string name, string url) + { + if (!url.StartsWith("http:", StringComparison.InvariantCulture) && !url.StartsWith("https:", StringComparison.InvariantCulture)) + url = AssetStoreUtils.instance.assetStoreUrl + url; + return new PackageLink { name = name, url = url }; + } + } +} diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreLocalInfo.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreLocalInfo.cs new file mode 100644 index 0000000000..9123aff8c9 --- /dev/null +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreLocalInfo.cs @@ -0,0 +1,53 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; + +namespace UnityEditor.PackageManager.UI.AssetStore +{ + [Serializable] + internal class LocalInfo + { + public string id; + public string versionString; + public string versionId; + public string publishedDate; + public string supportedVersion; + public string packagePath; + + public bool updateInfoFetched; + public bool canUpdate; + + public static LocalInfo ParseLocalInfo(UnityEditor.PackageInfo localInfo) + { + if (string.IsNullOrEmpty(localInfo.jsonInfo)) + return null; + + try + { + var jsonInfo = Json.Deserialize(localInfo.jsonInfo) as Dictionary; + var id = jsonInfo?.GetString("id"); + if (string.IsNullOrEmpty(id)) + return null; + + return new LocalInfo + { + id = id, + packagePath = localInfo.packagePath ?? string.Empty, + versionString = jsonInfo.GetString("version") ?? string.Empty, + versionId = jsonInfo.GetString("version_id") ?? string.Empty, + publishedDate = jsonInfo.GetString("pubdate") ?? string.Empty, + supportedVersion = jsonInfo.GetString("unity_version") ?? string.Empty, + updateInfoFetched = false, + canUpdate = false + }; + } + catch (Exception) + { + return null; + } + } + } +} diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreOAuth.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreOAuth.cs index 6647877b61..235f9e1bf4 100644 --- a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreOAuth.cs +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreOAuth.cs @@ -91,8 +91,6 @@ public bool isValid private class AssetStoreOAuthInternal : ScriptableSingleton, IAssetStoreOAuth { - private string m_Host = ""; - private string m_Secret = ""; private const string kOAuthUri = "/v1/oauth2/token"; private const string kTokenInfoUri = "/v1/oauth2/tokeninfo"; private const string kUserInfoUri = "/v1/users"; @@ -107,6 +105,17 @@ private class AssetStoreOAuthInternal : ScriptableSingleton> m_DoneCallbackList; + private string m_Host; + private string host + { + get + { + if (string.IsNullOrEmpty(m_Host)) + m_Host = UnityConnect.instance.GetConfigurationURL(CloudConfigUrl.CloudIdentity); + return m_Host; + } + } + private AssetStoreOAuthInternal() { m_UserInfo = new UserInfo(); @@ -117,16 +126,6 @@ private AssetStoreOAuthInternal() public void OnEnable() { - if (string.IsNullOrEmpty(m_Secret)) - { - m_Secret = UnityConnect.instance.GetConfigurationURL(CloudConfigUrl.CloudPackagesKey); - } - - if (string.IsNullOrEmpty(m_Host)) - { - m_Host = UnityConnect.instance.GetConfigurationURL(CloudConfigUrl.CloudIdentity); - } - if (ApplicationUtil.instance.isUserLoggedIn) GetAuthCode(); @@ -233,46 +232,59 @@ private void GetAccessToken() return; // a request is already running, no need to recall } - m_AccessTokenRequest = m_AsyncHTTPClient.GetASyncHTTPClient($"{m_Host}{kOAuthUri}", "POST"); - m_AccessTokenRequest.postData = $"grant_type=authorization_code&code={m_UserInfo.authCode}&client_id=packman&client_secret={m_Secret}&redirect_uri=packman://unity"; + + var secret = UnityConnect.instance.GetConfigurationURL(CloudConfigUrl.CloudPackagesKey); + + m_AccessTokenRequest = m_AsyncHTTPClient.GetASyncHTTPClient($"{host}{kOAuthUri}", "POST"); + m_AccessTokenRequest.postData = $"grant_type=authorization_code&code={m_UserInfo.authCode}&client_id=packman&client_secret={secret}&redirect_uri=packman://unity"; m_AccessTokenRequest.header["Content-Type"] = "application/x-www-form-urlencoded"; m_AccessTokenRequest.doneCallback = httpClient => { + m_AccessTokenRequest = null; + m_UserInfo.accessToken = null; if (httpClient.IsSuccess()) { - var res = Json.Deserialize(httpClient.text) as Dictionary; - if (res != null) + try { - var accessTokenResponse = new AccessToken(); - accessTokenResponse.access_token = res["access_token"] as string; - accessTokenResponse.token_type = res["token_type"] as string; - accessTokenResponse.expires_in = res["expires_in"] as string; - accessTokenResponse.refresh_token = res["refresh_token"] as string; - accessTokenResponse.user = res["user"] as string; - accessTokenResponse.display_name = res["display_name"] as string; - m_UserInfo.accessToken = accessTokenResponse; - if (m_UserInfo.accessToken.IsValid()) - GetTokenInfo(); - else + var res = Json.Deserialize(httpClient.text) as Dictionary; + if (res != null) { + var accessTokenResponse = new AccessToken(); + accessTokenResponse.access_token = res["access_token"] as string; + accessTokenResponse.token_type = res["token_type"] as string; + accessTokenResponse.expires_in = res["expires_in"] as string; + accessTokenResponse.refresh_token = res["refresh_token"] as string; + accessTokenResponse.user = res["user"] as string; + accessTokenResponse.display_name = res["display_name"] as string; + m_UserInfo.accessToken = accessTokenResponse; + if (m_UserInfo.accessToken.IsValid()) + { + GetTokenInfo(); + return; + } + m_UserInfo.errorMessage = "Access token invalid"; - OnDoneFetchUserInfo(); + } + else + { + m_UserInfo.errorMessage = "Failed to parse JSON."; } } - else + catch (Exception e) { - m_UserInfo.errorMessage = "Failed to parse JSON."; - m_UserInfo.accessToken = null; - OnDoneFetchUserInfo(); + m_UserInfo.errorMessage = $"Failed to parse JSON: {e.Message}"; } } else { - m_UserInfo.errorMessage = httpClient.text; - m_UserInfo.accessToken = null; - OnDoneFetchUserInfo(); + // At this point if the request return a 400, this mean the AuthCode is not valid anymore, we should restart + // the process from the beginning + m_UserInfo.authCode = string.Empty; + GetAuthCode(); + return; } - m_AccessTokenRequest = null; + + OnDoneFetchUserInfo(); }; m_AccessTokenRequest.Begin(); } @@ -295,44 +307,51 @@ private void GetTokenInfo() // a request is already running, no need to recall } - m_TokenRequest = m_AsyncHTTPClient.GetASyncHTTPClient($"{m_Host}{kTokenInfoUri}?access_token={m_UserInfo.accessToken.access_token}"); + m_TokenRequest = m_AsyncHTTPClient.GetASyncHTTPClient($"{host}{kTokenInfoUri}?access_token={m_UserInfo.accessToken.access_token}"); m_TokenRequest.doneCallback = httpClient => { + m_TokenRequest = null; + m_UserInfo.tokenInfo = null; + if (httpClient.IsSuccess()) { - var res = Json.Deserialize(httpClient.text) as Dictionary; - if (res != null) + try { - var tokenInfo = new TokenInfo(); - tokenInfo.sub = res["sub"] as string; - tokenInfo.scopes = res["scopes"] as string; - tokenInfo.expires_in = res["expires_in"] as string; - tokenInfo.client_id = res["client_id"] as string; - tokenInfo.ip_address = res["ip_address"] as string; - tokenInfo.access_token = res["access_token"] as string; - m_UserInfo.tokenInfo = tokenInfo; - if (m_UserInfo.tokenInfo.IsValid()) - GetUserInfo(); - else + var res = Json.Deserialize(httpClient.text) as Dictionary; + if (res != null) { + var tokenInfo = new TokenInfo(); + tokenInfo.sub = res["sub"] as string; + tokenInfo.scopes = res["scopes"] as string; + tokenInfo.expires_in = res["expires_in"] as string; + tokenInfo.client_id = res["client_id"] as string; + tokenInfo.ip_address = res["ip_address"] as string; + tokenInfo.access_token = res["access_token"] as string; + m_UserInfo.tokenInfo = tokenInfo; + if (m_UserInfo.tokenInfo.IsValid()) + { + GetUserInfo(); + return; + } + m_UserInfo.errorMessage = "TokenInfo invalid"; - OnDoneFetchUserInfo(); + } + else + { + m_UserInfo.errorMessage = "Failed to parse JSON."; } } - else + catch (Exception e) { - m_UserInfo.errorMessage = "Failed to parse JSON."; - m_UserInfo.tokenInfo = null; - OnDoneFetchUserInfo(); + m_UserInfo.errorMessage = $"Failed to parse JSON: {e.Message}"; } } else { m_UserInfo.errorMessage = httpClient.text; - m_UserInfo.tokenInfo = null; - OnDoneFetchUserInfo(); } - m_TokenRequest = null; + + OnDoneFetchUserInfo(); }; m_TokenRequest.Begin(); } @@ -355,37 +374,42 @@ private void GetUserInfo() // a request is already running, no need to recall } - m_UserInfoRequest = m_AsyncHTTPClient.GetASyncHTTPClient($"{m_Host}{kUserInfoUri}/{m_UserInfo.tokenInfo.sub}"); + m_UserInfoRequest = m_AsyncHTTPClient.GetASyncHTTPClient($"{host}{kUserInfoUri}/{m_UserInfo.tokenInfo.sub}"); m_UserInfoRequest.header["Authorization"] = "Bearer " + m_UserInfo.accessToken.access_token; m_UserInfoRequest.doneCallback = httpClient => { + m_UserInfoRequest = null; + m_UserInfo.isValid = false; + if (httpClient.IsSuccess()) { - var res = Json.Deserialize(httpClient.text) as Dictionary; - if (res != null) + try { - m_UserInfo.id = res["id"] as string; - m_UserInfo.username = res["username"] as string; - var extended = res["extendedProperties"] as Dictionary; - m_UserInfo.defaultOrganization = extended["UNITY_DEFAULT_ORGANIZATION"] as string; - m_UserInfo.isValid = true; - m_UserInfo.errorMessage = ""; - OnDoneFetchUserInfo(); + var res = Json.Deserialize(httpClient.text) as Dictionary; + if (res != null) + { + m_UserInfo.id = res["id"] as string; + m_UserInfo.username = res["username"] as string; + var extended = res["extendedProperties"] as Dictionary; + m_UserInfo.defaultOrganization = extended["UNITY_DEFAULT_ORGANIZATION"] as string; + m_UserInfo.isValid = true; + m_UserInfo.errorMessage = ""; + } + else + { + m_UserInfo.errorMessage = "Failed to parse JSON."; + } } - else + catch (Exception e) { - m_UserInfo.isValid = false; - m_UserInfo.errorMessage = "Failed to parse JSON."; - OnDoneFetchUserInfo(); + m_UserInfo.errorMessage = $"Failed to parse JSON: {e.Message}";; } } else { - m_UserInfo.isValid = false; m_UserInfo.errorMessage = httpClient.text; - OnDoneFetchUserInfo(); } - m_UserInfoRequest = null; + OnDoneFetchUserInfo(); }; m_UserInfoRequest.Begin(); } diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStorePackage.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStorePackage.cs index b4c71e64f8..f431e86e37 100644 --- a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStorePackage.cs +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStorePackage.cs @@ -13,88 +13,139 @@ namespace UnityEditor.PackageManager.UI.AssetStore internal class AssetStorePackage : IPackage { [SerializeField] - private string m_ProductId; + private string m_Name; + public string name => m_Name; - public string name => string.Empty; + [SerializeField] + private string m_ProductId; public string uniqueId => m_ProductId; - public string displayName => m_Versions.FirstOrDefault()?.displayName; + public string displayName => versions.FirstOrDefault()?.displayName; [SerializeField] - private List m_Versions; + private AssetStoreVersionList m_VersionList; - internal AssetStorePackageVersion m_InstalledVersion => m_Versions.FirstOrDefault(pv => pv.isAvailableOnDisk); - internal AssetStorePackageVersion m_FetchedVersion => m_Versions.FirstOrDefault(); - internal AssetStorePackageVersion m_LocalVersion => m_Versions.LastOrDefault(); + [SerializeField] + private UpmVersionList m_UpmVersionList; - public IEnumerable versions => m_Versions.Cast(); - public IEnumerable keyVersions => m_Versions.Cast(); - public IPackageVersion installedVersion => m_InstalledVersion; - public IPackageVersion latestVersion => m_FetchedVersion; - public IPackageVersion latestPatch => latestVersion; - public IPackageVersion recommendedVersion => latestVersion; - public IPackageVersion primaryVersion => installedVersion ?? latestVersion; + public IVersionList versionList => string.IsNullOrEmpty(name) ? m_VersionList as IVersionList : m_UpmVersionList as IVersionList; [SerializeField] - private PackageState m_State; - public PackageState state => m_State; - - public void SetState(PackageState state) - { - m_State = state; - } + private PackageProgress m_Progress; + public PackageProgress progress => m_Progress; public bool isDiscoverable => true; [SerializeField] private List m_Errors; - public IEnumerable errors => m_Errors; + // Combined errors for this package or any version. + // Stop lookup after first error encountered on a version to save time not looking up redundant errors. + public IEnumerable errors => (versions.Select(v => v.errors).FirstOrDefault(e => e?.Any() ?? false) ?? new List()).Concat(m_Errors); - public void AddError(Error error) + [SerializeField] + private PackageType m_Type; + public bool Is(PackageType type) { - m_Errors?.Add(error); + return (m_Type & type) != 0; } - public void ClearErrors() - { - m_Errors?.Clear(); - } + [SerializeField] + private List m_Images; + [SerializeField] + private List m_Links; + + public IEnumerable images => m_Images; + + public IEnumerable links => m_Links; + + public IEnumerable versions => versionList?.all; - public void AddVersion(AssetStorePackageVersion version) + public IEnumerable keyVersions => versionList?.key; + + public IPackageVersion installedVersion => versionList?.installed; + + public IPackageVersion latestVersion => versionList?.latest; + + public IPackageVersion latestPatch => versionList?.latestPatch; + + public IPackageVersion recommendedVersion => versionList?.recommended; + + public IPackageVersion primaryVersion => versionList?.primary; + + public void AddError(Error error) { - m_Versions.Add(version); + m_Errors?.Add(error); } - public void RemoveVersion(AssetStorePackageVersion version) + public void ClearErrors() { - m_Versions.Remove(version); + m_Errors?.Clear(); } public AssetStorePackage(string productId, Error error) { m_Errors = new List { error }; - m_State = PackageState.Error; + m_Progress = PackageProgress.None; + m_Type = PackageType.AssetStore; + m_Name = string.Empty; m_ProductId = productId; - m_Versions = new List(); + + m_Images = new List(); + m_Links = new List(); + m_VersionList = new AssetStoreVersionList(); + m_UpmVersionList = new UpmVersionList(); } - public AssetStorePackage(string productId, IDictionary productDetail) + public AssetStorePackage(FetchedInfo fetchedInfo, LocalInfo localInfo = null) { m_Errors = new List(); - m_State = PackageState.UpToDate; - m_ProductId = productId; - m_Versions = new List(); - try + m_Progress = PackageProgress.None; + m_Type = PackageType.AssetStore; + m_Name = string.Empty; + m_ProductId = fetchedInfo?.id.ToString(); + m_Images = fetchedInfo?.images ?? new List(); + m_Links = fetchedInfo?.links ?? new List(); + m_VersionList = new AssetStoreVersionList(); + m_UpmVersionList = new UpmVersionList(); + + if (string.IsNullOrEmpty(fetchedInfo?.id) || string.IsNullOrEmpty(fetchedInfo?.versionId)) + { + AddError(new Error(NativeErrorCode.Unknown, "Invalid product details.")); + } + else if (localInfo == null) { - m_Versions.Add(new AssetStorePackageVersion(productId, productDetail)); + m_VersionList.AddVersion(new AssetStorePackageVersion(fetchedInfo)); } - catch (Exception e) + else { - m_Errors.Add(new Error(NativeErrorCode.Unknown, e.Message)); - m_State = PackageState.Error; + m_VersionList.AddVersion(new AssetStorePackageVersion(fetchedInfo, localInfo)); + if (localInfo.canUpdate && (localInfo.versionId != fetchedInfo.versionId || localInfo.versionString != fetchedInfo.versionString)) + m_VersionList.AddVersion(new AssetStorePackageVersion(fetchedInfo)); } } + public AssetStorePackage(FetchedInfo fetchedInfo, UpmPackage package) + { + m_Errors = new List(); + m_Progress = PackageProgress.None; + m_Type = PackageType.AssetStore; + m_Name = package?.name ?? string.Empty; + m_ProductId = fetchedInfo?.id.ToString(); + + m_Images = fetchedInfo?.images ?? new List(); + m_Links = fetchedInfo?.links ?? new List(); + m_VersionList = new AssetStoreVersionList(); + + m_UpmVersionList = package?.versionList as UpmVersionList ?? new UpmVersionList(); + foreach (var version in m_UpmVersionList.all.Cast()) + version.UpdateFetchedInfo(fetchedInfo); + + if (string.IsNullOrEmpty(fetchedInfo?.id) || string.IsNullOrEmpty(fetchedInfo?.versionId)) + AddError(new Error(NativeErrorCode.Unknown, "Invalid product details.")); + else if (string.IsNullOrEmpty(package?.name)) + AddError(new Error(NativeErrorCode.Unknown, "Invalid package info.")); + } + public IPackage Clone() { return (IPackage)MemberwiseClone(); diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStorePackageVersion.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStorePackageVersion.cs index fdd7c3d4d7..bdd0782e14 100644 --- a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStorePackageVersion.cs +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStorePackageVersion.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Text.RegularExpressions; @@ -14,22 +15,11 @@ namespace UnityEditor.PackageManager.UI.AssetStore [Serializable] internal class AssetStorePackageVersion : IPackageVersion { - public class SpecificVersionInfo - { - public string versionString; - public string versionId; - public string publishedDate; - public string supportedVersion; - public string packagePath; - } - [SerializeField] private string m_PackageUniqueId; [SerializeField] private string m_DisplayName; [SerializeField] - private string m_Type; - [SerializeField] private string m_Author; [SerializeField] private string m_Description; @@ -40,7 +30,7 @@ public class SpecificVersionInfo [SerializeField] private SemVersion m_Version; [SerializeField] - private DateTime m_PublishedDate; + private long m_PublishedDateTicks; [SerializeField] private string m_PublisherId; [SerializeField] @@ -56,32 +46,48 @@ public class SpecificVersionInfo [SerializeField] private SemVersion m_SupportedUnityVersion; [SerializeField] - private List m_Images; - [SerializeField] private List m_SizeInfos; [SerializeField] - private List m_Links; - [SerializeField] private PackageTag m_Tag; public string name => string.Empty; public string displayName => m_DisplayName; - public string type => m_Type; - public string author => m_Author; + public string authorLink => $"{AssetStoreUtils.instance.assetStoreUrl}/publishers/{publisherId}"; + public string description => m_Description; public string category => m_Category; + private Dictionary m_CategoryLinks; + public IDictionary categoryLinks + { + get + { + if (m_CategoryLinks == null) + { + m_CategoryLinks = new Dictionary(); + var categories = m_Category.Split('/'); + var parentCategory = "/"; + foreach (var category in categories) + { + var lower = category.ToLower(CultureInfo.InvariantCulture); + var url = $"{AssetStoreUtils.instance.assetStoreUrl}{parentCategory}{lower}"; + parentCategory += lower + "/"; + m_CategoryLinks[category] = url; + } + } + return m_CategoryLinks; + } + } + public string packageUniqueId => m_PackageUniqueId; public string uniqueId => m_VersionId; - public PackageSource source => PackageSource.Unknown; - public IEnumerable errors => m_Errors; public IEnumerable samples => Enumerable.Empty(); @@ -94,7 +100,7 @@ public SemVersion version set { m_Version = value; } } - public DateTime? publishedDate => m_PublishedDate; + public DateTime? publishedDate => m_PublishedDateTicks == 0 ? (DateTime?)null : new DateTime(m_PublishedDateTicks, DateTimeKind.Utc); public string publisherId => m_PublisherId; @@ -108,8 +114,6 @@ public SemVersion version public bool isFullyFetched => true; - public bool isUserVisible => true; - public bool isAvailableOnDisk => m_IsAvailableOnDisk; public bool isVersionLocked => true; @@ -146,314 +150,75 @@ public string versionId public IEnumerable supportedVersions => m_SupportedUnityVersions; - public IEnumerable images => m_Images; - public IEnumerable sizes => m_SizeInfos; - public IEnumerable links => m_Links; - public bool HasTag(PackageTag tag) { return (m_Tag & tag) == tag; } - public AssetStorePackageVersion(AssetStorePackageVersion other, SpecificVersionInfo localInfo = null) + public AssetStorePackageVersion(FetchedInfo fetchedInfo, LocalInfo localInfo = null) { - m_PackageUniqueId = other.m_PackageUniqueId; - m_DisplayName = other.m_DisplayName; - m_Type = other.m_Type; - m_Author = other.m_Author; - m_Description = other.m_Description; - m_Category = other.m_Category; - m_Errors = other.m_Errors; - m_Version = other.m_Version; - m_PublishedDate = other.m_PublishedDate; - m_PublisherId = other.m_PublisherId; - m_IsAvailableOnDisk = other.m_IsAvailableOnDisk; - m_LocalPath = other.m_LocalPath; - m_VersionString = other.m_VersionString; - m_VersionId = other.m_VersionId; - m_SupportedUnityVersions = other.m_SupportedUnityVersions; - m_SupportedUnityVersion = other.m_SupportedUnityVersion; - m_Images = other.m_Images; - m_SizeInfos = other.m_SizeInfos; - m_Links = other.m_Links; - m_Tag = other.m_Tag; - - if (localInfo != null) - { - m_VersionString = localInfo.versionString; - m_VersionId = localInfo.versionId; - - SemVersion semVer; - if (!SemVersion.TryParse(m_VersionString.Trim(), out semVer)) - { - semVer = new SemVersion(0); - } - m_Version = semVer; - - m_PublishedDate = DateTime.Parse(localInfo.publishedDate); - - var simpleVersion = Regex.Replace(localInfo.supportedVersion, @"(?\d+)\.(?\d+).(?\d+)[abfp].+", "${major}.${minor}.${patch}"); - SemVersion.TryParse(simpleVersion.Trim(), out m_SupportedUnityVersion); - } - } - - public AssetStorePackageVersion(string productId, IDictionary productDetail, SpecificVersionInfo localInfo = null) - { - if (productDetail == null) - { - throw new ArgumentNullException(nameof(productDetail)); - } + if (fetchedInfo == null) + throw new ArgumentNullException(nameof(fetchedInfo)); m_Errors = new List(); - m_Type = "assetstore"; - m_Tag = PackageTag.AssetStore; - m_PackageUniqueId = productId; - - try - { - var description = productDetail.ContainsKey("description") ? productDetail["description"] as string : string.Empty; - m_Description = CleanUpHtml(description); - - var publisher = new Dictionary(); - if (productDetail.ContainsKey("productPublisher")) - { - publisher = productDetail["productPublisher"] as Dictionary; - if (publisher.ContainsKey("url") && publisher["url"] is string && (string)publisher["url"] == "http://unity3d.com") - m_Author = "Unity Technologies Inc."; - else - m_Author = publisher.ContainsKey("name") ? publisher["name"] as string : L10n.Tr("Unknown publisher"); - - m_PublisherId = publisher.ContainsKey("externalRef") ? publisher["externalRef"] as string : string.Empty; - } - else - { - m_Author = string.Empty; - m_PublisherId = string.Empty; - } + m_Tag = PackageTag.Downloadable | PackageTag.Importable; + m_PackageUniqueId = fetchedInfo.id.ToString(); - m_Category = string.Empty; - if (productDetail.ContainsKey("category")) - { - var categoryInfo = productDetail["category"] as IDictionary; - m_Category = categoryInfo["name"] as string; - } + m_Description = fetchedInfo.description; + m_Author = fetchedInfo.author; + m_PublisherId = fetchedInfo.publisherId; - if (localInfo != null) - { - m_VersionString = localInfo.versionString; - m_VersionId = localInfo.versionId; + m_Category = fetchedInfo.category; - SemVersion semVer; - if (!SemVersion.TryParse(m_VersionString.Trim(), out semVer)) - { - semVer = new SemVersion(0); - } - m_Version = semVer; + m_VersionString = localInfo?.versionString ?? fetchedInfo.versionString ?? string.Empty; + m_VersionId = localInfo?.versionId ?? fetchedInfo.versionId ?? string.Empty; + m_Version = SemVersion.TryParse(m_VersionString.Trim(), out m_Version) ? m_Version : new SemVersion(0); - m_PublishedDate = DateTime.Parse(localInfo.publishedDate); - } - else if (productDetail.ContainsKey("version")) - { - var versionInfo = productDetail["version"] as IDictionary; - m_VersionString = versionInfo["name"] as string; - m_VersionId = versionInfo["id"] as string; - SemVersion semVer; - if (!SemVersion.TryParse(m_VersionString.Trim(), out semVer)) - { - semVer = new SemVersion(0); - } - m_Version = semVer; + var publishDateString = localInfo?.publishedDate ?? fetchedInfo.publishedDate ?? string.Empty; + m_PublishedDateTicks = !string.IsNullOrEmpty(publishDateString) ? DateTime.Parse(publishDateString).Ticks : 0; + m_DisplayName = !string.IsNullOrEmpty(fetchedInfo.displayName) ? fetchedInfo.displayName : $"Package {m_PackageUniqueId}@{m_VersionId}"; - if (versionInfo.ContainsKey("publishedDate")) - { - var date = versionInfo["publishedDate"] as string; - m_PublishedDate = DateTime.Parse(date); - } - else - { - m_PublishedDate = new DateTime(); - } - } - else - { - m_VersionString = string.Empty; - m_VersionId = string.Empty; - m_Version = new SemVersion(0); - } - - m_DisplayName = productDetail.ContainsKey("displayName") ? productDetail["displayName"] as string : $"Package {m_PackageUniqueId}@{m_VersionId}"; - - m_SupportedUnityVersions = new List(); - if (productDetail.ContainsKey("supportedUnityVersions")) - { - var supportedVersions = productDetail["supportedUnityVersions"] as IList; - foreach (var supportedVersion in supportedVersions.Where(v => v is string)) - { - SemVersion version; - if (SemVersion.TryParse(supportedVersion as string, out version)) - m_SupportedUnityVersions.Add(version); - } - - m_SupportedUnityVersions.Sort((left, right) => left.CompareByPrecedence(right)); - } - - if (localInfo != null) - { - var simpleVersion = Regex.Replace(localInfo.supportedVersion, @"(?\d+)\.(?\d+).(?\d+)[abfp].+", "${major}.${minor}.${patch}"); - SemVersion.TryParse(simpleVersion.Trim(), out m_SupportedUnityVersion); - } - else - { - m_SupportedUnityVersion = m_SupportedUnityVersions.LastOrDefault(); - } - - m_Images = new List(); - if (productDetail.ContainsKey("mainImage")) - { - var mainImage = productDetail["mainImage"] as IDictionary; - var thumbnailUrl = mainImage["url"] as string; - thumbnailUrl = thumbnailUrl.Replace("//d2ujflorbtfzji.cloudfront.net/", "//assetstorev1-prd-cdn.unity3d.com/"); - m_Images.Add(new PackageImage - { - type = PackageImage.ImageType.Main, - thumbnailUrl = "http:" + thumbnailUrl, - url = string.Empty - }); - } - - if (productDetail.ContainsKey("images")) - { - var images = productDetail["images"] as IList; - foreach (var image in images) - { - var imageInfo = image as IDictionary; - var type = imageInfo["type"] as string; - if (string.IsNullOrEmpty(type)) - continue; - - var imageType = PackageImage.ImageType.Screenshot; - var thumbnailUrl = imageInfo["thumbnailUrl"] as string; - thumbnailUrl = thumbnailUrl.Replace("//d2ujflorbtfzji.cloudfront.net/", "//assetstorev1-prd-cdn.unity3d.com/"); - - if (type == "sketchfab") - imageType = PackageImage.ImageType.Sketchfab; - else if (type == "youtube") - imageType = PackageImage.ImageType.Youtube; - - var imageUrl = imageInfo["imageUrl"] as string; - if (imageType == PackageImage.ImageType.Screenshot) - imageUrl = "http:" + imageUrl; - - m_Images.Add(new PackageImage - { - type = imageType, - thumbnailUrl = "http:" + thumbnailUrl, - url = imageUrl - }); - } - } - - m_SizeInfos = new List(); - if (productDetail.ContainsKey("uploads")) - { - var uploads = productDetail["uploads"] as IDictionary; - foreach (var key in uploads.Keys) - { - var simpleVersion = Regex.Replace(key, @"(?\d+)\.(?\d+).(?\d+)[abfp].+", "${major}.${minor}.${patch}"); - SemVersion version; - if (SemVersion.TryParse(simpleVersion.Trim(), out version)) - { - var info = uploads[key] as IDictionary; - var assetCount = info["assetCount"] as string; - var downloadSize = info["downloadSize"] as string; - - m_SizeInfos.Add(new PackageSizeInfo - { - supportedUnityVersion = version, - assetCount = string.IsNullOrEmpty(assetCount) ? 0 : ulong.Parse(assetCount), - downloadSize = string.IsNullOrEmpty(downloadSize) ? 0 : ulong.Parse(downloadSize) - }); - } - } - - m_SizeInfos.Sort((left, right) => left.supportedUnityVersion.CompareByPrecedence(right.supportedUnityVersion)); - } - - m_Links = new List(); - - var slug = productDetail.ContainsKey("slug") ? productDetail["slug"] as string : m_PackageUniqueId; - m_Links.Add(new PackageLink {name = "View in the Asset Store", url = $"/packages/p/{slug}"}); - - if (publisher.ContainsKey("url")) - { - var url = publisher["url"] as string; - if (!string.IsNullOrEmpty(url) && Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute)) - m_Links.Add(new PackageLink {name = "Publisher Web Site", url = url}); - } - - if (publisher.ContainsKey("supportUrl")) - { - var url = publisher["supportUrl"] as string; - if (!string.IsNullOrEmpty(url) && Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute)) - m_Links.Add(new PackageLink {name = "Publisher Support", url = url}); - } - - if (productDetail.ContainsKey("state")) + m_SupportedUnityVersions = new List(); + if (fetchedInfo.supportedVersions?.Any() ?? false) + { + foreach (var supportedVersion in fetchedInfo.supportedVersions) { - var state = productDetail["state"] as string; - if (state.Equals("published", StringComparison.InvariantCultureIgnoreCase)) - m_Tag |= PackageTag.Published; - else if (state.Equals("deprecated", StringComparison.InvariantCultureIgnoreCase)) - m_Tag |= PackageTag.Deprecated; + SemVersion version; + if (SemVersion.TryParse(supportedVersion as string, out version)) + m_SupportedUnityVersions.Add(version); } + m_SupportedUnityVersions.Sort((left, right) => left.CompareByPrecedence(right)); + } - m_LocalPath = productDetail.ContainsKey("localPath") ? productDetail["localPath"] as string : string.Empty; - m_IsAvailableOnDisk = !string.IsNullOrEmpty(m_LocalPath) && File.Exists(m_LocalPath); + if (localInfo != null) + { + var simpleVersion = Regex.Replace(localInfo.supportedVersion, @"(?\d+)\.(?\d+).(?\d+)[abfp].+", "${major}.${minor}.${patch}"); + SemVersion.TryParse(simpleVersion.Trim(), out m_SupportedUnityVersion); } - catch (Exception e) + else { - m_Errors.Add(new Error(NativeErrorCode.Unknown, e.Message)); + m_SupportedUnityVersion = m_SupportedUnityVersions.LastOrDefault(); } - } - - private static string CleanUpHtml(string source) - { - if (string.IsNullOrEmpty(source)) - return source; - source = source.Replace("
", "\n"); + m_SizeInfos = new List(fetchedInfo.sizeInfos); + m_SizeInfos.Sort((left, right) => left.supportedUnityVersion.CompareByPrecedence(right.supportedUnityVersion)); - var array = new char[source.Length]; - var arrayIndex = 0; - var inside = false; + var state = fetchedInfo.state ?? string.Empty; + if (state.Equals("published", StringComparison.InvariantCultureIgnoreCase)) + m_Tag |= PackageTag.Published; + else if (state.Equals("deprecated", StringComparison.InvariantCultureIgnoreCase)) + m_Tag |= PackageTag.Deprecated; - foreach (var c in source.ToCharArray()) - { - if (c == '<') - inside = true; - else if (c == '>') - inside = false; - else - { - if (!inside) - array[arrayIndex++] = c; - } - } + m_LocalPath = localInfo?.packagePath ?? string.Empty; + m_IsAvailableOnDisk = !string.IsNullOrEmpty(m_LocalPath) && File.Exists(m_LocalPath); + } - var text = new string(array, 0, arrayIndex); - text = Regex.Replace(text, @"&#x?\d+;", ""); - text = text.Replace(" ", " "); - text = text.Replace("<", "<"); - text = text.Replace(">", ">"); - text = text.Replace("&", "&"); - text = text.Replace(""", "\""); - text = text.Replace("'", "'"); - text = Regex.Replace(text, @"[\n\r]+", "\n"); - text = text.Trim(' ', '\r', '\n', '\t'); - - return text; + public void SetUpmPackageFetchError(Error error) + { + m_Errors.Add(error); + m_Tag &= ~(PackageTag.Downloadable | PackageTag.Importable); } } } diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreRestAPI.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreRestAPI.cs index c06a5b252a..f16f38f44f 100644 --- a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreRestAPI.cs +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreRestAPI.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Linq; using UnityEditor.Connect; namespace UnityEditor.PackageManager.UI.AssetStore @@ -18,7 +19,6 @@ internal class AssetStoreRestAPIInternal : IAssetStoreRestAPI private static AssetStoreRestAPIInternal s_Instance; public static AssetStoreRestAPIInternal instance => s_Instance ?? (s_Instance = new AssetStoreRestAPIInternal()); - private string m_Host = ""; private const int kDefaultLimit = 100; private const string kListUri = "/-/api/purchases"; private const string kDetailUri = "/-/api/product"; @@ -27,10 +27,20 @@ internal class AssetStoreRestAPIInternal : IAssetStoreRestAPI private IASyncHTTPClientFactory m_AsyncHTTPClient; + private string m_Host; + private string host + { + get + { + if (string.IsNullOrEmpty(m_Host)) + m_Host = UnityConnect.instance.GetConfigurationURL(CloudConfigUrl.CloudPackagesApi); + return m_Host; + } + } + private AssetStoreRestAPIInternal() { m_AsyncHTTPClient = new ASyncHTTPClientFactory(); - m_Host = UnityConnect.instance.GetConfigurationURL(CloudConfigUrl.CloudPackagesApi); } public void GetProductIDList(int startIndex, int limit, string searchText, Action doneCallbackAction) @@ -55,7 +65,7 @@ public void GetProductIDList(int startIndex, int limit, string searchText, Actio limit = limit > 0 ? limit : kDefaultLimit; searchText = string.IsNullOrEmpty(searchText) ? "" : searchText; - var httpRequest = m_AsyncHTTPClient.GetASyncHTTPClient($"{m_Host}{kListUri}?offset={startIndex}&limit={limit}&query={System.Uri.EscapeDataString(searchText)}"); + var httpRequest = m_AsyncHTTPClient.GetASyncHTTPClient($"{host}{kListUri}?offset={startIndex}&limit={limit}&query={System.Uri.EscapeDataString(searchText)}"); httpRequest.header["Authorization"] = "Bearer " + userInfo.accessToken.access_token; httpRequest.doneCallback = httpClient => { @@ -118,7 +128,7 @@ public void GetProductDetail(long productID, Action> return; } - var httpRequest = m_AsyncHTTPClient.GetASyncHTTPClient($"{m_Host}{kDetailUri}/{productID}"); + var httpRequest = m_AsyncHTTPClient.GetASyncHTTPClient($"{host}{kDetailUri}/{productID}"); httpRequest.header["Authorization"] = "Bearer " + userInfo.accessToken.access_token; var etag = AssetStoreCache.instance.GetLastETag(productID); @@ -178,7 +188,7 @@ public void GetDownloadDetail(long productID, Action doneCa return; } - var httpRequest = m_AsyncHTTPClient.GetASyncHTTPClient($"{m_Host}{kDownloadUri}/{productID}"); + var httpRequest = m_AsyncHTTPClient.GetASyncHTTPClient($"{host}{kDownloadUri}/{productID}"); httpRequest.header["Content-Type"] = "application/json"; httpRequest.header["Authorization"] = "Bearer " + userInfo.accessToken.access_token; httpRequest.doneCallback = httpClient => @@ -219,7 +229,7 @@ public void GetDownloadDetail(long productID, Action doneCa }); } - public void GetProductUpdateDetail(List localPackages, Action> doneCallbackAction) + public void GetProductUpdateDetail(IEnumerable localInfos, Action> doneCallbackAction) { AssetStoreOAuth.instance.FetchUserInfo(userInfo => { @@ -231,7 +241,7 @@ public void GetProductUpdateDetail(List localPackages, return; } - if (localPackages?.Count == 0) + if (localInfos == null || !localInfos.Any()) { doneCallbackAction?.Invoke(new Dictionary()); return; @@ -239,40 +249,19 @@ public void GetProductUpdateDetail(List localPackages, var packageList = new List>(); - foreach (var product in localPackages) + foreach (var info in localInfos) { var dictData = new Dictionary(); - dictData["local_path"] = product.packagePath; - - try - { - var localPackageJson = Json.Deserialize(product.jsonInfo) as Dictionary; - if (localPackageJson == null) - { - var ret = new Dictionary(); - ret["errorMessage"] = "Failed to parse JSON in local package"; - doneCallbackAction?.Invoke(ret); - return; - } - - dictData["id"] = localPackageJson["id"] as string; - dictData["version"] = localPackageJson["version"] as string; - dictData["version_id"] = localPackageJson["version_id"] as string; - } - catch (Exception e) - { - Dictionary ret = new Dictionary(); - ret["errorMessage"] = e.Message; - doneCallbackAction?.Invoke(ret); - return; - } - + dictData["local_path"] = info?.packagePath ?? string.Empty; + dictData["id"] = info?.id ?? string.Empty; + dictData["version"] = info?.versionString ?? string.Empty; + dictData["version_id"] = info?.versionId ?? string.Empty; packageList.Add(dictData); } var data = Json.Serialize(packageList); - var url = $"{m_Host}{kUpdateUri}"; + var url = $"{host}{kUpdateUri}"; var httpRequest = m_AsyncHTTPClient.GetASyncHTTPClient(url, "POST"); httpRequest.postData = data; diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreVersionList.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreVersionList.cs new file mode 100644 index 0000000000..9160d7034f --- /dev/null +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreVersionList.cs @@ -0,0 +1,48 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor.PackageManager.UI.AssetStore; + +namespace UnityEditor.PackageManager.UI +{ + [Serializable] + internal class AssetStoreVersionList : IVersionList + { + private List m_Versions; + + public IEnumerable all => m_Versions.Cast(); + + public IEnumerable key => m_Versions.Cast(); + + public IPackageVersion installed => null; + + public IPackageVersion latest => m_Versions.LastOrDefault(); + + public IPackageVersion latestPatch => latest; + + public IPackageVersion importAvailable => m_Versions.FirstOrDefault(v => v.isAvailableOnDisk); + + public IPackageVersion recommended => latest; + + public IPackageVersion primary => importAvailable ?? latest; + + public AssetStoreVersionList() + { + m_Versions = new List(); + } + + public void AddVersion(AssetStorePackageVersion version) + { + m_Versions.Add(version); + } + + public void RemoveVersion(AssetStorePackageVersion version) + { + m_Versions.Remove(version); + } + } +} diff --git a/Modules/PackageManagerUI/Editor/Services/Common/PackageExtensions.cs b/Modules/PackageManagerUI/Editor/Services/Common/PackageExtensions.cs new file mode 100644 index 0000000000..7b7ed868bf --- /dev/null +++ b/Modules/PackageManagerUI/Editor/Services/Common/PackageExtensions.cs @@ -0,0 +1,35 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.Linq; + +namespace UnityEditor.PackageManager.UI +{ + internal static class PackageExtensions + { + public static PackageState GetState(this IPackage package) + { + if (package.progress != PackageProgress.None) + return PackageState.InProgress; + + if (package.errors.Any()) + return PackageState.Error; + + var primary = package.primaryVersion; + if (primary.HasTag(PackageTag.InDevelopment)) + return PackageState.InDevelopment; + + if (primary != package.recommendedVersion) + return PackageState.Outdated; + + if (package.versionList.importAvailable != null) + return PackageState.ImportAvailable; + + if (package.installedVersion != null) + return PackageState.Installed; + + return PackageState.UpToDate; + } + } +} diff --git a/Modules/PackageManagerUI/Editor/Services/Common/PlaceholderPackage.cs b/Modules/PackageManagerUI/Editor/Services/Common/PlaceholderPackage.cs index b6457318d6..e1cdaf1833 100644 --- a/Modules/PackageManagerUI/Editor/Services/Common/PlaceholderPackage.cs +++ b/Modules/PackageManagerUI/Editor/Services/Common/PlaceholderPackage.cs @@ -18,27 +18,42 @@ internal class PlaceholderPackage : IPackage public string name => string.Empty; public string uniqueId => m_UniqueId; - public string displayName => m_Version.displayName; + public string displayName => versions.FirstOrDefault().displayName; [SerializeField] - private PlaceholderPackageVersion m_Version; + private PlaceholderVersionList m_VersionList; - public IEnumerable versions => new[] { m_Version }; - public IEnumerable keyVersions => new[] { m_Version }; - public IPackageVersion installedVersion => null; - public IPackageVersion latestVersion => m_Version; - public IPackageVersion latestPatch => m_Version; - public IPackageVersion recommendedVersion => m_Version; - public IPackageVersion primaryVersion => m_Version; + public IVersionList versionList => m_VersionList; [SerializeField] - private PackageState m_State; - public PackageState state => m_State; + private PackageProgress m_Progress; + public PackageProgress progress => m_Progress; + + [SerializeField] + private PackageType m_Type; public bool isDiscoverable => true; public IEnumerable errors => Enumerable.Empty(); + public IEnumerable images => Enumerable.Empty(); + + public IEnumerable links => Enumerable.Empty(); + + public IEnumerable versions => versionList?.all; + + public IEnumerable keyVersions => versionList?.key; + + public IPackageVersion installedVersion => versionList?.installed; + + public IPackageVersion latestVersion => versionList?.latest; + + public IPackageVersion latestPatch => versionList?.latestPatch; + + public IPackageVersion recommendedVersion => versionList?.recommended; + + public IPackageVersion primaryVersion => versionList?.primary; + public void AddError(Error error) { } @@ -47,11 +62,17 @@ public void ClearErrors() { } - public PlaceholderPackage(string uniqueId, PackageTag tag = PackageTag.None, PackageSource source = PackageSource.Unknown, PackageState state = PackageState.InProgress) + public bool Is(PackageType type) + { + return (m_Type & type) != 0; + } + + public PlaceholderPackage(string uniqueId, PackageType type = PackageType.None, PackageTag tag = PackageTag.None, PackageProgress progress = PackageProgress.None) { + m_Type = type; m_UniqueId = uniqueId; - m_State = state; - m_Version = new PlaceholderPackageVersion(uniqueId, uniqueId, tag, source); + m_Progress = progress; + m_VersionList = new PlaceholderVersionList(new PlaceholderPackageVersion(uniqueId, uniqueId, tag)); } public IPackage Clone() diff --git a/Modules/PackageManagerUI/Editor/Services/Common/PlaceholderPackageVersion.cs b/Modules/PackageManagerUI/Editor/Services/Common/PlaceholderPackageVersion.cs index e7f41f62cc..757380742e 100644 --- a/Modules/PackageManagerUI/Editor/Services/Common/PlaceholderPackageVersion.cs +++ b/Modules/PackageManagerUI/Editor/Services/Common/PlaceholderPackageVersion.cs @@ -21,20 +21,18 @@ internal class PlaceholderPackageVersion : IPackageVersion public string uniqueId => m_UniqueId; public string name => string.Empty; - [SerializeField] - private PackageSource m_Source; - public PackageSource source => m_Source; - public string displayName => string.Empty; - public string type => string.Empty; - public string author => string.Empty; + public string authorLink => string.Empty; + public string description => string.Empty; public string category => string.Empty; + public IDictionary categoryLinks => null; + public IEnumerable errors => Enumerable.Empty(); public IEnumerable samples => Enumerable.Empty(); @@ -57,8 +55,6 @@ internal class PlaceholderPackageVersion : IPackageVersion public bool isFullyFetched => true; - public bool isUserVisible => true; - public bool isAvailableOnDisk => false; public bool isVersionLocked => true; @@ -77,12 +73,8 @@ internal class PlaceholderPackageVersion : IPackageVersion public IEnumerable supportedVersions => Enumerable.Empty(); - public IEnumerable images => Enumerable.Empty(); - public IEnumerable sizes => Enumerable.Empty(); - public IEnumerable links => Enumerable.Empty(); - public SemVersion supportedVersion => null; [SerializeField] @@ -97,7 +89,6 @@ public PlaceholderPackageVersion(string packageUniqueId, string uniqueId, Packag m_PackageUniqueId = packageUniqueId; m_UniqueId = uniqueId; m_Tag = tag; - m_Source = source; } } } diff --git a/Modules/PackageManagerUI/Editor/Services/Common/PlaceholderVersionList.cs b/Modules/PackageManagerUI/Editor/Services/Common/PlaceholderVersionList.cs new file mode 100644 index 0000000000..f600a45793 --- /dev/null +++ b/Modules/PackageManagerUI/Editor/Services/Common/PlaceholderVersionList.cs @@ -0,0 +1,36 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; + +namespace UnityEditor.PackageManager.UI +{ + [Serializable] + internal class PlaceholderVersionList : IVersionList + { + private PlaceholderPackageVersion[] m_Versions; + + public IEnumerable all => m_Versions; + + public IEnumerable key => m_Versions; + + public IPackageVersion installed => null; + + public IPackageVersion latest => m_Versions[0]; + + public IPackageVersion latestPatch => m_Versions[0]; + + public IPackageVersion importAvailable => null; + + public IPackageVersion recommended => m_Versions[0]; + + public IPackageVersion primary => m_Versions[0]; + + public PlaceholderVersionList(PlaceholderPackageVersion version) + { + m_Versions = new[] { version }; + } + } +} diff --git a/Modules/PackageManagerUI/Editor/Services/Interfaces/IAssetStoreClient.cs b/Modules/PackageManagerUI/Editor/Services/Interfaces/IAssetStoreClient.cs index fbf89a41b0..e89474b05b 100644 --- a/Modules/PackageManagerUI/Editor/Services/Interfaces/IAssetStoreClient.cs +++ b/Modules/PackageManagerUI/Editor/Services/Interfaces/IAssetStoreClient.cs @@ -15,6 +15,7 @@ internal interface IAssetStoreClient event Action onProductFetched; event Action> onPackagesChanged; + event Action onPackageVersionUpdated; event Action onDownloadProgress; @@ -32,9 +33,7 @@ internal interface IAssetStoreClient void FetchDetails(IEnumerable packageIds); - void Refresh(IPackage package); - - void Refresh(IEnumerable packages); + void RefreshLocal(); bool IsAnyDownloadInProgress(); @@ -50,10 +49,10 @@ internal interface IAssetStoreClient void OnDownloadProgress(string packageId, string message, ulong bytes, ulong total); - void Setup(); + void RegisterEvents(); - void Clear(); + void UnregisterEvents(); - void Reset(); + void ClearCache(); } } diff --git a/Modules/PackageManagerUI/Editor/Services/Interfaces/IAssetStoreRestAPI.cs b/Modules/PackageManagerUI/Editor/Services/Interfaces/IAssetStoreRestAPI.cs index 1bf7816ad6..e539b13af0 100644 --- a/Modules/PackageManagerUI/Editor/Services/Interfaces/IAssetStoreRestAPI.cs +++ b/Modules/PackageManagerUI/Editor/Services/Interfaces/IAssetStoreRestAPI.cs @@ -38,6 +38,6 @@ internal interface IAssetStoreRestAPI void GetDownloadDetail(long productID, Action doneCallbackAction); - void GetProductUpdateDetail(List localPackages, Action> doneCallbackAction); + void GetProductUpdateDetail(IEnumerable localInfos, Action> doneCallbackAction); } } diff --git a/Modules/PackageManagerUI/Editor/Services/Interfaces/IPackage.cs b/Modules/PackageManagerUI/Editor/Services/Interfaces/IPackage.cs index aa6c026660..2a8d726da7 100644 --- a/Modules/PackageManagerUI/Editor/Services/Interfaces/IPackage.cs +++ b/Modules/PackageManagerUI/Editor/Services/Interfaces/IPackage.cs @@ -14,6 +14,8 @@ internal interface IPackage string displayName { get; } + IVersionList versionList { get; } + IEnumerable versions { get; } IEnumerable keyVersions { get; } @@ -31,7 +33,13 @@ internal interface IPackage // it will be the default that will be displayed if no versions are selected IPackageVersion primaryVersion { get; } - PackageState state { get; } + PackageProgress progress { get; } + + IEnumerable images { get; } + + IEnumerable links { get; } + + bool Is(PackageType type); bool isDiscoverable { get; } diff --git a/Modules/PackageManagerUI/Editor/Services/Interfaces/IPackageDatabase.cs b/Modules/PackageManagerUI/Editor/Services/Interfaces/IPackageDatabase.cs index eb8b5769b8..89a8863bb9 100644 --- a/Modules/PackageManagerUI/Editor/Services/Interfaces/IPackageDatabase.cs +++ b/Modules/PackageManagerUI/Editor/Services/Interfaces/IPackageDatabase.cs @@ -26,10 +26,10 @@ internal interface IPackageDatabase event Action onDownloadProgress; - void Setup(); + void RegisterEvents(); - void Clear(); - void Reset(); + void UnregisterEvents(); + void Reload(); bool isEmpty { get; } bool isInstallOrUninstallInProgress { get; } diff --git a/Modules/PackageManagerUI/Editor/Services/Interfaces/IPackageVersion.cs b/Modules/PackageManagerUI/Editor/Services/Interfaces/IPackageVersion.cs index a401bd1087..6c6b6d16f9 100644 --- a/Modules/PackageManagerUI/Editor/Services/Interfaces/IPackageVersion.cs +++ b/Modules/PackageManagerUI/Editor/Services/Interfaces/IPackageVersion.cs @@ -13,21 +13,20 @@ internal interface IPackageVersion string displayName { get; } - string type { get; } - string author { get; } + string authorLink { get; } + string description { get; } string category { get; } + IDictionary categoryLinks { get; } + string packageUniqueId { get; } string uniqueId { get; } - // TODO: Might need to create a wrapper `PackageSource` to account for AssetStore package info. - PackageSource source { get; } - IEnumerable errors { get; } IEnumerable samples { get; } @@ -51,8 +50,6 @@ internal interface IPackageVersion // A version is fully fetched when the information isn't derived from another version (therefore may be inaccurate) bool isFullyFetched { get; } - bool isUserVisible { get; } - bool isAvailableOnDisk { get; } bool isVersionLocked { get; } @@ -73,12 +70,8 @@ internal interface IPackageVersion IEnumerable supportedVersions { get; } - IEnumerable images { get; } - IEnumerable sizes { get; } - IEnumerable links { get; } - EntitlementsInfo entitlements { get; } } } diff --git a/Modules/PackageManagerUI/Editor/Services/Interfaces/IUpmClient.cs b/Modules/PackageManagerUI/Editor/Services/Interfaces/IUpmClient.cs index e8fda42ffb..cf86d436af 100644 --- a/Modules/PackageManagerUI/Editor/Services/Interfaces/IUpmClient.cs +++ b/Modules/PackageManagerUI/Editor/Services/Interfaces/IUpmClient.cs @@ -16,8 +16,10 @@ internal interface IUpmClient event Action onEmbedOperation; event Action> onPackagesChanged; - - event Action onPackageVersionUpdated; + event Action onProductPackageChanged; + event Action onPackageVersionUpdated; + event Action onProductPackageVersionUpdated; + event Action onProductPackageFetchError; bool isAddRemoveOrEmbedInProgress { get; } @@ -28,6 +30,8 @@ internal interface IUpmClient void SearchAll(bool offlineMode = false); void ExtraFetch(string packageId); + void FetchForProduct(string productId, string packageName); + void List(bool offlineMode = false); void AddById(string packageId); void AddByPath(string path); @@ -38,10 +42,12 @@ internal interface IUpmClient void EmbedByName(string packageName); - void Setup(); + void RegisterEvents(); + + void UnregisterEvents(); - void Clear(); + void ClearCache(); - void Reset(); + void ClearProductCache(); } } diff --git a/Modules/PackageManagerUI/Editor/Services/Interfaces/IVersionList.cs b/Modules/PackageManagerUI/Editor/Services/Interfaces/IVersionList.cs new file mode 100644 index 0000000000..ae0f347335 --- /dev/null +++ b/Modules/PackageManagerUI/Editor/Services/Interfaces/IVersionList.cs @@ -0,0 +1,30 @@ +// 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; + +namespace UnityEditor.PackageManager.UI +{ + internal interface IVersionList + { + IEnumerable all { get; } + + IEnumerable key { get; } + + IPackageVersion installed { get; } + + IPackageVersion latest { get; } + + IPackageVersion latestPatch { get; } + + IPackageVersion importAvailable { get; } + + // the recommended version to install or update to + IPackageVersion recommended { get; } + + // the primary version is most important version that we want to show to the user + // it will be the default that will be displayed if no versions are selected + IPackageVersion primary { get; } + } +} diff --git a/Modules/PackageManagerUI/Editor/Services/Packages/PackageAssetPostprocessor.cs b/Modules/PackageManagerUI/Editor/Services/Packages/PackageAssetPostprocessor.cs index 730b927045..53a6239bcd 100644 --- a/Modules/PackageManagerUI/Editor/Services/Packages/PackageAssetPostprocessor.cs +++ b/Modules/PackageManagerUI/Editor/Services/Packages/PackageAssetPostprocessor.cs @@ -10,8 +10,8 @@ internal class PackageAssetPostprocessor : AssetPostprocessor { static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { - var windows = UnityEngine.Resources.FindObjectsOfTypeAll(); - if (windows == null || windows.Length == 0) + // only monitor assets change if page manager has been initialized before + if (!PageManager.instance.isInitialized) return; var allUpdatedAssets = importedAssets.Concat(deletedAssets).Concat(movedAssets).Concat(movedFromAssetPaths); diff --git a/Modules/PackageManagerUI/Editor/Services/Packages/PackageDatabase.cs b/Modules/PackageManagerUI/Editor/Services/Packages/PackageDatabase.cs index 5c632119e4..8a5fc8cb7f 100644 --- a/Modules/PackageManagerUI/Editor/Services/Packages/PackageDatabase.cs +++ b/Modules/PackageManagerUI/Editor/Services/Packages/PackageDatabase.cs @@ -47,8 +47,8 @@ private class PackageDatabaseInternal : ScriptableSingleton m_RefreshOperationsInProgress = new List(); - [SerializeField] - private bool m_SetupDone; + [NonSerialized] + private bool m_EventsRegistered; public bool isEmpty { get { return !m_Packages.Any(); } } @@ -191,10 +191,12 @@ public void ClearPackageErrors(IPackage package) public IEnumerable packagesInError => allPackages.Where(p => p.errors.Any()); - public void Setup() + public void RegisterEvents() { - System.Diagnostics.Debug.Assert(!m_SetupDone); - m_SetupDone = true; + if (m_EventsRegistered) + return; + + m_EventsRegistered = true; UpmClient.instance.onPackagesChanged += OnPackagesChanged; UpmClient.instance.onPackageVersionUpdated += OnUpmPackageVersionUpdated; @@ -204,25 +206,25 @@ public void Setup() UpmClient.instance.onAddOperation += OnUpmAddOperation; UpmClient.instance.onEmbedOperation += OnUpmEmbedOperation; UpmClient.instance.onRemoveOperation += OnUpmRemoveOperation; - UpmClient.instance.Setup(); + UpmClient.instance.RegisterEvents(); AssetStore.AssetStoreClient.instance.onPackagesChanged += OnPackagesChanged; + AssetStore.AssetStoreClient.instance.onPackageVersionUpdated += OnUpmPackageVersionUpdated; AssetStore.AssetStoreClient.instance.onDownloadProgress += OnDownloadProgress; AssetStore.AssetStoreClient.instance.onListOperationStart += OnAssetStoreOperationStart; AssetStore.AssetStoreClient.instance.onListOperationFinish += OnAssetStoreOperationFinish; AssetStore.AssetStoreClient.instance.onOperationError += OnAssetStoreOperationError; - AssetStore.AssetStoreClient.instance.Setup(); + AssetStore.AssetStoreClient.instance.RegisterEvents(); ApplicationUtil.instance.onUserLoginStateChange += OnUserLoginStateChange; - - //if (m_RefreshedOnce) - // Refresh(RefreshOptions.Purchased | RefreshOptions.OfflineMode); } - public void Clear() + public void UnregisterEvents() { - System.Diagnostics.Debug.Assert(m_SetupDone); - m_SetupDone = false; + if (!m_EventsRegistered) + return; + + m_EventsRegistered = false; UpmClient.instance.onPackagesChanged -= OnPackagesChanged; UpmClient.instance.onPackageVersionUpdated -= OnUpmPackageVersionUpdated; @@ -232,26 +234,27 @@ public void Clear() UpmClient.instance.onAddOperation -= OnUpmAddOperation; UpmClient.instance.onEmbedOperation -= OnUpmEmbedOperation; UpmClient.instance.onRemoveOperation -= OnUpmRemoveOperation; - UpmClient.instance.Clear(); + UpmClient.instance.UnregisterEvents(); AssetStore.AssetStoreClient.instance.onPackagesChanged -= OnPackagesChanged; + AssetStore.AssetStoreClient.instance.onPackageVersionUpdated -= OnUpmPackageVersionUpdated; AssetStore.AssetStoreClient.instance.onDownloadProgress -= OnDownloadProgress; AssetStore.AssetStoreClient.instance.onListOperationStart -= OnAssetStoreOperationStart; AssetStore.AssetStoreClient.instance.onListOperationFinish -= OnAssetStoreOperationFinish; AssetStore.AssetStoreClient.instance.onOperationError -= OnAssetStoreOperationError; - AssetStore.AssetStoreClient.instance.Clear(); + AssetStore.AssetStoreClient.instance.UnregisterEvents(); ApplicationUtil.instance.onUserLoginStateChange -= OnUserLoginStateChange; } - public void Reset() + public void Reload() { onPackagesChanged?.Invoke(Enumerable.Empty(), m_Packages.Values, Enumerable.Empty(), Enumerable.Empty()); - Clear(); + UnregisterEvents(); - AssetStore.AssetStoreClient.instance.Reset(); - UpmClient.instance.Reset(); + AssetStore.AssetStoreClient.instance.ClearCache(); + UpmClient.instance.ClearCache(); m_Packages.Clear(); m_SerializedUpmPackages = new List(); @@ -261,7 +264,7 @@ public void Reset() m_RefreshOperationsInProgress.Clear(); m_LastUpdateTimestamp = 0; - Setup(); + RegisterEvents(); } private void OnDownloadProgress(DownloadProgress progress) @@ -275,7 +278,7 @@ private void OnDownloadProgress(DownloadProgress progress) if (progress.state == DownloadProgress.State.Completed) { - AssetStore.AssetStoreClient.instance.Refresh(package); + AssetStore.AssetStoreClient.instance.RefreshLocal(); } onDownloadProgress?.Invoke(package, progress); @@ -455,14 +458,15 @@ private void OnListOrSearchOperationFinalized(IOperation operation) onRefreshOperationFinish(operation is UpmListOperation ? PackageFilterTab.Local : PackageFilterTab.All); } - private void OnUpmPackageVersionUpdated(IPackageVersion version) + private void OnUpmPackageVersionUpdated(string packageUniqueId, IPackageVersion version) { - var upmPackage = GetPackage(version.packageInfo.name) as UpmPackage; - if (upmPackage != null) + var package = GetPackage(packageUniqueId); + var upmVersions = package?.versionList as UpmVersionList; + if (upmVersions != null) { - var packagePreUpdate = upmPackage.Clone(); - upmPackage.UpdateVersion(version as UpmPackageVersion); - onPackagesChanged?.Invoke(k_EmptyList, k_EmptyList, new[] { packagePreUpdate }, new[] { upmPackage }); + var packagePreUpdate = package.Clone(); + upmVersions.UpdateVersion(version as UpmPackageVersion); + onPackagesChanged?.Invoke(k_EmptyList, k_EmptyList, new[] { packagePreUpdate }, new[] { package }); } } @@ -487,7 +491,7 @@ public void Uninstall(IPackage package) { if (package.installedVersion == null) return; - UpmClient.instance.RemoveByName(package.uniqueId); + UpmClient.instance.RemoveByName(package.name); } public void Embed(IPackageVersion packageVersion) @@ -501,7 +505,7 @@ public void RemoveEmbedded(IPackage package) { if (package.installedVersion == null) return; - UpmClient.instance.RemoveEmbeddedByName(package.uniqueId); + UpmClient.instance.RemoveEmbeddedByName(package.name); } public void FetchExtraInfo(IPackageVersion version) diff --git a/Modules/PackageManagerUI/Editor/Services/Packages/PackageFiltering.cs b/Modules/PackageManagerUI/Editor/Services/Packages/PackageFiltering.cs index e0449c1939..35872bae9d 100644 --- a/Modules/PackageManagerUI/Editor/Services/Packages/PackageFiltering.cs +++ b/Modules/PackageManagerUI/Editor/Services/Packages/PackageFiltering.cs @@ -18,15 +18,13 @@ internal static bool FilterByTab(IPackage package, PackageFilterTab tab) switch (tab) { case PackageFilterTab.Modules: - return package.versions.Any(v => v.HasTag(PackageTag.BuiltIn) && !v.HasTag(PackageTag.AssetStore)); + return package.Is(PackageType.BuiltIn); case PackageFilterTab.All: - return package.versions.Any(v => !v.HasTag(PackageTag.BuiltIn) && !v.HasTag(PackageTag.AssetStore)) - && (package.isDiscoverable || (package.installedVersion?.isDirectDependency ?? false)); + return package.Is(PackageType.Installable) && (package.isDiscoverable || (package.installedVersion?.isDirectDependency ?? false)); case PackageFilterTab.Local: - return package.versions.Any(v => !v.HasTag(PackageTag.BuiltIn) && !v.HasTag(PackageTag.AssetStore)) - && (package.installedVersion?.isDirectDependency ?? false); + return !package.Is(PackageType.BuiltIn) && (package.installedVersion?.isDirectDependency ?? false); case PackageFilterTab.AssetStore: - return ApplicationUtil.instance.isUserLoggedIn && (package.primaryVersion?.HasTag(PackageTag.AssetStore) ?? false); + return ApplicationUtil.instance.isUserLoggedIn && package.Is(PackageType.AssetStore); case PackageFilterTab.InDevelopment: return package.installedVersion?.HasTag(PackageTag.InDevelopment) ?? false; default: @@ -34,12 +32,12 @@ internal static bool FilterByTab(IPackage package, PackageFilterTab tab) } } - internal static bool FilterByText(IPackageVersion version, string text) + internal static bool FilterByText(IPackage package, IPackageVersion version, string text) { if (string.IsNullOrEmpty(text)) return true; - if (version == null) + if (package == null || version == null) return false; if (version.name.IndexOf(text, StringComparison.CurrentCultureIgnoreCase) >= 0) @@ -48,39 +46,24 @@ internal static bool FilterByText(IPackageVersion version, string text) if (!string.IsNullOrEmpty(version.displayName) && version.displayName.IndexOf(text, StringComparison.CurrentCultureIgnoreCase) >= 0) return true; - if (!version.HasTag(PackageTag.BuiltIn)) - { - var prerelease = text.StartsWith("-") ? text.Substring(1) : text; - if (version.version != null && version.version.Prerelease.IndexOf(prerelease, StringComparison.CurrentCultureIgnoreCase) >= 0) - return true; - - if (version.version.StripTag().StartsWith(text, StringComparison.CurrentCultureIgnoreCase)) - return true; + var prerelease = text.StartsWith("-") ? text.Substring(1) : text; + if (version.version != null && version.version.Prerelease.IndexOf(prerelease, StringComparison.CurrentCultureIgnoreCase) >= 0) + return true; - if (version.HasTag(PackageTag.Preview)) - { - if (PackageTag.Preview.ToString().IndexOf(text, StringComparison.CurrentCultureIgnoreCase) >= 0) - return true; - } + if (version.HasTag(PackageTag.Preview) && PackageTag.Preview.ToString().IndexOf(text, StringComparison.CurrentCultureIgnoreCase) >= 0) + return true; - if (version.HasTag(PackageTag.Verified)) - { - if (PackageTag.Verified.ToString().IndexOf(text, StringComparison.CurrentCultureIgnoreCase) >= 0) - return true; - } + if (version.HasTag(PackageTag.Verified) && PackageTag.Verified.ToString().IndexOf(text, StringComparison.CurrentCultureIgnoreCase) >= 0) + return true; - if (version.HasTag(PackageTag.Core)) - { - if (PackageTag.BuiltIn.ToString().IndexOf(text, StringComparison.CurrentCultureIgnoreCase) >= 0) - return true; - } - } + if (version.version.StripTag().StartsWith(text, StringComparison.CurrentCultureIgnoreCase)) + return true; - if (version.HasTag(PackageTag.AssetStore)) + if (!string.IsNullOrEmpty(version.category)) { - var words = text.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries); - var categories = version.category?.Split('/'); - if (categories != null && words.All(word => word.Length >= 2 && categories.Any(category => category.StartsWith(word, StringComparison.CurrentCultureIgnoreCase)))) + var words = text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + var categories = version.category.Split('/'); + if (words.All(word => word.Length >= 2 && categories.Any(category => category.StartsWith(word, StringComparison.CurrentCultureIgnoreCase)))) return true; } @@ -133,7 +116,7 @@ public bool FilterByCurrentSearchText(IPackage package) var trimText = currentSearchText.Trim(' ', '\t'); trimText = Regex.Replace(trimText, @"[ ]{2,}", " "); - return string.IsNullOrEmpty(trimText) || FilterByText(package.primaryVersion, trimText); + return string.IsNullOrEmpty(trimText) || FilterByText(package, package.primaryVersion, trimText); } public bool FilterByCurrentTab(IPackage package) diff --git a/Modules/PackageManagerUI/Editor/Services/Packages/PackageOrigin.cs b/Modules/PackageManagerUI/Editor/Services/Packages/PackageProgress.cs similarity index 62% rename from Modules/PackageManagerUI/Editor/Services/Packages/PackageOrigin.cs rename to Modules/PackageManagerUI/Editor/Services/Packages/PackageProgress.cs index b6e474079c..161b6ec74c 100644 --- a/Modules/PackageManagerUI/Editor/Services/Packages/PackageOrigin.cs +++ b/Modules/PackageManagerUI/Editor/Services/Packages/PackageProgress.cs @@ -4,11 +4,12 @@ namespace UnityEditor.PackageManager.UI { - internal enum PackageOrigin + internal enum PackageProgress { - Unknown, - Builtin, - Registry, - AssetStore, + None, + Refreshing, + Downloading, + Installing, + Removing } } diff --git a/Modules/PackageManagerUI/Editor/Services/Packages/PackageState.cs b/Modules/PackageManagerUI/Editor/Services/Packages/PackageState.cs index f1fef1c45b..fa52b886d0 100644 --- a/Modules/PackageManagerUI/Editor/Services/Packages/PackageState.cs +++ b/Modules/PackageManagerUI/Editor/Services/Packages/PackageState.cs @@ -7,6 +7,9 @@ namespace UnityEditor.PackageManager.UI internal enum PackageState { UpToDate, + Installed, + ImportAvailable, + InDevelopment, Outdated, InProgress, Error diff --git a/Modules/PackageManagerUI/Editor/Services/Packages/PackageTag.cs b/Modules/PackageManagerUI/Editor/Services/Packages/PackageTag.cs index 3afe5ca83f..4387b2c796 100644 --- a/Modules/PackageManagerUI/Editor/Services/Packages/PackageTag.cs +++ b/Modules/PackageManagerUI/Editor/Services/Packages/PackageTag.cs @@ -11,19 +11,26 @@ internal enum PackageTag : uint { None = 0, - // package source/origin + // package type InDevelopment = 1 << 0, Local = 1 << 1, Git = 1 << 2, - BuiltIn = 1 << 3, - Core = 1 << 4, - AssetStore = 1 << 5, - Published = 1 << 6, - Deprecated = 1 << 7, + Bundled = 1 << 3, + BuiltIn = 1 << 4, - // preview status - Verified = 1 << 10, // the recommended version if major version > 0 - Preview = 1 << 11, // with `preview`, `preview.x` tag or with `0` as major version - Release = 1 << 12 // no pre-release tag & major version > 0 + // attributes + VersionLocked = 1 << 8, + Installable = 1 << 9, + Removable = 1 << 10, + Downloadable = 1 << 11, + Importable = 1 << 12, + Embeddable = 1 << 13, + + // status + Published = 1 << 16, + Deprecated = 1 << 17, + Verified = 1 << 18, // the recommended version if major version > 0 + Preview = 1 << 19, // with `preview`, `preview.x` tag or with `0` as major version + Release = 1 << 20 // no pre-release tag & major version > 0 } } diff --git a/Modules/PackageManagerUI/Editor/Services/Packages/PackageType.cs b/Modules/PackageManagerUI/Editor/Services/Packages/PackageType.cs new file mode 100644 index 0000000000..5e1b3319bf --- /dev/null +++ b/Modules/PackageManagerUI/Editor/Services/Packages/PackageType.cs @@ -0,0 +1,18 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; + +namespace UnityEditor.PackageManager.UI +{ + [Flags] + internal enum PackageType + { + None = 0, + + Installable = 1 << 0, + BuiltIn = 1 << 1, + AssetStore = 1 << 2 + } +} diff --git a/Modules/PackageManagerUI/Editor/Services/Upm/UpmAddOperation.cs b/Modules/PackageManagerUI/Editor/Services/Upm/UpmAddOperation.cs index 72ad1a3588..b29662c734 100644 --- a/Modules/PackageManagerUI/Editor/Services/Upm/UpmAddOperation.cs +++ b/Modules/PackageManagerUI/Editor/Services/Upm/UpmAddOperation.cs @@ -14,11 +14,12 @@ internal class UpmAddOperation : UpmBaseOperation public override string specialUniqueId { get { return m_SpecialUniqueId; } } - public void Add(string packageId) + public void Add(string packageId, string packageUniqueId = null) { m_PackageId = packageId; m_PackageName = string.Empty; m_SpecialUniqueId = string.Empty; + m_PackageUniqueId = packageUniqueId ?? packageName; Start(); } diff --git a/Modules/PackageManagerUI/Editor/Services/Upm/UpmBaseOperation.cs b/Modules/PackageManagerUI/Editor/Services/Upm/UpmBaseOperation.cs index b3371585c1..cf8b91c655 100644 --- a/Modules/PackageManagerUI/Editor/Services/Upm/UpmBaseOperation.cs +++ b/Modules/PackageManagerUI/Editor/Services/Upm/UpmBaseOperation.cs @@ -30,7 +30,8 @@ public string packageName protected string m_PackageId = string.Empty; public string packageId { get { return m_PackageId; } } - public string packageUniqueId { get { return packageName; } } + protected string m_PackageUniqueId = string.Empty; + public string packageUniqueId { get { return m_PackageUniqueId; } } public string versionUniqueId { get { return packageId; } } public virtual string specialUniqueId { get { return string.Empty; } } diff --git a/Modules/PackageManagerUI/Editor/Services/Upm/UpmClient.cs b/Modules/PackageManagerUI/Editor/Services/Upm/UpmClient.cs index 426386f2c7..15884e2582 100644 --- a/Modules/PackageManagerUI/Editor/Services/Upm/UpmClient.cs +++ b/Modules/PackageManagerUI/Editor/Services/Upm/UpmClient.cs @@ -26,7 +26,11 @@ internal class UpmClientInternal : ScriptableSingleton, IUpmC public event Action onEmbedOperation = delegate {}; public event Action> onPackagesChanged = delegate {}; - public event Action onPackageVersionUpdated = delegate {}; + public event Action onProductPackageChanged = delegate {}; + + public event Action onPackageVersionUpdated = delegate {}; + public event Action onProductPackageVersionUpdated = delegate {}; + public event Action onProductPackageFetchError = delegate {}; private UpmSearchOperation m_SearchOperation; private UpmSearchOperation m_SearchOfflineOperation; @@ -51,6 +55,11 @@ private PackageInfo GetInstalledPackageInfo(string packageName) return m_InstalledPackageInfos.TryGetValue(packageName, out result) ? result : null; } + private Dictionary m_ProductPackageInfos = new Dictionary(); + + // the mapping between package name (key) to asset store product id (value) + private Dictionary m_ProductIdMap = new Dictionary(); + private readonly Dictionary m_ExtraFetchOperations = new Dictionary(); private Dictionary> m_ExtraPackageInfo = new Dictionary>(); private void AddExtraPackageInfo(PackageInfo packageInfo) @@ -67,10 +76,13 @@ private void AddExtraPackageInfo(PackageInfo packageInfo) // arrays created to help serialize dictionaries private PackageInfo[] m_SerializedInstalledPackageInfos; private PackageInfo[] m_SerializedSearchPackageInfos; + private PackageInfo[] m_SerializedProductPackageInfos; private PackageInfo[] m_SerializedExtraPackageInfos; + private string[] m_SerializedProductIdMapKeys; + private string[] m_SerializedProductIdMapValues; - [SerializeField] - private bool m_SetupDone; + [NonSerialized] + private bool m_EventsRegistered; public bool isAddRemoveOrEmbedInProgress { @@ -108,7 +120,8 @@ public void AddById(string packageId) { if (isAddRemoveOrEmbedInProgress) return; - m_AddOperation.Add(packageId); + var packageName = packageId.Split(new[] { '@' }, 2)[0]; + m_AddOperation.Add(packageId, m_ProductIdMap.Get(packageName)); SetupAddOperation(); } @@ -210,7 +223,7 @@ public void EmbedByName(string packageName) { if (isAddRemoveOrEmbedInProgress) return; - m_EmbedOperation.Embed(packageName); + m_EmbedOperation.Embed(packageName, m_ProductIdMap.Get(packageName)); m_EmbedOperation.onProcessResult += OnProcessAddResult; m_EmbedOperation.onOperationError += error => Debug.LogError($"Error embedding package: {m_EmbedOperation.packageName}."); onEmbedOperation(m_EmbedOperation); @@ -220,7 +233,7 @@ public void RemoveByName(string packageName) { if (isAddRemoveOrEmbedInProgress) return; - m_RemoveOperation.Remove(packageName); + m_RemoveOperation.Remove(packageName, m_ProductIdMap.Get(packageName)); SetupRemoveOperation(); } @@ -300,28 +313,72 @@ private void OnProcessSearchAllResult(SearchRequest request, bool offlineMode) OnPackageInfosUpdated(FindUpdatedPackageInfos(oldSearchResult, searchResult)); } - public void ExtraFetch(string packageIdOrName) + public void ExtraFetch(string packageId) + { + ExtraFetchInternal(packageId); + } + + private void ExtraFetchInternal(string packageIdOrName, string productId = null) { if (m_ExtraFetchOperations.ContainsKey(packageIdOrName)) return; var operation = new UpmSearchOperation(); - operation.Search(packageIdOrName); - operation.onProcessResult += OnProcessExtraFetchResult; - operation.onOperationError += error => Debug.LogError($"Error searching for package {packageIdOrName} online."); + operation.Search(packageIdOrName, productId); + operation.onProcessResult += (requst) => OnProcessExtraFetchResult(requst, productId); + operation.onOperationError += (error) => OnProcessExtraFetchError(error, productId); + operation.onOperationFinalized += () => OnExtraFetchFinalized(packageIdOrName); m_ExtraFetchOperations[packageIdOrName] = operation; } - private void OnProcessExtraFetchResult(SearchRequest request) + private void OnProcessExtraFetchResult(SearchRequest request, string productId = null) { var packageInfo = request.Result.FirstOrDefault(); - AddExtraPackageInfo(packageInfo); - m_ExtraFetchOperations.Remove(packageInfo.packageId); + if (!string.IsNullOrEmpty(productId)) + { + var oldInfo = m_ProductPackageInfos.Get(packageInfo.name); + // remove the created package that's created before asset store info was fetched + if (oldInfo == null && m_InstalledPackageInfos.ContainsKey(packageInfo.name)) + onPackagesChanged(new[] { CreateUpmPackage(null, null, packageInfo.name) }); + + if (oldInfo == null || IsDifferent(oldInfo, packageInfo)) + { + m_ProductPackageInfos[packageInfo.name] = packageInfo; + OnPackageInfosUpdated(request.Result.Take(1)); + } + } + else + { + AddExtraPackageInfo(packageInfo); + + // only trigger the call when the package is not installed, as installed version always have the most up-to-date package info + var installedPackageInfo = m_InstalledPackageInfos.Get(packageInfo.name); + if (installedPackageInfo?.packageId != packageInfo.packageId) + { + productId = m_ProductIdMap.Get(packageInfo.name); + if (string.IsNullOrEmpty(productId)) + onPackageVersionUpdated?.Invoke(packageInfo.name, new UpmPackageVersion(packageInfo, false)); + else + onProductPackageVersionUpdated?.Invoke(productId, new UpmPackageVersion(packageInfo, false)); + } + } + } - // only trigger the call when the package is not installed, as installed version always have the most up-to-date package info - var installedPackageInfo = GetInstalledPackageInfo(packageInfo.name); - if (installedPackageInfo?.packageId != packageInfo.packageId) - onPackageVersionUpdated(new UpmPackageVersion(packageInfo, false)); + private void OnProcessExtraFetchError(Error error, string productId = null) + { + if (!string.IsNullOrEmpty(productId)) + onProductPackageFetchError?.Invoke(productId, error); + } + + private void OnExtraFetchFinalized(string packageIdOrName) + { + m_ExtraFetchOperations.Remove(packageIdOrName); + } + + public void FetchForProduct(string productId, string packageName) + { + m_ProductIdMap[packageName] = productId; + ExtraFetchInternal(packageName, productId); } private void OnPackageInfosUpdated(IEnumerable packageInfos) @@ -330,58 +387,85 @@ private void OnPackageInfosUpdated(IEnumerable packageInfos) return; var upmPackages = new List(); + var productPackages = new List(); var showPreview = PackageManagerPrefs.instance.showPreviewPackages; foreach (var p in packageInfos) { - var installedInfo = GetInstalledPackageInfo(p.name); - var searchInfo = GetSearchPackageInfo(p.name); - - upmPackages.Add(CreateUpmPackage(searchInfo, installedInfo, p.name)); + var productId = m_ProductIdMap.Get(p.name); + var installedInfo = m_InstalledPackageInfos.Get(p.name); + if (string.IsNullOrEmpty(productId)) + upmPackages.Add(CreateUpmPackage(m_SearchPackageInfos.Get(p.name), installedInfo, p.name)); + else + productPackages.Add(CreateUpmPackage(m_ProductPackageInfos.Get(p.name), installedInfo, p.name)); } - foreach (var package in upmPackages) + foreach (var package in upmPackages.Concat(productPackages)) { - if (!showPreview && ShouldPreviewsBeRemoved(package)) + if (!showPreview && ShouldPreviewsBeRemoved(package.versionList)) RemovePreviewVersions(package); - UpdateExtraPackageInfos(package); + UpdateExtraPackageInfos(package.name, package.versionList); } if (upmPackages.Any()) onPackagesChanged(upmPackages.Cast()); + + foreach (var package in productPackages) + onProductPackageChanged?.Invoke(m_ProductIdMap.Get(package.name), package); } private void OnShowPreviewPackagesChanged(bool showPreview) { var updatedUpmPackages = new List(); + var updatedProductPackages = new List(); foreach (var installedInfo in m_InstalledPackageInfos.Values) { - var package = CreateUpmPackage(GetSearchPackageInfo(installedInfo.name), installedInfo); - if (ShouldPreviewsBeRemoved(package)) - updatedUpmPackages.Add(package); + var productId = m_ProductIdMap.Get(installedInfo.name); + if (string.IsNullOrEmpty(productId)) + { + var package = CreateUpmPackage(m_SearchPackageInfos.Get(installedInfo.name), installedInfo); + if (ShouldPreviewsBeRemoved(package.versionList)) + updatedUpmPackages.Add(package); + } + else + { + var package = CreateUpmPackage(m_ProductPackageInfos.Get(installedInfo.name), installedInfo); + if (ShouldPreviewsBeRemoved(package.versionList)) + updatedProductPackages.Add(package); + } } foreach (var searchInfo in m_SearchPackageInfos.Values.Where(p => !m_InstalledPackageInfos.ContainsKey(p.name))) { var package = CreateUpmPackage(searchInfo, null); - if (ShouldPreviewsBeRemoved(package)) + if (ShouldPreviewsBeRemoved(package.versionList)) updatedUpmPackages.Add(package); } - foreach (var package in updatedUpmPackages) + foreach (var productPackageInfo in m_ProductPackageInfos.Values.Where(p => !m_InstalledPackageInfos.ContainsKey(p.name))) + { + var package = CreateUpmPackage(productPackageInfo, null); + if (ShouldPreviewsBeRemoved(package.versionList)) + updatedProductPackages.Add(package); + } + + foreach (var package in updatedUpmPackages.Concat(updatedProductPackages)) { if (!showPreview) RemovePreviewVersions(package); - UpdateExtraPackageInfos(package); + UpdateExtraPackageInfos(package.name, package.versionList); } if (updatedUpmPackages.Any()) - onPackagesChanged(updatedUpmPackages.Cast()); + onPackagesChanged?.Invoke(updatedUpmPackages.Cast()); + + foreach (var package in updatedProductPackages) + onProductPackageChanged?.Invoke(m_ProductIdMap.Get(package.name), package); } private UpmPackage CreateUpmPackage(PackageInfo searchInfo, PackageInfo installedInfo, string packageName = null) { if (searchInfo == null && installedInfo == null) - return new UpmPackage(packageName, Enumerable.Empty(), false); + return new UpmPackage(packageName, false, PackageType.Installable); UpmPackage result; if (searchInfo == null) @@ -395,15 +479,15 @@ private UpmPackage CreateUpmPackage(PackageInfo searchInfo, PackageInfo installe return result; } - private void UpdateExtraPackageInfos(UpmPackage package) + private void UpdateExtraPackageInfos(string packageName, IVersionList versions) { - if (!package.versions.Any()) + if (!versions.all.Any()) return; Dictionary extraVersions; - if (m_ExtraPackageInfo.TryGetValue(package.name, out extraVersions)) + if (m_ExtraPackageInfo.TryGetValue(packageName, out extraVersions)) { - foreach (var version in package.versions.Cast()) + foreach (var version in versions.all.Cast()) { if (version.isFullyFetched) continue; @@ -415,14 +499,14 @@ private void UpdateExtraPackageInfos(UpmPackage package) // if the primary version is not fully fetched, trigger an extra fetch automatically right away to get results early // since the primary version's display name is used in the package list - var primaryVersion = package.primaryVersion; + var primaryVersion = versions.primary; if (!primaryVersion.isFullyFetched) ExtraFetch(primaryVersion.uniqueId); } - private static bool IsPreviewInstalled(UpmPackage package) + private static bool IsPreviewInstalled(IVersionList versions) { - return (!package.installedVersion?.HasTag(PackageTag.Release)) ?? false; + return (!versions.installed?.HasTag(PackageTag.Release)) ?? false; } private static bool IsPreviewInstalled(PackageInfo packageInfo) @@ -432,11 +516,11 @@ private static bool IsPreviewInstalled(PackageInfo packageInfo) } // check if the preview versions be filtered out if the user have `show previews` turned off - private static bool ShouldPreviewsBeRemoved(UpmPackage package) + private static bool ShouldPreviewsBeRemoved(IVersionList versions) { - if (IsPreviewInstalled(package)) + if (IsPreviewInstalled(versions)) return false; - return package.versions.Any(v => !v.HasTag(PackageTag.Release)); + return versions.all.Any(v => !v.HasTag(PackageTag.Release)); } private static void RemovePreviewVersions(UpmPackage package) @@ -449,20 +533,26 @@ private static void RemovePreviewVersions(UpmPackage package) private static bool IsDifferent(PackageInfo oldInfo, PackageInfo newInfo) { if (oldInfo.packageId != newInfo.packageId || + oldInfo.version != newInfo.version || oldInfo.source != newInfo.source || oldInfo.resolvedPath != newInfo.resolvedPath || oldInfo.isDirectDependency != newInfo.isDirectDependency || - oldInfo.entitlements.isAllowed != newInfo.entitlements.isAllowed) + oldInfo.entitlements.isAllowed != newInfo.entitlements.isAllowed || + oldInfo.name != newInfo.name || + oldInfo.category != newInfo.category || + oldInfo.displayName != newInfo.displayName || + oldInfo.description != newInfo.description) return true; - var oldVersions = oldInfo.versions.compatible; - var newVersions = newInfo.versions.compatible; - if (oldVersions.Length != newVersions.Length || !oldVersions.SequenceEqual(newVersions)) + if (oldInfo.versions.compatible.Length != newInfo.versions.compatible.Length || !oldInfo.versions.compatible.SequenceEqual(newInfo.versions.compatible)) return true; if (oldInfo.errors.Length != newInfo.errors.Length || !oldInfo.errors.SequenceEqual(newInfo.errors)) return true; + if (oldInfo.dependencies.Length != newInfo.dependencies.Length || !oldInfo.dependencies.SequenceEqual(newInfo.dependencies)) + return true; + return false; } @@ -477,7 +567,10 @@ public void OnBeforeSerialize() { m_SerializedInstalledPackageInfos = m_InstalledPackageInfos.Values.ToArray(); m_SerializedSearchPackageInfos = m_SearchPackageInfos.Values.ToArray(); + m_SerializedProductPackageInfos = m_ProductPackageInfos.Values.ToArray(); m_SerializedExtraPackageInfos = m_ExtraPackageInfo.Values.SelectMany(p => p.Values).ToArray(); + m_SerializedProductIdMapKeys = m_ProductIdMap.Keys.ToArray(); + m_SerializedProductIdMapValues = m_ProductIdMap.Values.ToArray(); } public void OnAfterDeserialize() @@ -488,8 +581,13 @@ public void OnAfterDeserialize() foreach (var p in m_SerializedSearchPackageInfos) m_SearchPackageInfos[p.name] = p; + m_ProductPackageInfos = m_SerializedProductPackageInfos.ToDictionary(p => p.name, p => p); + foreach (var p in m_SerializedExtraPackageInfos) AddExtraPackageInfo(p); + + for (var i = 0; i < m_SerializedProductIdMapKeys.Length; i++) + m_ProductIdMap[m_SerializedProductIdMapKeys[i]] = m_SerializedProductIdMapValues[i]; } public void OnEnable() @@ -501,31 +599,49 @@ public void OnEnable() SetupRemoveOperation(); } - public void Setup() + public void RegisterEvents() { - System.Diagnostics.Debug.Assert(!m_SetupDone); - m_SetupDone = true; + if (m_EventsRegistered) + return; + + m_EventsRegistered = true; PackageManagerPrefs.instance.onShowPreviewPackagesChanged += OnShowPreviewPackagesChanged; } - public void Clear() + public void UnregisterEvents() { - System.Diagnostics.Debug.Assert(m_SetupDone); - m_SetupDone = false; + if (!m_EventsRegistered) + return; + + m_EventsRegistered = false; PackageManagerPrefs.instance.onShowPreviewPackagesChanged -= OnShowPreviewPackagesChanged; } - public void Reset() + public void ClearCache() { m_InstalledPackageInfos.Clear(); m_SearchPackageInfos.Clear(); m_ExtraPackageInfo.Clear(); + m_ProductIdMap.Clear(); + m_ExtraFetchOperations.Clear(); m_SerializedInstalledPackageInfos = new PackageInfo[0]; m_SerializedSearchPackageInfos = new PackageInfo[0]; m_SerializedExtraPackageInfos = new PackageInfo[0]; + + ClearProductCache(); + } + + public void ClearProductCache() + { + m_ProductPackageInfos.Clear(); + m_ProductIdMap.Clear(); + + m_SerializedProductPackageInfos = new PackageInfo[0]; + m_SerializedProductIdMapKeys = new string[0]; + m_SerializedProductIdMapValues = new string[0]; } } } diff --git a/Modules/PackageManagerUI/Editor/Services/Upm/UpmEmbedOperation.cs b/Modules/PackageManagerUI/Editor/Services/Upm/UpmEmbedOperation.cs index e4eec82dbe..b60b006983 100644 --- a/Modules/PackageManagerUI/Editor/Services/Upm/UpmEmbedOperation.cs +++ b/Modules/PackageManagerUI/Editor/Services/Upm/UpmEmbedOperation.cs @@ -8,9 +8,10 @@ namespace UnityEditor.PackageManager.UI { internal class UpmEmbedOperation : UpmBaseOperation { - public void Embed(string packageName) + public void Embed(string packageName, string packageUniqueId = null) { m_PackageName = packageName; + m_PackageUniqueId = packageUniqueId ?? packageName; Start(); } diff --git a/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackage.cs b/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackage.cs index 2b66a50fc4..23ebc39cbe 100644 --- a/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackage.cs +++ b/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackage.cs @@ -15,91 +15,25 @@ internal class UpmPackage : IPackage public string name { get { return m_Name; } } public string uniqueId { get { return m_Name; } } - public string displayName { get { return m_Versions.First().displayName; } } - - private PackageState m_State; - public PackageState state { get { return m_State; } } + public string displayName { get { return versions.First().displayName; } } private bool m_IsDiscoverable; public bool isDiscoverable { get { return m_IsDiscoverable; } } - private List m_Versions; - public IEnumerable versions { get { return m_Versions.Cast(); } } + private UpmVersionList m_VersionList; + public IVersionList versionList => m_VersionList; - public IEnumerable keyVersions - { - get - { - // Get key versions -- Latest, Verified, LatestPatch, Installed. - var keyVersions = new HashSet(); - - var installed = installedVersion; - var latestRelease = m_Versions.LastOrDefault(v => v.HasTag(PackageTag.Release)); - var verifiedVersion = m_Versions.FirstOrDefault(v => v.HasTag(PackageTag.Verified)); - keyVersions.Add(installed); - keyVersions.Add(latestRelease); - keyVersions.Add(verifiedVersion); - keyVersions.Add(latestPatch); - keyVersions.Add(recommendedVersion); - if (installed == null && latestRelease == null) - keyVersions.Add(latestVersion); - return keyVersions.Where(v => v != null).OrderBy(package => package.version); - } - } + public IEnumerable keyVersions => versionList?.key; - // keeping the index makes it easier to find newer versions - private int m_InstalledIndex; - public IPackageVersion installedVersion { get { return m_InstalledIndex < 0 ? null : m_Versions[m_InstalledIndex]; } } + public IPackageVersion installedVersion => versionList?.installed; - public IPackageVersion latestPatch - { - get - { - if (m_InstalledIndex < 0) - return null; - - var installed = m_Versions[m_InstalledIndex].version; - for (var i = m_Versions.Count - 1; i > m_InstalledIndex; --i) - { - if (m_Versions[i].version.IsPatchOf(installed)) - return m_Versions[i]; - } - return null; - } - } + public IPackageVersion latestPatch => versionList?.latestPatch; - public IPackageVersion latestVersion { get { return m_Versions.Last(); } } + public IPackageVersion latestVersion => versionList?.latest; - public IPackageVersion recommendedVersion - { - get - { - // Override with current when it's version locked - var installed = installedVersion; - if (installed != null && installed.isVersionLocked) - return installed; - - // Only try to find recommended version in versions newer than the installed version - var newerVersions = installed == null ? m_Versions : m_Versions.Skip(m_InstalledIndex + 1).SkipWhile(v => v.version <= installed.version); - - var verifiedVersion = newerVersions.FirstOrDefault(v => v.HasTag(PackageTag.Verified)); - if (verifiedVersion != null) - return verifiedVersion; - - var latestRelease = newerVersions.LastOrDefault(v => v.HasTag(PackageTag.Release)); - if (latestRelease != null && (installed == null || !installed.HasTag(PackageTag.Verified))) - return latestRelease; - - var latestPreview = newerVersions.LastOrDefault(package => package.HasTag(PackageTag.Preview)); - if (latestPreview != null && (installed == null || installed.HasTag(PackageTag.Preview))) - return latestPreview; - - // Show current if it exists, otherwise latest user visible, and then otherwise show the absolute latest - return installed ?? latestVersion; - } - } + public IPackageVersion recommendedVersion => versionList?.recommended; - public IPackageVersion primaryVersion { get { return installedVersion ?? recommendedVersion; } } + public IPackageVersion primaryVersion => versionList?.primary; // errors on the package level (not just about a particular version) List m_UpmErrors; @@ -108,100 +42,51 @@ public IPackageVersion recommendedVersion // Stop lookup after first error encountered on a version to save time not looking up redundant errors. public IEnumerable errors => (versions.Select(v => v.errors).FirstOrDefault(e => e?.Any() ?? false) ?? new List()).Concat(m_UpmErrors); - public UpmPackage(string name, IEnumerable versions, bool isDiscoverable) - { - Initialize(name, versions, isDiscoverable); - } + public IEnumerable images => Enumerable.Empty(); - public UpmPackage(PackageInfo info, bool isInstalled, bool isDiscoverable) - { - var mainVersion = new UpmPackageVersion(info, isInstalled); + public IEnumerable links => Enumerable.Empty(); + + public IEnumerable versions => versionList?.all; - var versions = info.versions.compatible.Select(v => new UpmPackageVersion(info, false, v, mainVersion.displayName)).ToList(); - AddToSortedVersions(versions, mainVersion); - Initialize(info.name, versions, isDiscoverable); + private PackageProgress m_Progress; + public PackageProgress progress => m_Progress; + + private PackageType m_Type; + public bool Is(PackageType type) + { + return (m_Type & type) != 0; } - private void Initialize(string name, IEnumerable versions, bool isDiscoverable) + public UpmPackage(string name, bool isDiscoverable, PackageType type = PackageType.None) { + m_Progress = PackageProgress.None; m_Name = name; - m_Versions = versions.ToList(); m_IsDiscoverable = isDiscoverable; - + m_VersionList = new UpmVersionList(); m_UpmErrors = new List(); - - SetInstalledVersion(m_Versions.FindIndex(v => v.isInstalled)); + m_Type = type; } - private void SetInstalledVersion(int newInstalledIndex) + public UpmPackage(PackageInfo info, bool isInstalled, bool isDiscoverable) { - m_InstalledIndex = newInstalledIndex; - m_State = PackageState.UpToDate; - if (m_Versions.Any(v => v.errors.Any())) - m_State = PackageState.Error; - else if (m_InstalledIndex >= 0 && !recommendedVersion.isInstalled) - m_State = PackageState.Outdated; + m_Progress = PackageProgress.None; + m_Name = info.name; + m_UpmErrors = new List(); + m_IsDiscoverable = isDiscoverable; + m_VersionList = new UpmVersionList(info, isInstalled); + m_Type = primaryVersion.HasTag(PackageTag.BuiltIn) ? PackageType.BuiltIn : PackageType.Installable; } internal void UpdateVersions(IEnumerable updatedVersions) { - Initialize(name, updatedVersions, isDiscoverable); - } - - internal void UpdateVersion(UpmPackageVersion version) - { - for (var i = 0; i < m_Versions.Count; ++i) - { - if (m_Versions[i].uniqueId != version.uniqueId) - continue; - m_Versions[i] = version; - return; - } - } - - private static int AddToSortedVersions(List sortedVersions, UpmPackageVersion versionToAdd) - { - for (var i = 0; i < sortedVersions.Count; ++i) - { - if (sortedVersions[i].version.CompareByPrecedence(versionToAdd.version) < 0) - continue; - // note that the difference between this and the previous function is that - // two upm package versions could have the the same version but different package id - if (sortedVersions[i].uniqueId == versionToAdd.uniqueId) - { - sortedVersions[i] = versionToAdd; - return i; - } - sortedVersions.Insert(i, versionToAdd); - return i; - } - sortedVersions.Add(versionToAdd); - return sortedVersions.Count - 1; + m_VersionList = new UpmVersionList(updatedVersions); + m_UpmErrors.Clear(); } // This function is only used to update the object, not to actually perform the add operation public void AddInstalledVersion(UpmPackageVersion newVersion) { - if (m_InstalledIndex >= 0) - { - m_Versions[m_InstalledIndex].isInstalled = false; - if (m_Versions[m_InstalledIndex].HasTag(PackageTag.Git | PackageTag.Local | PackageTag.InDevelopment)) - m_Versions.RemoveAt(m_InstalledIndex); - } - newVersion.isInstalled = true; - SetInstalledVersion(AddToSortedVersions(m_Versions, newVersion)); - } - - // This function is only used to update the object, not to actually perform the remove operation - public void RemoveInstalledVersion() - { - if (m_InstalledIndex >= 0) - { - m_Versions[m_InstalledIndex].isInstalled = false; - if (m_Versions[m_InstalledIndex].HasTag(PackageTag.Git | PackageTag.Local | PackageTag.InDevelopment)) - m_Versions.RemoveAt(m_InstalledIndex); - SetInstalledVersion(-1); - } + m_VersionList.AddInstalledVersion(newVersion); } public void AddError(Error error) diff --git a/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackageDocs.cs b/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackageDocs.cs index f73274d070..29b69b2489 100644 --- a/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackageDocs.cs +++ b/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackageDocs.cs @@ -96,14 +96,11 @@ public static string GetDocumentationUrl(IPackageVersion version, bool offline = if (offline) return GetOfflineDocumentationUrl(upmVersion); - if (upmVersion.source == PackageSource.BuiltIn) + if (upmVersion.HasTag(PackageTag.BuiltIn) && !string.IsNullOrEmpty(upmVersion.description)) { - if (!string.IsNullOrEmpty(upmVersion.description)) - { - var split = SplitBuiltinDescription(upmVersion); - if (split.Length > 1) - return split[1]; - } + var split = SplitBuiltinDescription(upmVersion); + if (split.Length > 1) + return split[1]; } return $"http://docs.unity3d.com/Packages/{upmVersion.shortVersionId}/index.html"; } @@ -157,9 +154,19 @@ private static string GetOfflineLicensesUrl(UpmPackageVersion version) return string.Empty; } + public static bool HasDocs(IPackageVersion version) + { + return (version as UpmPackageVersion) != null; + } + public static bool HasChangelog(IPackageVersion version) { - return version != null && !version.HasTag(PackageTag.BuiltIn) && string.IsNullOrEmpty(GetPackageUrlRedirect(version)); + return (version as UpmPackageVersion) != null && !version.HasTag(PackageTag.BuiltIn) && string.IsNullOrEmpty(GetPackageUrlRedirect(version)); + } + + public static bool HasLicenses(IPackageVersion version) + { + return (version as UpmPackageVersion) != null && !version.HasTag(PackageTag.BuiltIn); } } } diff --git a/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackageVersion.cs b/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackageVersion.cs index 1b9fae77af..4e18be3174 100644 --- a/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackageVersion.cs +++ b/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackageVersion.cs @@ -7,6 +7,7 @@ using System.Globalization; using System.IO; using System.Linq; +using UnityEditorInternal; using UnityEngine; namespace UnityEditor.PackageManager.UI @@ -22,6 +23,9 @@ internal class UpmPackageVersion : IPackageVersion public string name { get { return m_PackageInfo.name; } } public string type { get { return m_PackageInfo.type; } } public string category { get { return m_PackageInfo.category; } } + + public IDictionary categoryLinks => null; + public IEnumerable errors => m_PackageInfo.errors.Concat(entitlementsError != null ? new List { entitlementsError } : new List()); public bool isDirectDependency { get { return isFullyFetched && m_PackageInfo.isDirectDependency; } } @@ -34,14 +38,14 @@ internal class UpmPackageVersion : IPackageVersion private string m_PackageId; public string uniqueId { get { return m_PackageId; } } - public string packageUniqueId { get { return name; } } - - private PackageSource m_Source; - public PackageSource source { get { return m_Source; } } + private string m_PackageUniqueId; + public string packageUniqueId => m_PackageUniqueId; private string m_Author; public string author { get { return m_Author; } } + public string authorLink => string.Empty; + private string m_DisplayName; public string displayName { get { return m_DisplayName; } } @@ -116,12 +120,10 @@ public bool isInstalled set { m_IsInstalled = value; - m_Source = m_PackageInfo.source == PackageSource.BuiltIn || m_IsInstalled ? m_PackageInfo.source : PackageSource.Registry; + RefreshTags(); } } - public bool isUserVisible { get { return isInstalled || HasTag(PackageTag.Release | PackageTag.Preview | PackageTag.Verified | PackageTag.Core); } } - private string m_Description; public string description { get { return !string.IsNullOrEmpty(m_Description) ? m_Description : m_PackageInfo.description; } } @@ -132,28 +134,13 @@ public bool HasTag(PackageTag tag) return (m_Tag & tag) != 0; } - public bool isVersionLocked - { - get { return source == PackageSource.Embedded || source == PackageSource.Git || source == PackageSource.BuiltIn; } - } + public bool isVersionLocked => HasTag(PackageTag.VersionLocked); - public bool canBeRemoved - { - get { return source != PackageSource.Unknown; } - } + public bool canBeRemoved => HasTag(PackageTag.Removable); - public bool canBeEmbedded - { - get - { - return isInstalled && isDirectDependency && (source == PackageSource.Registry || HasTag(PackageTag.Core)); - } - } + public bool canBeEmbedded => HasTag(PackageTag.Embeddable); - private bool hasPathInId - { - get { return source == PackageSource.Local || source == PackageSource.Embedded || source == PackageSource.LocalTarball; } - } + public bool installedFromPath => HasTag(PackageTag.Local | PackageTag.InDevelopment | PackageTag.Git); public bool isAvailableOnDisk { @@ -162,7 +149,8 @@ public bool isAvailableOnDisk public string shortVersionId { get { return FormatPackageId(name, version.ShortVersion()); } } - public DateTime? publishedDate { get { return m_PackageInfo.datePublished; } } + private long m_PublishedDateTicks; + public DateTime? publishedDate => m_PublishedDateTicks == 0 ? m_PackageInfo.datePublished : new DateTime(m_PublishedDateTicks, DateTimeKind.Utc); public string publisherId => m_Author; @@ -183,17 +171,14 @@ public string localPath public IEnumerable supportedVersions => Enumerable.Empty(); - public IEnumerable images => Enumerable.Empty(); - public IEnumerable sizes => Enumerable.Empty(); - public IEnumerable links => Enumerable.Empty(); - public UpmPackageVersion(PackageInfo packageInfo, bool isInstalled, SemVersion version, string displayName) { m_Version = version; m_DisplayName = displayName; m_IsInstalled = isInstalled; + m_PackageUniqueId = packageInfo.name; UpdatePackageInfo(packageInfo); } @@ -207,14 +192,19 @@ internal void UpdatePackageInfo(PackageInfo newPackageInfo) { m_IsFullyFetched = m_Version == newPackageInfo.version; m_PackageInfo = newPackageInfo; - m_Source = m_PackageInfo.source == PackageSource.BuiltIn || m_IsInstalled ? m_PackageInfo.source : PackageSource.Registry; + m_PackageUniqueId = m_PackageInfo.name; RefreshTags(); + // For core packages, or packages that are bundled with Unity without being published, use Unity's build date + m_PublishedDateTicks = 0; + if (HasTag(PackageTag.Bundled) && m_PackageInfo.datePublished == null) + m_PublishedDateTicks = new DateTime(1970, 1, 1).Ticks + InternalEditorUtility.GetUnityVersionDate() * TimeSpan.TicksPerSecond; + m_Author = string.IsNullOrEmpty(m_PackageInfo.author.name) && m_PackageInfo.name.StartsWith(k_UnityPrefix) ? "Unity Technologies Inc." : m_PackageInfo.author.name; - if (m_Source == PackageSource.BuiltIn) + if (HasTag(PackageTag.BuiltIn)) m_Description = UpmPackageDocs.SplitBuiltinDescription(this)[0]; // reset sample parse status on package info update, such that the sample list gets regenerated @@ -224,7 +214,7 @@ internal void UpdatePackageInfo(PackageInfo newPackageInfo) { m_DisplayName = GetDisplayName(m_PackageInfo); m_PackageId = m_PackageInfo.packageId; - if (hasPathInId) + if (installedFromPath) m_PackageId = m_PackageId.Replace("\\", "/"); } else @@ -233,24 +223,38 @@ internal void UpdatePackageInfo(PackageInfo newPackageInfo) } } + internal void UpdateFetchedInfo(AssetStore.FetchedInfo fetchedInfo) + { + m_PackageUniqueId = fetchedInfo.id; + + // override version info with product info + m_DisplayName = fetchedInfo.displayName; + m_Description = fetchedInfo.description; + } + private void RefreshTags() { - switch (m_Source) + // in the case of git/local packages, we always assume that the non-installed versions are from the registry + var source = m_PackageInfo.source == PackageSource.BuiltIn || m_IsInstalled ? m_PackageInfo.source : PackageSource.Registry; + switch (source) { case PackageSource.BuiltIn: - m_Tag = type.Equals("module") ? PackageTag.BuiltIn : PackageTag.Core; + m_Tag = PackageTag.Bundled | PackageTag.VersionLocked; + if (m_PackageInfo.type == "module") + m_Tag |= PackageTag.BuiltIn; break; case PackageSource.Embedded: - m_Tag = PackageTag.InDevelopment; + m_Tag = PackageTag.InDevelopment | PackageTag.VersionLocked; break; case PackageSource.Local: + case PackageSource.LocalTarball: m_Tag = PackageTag.Local; break; case PackageSource.Git: - m_Tag = PackageTag.Git; + m_Tag = PackageTag.Git | PackageTag.VersionLocked; break; case PackageSource.Unknown: @@ -260,10 +264,14 @@ private void RefreshTags() break; } + m_Tag |= PackageTag.Installable | PackageTag.Removable; + if (isInstalled && isDirectDependency && !installedFromPath && !HasTag(PackageTag.BuiltIn)) + m_Tag |= PackageTag.Embeddable; + if (m_Version.IsRelease()) { m_Tag |= PackageTag.Release; - if (m_Version == m_PackageInfo.versions.verified && !HasTag(PackageTag.InDevelopment | PackageTag.Local | PackageTag.Git)) + if (m_Version == m_PackageInfo.versions.verified && !installedFromPath) m_Tag |= PackageTag.Verified; } else diff --git a/Modules/PackageManagerUI/Editor/Services/Upm/UpmRemoveOperation.cs b/Modules/PackageManagerUI/Editor/Services/Upm/UpmRemoveOperation.cs index 6778dc34fe..63265d30db 100644 --- a/Modules/PackageManagerUI/Editor/Services/Upm/UpmRemoveOperation.cs +++ b/Modules/PackageManagerUI/Editor/Services/Upm/UpmRemoveOperation.cs @@ -10,9 +10,10 @@ namespace UnityEditor.PackageManager.UI [Serializable] internal class UpmRemoveOperation : UpmBaseOperation { - public void Remove(string packageName) + public void Remove(string packageName, string packageUniqueId = null) { m_PackageName = packageName; + m_PackageUniqueId = packageUniqueId ?? packageName; Start(); } diff --git a/Modules/PackageManagerUI/Editor/Services/Upm/UpmSearchOperation.cs b/Modules/PackageManagerUI/Editor/Services/Upm/UpmSearchOperation.cs index 820f423b87..88c7c0d90b 100644 --- a/Modules/PackageManagerUI/Editor/Services/Upm/UpmSearchOperation.cs +++ b/Modules/PackageManagerUI/Editor/Services/Upm/UpmSearchOperation.cs @@ -42,18 +42,20 @@ public void SearchAllOffline(long timestamp) Start(); } - public void Search(string packageNameOrId) + public void Search(string packageNameOrId, string productId = null) { m_OfflineMode = false; SetPackageNameOrId(packageNameOrId); + m_PackageUniqueId = productId ?? packageName; Start(); } - public void SearchOffline(string packageNameOrId, long timestamp) + public void SearchOffline(string packageNameOrId, long timestamp, string productId = null) { m_OfflineMode = true; m_Timestamp = timestamp; SetPackageNameOrId(packageNameOrId); + m_PackageUniqueId = productId ?? packageName; Start(); } diff --git a/Modules/PackageManagerUI/Editor/Services/Upm/UpmVersionList.cs b/Modules/PackageManagerUI/Editor/Services/Upm/UpmVersionList.cs new file mode 100644 index 0000000000..8ea6d47f89 --- /dev/null +++ b/Modules/PackageManagerUI/Editor/Services/Upm/UpmVersionList.cs @@ -0,0 +1,152 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace UnityEditor.PackageManager.UI +{ + [Serializable] + internal class UpmVersionList : IVersionList + { + private List m_Versions; + public IEnumerable all => m_Versions.Cast(); + + public IEnumerable key + { + get + { + // Get key versions -- Latest, Verified, LatestPatch, Installed. + var keyVersions = new HashSet(); + + var installed = this.installed; + var latestRelease = m_Versions.LastOrDefault(v => v.HasTag(PackageTag.Release)); + var verifiedVersion = m_Versions.FirstOrDefault(v => v.HasTag(PackageTag.Verified)); + keyVersions.Add(installed); + keyVersions.Add(latestRelease); + keyVersions.Add(verifiedVersion); + keyVersions.Add(latestPatch); + keyVersions.Add(recommended); + if (installed == null && latestRelease == null) + keyVersions.Add(latest); + return keyVersions.Where(v => v != null).OrderBy(package => package.version); + } + } + + private int m_InstalledIndex; + public IPackageVersion installed { get { return m_InstalledIndex < 0 ? null : m_Versions[m_InstalledIndex]; } } + + public IPackageVersion latestPatch + { + get + { + if (m_InstalledIndex < 0) + return null; + + var installed = m_Versions[m_InstalledIndex].version; + for (var i = m_Versions.Count - 1; i > m_InstalledIndex; --i) + { + if (m_Versions[i].version.IsPatchOf(installed)) + return m_Versions[i]; + } + return null; + } + } + + public IPackageVersion latest => m_Versions.Last(); + + public IPackageVersion recommended + { + get + { + // Override with current when it's version locked + var installed = this.installed; + if (installed?.HasTag(PackageTag.VersionLocked) ?? false) + return installed; + + // Only try to find recommended version in versions newer than the installed version + var newerVersions = installed == null ? m_Versions : m_Versions.Skip(m_InstalledIndex + 1).SkipWhile(v => v.version <= installed.version); + + var verifiedVersion = newerVersions.FirstOrDefault(v => v.HasTag(PackageTag.Verified)); + if (verifiedVersion != null) + return verifiedVersion; + + var latestRelease = newerVersions.LastOrDefault(v => v.HasTag(PackageTag.Release)); + if (latestRelease != null && (installed == null || !installed.HasTag(PackageTag.Verified))) + return latestRelease; + + var latestPreview = newerVersions.LastOrDefault(package => package.HasTag(PackageTag.Preview)); + if (latestPreview != null && (installed == null || installed.HasTag(PackageTag.Preview))) + return latestPreview; + + // Show current if it exists, otherwise latest user visible, and then otherwise show the absolute latest + return installed ?? latest; + } + } + + public IPackageVersion primary => installed ?? recommended; + + public IPackageVersion importAvailable => null; + + internal void UpdateVersion(UpmPackageVersion version) + { + for (var i = 0; i < m_Versions.Count; ++i) + { + if (m_Versions[i].uniqueId != version.uniqueId) + continue; + m_Versions[i] = version; + return; + } + } + + // This function is only used to update the object, not to actually perform the add operation + public void AddInstalledVersion(UpmPackageVersion newVersion) + { + if (m_InstalledIndex >= 0) + { + m_Versions[m_InstalledIndex].isInstalled = false; + if (m_Versions[m_InstalledIndex].installedFromPath) + m_Versions.RemoveAt(m_InstalledIndex); + } + newVersion.isInstalled = true; + m_InstalledIndex = AddToSortedVersions(m_Versions, newVersion); + } + + private static int AddToSortedVersions(List sortedVersions, UpmPackageVersion versionToAdd) + { + for (var i = 0; i < sortedVersions.Count; ++i) + { + if (sortedVersions[i].version.CompareByPrecedence(versionToAdd.version) < 0) + continue; + // note that the difference between this and the previous function is that + // two upm package versions could have the the same version but different package id + if (sortedVersions[i].uniqueId == versionToAdd.uniqueId) + { + sortedVersions[i] = versionToAdd; + return i; + } + sortedVersions.Insert(i, versionToAdd); + return i; + } + sortedVersions.Add(versionToAdd); + return sortedVersions.Count - 1; + } + + public UpmVersionList(IEnumerable versions = null) + { + m_Versions = versions?.ToList() ?? new List(); + m_InstalledIndex = m_Versions.FindIndex(v => v.isInstalled); + } + + public UpmVersionList(PackageInfo info, bool isInstalled) + { + var mainVersion = new UpmPackageVersion(info, isInstalled); + m_Versions = info.versions.compatible.Select(v => new UpmPackageVersion(info, false, v, mainVersion.displayName)).ToList(); + AddToSortedVersions(m_Versions, mainVersion); + + m_InstalledIndex = m_Versions.FindIndex(v => v.isInstalled); + } + } +} diff --git a/Modules/PackageManagerUI/Editor/UI/Common/DictionaryExtensions.cs b/Modules/PackageManagerUI/Editor/UI/Common/DictionaryExtensions.cs new file mode 100644 index 0000000000..c0209ec1b1 --- /dev/null +++ b/Modules/PackageManagerUI/Editor/UI/Common/DictionaryExtensions.cs @@ -0,0 +1,51 @@ +// 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.Linq; + +namespace UnityEditor.PackageManager.UI +{ + internal static class DictionaryExtensions + { + public static T Get(this IDictionary dict, string key) where T : class + { + object result; + return dict.TryGetValue(key, out result) ? result as T : null; + } + + public static T Get(this IDictionary dict, string key, T fallbackValue = default(T)) where T : struct + { + object result; + return dict.TryGetValue(key, out result) ? (T)result : fallbackValue; + } + + public static T Get(this IDictionary dict, string key) where T : class + { + T result; + return dict.TryGetValue(key, out result) ? result : null; + } + + public static T Get(this IDictionary dict, string key, T fallbackValue = default(T)) where T : struct + { + T result; + return dict.TryGetValue(key, out result) ? result : fallbackValue; + } + + public static IDictionary GetDictionary(this IDictionary dict, string key) + { + return Get>(dict, key); + } + + public static IEnumerable GetList(this IDictionary dict, string key) + { + return Get>(dict, key)?.OfType(); + } + + public static string GetString(this IDictionary dict, string key) + { + return Get(dict, key); + } + } +} diff --git a/Modules/PackageManagerUI/Editor/UI/Common/Page.cs b/Modules/PackageManagerUI/Editor/UI/Common/Page.cs index faf3e53234..8e819c548a 100644 --- a/Modules/PackageManagerUI/Editor/UI/Common/Page.cs +++ b/Modules/PackageManagerUI/Editor/UI/Common/Page.cs @@ -224,8 +224,8 @@ public void RebuildList(IEnumerable addOrUpdateList = null, IEnumerabl m_PackageVisualStateLookup = newLookupTable; } - if (addOrUpdateList != null && removeList != null) - onPageUpdate?.Invoke(addOrUpdateList, removeList); + if (addOrUpdateList != null || removeList != null) + onPageUpdate?.Invoke(addOrUpdateList ?? Enumerable.Empty(), removeList ?? Enumerable.Empty()); FilterBySearchText(); } @@ -291,8 +291,24 @@ public void OnProductListFetched(ProductList productList, bool fetchDetailsCalle return; } + var rebuildList = PackageFiltering.instance.currentFilterTab == PackageFilterTab.AssetStore; + + HashSet removed = null; + List added = null; if (productList.startIndex == 0) { + if (rebuildList) + { + removed = new HashSet(targetList.list); + added = new List(); + foreach (var id in productList.list) + { + if (removed.Contains(id)) + removed.Remove(id); + else + added.Add(id); + } + } // override the result if the new list starts from index 0 (meaning it's a refresh) targetList.list = productList.list; targetList.total = productList.total; @@ -302,6 +318,8 @@ public void OnProductListFetched(ProductList productList, bool fetchDetailsCalle { // append the result if it is the next page targetList.list.AddRange(productList.list); + if (rebuildList) + added = productList.list; } else { @@ -316,10 +334,12 @@ public void OnProductListFetched(ProductList productList, bool fetchDetailsCalle if (!fetchDetailsCalled && productList.list.Any()) AssetStore.AssetStoreClient.instance.FetchDetails(productList.list); - if (PackageFiltering.instance.currentFilterTab == PackageFilterTab.AssetStore) + if (rebuildList) { m_IsAlreadyFetched = true; - RebuildList(); + var addedPackages = added?.Select(id => PackageDatabase.instance.GetPackage(id.ToString())); + var removedPackages = removed?.Select(id => PackageDatabase.instance.GetPackage(id.ToString())); + RebuildList(addedPackages, removedPackages); } } diff --git a/Modules/PackageManagerUI/Editor/UI/Common/PageManager.cs b/Modules/PackageManagerUI/Editor/UI/Common/PageManager.cs index 5202a0fbd4..a34e0ae9c7 100644 --- a/Modules/PackageManagerUI/Editor/UI/Common/PageManager.cs +++ b/Modules/PackageManagerUI/Editor/UI/Common/PageManager.cs @@ -30,10 +30,17 @@ internal class PageManagerInternal : ScriptableSingleton, I [SerializeField] private Page[] m_SerializedPages = new Page[0]; + [NonSerialized] + private bool m_EventsRegistered; + + [SerializeField] + private bool m_Initialized; + public bool isInitialized => m_Initialized; + [MenuItem("internal:Packages/Reset Package Database")] public static void ResetPackageDatabase() { - instance.Reset(); + instance.Reload(); instance.Refresh(RefreshOptions.All | RefreshOptions.Purchased); } @@ -209,6 +216,9 @@ public void Refresh(PackageFilterTab tab) public void Refresh(RefreshOptions options) { + // make sure the events are registered before actually calling the actual refresh functions + // such that we don't lose any callbacks events + RegisterEvents(); if ((options & RefreshOptions.CurrentFilter) != 0) options |= GetRefreshOptionsFromFilterTab(PackageFiltering.instance.currentFilterTab); @@ -223,7 +233,7 @@ public void Refresh(RefreshOptions options) if ((options & RefreshOptions.Purchased) != 0) AssetStore.AssetStoreClient.instance.List(0, k_DefaultPageSize, string.Empty); if ((options & RefreshOptions.PurchasedOffline) != 0) - AssetStore.AssetStoreClient.instance.Refresh(PackageDatabase.instance.assetStorePackages); + AssetStore.AssetStoreClient.instance.RefreshLocal(); } public void Fetch(string uniqueId) @@ -248,7 +258,18 @@ private void OnUserLoginStateChange(bool loggedIn) public void Setup() { - PackageDatabase.instance.Setup(); + m_Initialized = true; + RegisterEvents(); + } + + public void RegisterEvents() + { + if (m_EventsRegistered) + return; + + m_EventsRegistered = true; + + PackageDatabase.instance.RegisterEvents(); AssetStore.AssetStoreClient.instance.onProductListFetched += OnProductListFetched; AssetStore.AssetStoreClient.instance.onProductFetched += OnProductFetched; @@ -263,8 +284,13 @@ public void Setup() ApplicationUtil.instance.onUserLoginStateChange += OnUserLoginStateChange; } - public void Clear() + public void UnregisterEvents() { + if (!m_EventsRegistered) + return; + + m_EventsRegistered = false; + AssetStore.AssetStoreClient.instance.onProductListFetched -= OnProductListFetched; AssetStore.AssetStoreClient.instance.onProductFetched -= OnProductFetched; @@ -277,12 +303,12 @@ public void Clear() ApplicationUtil.instance.onUserLoginStateChange -= OnUserLoginStateChange; - PackageDatabase.instance.Clear(); + PackageDatabase.instance.UnregisterEvents(); } - internal void Reset() + internal void Reload() { - Clear(); + UnregisterEvents(); foreach (var page in m_Pages.Values) { @@ -292,9 +318,9 @@ internal void Reset() } m_Pages.Clear(); - PackageDatabase.instance.Reset(); + PackageDatabase.instance.Reload(); - Setup(); + RegisterEvents(); } } } diff --git a/Modules/PackageManagerUI/Editor/UI/Common/UIUtils.cs b/Modules/PackageManagerUI/Editor/UI/Common/UIUtils.cs index aaaabc2a33..bdca351c54 100644 --- a/Modules/PackageManagerUI/Editor/UI/Common/UIUtils.cs +++ b/Modules/PackageManagerUI/Editor/UI/Common/UIUtils.cs @@ -43,10 +43,14 @@ public static void ScrollIfNeeded(ScrollView container, VisualElement target) if (target == null || container == null) return; - var minY = container.worldBound.yMin; - var maxY = container.worldBound.yMax; - var itemMinY = target.worldBound.yMin; - var itemMaxY = target.worldBound.yMax; + var containerWorldBound = container.worldBound; + var targetWorldBound = target.worldBound; + + var minY = containerWorldBound.yMin; + var maxY = containerWorldBound.yMax; + var itemMinY = targetWorldBound.yMin; + var itemMaxY = targetWorldBound.yMax; + var scroll = container.scrollOffset; if (itemMinY < minY) diff --git a/Modules/PackageManagerUI/Editor/UI/Interfaces/IPageManager.cs b/Modules/PackageManagerUI/Editor/UI/Interfaces/IPageManager.cs index 7c4cb66d78..905c56de03 100644 --- a/Modules/PackageManagerUI/Editor/UI/Interfaces/IPageManager.cs +++ b/Modules/PackageManagerUI/Editor/UI/Interfaces/IPageManager.cs @@ -29,6 +29,8 @@ internal enum RefreshOptions : uint internal interface IPageManager { + bool isInitialized { get; } + event Action onSelectionChanged; // arg1: the updated page, arg2: packages added/updated in the page, arg3: packages removed from the page @@ -51,7 +53,9 @@ internal interface IPageManager void Setup(); - void Clear(); + void RegisterEvents(); + + void UnregisterEvents(); void Refresh(PackageFilterTab tab); diff --git a/Modules/PackageManagerUI/Editor/UI/PackageDependencies.cs b/Modules/PackageManagerUI/Editor/UI/PackageDependencies.cs index c3172d6629..7b295ba659 100644 --- a/Modules/PackageManagerUI/Editor/UI/PackageDependencies.cs +++ b/Modules/PackageManagerUI/Editor/UI/PackageDependencies.cs @@ -51,7 +51,10 @@ private string BuildStatusText(DependencyInfo dependency) public void SetDependencies(DependencyInfo[] dependencies) { - if (dependencies == null || dependencies.Length == 0) + var showDependency = PackageManagerPrefs.instance.showPackageDependencies && dependencies != null; + UIUtils.SetElementDisplay(this, showDependency); + + if (!showDependency || dependencies.Length == 0) { ClearDependencies(); return; diff --git a/Modules/PackageManagerUI/Editor/UI/PackageDetails.cs b/Modules/PackageManagerUI/Editor/UI/PackageDetails.cs index 9315ff9e85..2161ed105f 100644 --- a/Modules/PackageManagerUI/Editor/UI/PackageDetails.cs +++ b/Modules/PackageManagerUI/Editor/UI/PackageDetails.cs @@ -71,7 +71,6 @@ internal enum PackageAction PackageTag.Local, PackageTag.Git, PackageTag.Preview, - PackageTag.AssetStore, PackageTag.Deprecated }; @@ -113,7 +112,7 @@ public PackageDetails() detailDesc.RegisterCallback(DescriptionGeometryChangeEvent); - GetTagLabel(PackageTag.Verified).text = ApplicationUtil.instance.shortUnityVersion + " verified"; + GetTagLabel(PackageTag.Verified.ToString()).text = ApplicationUtil.instance.shortUnityVersion + " verified"; } private void OnEditorSelectionChanged() @@ -158,7 +157,7 @@ public void OnEnable() PageManager.instance.onPageRebuild += page => OnSelectionChanged(PageManager.instance.GetSelectedVersion()); PageManager.instance.onSelectionChanged += OnSelectionChanged; - PackageManagerPrefs.instance.onShowDependenciesChanged += (value) => RefreshDependencies(value); + PackageManagerPrefs.instance.onShowDependenciesChanged += (value) => RefreshDependencies(); // manually call the callback function once on initialization to refresh the UI OnSelectionChanged(PageManager.instance.GetSelectedVersion()); @@ -214,10 +213,8 @@ internal void OnSelectionChanged(IPackageVersion version) SetPackage(null); } - private void RefreshDependencies(bool? dependenciesVisibility = null) + private void RefreshDependencies() { - if (dependenciesVisibility != null) - UIUtils.SetElementDisplay(dependencies, (bool)dependenciesVisibility); dependencies.SetDependencies(displayVersion?.dependencies); } @@ -252,15 +249,14 @@ private void SetDisplayVersion(IPackageVersion version) } else { - var isBuiltIn = displayVersion.HasTag(PackageTag.BuiltIn); - var isAssetStorePackage = displayVersion.HasTag(PackageTag.AssetStore); + var isBuiltIn = package.Is(PackageType.BuiltIn); SetUpdateVisibility(true); detailTitle.text = displayVersion.displayName; - UIUtils.SetElementDisplay(detailNameContainer, !isAssetStorePackage); - detailName.text = displayVersion.name; + UIUtils.SetElementDisplay(detailNameContainer, !string.IsNullOrEmpty(package.name)); + detailName.text = package.name; RefreshLinks(); @@ -269,15 +265,16 @@ private void SetDisplayVersion(IPackageVersion version) RefreshCategories(); detailVersion.text = $"Version {displayVersion.version.StripTag()}"; - if (isAssetStorePackage && displayVersion.versionString != displayVersion.version.ToString()) + if (displayVersion.versionString != displayVersion.version.ToString()) { detailVersion.text = $"Version {displayVersion.versionString}"; } foreach (var tag in k_VisibleTags) - UIUtils.SetElementDisplay(GetTagLabel(tag), displayVersion.HasTag(tag)); + UIUtils.SetElementDisplay(GetTagLabel(tag.ToString()), displayVersion.HasTag(tag)); + UIUtils.SetElementDisplay(GetTagLabel(PackageType.AssetStore.ToString()), package.Is(PackageType.AssetStore)); - UIUtils.SetElementDisplay(editButton, displayVersion.isInstalled && !displayVersion.HasTag(PackageTag.BuiltIn)); + UIUtils.SetElementDisplay(editButton, displayVersion.isInstalled && !isBuiltIn); sampleList.SetPackageVersion(displayVersion); @@ -290,7 +287,7 @@ private void SetDisplayVersion(IPackageVersion version) UIUtils.SetElementDisplay(customContainer, true); RefreshExtensions(displayVersion); - RefreshDependencies(PackageManagerPrefs.instance.showPackageDependencies); + RefreshDependencies(); RefreshSupportedUnityVersions(); @@ -311,12 +308,12 @@ private void SetDisplayVersion(IPackageVersion version) private void DescriptionGeometryChangeEvent(GeometryChangedEvent evt) { - if (displayVersion == null) + // only hide lengthly description when there are images to be displayed + if (!(package?.images.Any() ?? false)) return; - var isAssetStorePackage = displayVersion.HasTag(PackageTag.AssetStore); var lineHeight = detailDesc.computedStyle.unityFont.value.lineHeight + 2; - if (isAssetStorePackage && !UIUtils.IsElementVisible(detailDescMore) && !UIUtils.IsElementVisible(detailDescLess) && + if (!UIUtils.IsElementVisible(detailDescMore) && !UIUtils.IsElementVisible(detailDescLess) && evt.newRect.height > lineHeight * 4) { UIUtils.SetElementDisplay(detailDescMore, true); @@ -340,8 +337,7 @@ private void RefreshAuthor() UIUtils.SetElementDisplay(detailAuthorContainer, !string.IsNullOrEmpty(displayVersion.author)); if (!string.IsNullOrEmpty(displayVersion.author)) { - var isAssetStorePackage = displayVersion.HasTag(PackageTag.AssetStore); - if (isAssetStorePackage) + if (!string.IsNullOrEmpty(displayVersion.authorLink)) { UIUtils.SetElementDisplay(detailAuthorText, false); UIUtils.SetElementDisplay(detailAuthorLink, true); @@ -358,32 +354,11 @@ private void RefreshAuthor() private void RefreshPublishedDate() { - var isAssetStorePackage = displayVersion.HasTag(PackageTag.AssetStore); - if (isAssetStorePackage) + // If the package details is not enabled, don't update the date yet as we are fetching new information + if (enabledSelf) { - detailDate.text = (displayVersion.publishedDate != null) ? $"{displayVersion.publishedDate.Value:MMMM dd, yyyy}" : string.Empty; - } - else - { - // If the package details is not enabled, don't update the date yet as we are fetching new information - if (enabledSelf) - { - detailDate.text = string.Empty; - - // In Development packages are not published, so we do not show any published date - if (displayVersion != null && !displayVersion.HasTag(PackageTag.InDevelopment)) - { - if (displayVersion.publishedDate != null) - detailDate.text = $"{displayVersion.publishedDate.Value:MMMM dd, yyyy}"; - else if (displayVersion.HasTag(PackageTag.Core) || displayVersion.isInstalled) - { - // For core packages, or installed packages that are bundled with Unity without being published, use Unity's build date - var unityBuildDate = new DateTime(1970, 1, 1, 0, 0, 0, 0); - unityBuildDate = unityBuildDate.AddSeconds(InternalEditorUtility.GetUnityVersionDate()); - detailDate.text = $"{unityBuildDate:MMMM dd, yyyy}"; - } - } - } + var dt = displayVersion.publishedDate ?? DateTime.Now; + detailDate.text = displayVersion.publishedDate != null ? dt.ToString("MMMM dd, yyyy", CultureInfo.CreateSpecificCulture("en-US")) : string.Empty; } UIUtils.SetElementDisplay(detailDateContainer, !string.IsNullOrEmpty(detailDate.text)); @@ -391,67 +366,73 @@ private void RefreshPublishedDate() private void RefreshCategories() { - var isAssetStorePackage = displayVersion.HasTag(PackageTag.AssetStore); - UIUtils.SetElementDisplay(detailCategories, isAssetStorePackage && !string.IsNullOrEmpty(displayVersion.category)); - detailCategories.Clear(); + var categoryLinks = displayVersion?.categoryLinks; + UIUtils.SetElementDisplay(detailCategories, categoryLinks != null); - if (isAssetStorePackage) + detailCategories.Clear(); + if (categoryLinks != null) { - var categories = displayVersion.category.Split('/'); - var parentCategory = "/"; - foreach (var category in categories) + foreach (var item in categoryLinks) { - var lower = category.ToLower(CultureInfo.InvariantCulture); - var url = $"{AssetStoreUtils.instance.assetStoreUrl}{parentCategory}{lower}"; + var category = item.Key; + var url = item.Value; detailCategories.Add(new Button(() => { ApplicationUtil.instance.OpenURL(url); }) - {text = category, classList = {"category", "unity-button", "link"}}); - parentCategory += lower + "/"; + { text = category, classList = { "category", "unity-button", "link" } }); } } } private void RefreshLinks() { - var isBuiltIn = displayVersion.HasTag(PackageTag.BuiltIn); - var isAssetStorePackage = displayVersion.HasTag(PackageTag.AssetStore); detailLinks.Clear(); - if (!isAssetStorePackage) - { - UIUtils.SetElementDisplay(detailLinksContainer, true); - detailLinks.Add(new Button(ViewDocClick) { text = "View documentation", classList = { "unity-button", "link" }}); - - if (UpmPackageDocs.HasChangelog(displayVersion)) - detailLinks.Add(new Button(ViewChangelogClick) { text = "View changelog", classList = { "unity-button", "link" } }); - - if (!isBuiltIn) - detailLinks.Add(new Button(ViewLicensesClick) { text = "View licenses", classList = { "unity-button", "link" } }); - } - else + // add links from the package + foreach (var link in package.links) { - UIUtils.SetElementDisplay(detailLinksContainer, displayVersion.links.Any()); - foreach (var link in displayVersion.links) + detailLinks.Add(new Button(() => { ApplicationUtil.instance.OpenURL(link.url); }) { - var tooltip = link.url; - var url = link.url; - if (!url.StartsWith("http:", StringComparison.InvariantCulture) && !url.StartsWith("https:", StringComparison.InvariantCulture)) - { - url = AssetStoreUtils.instance.assetStoreUrl + url; - tooltip = string.Empty; - } - detailLinks.Add(new Button(() => { ApplicationUtil.instance.OpenURL(url); }) - { - text = link.name, - tooltip = tooltip, - classList = { "unity-button", "link" } - }); - } + text = link.name, + tooltip = link.url, + classList = { "unity-button", "link" } + }); } + + // add links related to the upm version + if (UpmPackageDocs.HasDocs(displayVersion)) + detailLinks.Add(new Button(ViewDocClick) { text = "View documentation", classList = { "unity-button", "link" } }); + + if (UpmPackageDocs.HasChangelog(displayVersion)) + detailLinks.Add(new Button(ViewChangelogClick) { text = "View changelog", classList = { "unity-button", "link" } }); + + if (UpmPackageDocs.HasLicenses(displayVersion)) + detailLinks.Add(new Button(ViewLicensesClick) { text = "View licenses", classList = { "unity-button", "link" } }); + + UIUtils.SetElementDisplay(detailLinksContainer, detailLinks.childCount != 0); } private void RefreshSupportedUnityVersions() { - UIUtils.SetElementDisplay(detailUnityVersionsContainer, displayVersion.supportedVersion != null); - detailUnityVersions.text = $"{displayVersion.supportedVersion} or higher"; + var supportedVersion = displayVersion.supportedVersions?.FirstOrDefault(); + if (supportedVersion == null) + supportedVersion = displayVersion.supportedVersion; + + UIUtils.SetElementDisplay(detailUnityVersionsContainer, supportedVersion != null); + if (supportedVersion != null) + { + detailUnityVersions.text = $"{supportedVersion} or higher"; + var tooltip = supportedVersion.ToString(); + if (displayVersion.supportedVersions != null && displayVersion.supportedVersions.Any()) + { + var versions = displayVersion.supportedVersions.Select(version => version.ToString()).ToArray(); + tooltip = versions.Length == 1 ? versions[0] : + $"{string.Join(", ", versions, 0, versions.Length - 1)} and {versions[versions.Length - 1]} to improve compatibility with the range of these versions of Unity"; + } + detailUnityVersions.tooltip = $"Package has been submitted using Unity {tooltip}."; + } + else + { + detailUnityVersions.text = string.Empty; + detailUnityVersions.tooltip = string.Empty; + } } private void RefreshSizeInfo() @@ -483,16 +464,16 @@ private void ClearSupportingImages() private void RefreshSupportingImages() { - UIUtils.SetElementDisplay(detailImagesContainer, displayVersion.images.Any()); + UIUtils.SetElementDisplay(detailImagesContainer, package.images.Any()); ClearSupportingImages(); if (s_LoadingTexture == null) s_LoadingTexture = (Texture2D)EditorGUIUtility.LoadRequired("Icons/UnityLogo.png"); long id; - if (long.TryParse(displayVersion.uniqueId, out id)) + if (long.TryParse(package.uniqueId, out id)) { - foreach (var packageImage in displayVersion.images) + foreach (var packageImage in package.images) { var image = new Label {classList = {"image"}}; @@ -507,7 +488,7 @@ private void RefreshSupportingImages() AssetStoreDownloadOperation.instance.DownloadImageAsync(id, packageImage.thumbnailUrl, (retId, texture) => { - if (retId.ToString() == displayVersion?.uniqueId) + if (retId.ToString() == package?.uniqueId) { texture.hideFlags = HideFlags.HideAndDontSave; image.style.backgroundImage = texture; @@ -521,7 +502,16 @@ public void SetPackage(IPackage package, IPackageVersion version = null) { version = version ?? package?.primaryVersion; this.package = package; - ShowVersion(version); + + SetEnabled(true); + + if (version != null && !version.isFullyFetched) + { + SetEnabled(false); + PackageDatabase.instance.FetchExtraInfo(version); + } + + SetDisplayVersion(version); } internal void OnPackagesUpdated(IEnumerable updatedPackages) @@ -541,19 +531,6 @@ internal void OnPackagesUpdated(IEnumerable updatedPackages) } } - private void ShowVersion(IPackageVersion version) - { - SetEnabled(true); - - if (version != null && !version.isFullyFetched) - { - SetEnabled(false); - PackageDatabase.instance.FetchExtraInfo(version); - } - - SetDisplayVersion(version); - } - private void RefreshErrorDisplay() { var error = displayVersion?.errors?.FirstOrDefault() ?? package?.errors?.FirstOrDefault(); @@ -581,7 +558,8 @@ private void RefreshAddButton() { var installed = package?.installedVersion; var targetVersion = this.targetVersion; - var visibleFlag = !(installed?.isVersionLocked ?? false) && displayVersion != null && !displayVersion.HasTag(PackageTag.AssetStore) && targetVersion != null; + var installable = targetVersion?.HasTag(PackageTag.Installable) ?? false; + var visibleFlag = !(installed?.HasTag(PackageTag.VersionLocked) ?? false) && displayVersion != null && installable; if (visibleFlag) { var installInProgress = PackageDatabase.instance.IsInstallInProgress(displayVersion); @@ -608,7 +586,7 @@ private void RefreshAddButton() private void RefreshRemoveButton() { - var visibleFlag = displayVersion?.canBeRemoved ?? false; + var visibleFlag = displayVersion?.HasTag(PackageTag.Removable) ?? false; if (visibleFlag) { var installed = package?.installedVersion; @@ -629,34 +607,36 @@ private void RefreshImportAndDownloadButtons() if (displayVersion == null) return; - var isAssetStore = displayVersion.HasTag(PackageTag.AssetStore); - if (!isAssetStore) - { - UIUtils.SetElementDisplay(importButton, false); - UIUtils.SetElementDisplay(downloadButton, false); - downloadProgress.Hide(); - return; - } - - importButton.text = GetButtonText(PackageAction.Import); - - var progress = PackageDatabase.instance.GetDownloadProgress(displayVersion); - var downloadInProgress = progress != null && (progress.state == DownloadProgress.State.InProgress || progress.state == DownloadProgress.State.Started); - downloadButton.text = GetButtonText(package.state == PackageState.Outdated ? PackageAction.Upgrade : PackageAction.Download, downloadInProgress); - var enableButton = !ApplicationUtil.instance.isCompiling; + var downloadInProgress = false; - UIUtils.SetElementDisplay(importButton, true); - importButton.SetEnabled(enableButton && displayVersion.isAvailableOnDisk); - - UIUtils.SetElementDisplay(downloadButton, true); - var notOnDiskOrUpgradeAvailable = !displayVersion.isAvailableOnDisk || package.state == PackageState.Outdated; - downloadButton.SetEnabled(enableButton && notOnDiskOrUpgradeAvailable); - - if (downloadInProgress) - downloadProgress.SetProgress(progress.total == 0 ? 0 : progress.current / (float)progress.total); + var downloadable = displayVersion.HasTag(PackageTag.Downloadable); + UIUtils.SetElementDisplay(downloadButton, downloadable); + if (downloadable && downloadInProgress) + downloadProgress.Show(); else downloadProgress.Hide(); + if (downloadable) + { + var progress = PackageDatabase.instance.GetDownloadProgress(displayVersion); + var state = package.GetState(); + downloadInProgress = progress != null && (progress.state == DownloadProgress.State.InProgress || progress.state == DownloadProgress.State.Started); + downloadButton.text = GetButtonText(state == PackageState.Outdated ? PackageAction.Upgrade : PackageAction.Download, downloadInProgress); + + var enableDownloadButton = !displayVersion.isAvailableOnDisk || state == PackageState.InProgress || state == PackageState.Outdated; + downloadButton.SetEnabled(enableButton && enableDownloadButton); + + if (downloadInProgress) + downloadProgress.SetProgress(progress.total == 0 ? 0 : progress.current / (float)progress.total); + } + + var importable = displayVersion.HasTag(PackageTag.Importable); + UIUtils.SetElementDisplay(importButton, importable); + if (importable) + { + importButton.text = GetButtonText(PackageAction.Import); + importButton.SetEnabled(enableButton && displayVersion.isAvailableOnDisk); + } } private string GetButtonText(PackageAction action, bool inProgress = false, SemVersion version = null) @@ -681,10 +661,9 @@ private void DescLessClick() private void AuthorClick() { - if (displayVersion == null || !displayVersion.HasTag(PackageTag.AssetStore)) - return; - - ApplicationUtil.instance.OpenURL($"{AssetStoreUtils.instance.assetStoreUrl}/publishers/{displayVersion.publisherId}"); + var authorLink = displayVersion?.authorLink ?? string.Empty; + if (!string.IsNullOrEmpty(authorLink)) + ApplicationUtil.instance.OpenURL(authorLink); } private void UpdateClick() @@ -878,6 +857,8 @@ private void ImportClick() { PackageDatabase.instance.Import(package); RefreshImportAndDownloadButtons(); + + PackageManagerWindowAnalytics.SendEvent("import", package.uniqueId); } private void DownloadOrCancelClick() @@ -896,6 +877,9 @@ private void DownloadOrCancelClick() PackageDatabase.instance.Download(package); RefreshImportAndDownloadButtons(); + + var eventName = downloadInProgress ? "abortDownload" : "startDownload"; + PackageManagerWindowAnalytics.SendEvent(eventName, package.uniqueId); } private VisualElementCache cache { get; set; } @@ -936,6 +920,6 @@ private void DownloadOrCancelClick() private VisualElement detailSizes { get { return cache.Get("detailSizes"); } } private VisualElement detailImagesContainer { get { return cache.Get("detailImagesContainer"); } } private VisualElement detailImages { get { return cache.Get("detailImages"); } } - internal Label GetTagLabel(PackageTag tag) { return cache.Get