diff --git a/Editor/Mono/BuildProfile/BuildProfile.cs b/Editor/Mono/BuildProfile/BuildProfile.cs
index fe8868c1c6..2b5dbf9d1d 100644
--- a/Editor/Mono/BuildProfile/BuildProfile.cs
+++ b/Editor/Mono/BuildProfile/BuildProfile.cs
@@ -22,6 +22,12 @@ namespace UnityEditor.Build.Profile
[HelpURL("build-profiles-reference")]
public sealed partial class BuildProfile : ScriptableObject
{
+ ///
+ /// Asset Schema Version
+ ///
+ [SerializeField]
+ uint m_AssetVersion = 1;
+
///
/// Build Target used to fetch module and build profile extension.
///
@@ -78,6 +84,17 @@ internal BuildProfilePlatformSettingsBase platformBuildProfile
set => m_PlatformBuildProfile = value;
}
+ ///
+ /// When set, this build profiles used when building.
+ ///
+ ///
+ [SerializeField] private bool m_OverrideGlobalSceneList = false;
+ internal bool overrideGlobalSceneList
+ {
+ get => m_OverrideGlobalSceneList;
+ set => m_OverrideGlobalSceneList = value;
+ }
+
///
/// List of scenes specified in the build profile.
///
@@ -97,7 +114,7 @@ public EditorBuildSettingsScene[] scenes
m_Scenes = value;
CheckSceneListConsistency();
- if (this == BuildProfileContext.activeProfile)
+ if (this == BuildProfileContext.activeProfile && m_OverrideGlobalSceneList)
EditorBuildSettings.SceneListChanged();
}
}
@@ -202,7 +219,7 @@ void OnEnable()
void OnDisable()
{
- if (IsActiveBuildProfileOrPlatform())
+ if (BuildProfileContext.activeProfile == this)
EditorUserBuildSettings.SetActiveProfileScriptingDefines(m_ScriptingDefines);
var playerSettingsDirty = EditorUtility.IsDirty(m_PlayerSettings);
diff --git a/Editor/Mono/BuildTargetDiscovery.bindings.cs b/Editor/Mono/BuildTargetDiscovery.bindings.cs
index ac8825c16e..85552ce76b 100644
--- a/Editor/Mono/BuildTargetDiscovery.bindings.cs
+++ b/Editor/Mono/BuildTargetDiscovery.bindings.cs
@@ -349,7 +349,6 @@ internal static bool DoesBuildTargetSupportSinglePassStereoRendering(BuildTarget
s_platform_43,
s_platform_45,
s_platform_46,
- s_platform_47,
s_platform_48,
};
diff --git a/Editor/Mono/EditorBuildSettings.bindings.cs b/Editor/Mono/EditorBuildSettings.bindings.cs
index 52d18935d0..bda07fd7d0 100644
--- a/Editor/Mono/EditorBuildSettings.bindings.cs
+++ b/Editor/Mono/EditorBuildSettings.bindings.cs
@@ -86,7 +86,8 @@ public static EditorBuildSettingsScene[] scenes
{
get
{
- if (BuildProfileContext.activeProfile is not null)
+ if (BuildProfileContext.activeProfile is not null
+ && BuildProfileContext.activeProfile.overrideGlobalSceneList)
{
return BuildProfileContext.activeProfile.scenes;
}
@@ -95,7 +96,8 @@ public static EditorBuildSettingsScene[] scenes
}
set
{
- if (BuildProfileContext.activeProfile is not null)
+ if (BuildProfileContext.activeProfile is not null
+ && BuildProfileContext.activeProfile.overrideGlobalSceneList)
{
BuildProfileContext.activeProfile.scenes = value;
}
@@ -109,7 +111,9 @@ public static EditorBuildSettingsScene[] scenes
[RequiredByNativeCode]
static EditorBuildSettingsScene[] GetActiveBuildProfileSceneList()
{
- if (!EditorUserBuildSettings.isBuildProfileAvailable || BuildProfileContext.activeProfile is null)
+ if (!EditorUserBuildSettings.isBuildProfileAvailable
+ || BuildProfileContext.activeProfile is null
+ || !BuildProfileContext.activeProfile.overrideGlobalSceneList)
return null;
return BuildProfileContext.activeProfile.scenes;
diff --git a/Editor/Mono/EditorWindow.cs b/Editor/Mono/EditorWindow.cs
index 91af95b475..ade759998f 100644
--- a/Editor/Mono/EditorWindow.cs
+++ b/Editor/Mono/EditorWindow.cs
@@ -1081,6 +1081,12 @@ public void Close()
if (WindowLayout.IsMaximized(this))
WindowLayout.Unmaximize(this);
+ // [UUM-58449] If the focused window got closed, reset the IME composition mode to the default value. The normal codepaths may not run since this object is immediately destroyed.
+ if (focusedWindow == this)
+ {
+ GUIUtility.imeCompositionMode = IMECompositionMode.Auto;
+ }
+
DockArea da = m_Parent as DockArea;
if (da)
{
diff --git a/Editor/Mono/GI/Lightmapping.bindings.cs b/Editor/Mono/GI/Lightmapping.bindings.cs
index d0c549a00d..314a1bf5b6 100644
--- a/Editor/Mono/GI/Lightmapping.bindings.cs
+++ b/Editor/Mono/GI/Lightmapping.bindings.cs
@@ -100,6 +100,13 @@ public enum GIWorkflowMode
Legacy = 2
}
+ [NativeHeader("Runtime/Graphics/LightmapSettings.h")]
+ public enum BakeOnSceneLoadMode
+ {
+ Never = 0,
+ IfMissingLightingData = 1,
+ };
+
// Obsolete, please use Actions instead
public delegate void OnStartedFunction();
public delegate void OnCompletedFunction();
@@ -447,6 +454,9 @@ public static void Tetrahedralize(Vector3[] positions, out int[] outIndices, out
[FreeFunction]
public static extern void GetTerrainGIChunks([NotNull] Terrain terrain, ref int numChunksX, ref int numChunksY);
+ [StaticAccessor("GetLightmapSettings()")]
+ public static extern BakeOnSceneLoadMode bakeOnSceneLoad { get; set; }
+
[StaticAccessor("GetLightmapSettings()")]
public static extern LightingDataAsset lightingDataAsset { get; set; }
diff --git a/Editor/Mono/GUI/EditorStyles.cs b/Editor/Mono/GUI/EditorStyles.cs
index 412d882124..341eb3260c 100644
--- a/Editor/Mono/GUI/EditorStyles.cs
+++ b/Editor/Mono/GUI/EditorStyles.cs
@@ -257,6 +257,9 @@ public sealed class EditorStyles
public static GUIStyle inspectorDefaultMargins { get { return s_Current.m_InspectorDefaultMargins; } }
private GUIStyle m_InspectorDefaultMargins;
+ internal static GUIStyle inspectorHorizontalDefaultMargins => s_Current.m_InspectorHorizontalDefaultMargins;
+ private GUIStyle m_InspectorHorizontalDefaultMargins;
+
public static GUIStyle inspectorFullWidthMargins { get { return s_Current.m_InspectorFullWidthMargins; } }
private GUIStyle m_InspectorFullWidthMargins;
@@ -564,6 +567,11 @@ private void InitSharedStyles()
padding = new RectOffset(kInspectorPaddingLeft, kInspectorPaddingRight, kInspectorPaddingTop, 0)
};
+ m_InspectorHorizontalDefaultMargins = new GUIStyle
+ {
+ padding = new RectOffset(kInspectorPaddingLeft, kInspectorPaddingRight, 0, 0)
+ };
+
// For the full width margins, use padding from right side in both sides,
// though adjust for overdraw by adding one in left side to get even margins.
m_InspectorFullWidthMargins = new GUIStyle
diff --git a/Editor/Mono/Inspector/Core/ScriptAttributeGUI/PropertyHandler.cs b/Editor/Mono/Inspector/Core/ScriptAttributeGUI/PropertyHandler.cs
index 45e89be784..b4d045eeae 100644
--- a/Editor/Mono/Inspector/Core/ScriptAttributeGUI/PropertyHandler.cs
+++ b/Editor/Mono/Inspector/Core/ScriptAttributeGUI/PropertyHandler.cs
@@ -300,11 +300,7 @@ internal bool OnGUI(Rect position, SerializedProperty property, GUIContent label
if (childrenAreExpanded)
{
SerializedProperty endProperty = prop.GetEndProperty();
- // Children need to be indented
- int prevIndent = EditorGUI.indentLevel;
- EditorGUI.indentLevel++;
- position = EditorGUI.IndentedRect(position);
- EditorGUI.indentLevel = prevIndent;
+
while (prop.NextVisible(childrenAreExpanded) && !SerializedProperty.EqualContents(prop, endProperty))
{
if (GUI.isInsideList && prop.depth <= EditorGUI.GetInsideListDepth())
diff --git a/Editor/Mono/Overlays/Overlay.cs b/Editor/Mono/Overlays/Overlay.cs
index bbe2447b1d..392d5f5b2c 100644
--- a/Editor/Mono/Overlays/Overlay.cs
+++ b/Editor/Mono/Overlays/Overlay.cs
@@ -766,6 +766,11 @@ internal void ToggleCollapsedPopup()
m_ModalPopup.Focus();
}
+ public void RefreshPopup()
+ {
+ m_ModalPopup?.Refresh();
+ }
+
void ClosePopup()
{
m_ModalPopup?.RemoveFromHierarchy();
diff --git a/Editor/Mono/Overlays/OverlayPopup.cs b/Editor/Mono/Overlays/OverlayPopup.cs
index e56f4d8b7a..3b7b54f3e7 100644
--- a/Editor/Mono/Overlays/OverlayPopup.cs
+++ b/Editor/Mono/Overlays/OverlayPopup.cs
@@ -33,13 +33,26 @@ class OverlayPopup : VisualElement
AddToClassList(Overlay.ussClassName);
style.position = Position.Absolute;
+ Refresh();
+
+ RegisterCallback(evt => m_CursorIsOverPopup = true);
+ RegisterCallback(evt => m_CursorIsOverPopup = false);
+ }
+
+ public void Refresh()
+ {
var root = this.Q("overlay-content");
+
+ root.Clear();
+
root.renderHints = RenderHints.ClipWithScissors;
+ style.maxHeight = StyleKeyword.Initial;
+ style.maxWidth = StyleKeyword.Initial;
+
root.Add(overlay.GetSimpleHeader());
root.Add(overlay.CreatePanelContent());
- RegisterCallback(evt => m_CursorIsOverPopup = true);
- RegisterCallback(evt => m_CursorIsOverPopup = false);
+ root.Focus();
}
public static OverlayPopup CreateUnderOverlay(Overlay overlay)
diff --git a/Editor/Mono/PlayerSettingsVulkan.bindings.cs b/Editor/Mono/PlayerSettingsVulkan.bindings.cs
index 4486f94e1f..44d40568cb 100644
--- a/Editor/Mono/PlayerSettingsVulkan.bindings.cs
+++ b/Editor/Mono/PlayerSettingsVulkan.bindings.cs
@@ -10,7 +10,27 @@ namespace UnityEditor
public partial class PlayerSettings : UnityEngine.Object
{
public static extern bool vulkanEnableSetSRGBWrite { get; set; }
- public static extern UInt32 vulkanNumSwapchainBuffers { get; set; }
+
+ private static extern UInt32 GetVulkanNumSwapchainBuffersImpl();
+ private static extern void SetVulkanNumSwapchainBuffersImpl(UInt32 value);
+
+ // NOTE: While in the editor, changing this value can be destructive so we force 3 swapchain buffers while running in the editor.
+ public static UInt32 vulkanNumSwapchainBuffers
+ {
+ get
+ {
+ // Must match the value PlayerSettings::kFixedEditorVulkanSwapchainBufferCount in native code,
+ // explicitly report the current value being used.
+ const UInt32 kFixedEditorVulkanSwapchainBufferCount = 3;
+ if (EditorApplication.isPlaying)
+ return kFixedEditorVulkanSwapchainBufferCount;
+ else
+ return GetVulkanNumSwapchainBuffersImpl();
+ }
+
+ set => SetVulkanNumSwapchainBuffersImpl(value);
+ }
+
public static extern bool vulkanEnableLateAcquireNextImage { get; set; }
[Obsolete("Vulkan SW command buffers are deprecated, vulkanUseSWCommandBuffers will be ignored.")]
diff --git a/Editor/Mono/SceneModeWindows/LightingWindow.cs b/Editor/Mono/SceneModeWindows/LightingWindow.cs
index 4728dd9222..44566460cb 100644
--- a/Editor/Mono/SceneModeWindows/LightingWindow.cs
+++ b/Editor/Mono/SceneModeWindows/LightingWindow.cs
@@ -39,6 +39,8 @@ static class Styles
public static readonly GUIContent progressiveGPUChangeWarning = EditorGUIUtility.TrTextContent("Changing the compute device used by the Progressive GPU Lightmapper requires the editor to be relaunched. Do you want to change device and restart?");
public static readonly GUIContent gpuBakingProfile = EditorGUIUtility.TrTextContent("GPU Baking Profile", "The profile chosen for trading off between performance and memory usage when baking using the GPU.");
+ public static readonly GUIContent bakeOnSceneLoad = EditorGUIUtility.TrTextContent("Bake On Scene Load", "Whether to automatically generate lighting for Scenes that do not have valid lighting data when first opened.");
+
public static readonly GUIContent invalidEnvironmentLabel = EditorGUIUtility.TrTextContentWithIcon("Baked environment lighting does not match the current Scene state. Generate Lighting to update this.", MessageType.Warning);
public static readonly GUIContent unsupportedDenoisersLabel = EditorGUIUtility.TrTextContentWithIcon("Unsupported denoiser selected", MessageType.Error);
@@ -539,6 +541,16 @@ void DrawBakingProfileSelector()
}
}
+ void DrawBakeOnLoadSelector()
+ {
+ var selected = (Lightmapping.BakeOnSceneLoadMode)EditorGUILayout.EnumPopup(Styles.bakeOnSceneLoad, Lightmapping.bakeOnSceneLoad);
+ if (selected != Lightmapping.bakeOnSceneLoad)
+ {
+ Undo.RecordObject(LightmapEditorSettings.GetLightmapSettings(), "Change Bake On Load Setting");
+ Lightmapping.bakeOnSceneLoad = selected;
+ }
+ }
+
void DrawBottomBarGUI(Mode selectedMode)
{
using (new EditorGUI.DisabledScope(EditorApplication.isPlayingOrWillChangePlaymode))
@@ -552,6 +564,7 @@ void DrawBottomBarGUI(Mode selectedMode)
// Bake settings.
DrawGPUDeviceSelector();
DrawBakingProfileSelector();
+ DrawBakeOnLoadSelector();
{
// Bake button if we are not currently baking
diff --git a/Editor/Mono/SceneView/SceneView.cs b/Editor/Mono/SceneView/SceneView.cs
index b8bb4e8ab1..473a7d700d 100644
--- a/Editor/Mono/SceneView/SceneView.cs
+++ b/Editor/Mono/SceneView/SceneView.cs
@@ -2459,6 +2459,10 @@ void HandleViewToolCursor(Rect cameraRect)
{
if (!Tools.viewToolActive || Event.current.type != EventType.Repaint)
return;
+ // In case multiple scene views are opened, we only want to set the cursor for the one being hovered
+ // Skip if the mouse is over an overlay or an area that should not use a custom cursor
+ if (mouseOverWindow is SceneView view && (mouseOverWindow != this || !view.sceneViewMotion.viewportsUnderMouse))
+ return;
var cursor = MouseCursor.Arrow;
switch (Tools.viewTool)
@@ -3012,13 +3016,6 @@ void HandleMouseCursor()
bool repaintView = false;
MouseCursor cursor = MouseCursor.Arrow;
- //Reset the cursor if the mouse is over an overlay or an area that should not use a custom cursor
- if (mouseOverWindow is SceneView view && !view.sceneViewMotion.viewportsUnderMouse)
- {
- InternalEditorUtility.ResetCursor();
- return;
- }
-
foreach (CursorRect r in s_MouseRects)
{
if (r.rect.Contains(evt.mousePosition))
diff --git a/Editor/Mono/UIElements/Controls/PropertyField.cs b/Editor/Mono/UIElements/Controls/PropertyField.cs
index 76bcdf206d..83536c56f3 100644
--- a/Editor/Mono/UIElements/Controls/PropertyField.cs
+++ b/Editor/Mono/UIElements/Controls/PropertyField.cs
@@ -152,6 +152,8 @@ public string label
///
public static readonly string inspectorElementUssClassName = ussClassName + "__inspector-property";
+ internal static readonly string imguiContainerPropertyUssClassName = ussClassName + "__imgui-container-property";
+
///
/// PropertyField constructor.
///
@@ -326,6 +328,9 @@ void Reset(SerializedProperty newProperty)
if (customPropertyGUI == null)
{
customPropertyGUI = CreatePropertyIMGUIContainer();
+
+ AddToClassList(imguiContainerPropertyUssClassName);
+
m_imguiChildField = customPropertyGUI;
}
else
@@ -410,11 +415,11 @@ private void Reset(SerializedPropertyBindEvent evt)
private VisualElement CreatePropertyIMGUIContainer()
{
- GUIContent customLabel = string.IsNullOrEmpty(label) ? null : new GUIContent(label);
-
var imguiContainer = new IMGUIContainer(() =>
{
var originalWideMode = InspectorElement.SetWideModeForWidth(this);
+ var originalHierarchyMode = EditorGUIUtility.hierarchyMode;
+ EditorGUIUtility.hierarchyMode = true;
var oldLabelWidth = EditorGUIUtility.labelWidth;
try
@@ -422,91 +427,95 @@ private VisualElement CreatePropertyIMGUIContainer()
if (!serializedProperty.isValid || !serializedObject.isValid)
return;
- if (m_InspectorElement is InspectorElement inspectorElement)
- {
- //set the current PropertyHandlerCache to the current editor
- ScriptAttributeUtility.propertyHandlerCache = inspectorElement.editor.propertyHandlerCache;
- }
-
- EditorGUI.BeginChangeCheck();
- serializedProperty.serializedObject.Update();
-
- if (classList.Contains(inspectorElementUssClassName))
+ EditorGUILayout.BeginVertical(EditorStyles.inspectorHorizontalDefaultMargins);
{
- var spacing = 0f;
-
- if (m_imguiChildField != null)
+ if (m_InspectorElement is InspectorElement inspectorElement)
{
- spacing = m_imguiChildField.worldBound.x - m_InspectorElement.worldBound.x - m_InspectorElement.resolvedStyle.paddingLeft;
+ //set the current PropertyHandlerCache to the current editor
+ ScriptAttributeUtility.propertyHandlerCache = inspectorElement.editor.propertyHandlerCache;
}
- var imguiSpacing = EditorGUI.kLabelWidthMargin - EditorGUI.kLabelWidthPadding;
- var contextWidthElement = m_ContextWidthElement ?? m_InspectorElement;
- var contextWidth = contextWidthElement.resolvedStyle.width;
- var labelWidth = (contextWidth * EditorGUI.kLabelWidthRatio - imguiSpacing - spacing);
- var minWidth = EditorGUI.kMinLabelWidth + EditorGUI.kLabelWidthPadding;
- var minLabelWidth = Mathf.Max(minWidth - spacing, 0f);
+ EditorGUI.BeginChangeCheck();
+ serializedProperty.serializedObject.Update();
- EditorGUIUtility.labelWidth = Mathf.Max(labelWidth, minLabelWidth);
- }
- else
- {
- if (m_FoldoutDepth > 0)
- EditorGUI.indentLevel += m_FoldoutDepth;
- }
+ if (classList.Contains(inspectorElementUssClassName))
+ {
+ var spacing = 0f;
- // Wait at last minute to call GetHandler, sometimes the handler cache is cleared between calls.
- var handler = ScriptAttributeUtility.GetHandler(serializedProperty);
- using (var nestingContext = handler.ApplyNestingContext(m_DrawNestingLevel))
- {
- // Decorator drawers are already handled on the uitk side
- handler.skipDecoratorDrawers = true;
+ if (m_imguiChildField != null)
+ {
+ spacing = m_imguiChildField.worldBound.x - m_InspectorElement.worldBound.x - m_InspectorElement.resolvedStyle.paddingLeft - resolvedStyle.marginLeft;
+ }
- var previousLeftMarginCoord = EditorGUIUtility.leftMarginCoord;
+ var imguiSpacing = EditorGUI.kLabelWidthMargin;
+ var contextWidthElement = m_ContextWidthElement ?? m_InspectorElement;
+ var contextWidth = contextWidthElement.resolvedStyle.width;
+ var labelWidth = (contextWidth * EditorGUI.kLabelWidthRatio - imguiSpacing - spacing);
+ var minWidth = EditorGUI.kMinLabelWidth + EditorGUI.kLabelWidthPadding;
+ var minLabelWidth = Mathf.Max(minWidth - spacing, 0f);
- if (m_InspectorElement != null && m_imguiChildField != null)
- {
- // Set a left margin offset to align the prefab override bar with the property field
- var extraLeftMargin = m_InspectorElement.worldBound.x;
- EditorGUIUtility.leftMarginCoord = -m_imguiChildField.worldBound.x + extraLeftMargin;
+ EditorGUIUtility.labelWidth = Mathf.Max(labelWidth, minLabelWidth);
}
-
- if (label == null)
+ else
{
- EditorGUILayout.PropertyField(serializedProperty, true);
+ if (m_FoldoutDepth > 0)
+ EditorGUI.indentLevel += m_FoldoutDepth;
}
- else if (label == string.Empty)
+
+ // Wait at last minute to call GetHandler, sometimes the handler cache is cleared between calls.
+ var handler = ScriptAttributeUtility.GetHandler(serializedProperty);
+ using (var nestingContext = handler.ApplyNestingContext(m_DrawNestingLevel))
{
- EditorGUILayout.PropertyField(serializedProperty, GUIContent.none, true);
+ // Decorator drawers are already handled on the uitk side
+ handler.skipDecoratorDrawers = true;
+
+ var previousLeftMarginCoord = EditorGUIUtility.leftMarginCoord;
+
+ if (m_InspectorElement != null && m_imguiChildField != null)
+ {
+ // Set a left margin offset to align the prefab override bar with the property field
+ var extraLeftMargin = m_InspectorElement.worldBound.x;
+ EditorGUIUtility.leftMarginCoord = -m_imguiChildField.worldBound.x + extraLeftMargin;
+ }
+
+ if (label == null)
+ {
+ EditorGUILayout.PropertyField(serializedProperty, true);
+ }
+ else if (label == string.Empty)
+ {
+ EditorGUILayout.PropertyField(serializedProperty, GUIContent.none, true);
+ }
+ else
+ {
+ EditorGUILayout.PropertyField(serializedProperty, new GUIContent(label), true);
+ }
+
+ if (m_InspectorElement != null && m_imguiChildField != null)
+ {
+ // Reset the left margin to the original value
+ EditorGUIUtility.leftMarginCoord = previousLeftMarginCoord;
+ }
}
- else
+
+ if (!classList.Contains(inspectorElementUssClassName))
{
- EditorGUILayout.PropertyField(serializedProperty, new GUIContent(label), true);
+ if (m_FoldoutDepth > 0)
+ EditorGUI.indentLevel -= m_FoldoutDepth;
}
- if (m_InspectorElement != null && m_imguiChildField != null)
+ serializedProperty.serializedObject.ApplyModifiedProperties();
+ if (EditorGUI.EndChangeCheck())
{
- // Reset the left margin to the original value
- EditorGUIUtility.leftMarginCoord = previousLeftMarginCoord;
+ DispatchPropertyChangedEvent();
}
}
-
- if (!classList.Contains(inspectorElementUssClassName))
- {
- if (m_FoldoutDepth > 0)
- EditorGUI.indentLevel -= m_FoldoutDepth;
- }
-
- serializedProperty.serializedObject.ApplyModifiedProperties();
- if (EditorGUI.EndChangeCheck())
- {
- DispatchPropertyChangedEvent();
- }
+ EditorGUILayout.EndVertical();
}
finally
{
EditorGUIUtility.wideMode = originalWideMode;
-
+ EditorGUIUtility.hierarchyMode = originalHierarchyMode;
if (classList.Contains(inspectorElementUssClassName))
{
EditorGUIUtility.labelWidth = oldLabelWidth;
diff --git a/Editor/Mono/UIElements/Inspector/InspectorElement.cs b/Editor/Mono/UIElements/Inspector/InspectorElement.cs
index f393650e6b..efd78e4689 100644
--- a/Editor/Mono/UIElements/Inspector/InspectorElement.cs
+++ b/Editor/Mono/UIElements/Inspector/InspectorElement.cs
@@ -179,6 +179,7 @@ public UxmlTraits()
bool m_IsOpenForEdit;
bool m_InvalidateGUIBlockCache = true;
bool m_Rebind;
+ VisualElement m_ContextWidthElement;
///
/// Gets or sets the editor backing this inspector element.
@@ -338,6 +339,19 @@ void OnAttachToPanel(AttachToPanelEvent evt)
m_Rebind = true;
this.Bind(boundObject);
}
+
+ var currentElement = parent;
+ while (currentElement != null)
+ {
+ if (!currentElement.ClassListContains(PropertyEditor.s_MainContainerClassName))
+ {
+ currentElement = currentElement.parent;
+ continue;
+ }
+
+ m_ContextWidthElement = currentElement;
+ break;
+ }
}
void OnDetachFromPanel(DetachFromPanelEvent evt)
@@ -686,7 +700,7 @@ VisualElement CreateInspectorElementUsingIMGUI(Editor targetEditor)
EditorGUIUtility.hierarchyMode = true;
EditorGUIUtility.comparisonViewMode = comparisonViewMode;
- var originalWideMode = SetWideModeForWidth(inspector);
+ var originalWideMode = SetWideModeForWidth(m_ContextWidthElement ?? inspector);
GUIStyle editorWrapper = (targetEditor.UseDefaultMargins() && targetEditor.CanBeExpandedViaAFoldoutWithoutUpdate()
? EditorStyles.inspectorDefaultMargins
diff --git a/Editor/Mono/UnityConnect/CloudProjectSettings.cs b/Editor/Mono/UnityConnect/CloudProjectSettings.cs
index 5d98a54f7e..2721f5cf49 100644
--- a/Editor/Mono/UnityConnect/CloudProjectSettings.cs
+++ b/Editor/Mono/UnityConnect/CloudProjectSettings.cs
@@ -3,6 +3,8 @@
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License
using System;
+using System.Threading;
+using System.Threading.Tasks;
using UnityEditor.Connect;
namespace UnityEditor
@@ -40,6 +42,9 @@ public static string accessToken
}
}
+ public static Task GetServiceTokenAsync(CancellationToken cancellationToken = default)
+ => ServiceToken.Instance.GetServiceTokenAsync(accessToken, cancellationToken);
+
public static void RefreshAccessToken(Action refresh)
{
UnityConnect.instance.RefreshAccessToken(refresh);
diff --git a/Modules/UnityConnectEditor/Common/UnityConnectWebRequestException.cs b/Editor/Mono/UnityConnect/Network/UnityConnectWebRequestException.cs
similarity index 97%
rename from Modules/UnityConnectEditor/Common/UnityConnectWebRequestException.cs
rename to Editor/Mono/UnityConnect/Network/UnityConnectWebRequestException.cs
index 142d30783d..f1fd950a6d 100644
--- a/Modules/UnityConnectEditor/Common/UnityConnectWebRequestException.cs
+++ b/Editor/Mono/UnityConnect/Network/UnityConnectWebRequestException.cs
@@ -2,8 +2,6 @@
// 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;
diff --git a/Editor/Mono/UnityConnect/Network/UnityConnectWebRequestUtils.cs b/Editor/Mono/UnityConnect/Network/UnityConnectWebRequestUtils.cs
new file mode 100644
index 0000000000..68110b9b34
--- /dev/null
+++ b/Editor/Mono/UnityConnect/Network/UnityConnectWebRequestUtils.cs
@@ -0,0 +1,83 @@
+// Unity C# reference source
+// Copyright (c) Unity Technologies. For terms of use, see
+// https://unity3d.com/legal/licenses/Unity_Reference_Only_License
+
+using System.Threading;
+using System.Threading.Tasks;
+using UnityEngine;
+using UnityEngine.Networking;
+
+namespace UnityEditor.Connect
+{
+ internal static class UnityConnectWebRequestUtils
+ {
+ internal static UnityConnectWebRequestException CreateUnityWebRequestException(UnityWebRequest request,
+ string message)
+ => new(L10n.Tr(message))
+ {
+ error = request.error,
+ method = request.method,
+ timeout = request.timeout,
+ url = request.url,
+ responseHeaders = request.GetResponseHeaders(),
+ responseCode = request.responseCode,
+ isHttpError = request.result == UnityWebRequest.Result.ProtocolError,
+ isNetworkError = request.result == UnityWebRequest.Result.ConnectionError
+ };
+
+ ///
+ /// Used to determine if the UnityWebRequest had an error or error code
+ ///
+ internal static bool IsRequestError(UnityWebRequest request)
+ {
+ if (!string.IsNullOrEmpty(request.error))
+ {
+ return true;
+ }
+
+ switch (request.result)
+ {
+ case UnityWebRequest.Result.ConnectionError:
+ case UnityWebRequest.Result.ProtocolError:
+ case UnityWebRequest.Result.DataProcessingError:
+ return true;
+ }
+
+ if (request.responseCode is < 200 or >= 300)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ internal static bool IsUnityWebRequestReadyForJsonExtract(UnityWebRequest unityWebRequest)
+ {
+ return !IsRequestError(unityWebRequest)
+ && !string.IsNullOrEmpty(unityWebRequest.downloadHandler.text);
+ }
+
+ ///
+ /// Used to run a UnityWebRequest on the main thread in an awaitable manner, while handling CancellationToken
+ ///
+ internal static async Task SendWebRequestAsync(
+ UnityWebRequest unityWebRequest,
+ CancellationToken cancellationToken = default)
+ {
+ var webRequestTask = AsyncUtils.RunUnityWebRequestOnMainThread(unityWebRequest);
+
+ while (!unityWebRequest.isDone)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ unityWebRequest.Abort();
+ cancellationToken.ThrowIfCancellationRequested();
+ }
+
+ await Task.Yield();
+ }
+
+ await webRequestTask;
+ }
+ }
+}
diff --git a/Editor/Mono/UnityConnect/ServiceToken/Caching/GenesisAndServiceTokenCaching.cs b/Editor/Mono/UnityConnect/ServiceToken/Caching/GenesisAndServiceTokenCaching.cs
new file mode 100644
index 0000000000..9c4d480e98
--- /dev/null
+++ b/Editor/Mono/UnityConnect/ServiceToken/Caching/GenesisAndServiceTokenCaching.cs
@@ -0,0 +1,62 @@
+// 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.Connect
+{
+ class GenesisAndServiceTokenCaching : IGenesisAndServiceTokenCaching
+ {
+ internal const string CacheKey = "Editor.GatewayTokens.Cache";
+ readonly TimeSpan m_RefreshGracePeriod = TimeSpan.FromMinutes(30);
+
+ public Tokens LoadCache()
+ {
+ var serializedTokens = SessionState.GetString(CacheKey, string.Empty);
+
+ if (string.IsNullOrEmpty(serializedTokens))
+ {
+ return new Tokens();
+ }
+
+ var deserializedTokens = Json.Deserialize(serializedTokens) as Dictionary;
+
+ if (deserializedTokens == null)
+ {
+ return new Tokens();
+ }
+
+ return new Tokens()
+ {
+ GenesisToken = deserializedTokens.GetValueOrDefault(nameof(Tokens.GenesisToken))?.ToString(),
+ GatewayToken = deserializedTokens.GetValueOrDefault(nameof(Tokens.GatewayToken))?.ToString()
+ };
+ }
+
+ public void SaveCache(Tokens tokens)
+ {
+ var serialized = Json.Serialize(tokens);
+ SessionState.SetString(CacheKey, serialized);
+ }
+
+ public DateTime GetNextRefreshTime(string gatewayToken)
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(gatewayToken))
+ {
+ return new DateTime();
+ }
+
+ var jwt = JsonWebToken.Decode(gatewayToken);
+ return jwt.exp - m_RefreshGracePeriod;
+ }
+ catch
+ {
+ return new DateTime();
+ }
+ }
+ }
+}
diff --git a/Editor/Mono/UnityConnect/ServiceToken/Caching/IGenesisAndServiceTokenCaching.cs b/Editor/Mono/UnityConnect/ServiceToken/Caching/IGenesisAndServiceTokenCaching.cs
new file mode 100644
index 0000000000..7e758f5c23
--- /dev/null
+++ b/Editor/Mono/UnityConnect/ServiceToken/Caching/IGenesisAndServiceTokenCaching.cs
@@ -0,0 +1,16 @@
+// Unity C# reference source
+// Copyright (c) Unity Technologies. For terms of use, see
+// https://unity3d.com/legal/licenses/Unity_Reference_Only_License
+
+using System;
+
+namespace UnityEditor.Connect
+{
+ interface IGenesisAndServiceTokenCaching
+ {
+ public Tokens LoadCache();
+ public void SaveCache(Tokens tokens);
+ public DateTime GetNextRefreshTime(string gatewayToken);
+ }
+}
+
diff --git a/Editor/Mono/UnityConnect/ServiceToken/Caching/JsonWebToken.cs b/Editor/Mono/UnityConnect/ServiceToken/Caching/JsonWebToken.cs
new file mode 100644
index 0000000000..cde2f60271
--- /dev/null
+++ b/Editor/Mono/UnityConnect/ServiceToken/Caching/JsonWebToken.cs
@@ -0,0 +1,58 @@
+// 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.Text;
+
+namespace UnityEditor.Connect
+{
+ readonly struct JsonWebToken
+ {
+ static readonly char[] k_JwtSeparator = { '.' };
+ static readonly DateTime k_UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
+
+ public DateTime exp { get; }
+
+ public JsonWebToken(long exp)
+ {
+ this.exp = k_UnixEpoch.AddSeconds(exp);
+ }
+
+ public override string ToString()
+ {
+ return Json.Serialize(this);
+ }
+
+ public static JsonWebToken Decode(string token)
+ {
+ var parts = token.Split(k_JwtSeparator);
+ if (parts.Length != 3)
+ {
+ throw new ArgumentException($"The authentication token is malformed or invalid. " +
+ $"JWT has an invalid number of sections. Token: '{token}'");
+ }
+
+ var payload = parts[1];
+ var payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload));
+ var deserialized = Json.Deserialize(payloadJson) as Dictionary;
+ return new JsonWebToken(Convert.ToInt64(deserialized.GetValueOrDefault(nameof(exp))));
+ }
+
+ static byte[] Base64UrlDecode(string input)
+ {
+ var output = input;
+ output = output.Replace('-', '+'); // 62nd char of encoding
+ output = output.Replace('_', '/'); // 63rd char of encoding
+
+ var mod4 = input.Length % 4;
+ if (mod4 > 0)
+ {
+ output += new string('=', 4 - mod4);
+ }
+
+ return Convert.FromBase64String(output);
+ }
+ }
+}
diff --git a/Editor/Mono/UnityConnect/ServiceToken/Caching/Tokens.cs b/Editor/Mono/UnityConnect/ServiceToken/Caching/Tokens.cs
new file mode 100644
index 0000000000..1962b4c156
--- /dev/null
+++ b/Editor/Mono/UnityConnect/ServiceToken/Caching/Tokens.cs
@@ -0,0 +1,20 @@
+// Unity C# reference source
+// Copyright (c) Unity Technologies. For terms of use, see
+// https://unity3d.com/legal/licenses/Unity_Reference_Only_License
+
+namespace UnityEditor.Connect
+{
+ internal struct Tokens
+ {
+ public string GatewayToken;
+ public string GenesisToken;
+
+ public Tokens()
+ {
+ GatewayToken = default;
+ GenesisToken = default;
+ }
+ }
+}
+
+
diff --git a/Editor/Mono/UnityConnect/ServiceToken/ConfigurationProvider/CloudEnvironmentConfigProvider.cs b/Editor/Mono/UnityConnect/ServiceToken/ConfigurationProvider/CloudEnvironmentConfigProvider.cs
new file mode 100644
index 0000000000..e902c7587d
--- /dev/null
+++ b/Editor/Mono/UnityConnect/ServiceToken/ConfigurationProvider/CloudEnvironmentConfigProvider.cs
@@ -0,0 +1,54 @@
+// 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;
+
+namespace UnityEditor.Connect
+{
+ class CloudEnvironmentConfigProvider : ICloudEnvironmentConfigProvider
+ {
+ internal const string CloudEnvironmentArg = "-cloudEnvironment";
+ internal const string StagingEnv = "staging";
+
+ public bool IsStaging()
+ {
+ return GetCloudEnvironment(Environment.GetCommandLineArgs()) == StagingEnv;
+ }
+
+ internal string GetCloudEnvironment(string[] commandLineArgs)
+ {
+ string cloudEnvironmentField = null;
+
+ foreach (var arg in commandLineArgs)
+ {
+ if (arg.StartsWith(CloudEnvironmentArg))
+ {
+ cloudEnvironmentField = arg;
+ break;
+ }
+ }
+
+ if (cloudEnvironmentField != null)
+ {
+ var cloudEnvironmentIndex = Array.IndexOf(commandLineArgs, cloudEnvironmentField);
+
+ if (cloudEnvironmentField == CloudEnvironmentArg)
+ {
+ if (cloudEnvironmentIndex <= commandLineArgs.Length - 2)
+ {
+ return commandLineArgs[cloudEnvironmentIndex + 1];
+ }
+ }
+ else if (cloudEnvironmentField.Contains('='))
+ {
+ var value = cloudEnvironmentField.Substring(cloudEnvironmentField.IndexOf('=') + 1);
+ return !string.IsNullOrEmpty(value) ? value : null;
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/Editor/Mono/UnityConnect/ServiceToken/ConfigurationProvider/ICloudEnvironmentConfigProvider.cs b/Editor/Mono/UnityConnect/ServiceToken/ConfigurationProvider/ICloudEnvironmentConfigProvider.cs
new file mode 100644
index 0000000000..d3552daed4
--- /dev/null
+++ b/Editor/Mono/UnityConnect/ServiceToken/ConfigurationProvider/ICloudEnvironmentConfigProvider.cs
@@ -0,0 +1,11 @@
+// Unity C# reference source
+// Copyright (c) Unity Technologies. For terms of use, see
+// https://unity3d.com/legal/licenses/Unity_Reference_Only_License
+
+namespace UnityEditor.Connect
+{
+ interface ICloudEnvironmentConfigProvider
+ {
+ bool IsStaging();
+ }
+}
diff --git a/Editor/Mono/UnityConnect/ServiceToken/ServiceToken.cs b/Editor/Mono/UnityConnect/ServiceToken/ServiceToken.cs
new file mode 100644
index 0000000000..56a3ed26bc
--- /dev/null
+++ b/Editor/Mono/UnityConnect/ServiceToken/ServiceToken.cs
@@ -0,0 +1,73 @@
+// 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.Threading;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace UnityEditor.Connect
+{
+ class ServiceToken
+ {
+ readonly ITokenExchange m_TokenExchange;
+ readonly IGenesisAndServiceTokenCaching m_GenesisAndServiceTokenCaching;
+ readonly DateTime m_DateTime;
+
+ internal static ServiceToken Instance => k_LazyInstance.Value;
+
+ static readonly Lazy k_LazyInstance = new Lazy(() =>
+ {
+ var cloudEnvironmentConfigProvider = new CloudEnvironmentConfigProvider();
+ var tokenExchange = new TokenExchange(cloudEnvironmentConfigProvider);
+ var tokenCaching = new GenesisAndServiceTokenCaching();
+ return new ServiceToken(tokenExchange, tokenCaching);
+ });
+
+ internal ServiceToken(
+ ITokenExchange tokenExchange,
+ IGenesisAndServiceTokenCaching genesisAndServiceTokenCaching)
+ {
+ m_TokenExchange = tokenExchange;
+ m_GenesisAndServiceTokenCaching = genesisAndServiceTokenCaching;
+ m_DateTime = DateTime.UtcNow;
+ }
+
+ public async Task GetServiceTokenAsync(string genesisToken, CancellationToken cancellationToken = default)
+ {
+ Tokens cachedTokens = new();
+ await AsyncUtils.RunNextActionOnMainThread(() => cachedTokens = m_GenesisAndServiceTokenCaching.LoadCache());
+
+ var nextRefreshTime = m_GenesisAndServiceTokenCaching.GetNextRefreshTime(cachedTokens.GatewayToken);
+
+ if (genesisToken != cachedTokens.GenesisToken || m_DateTime.ToUniversalTime() >= nextRefreshTime)
+ {
+ if (!string.IsNullOrEmpty(genesisToken))
+ {
+ try
+ {
+ cachedTokens.GatewayToken =
+ await m_TokenExchange.GetServiceTokenAsync(genesisToken, cancellationToken);
+ }
+ catch
+ {
+ cachedTokens.GatewayToken = null;
+ throw;
+ }
+ }
+ else
+ {
+ cachedTokens.GatewayToken = null;
+ }
+
+ cachedTokens.GenesisToken = genesisToken;
+ }
+
+ await AsyncUtils.RunNextActionOnMainThread(() => m_GenesisAndServiceTokenCaching.SaveCache(cachedTokens));
+ return cachedTokens.GatewayToken;
+ }
+ }
+}
+
+
diff --git a/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/ITokenExchange.cs b/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/ITokenExchange.cs
new file mode 100644
index 0000000000..fd22ef6739
--- /dev/null
+++ b/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/ITokenExchange.cs
@@ -0,0 +1,14 @@
+// Unity C# reference source
+// Copyright (c) Unity Technologies. For terms of use, see
+// https://unity3d.com/legal/licenses/Unity_Reference_Only_License
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace UnityEditor.Connect
+{
+ interface ITokenExchange
+ {
+ Task GetServiceTokenAsync(string genesisToken, CancellationToken cancellationToken);
+ }
+}
diff --git a/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/Model/TokenExchangeRequest.cs b/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/Model/TokenExchangeRequest.cs
new file mode 100644
index 0000000000..6088c26d9e
--- /dev/null
+++ b/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/Model/TokenExchangeRequest.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 System;
+
+namespace UnityEditor.Connect
+{
+ [Serializable]
+ class TokenExchangeRequest
+ {
+ public string token;
+
+ public TokenExchangeRequest() {}
+
+ public TokenExchangeRequest(string token)
+ {
+ this.token = token;
+ }
+ }
+}
diff --git a/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/Model/TokenExchangeResponse.cs b/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/Model/TokenExchangeResponse.cs
new file mode 100644
index 0000000000..af101c3e6b
--- /dev/null
+++ b/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/Model/TokenExchangeResponse.cs
@@ -0,0 +1,14 @@
+// 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.Connect
+{
+ [Serializable]
+ class TokenExchangeResponse
+ {
+ public string token;
+ }
+}
diff --git a/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/TokenExchange.cs b/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/TokenExchange.cs
new file mode 100644
index 0000000000..695a0f4c42
--- /dev/null
+++ b/Editor/Mono/UnityConnect/ServiceToken/TokenExchange/TokenExchange.cs
@@ -0,0 +1,150 @@
+// 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.Runtime.Serialization;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using UnityEngine;
+using UnityEngine.Networking;
+
+namespace UnityEditor.Connect
+{
+ class TokenExchange : ITokenExchange
+ {
+ const string k_RequestContentType = "application/json";
+ const string k_StagingServicesGatewayTokenExchangeUrl =
+ "https://staging.services.unity.com/api/auth/v1/genesis-token-exchange/unity";
+ const string k_ProductionServicesGatewayTokenExchangeUrl =
+ "https://services.unity.com/api/auth/v1/genesis-token-exchange/unity";
+
+ const string k_SerializationFailureMessage =
+ "Token Exchange failed due to an issue with serialization/deserialization. ";
+ const string k_WebRequestFailureMessage =
+ "Token Exchange failed due a failure with the web request.";
+ const string k_PayloadDeserializationFailureMessage =
+ k_SerializationFailureMessage + "Payload that failed to deserialize: ";
+ const string k_KeyMissingSerializationFailureMessage =
+ k_SerializationFailureMessage + "Deserialized response does not contain the key: ";
+
+ readonly ICloudEnvironmentConfigProvider m_CloudEnvironmentConfigProvider;
+
+ internal TokenExchange(ICloudEnvironmentConfigProvider cloudEnvironmentConfigProvider)
+ {
+ m_CloudEnvironmentConfigProvider = cloudEnvironmentConfigProvider;
+ }
+
+ public async Task GetServiceTokenAsync(
+ string genesisToken,
+ CancellationToken cancellationToken = default)
+ {
+ var tokenExchangeRequest = new TokenExchangeRequest(genesisToken);
+ Dictionary deserializedResponse;
+
+ var exchangeResult = await TokenExchangeRequestAsync(tokenExchangeRequest, cancellationToken);
+
+ try
+ {
+ deserializedResponse = Json.Deserialize(exchangeResult.ResponseJson) as Dictionary;
+ }
+ catch (Exception exception)
+ {
+ throw new SerializationException(k_PayloadDeserializationFailureMessage +
+ $"'{exchangeResult.ResponseJson}'", exception);
+ }
+
+ if (deserializedResponse is null)
+ {
+ throw new SerializationException(k_PayloadDeserializationFailureMessage +
+ $"'{exchangeResult.ResponseJson}'");
+ }
+
+ if (!TokenExchangeResponseContainsTokenKey(deserializedResponse))
+ {
+ throw new SerializationException(k_KeyMissingSerializationFailureMessage +
+ $"'{nameof(TokenExchangeResponse.token)}'");
+ }
+
+ return deserializedResponse[nameof(TokenExchangeResponse.token)].ToString();
+ }
+
+ async Task TokenExchangeRequestAsync(
+ TokenExchangeRequest tokenExchangeRequest,
+ CancellationToken cancellationToken = default)
+ {
+ var jsonPayload = Json.Serialize(tokenExchangeRequest);
+ var postBytes = Encoding.UTF8.GetBytes(jsonPayload);
+ var endpoint = GetEndpoint();
+
+ using (var exchangeRequest = new UnityWebRequest(endpoint, UnityWebRequest.kHttpVerbPOST))
+ {
+ exchangeRequest.uploadHandler = new UploadHandlerRaw(postBytes) {contentType = k_RequestContentType};
+ exchangeRequest.downloadHandler = new DownloadHandlerBuffer();
+
+ await UnityConnectWebRequestUtils.SendWebRequestAsync(exchangeRequest, cancellationToken);
+
+ VerifyTokenExchangeResponse(exchangeRequest);
+
+ return new TokenExchangeResult(
+ exchangeRequest.result.ToString(),
+ exchangeRequest.error,
+ exchangeRequest.responseCode.ToString(),
+ exchangeRequest.downloadHandler.text);
+ }
+ }
+
+ static void VerifyTokenExchangeResponse(UnityWebRequest exchangeRequest)
+ {
+ if (UnityConnectWebRequestUtils.IsUnityWebRequestReadyForJsonExtract(exchangeRequest))
+ {
+ return;
+ }
+
+ throw UnityConnectWebRequestUtils
+ .CreateUnityWebRequestException(exchangeRequest, k_WebRequestFailureMessage);
+ }
+
+ string GetEndpoint()
+ {
+ string endpoint = k_ProductionServicesGatewayTokenExchangeUrl;
+
+ try
+ {
+ if (m_CloudEnvironmentConfigProvider.IsStaging())
+ {
+ endpoint = k_StagingServicesGatewayTokenExchangeUrl;
+ }
+ }
+ catch (Exception e)
+ {
+ Debug.LogError("Error while parsing the Unity build command" +
+ " line environment argument, defaulting environment to production for token" +
+ $" exchange. Details: '{e}'.");
+ }
+
+ return endpoint;
+ }
+
+ bool TokenExchangeResponseContainsTokenKey(Dictionary deserializedResponse)
+ => deserializedResponse.ContainsKey(nameof(TokenExchangeResponse.token));
+ }
+
+ struct TokenExchangeResult
+ {
+ public string Result { get; }
+ public string Error { get; }
+ public string ResponseCode { get; }
+ public string ResponseJson { get; }
+
+ public TokenExchangeResult(string result, string error, string responseCode, string responseJson)
+ {
+ Result = result;
+ Error = error;
+ ResponseCode = responseCode;
+ ResponseJson = responseJson;
+ }
+ }
+}
diff --git a/Editor/Mono/UnityConnect/Utils/AsyncUtils.cs b/Editor/Mono/UnityConnect/Utils/AsyncUtils.cs
new file mode 100644
index 0000000000..2e77d418e1
--- /dev/null
+++ b/Editor/Mono/UnityConnect/Utils/AsyncUtils.cs
@@ -0,0 +1,79 @@
+// 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.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using UnityEngine;
+using UnityEngine.Networking;
+
+namespace UnityEditor.Connect
+{
+ internal static class AsyncUtils
+ {
+ ///
+ /// Used to run an action on the main thread of Unity
+ ///
+ /// Awaitable task that indicates when the action is completed
+ internal static Task RunNextActionOnMainThread(
+ Action action,
+ [CallerFilePath] string file = null,
+ [CallerMemberName] string caller = null,
+ [CallerLineNumber] int line = 0)
+ {
+ var taskCompletionSource = new TaskCompletionSource();
+ EditorApplication.CallbackFunction callback = null;
+ callback = () =>
+ {
+ EditorApplication.update -= callback;
+ try
+ {
+ action();
+ taskCompletionSource.SetResult(true);
+ }
+ catch (Exception e) when (caller != null && file != null && line != 0)
+ {
+ taskCompletionSource.SetException(e);
+ throw new Exception($"Exception thrown from invocation made by '{file}'({line}) by {caller}", e);
+ }
+ };
+ EditorApplication.update += callback;
+ return taskCompletionSource.Task;
+ }
+
+ ///
+ /// Used to run a UnityWebRequest on the main thread of Unity
+ ///
+ /// Awaitable task that indicates when the web request is completed
+ internal static Task RunUnityWebRequestOnMainThread(
+ UnityWebRequest request,
+ [CallerFilePath] string file = null,
+ [CallerMemberName] string caller = null,
+ [CallerLineNumber] int line = 0)
+ {
+ var taskCompletionSource = new TaskCompletionSource();
+ EditorApplication.update += Callback;
+ return taskCompletionSource.Task;
+
+ void Callback()
+ {
+ EditorApplication.update -= Callback;
+ try
+ {
+ request.SendWebRequest().completed += RequestCompleted;
+ }
+ catch (Exception e) when (caller != null && file != null && line != 0)
+ {
+ taskCompletionSource.SetException(e);
+ throw new Exception($"Exception thrown from invocation made by '{file}'({line}) by {caller}", e);
+ }
+ }
+
+ void RequestCompleted(AsyncOperation _)
+ {
+ taskCompletionSource.SetResult(true);
+ }
+ }
+ }
+}
diff --git a/Modules/BuildPipeline/Editor/Managed/BuildDefines.cs b/Modules/BuildPipeline/Editor/Managed/BuildDefines.cs
index 36e5d7afc8..7316dfb812 100644
--- a/Modules/BuildPipeline/Editor/Managed/BuildDefines.cs
+++ b/Modules/BuildPipeline/Editor/Managed/BuildDefines.cs
@@ -32,6 +32,12 @@ public static string[] GetScriptCompilationDefines(BuildTarget target, string[]
[RequiredByNativeCode]
public static string[] GetBuildProfileScriptDefines()
{
+ // Profile scripting defines are cached when profile is first activated
+ // and when the domain is unloaded. Workaround for active build profile
+ // not reachable when AssetDatabase.IsReadOnly() is true.
+ if (!EditorUserBuildSettings.isBuildProfileAvailable)
+ return EditorUserBuildSettings.GetActiveProfileScriptingDefines();
+
var profile = EditorUserBuildSettings.activeBuildProfile;
if (profile == null)
return EditorUserBuildSettings.GetActiveProfileScriptingDefines();
diff --git a/Modules/BuildProfileEditor/ActiveBuildProfilerListener.cs b/Modules/BuildProfileEditor/ActiveBuildProfilerListener.cs
new file mode 100644
index 0000000000..b0e1a7cd53
--- /dev/null
+++ b/Modules/BuildProfileEditor/ActiveBuildProfilerListener.cs
@@ -0,0 +1,40 @@
+// Unity C# reference source
+// Copyright (c) Unity Technologies. For terms of use, see
+// https://unity3d.com/legal/licenses/Unity_Reference_Only_License
+
+using System;
+using UnityEngine;
+using UnityEditor;
+using UnityEditor.Build;
+
+namespace UnityEditor.Build.Profile
+{
+ ///
+ /// Listener for when the active build target changes.
+ /// This is used to update the build profile window and other components when the active build target changes.
+ ///
+ ///
+ /// Calls to `EditorUserBuildSettings.SwitchActiveBuildTarget(buildTargetGroup, target)` will trigger this event.
+ /// The event is called after the build target is changed, and an instance of this class is created when the event occurs.
+ ///
+ internal class ActiveBuildTargetListener : IActiveBuildTargetChanged
+ {
+ ///
+ /// The order in which the callback will be called. Lower numbers are called first.
+ ///
+ public int callbackOrder => 0;
+
+ ///
+ /// Called when the active build target changes.
+ ///
+ public void OnActiveBuildTargetChanged(BuildTarget previousTarget, BuildTarget newTarget)
+ {
+ activeBuildTargetChanged?.Invoke(previousTarget, newTarget);
+ }
+
+ ///
+ /// Event that is called when the active build platform changes.
+ ///
+ static public event Action activeBuildTargetChanged;
+ }
+}
diff --git a/Modules/BuildProfileEditor/BuildProfileEditor.cs b/Modules/BuildProfileEditor/BuildProfileEditor.cs
index 1ae36a00d9..62d185d8af 100644
--- a/Modules/BuildProfileEditor/BuildProfileEditor.cs
+++ b/Modules/BuildProfileEditor/BuildProfileEditor.cs
@@ -28,6 +28,7 @@ internal class BuildProfileEditor : Editor
const string k_SceneListFoldoutAddOpenButton = "scene-list-foldout-add-open-button";
const string k_SceneListFoldoutClassicSection = "scene-list-foldout-classic-section";
const string k_SceneListFoldoutClassicButton = "scene-list-foldout-classic-button";
+ const string k_SceneListGlobalToggle = "scene-list-global-toggle";
const string k_CompilingWarningHelpBox = "compiling-warning-help-box";
const string k_VirtualTextureWarningHelpBox = "virtual-texture-warning-help-box";
const string k_PlatformBuildWarningsRoot = "platform-build-warning-root";
@@ -279,6 +280,8 @@ void AddSceneList(VisualElement root, BuildProfile profile = null)
bool isEnable = isGlobalSceneList || !isClassicPlatform;
var sceneListFoldout = root.Q(k_SceneListFoldout);
+ var globalToggle = root.Q(k_SceneListGlobalToggle);
+ globalToggle.label = TrText.sceneListOverride;
sceneListFoldout.text = TrText.sceneList;
m_SceneList = (isGlobalSceneList || isClassicPlatform)
? new BuildProfileSceneList()
@@ -303,8 +306,35 @@ void AddSceneList(VisualElement root, BuildProfile profile = null)
root.Q(k_SceneListFoldoutClassicSection).Show();
var globalSceneListButton = root.Q
- [EditorWindowTitle(title = "Build Settings")]
internal class BuildProfileWindow : EditorWindow
{
const string k_DevOpsUrl = "https://unity.com/products/unity-devops?utm_medium=desktop-app&utm_source=unity-editor-window-menu&utm_content=buildsettings";
@@ -152,6 +151,7 @@ public void CreateGUI()
BuildProfileContext.activeProfileChanged -= OnActiveProfileChanged;
BuildProfileContext.activeProfileChanged += OnActiveProfileChanged;
+ ActiveBuildTargetListener.activeBuildTargetChanged += OnActiveBuildTargetChanged;
}
public void OnDisable()
@@ -159,6 +159,7 @@ public void OnDisable()
DestroyImmediate(buildProfileEditor);
BuildProfileContext.activeProfileChanged -= OnActiveProfileChanged;
+ ActiveBuildTargetListener.activeBuildTargetChanged -= OnActiveBuildTargetChanged;
if (m_BuildProfileDataSource != null)
{
@@ -536,6 +537,15 @@ void OnActiveProfileChanged(BuildProfile prev, BuildProfile cur)
UpdateFormButtonState(m_BuildProfileSelection.Get(0));
}
+ ///
+ /// Callback invoked when the active build target changes.
+ ///
+ void OnActiveBuildTargetChanged(BuildTarget prev, BuildTarget cur)
+ {
+ m_ProfileListViews.Rebuild();
+ UpdateFormButtonState(m_BuildProfileSelection.Get(0));
+ }
+
void RebuildBuildProfileEditor(BuildProfile profile)
{
// Rebuild the BuildProfile inspector, targeting the newly selected BuildProfile.
diff --git a/Modules/BuildProfileEditor/Handlers/BuildProfileContextMenu.cs b/Modules/BuildProfileEditor/Handlers/BuildProfileContextMenu.cs
index 03dc847676..b1e4cac3a4 100644
--- a/Modules/BuildProfileEditor/Handlers/BuildProfileContextMenu.cs
+++ b/Modules/BuildProfileEditor/Handlers/BuildProfileContextMenu.cs
@@ -3,6 +3,8 @@
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License
using System;
+using System.Text;
+using System.Collections.Generic;
using UnityEditor.Build.Profile.Elements;
using UnityEngine.UIElements;
@@ -17,8 +19,13 @@ internal class BuildProfileContextMenu
static readonly string k_DeleteContinue = L10n.Tr("Continue");
static readonly string k_DeleteCancel = L10n.Tr("Cancel");
- static readonly string k_DeleteTitle = L10n.Tr("Delete Active Build Profile");
- static readonly string k_DeleteMessage = L10n.Tr("This will delete your active build profile and activate the respective platform build profile. This cannot be undone.");
+ static readonly string k_DeleteActiveProfileTitle = L10n.Tr("Delete Active Build Profile");
+ static readonly string k_DeleteActiveProfileMessage = L10n.Tr("This will delete your active build profile and activate the respective platform build profile. This cannot be undone.");
+ static readonly string k_DeleteProfileTitle = L10n.Tr("Delete Selected Build Profile");
+ static readonly string k_DeleteProfileMessage = L10n.Tr("This will delete the selected build profile. This cannot be undone.");
+ static readonly string k_DeleteMultipleProfilesTitle = L10n.Tr("Delete Selected Build Profiles");
+ static readonly string k_DeleteActiveAndOtherProfilesMessage = L10n.Tr("This will delete the selected build profiles, including your active build profile, and activate the respective platform build profile. This cannot be undone.");
+ static readonly string k_DeleteMultipleProfilesMessage = L10n.Tr("This will delete the selected build profiles. This cannot be undone.");
readonly BuildProfileWindowSelection m_ProfileSelection;
readonly BuildProfileDataSource m_ProfileDataSource;
@@ -132,20 +139,55 @@ void SelectBuildProfileInView(BuildProfile buildProfile, bool isClassic, bool sh
m_ProfileSelection.visualElement.SelectBuildProfile(index);
}
+ ///
+ /// Given a list of build profiles, prompts for user confirmation for deletion.
+ ///
+ /// true, if user approves deletion.
+ bool ShowDeleteSelectedProfilesDialog(List selectedProfiles)
+ {
+ var maxPathsToShow = 3;
+ var paths = new StringBuilder();
+ var containsActiveProfile = false;
+ for (int i = selectedProfiles.Count - 1; i >= 0; --i)
+ {
+ if (i < selectedProfiles.Count - maxPathsToShow)
+ {
+ paths.Append("...\n");
+ break;
+ }
+
+ var profile = selectedProfiles[i];
+ if (BuildProfileContext.activeProfile == profile)
+ containsActiveProfile = true;
+
+ var path = AssetDatabase.GetAssetPath(profile);
+ paths.Append(path + "\n");
+ }
+
+ var multipleProfiles = selectedProfiles.Count > 1;
+ var title = multipleProfiles
+ ? k_DeleteMultipleProfilesTitle
+ : containsActiveProfile ? k_DeleteActiveProfileTitle : k_DeleteProfileTitle;
+ var message = paths + "\n" + (multipleProfiles
+ ? (containsActiveProfile ? k_DeleteActiveAndOtherProfilesMessage : k_DeleteMultipleProfilesMessage)
+ : (containsActiveProfile ? k_DeleteActiveProfileMessage : k_DeleteProfileMessage));
+
+ return EditorUtility.DisplayDialog(title, message, k_DeleteContinue, k_DeleteCancel);
+ }
+
void HandleDeleteSelectedProfiles()
{
var selectedProfiles = m_ProfileSelection.GetAll();
+ var isDeleteConfirmed = ShowDeleteSelectedProfilesDialog(selectedProfiles);
+ if (!isDeleteConfirmed)
+ return;
+
+ var profilesDeleted = false;
for (int i = selectedProfiles.Count - 1; i >= 0; --i)
{
var profile = selectedProfiles[i];
if (BuildProfileContext.activeProfile == profile)
{
- string path = AssetDatabase.GetAssetPath(profile);
- string finalMessage = $"{path}\n\n{k_DeleteMessage}";
- if (!EditorUtility.DisplayDialog(k_DeleteTitle, finalMessage, k_DeleteContinue, k_DeleteCancel))
- {
- continue;
- }
// if we're deleting an active profile, we want to compare the value of its settings that require a restart
// to the value of the settings for the platform we'll be activating after we delete the current platform
// and show a restart editor prompt if they're different so the settings take effect
@@ -158,11 +200,13 @@ void HandleDeleteSelectedProfiles()
}
m_ProfileDataSource.DeleteAsset(profile);
+ profilesDeleted = true;
}
// No need to select a profile after deletion, since the method below
// selects the active profile after repaint
- m_ProfileWindow.RepaintAndClearSelection();
+ if (profilesDeleted)
+ m_ProfileWindow.RepaintAndClearSelection();
}
}
}
diff --git a/Modules/BuildProfileEditor/TrText.cs b/Modules/BuildProfileEditor/TrText.cs
index 1ff51f2242..ff4156f6d5 100644
--- a/Modules/BuildProfileEditor/TrText.cs
+++ b/Modules/BuildProfileEditor/TrText.cs
@@ -35,6 +35,7 @@ internal class TrText
public static readonly string activatePlatform = L10n.Tr("Switch Platform");
public static readonly string sceneList = L10n.Tr("Scene List");
public static readonly string addOpenScenes = L10n.Tr("Add Open Scenes");
+ public static readonly string sceneListOverride = L10n.Tr("Override Global Scene List");
public static readonly string openSceneList = L10n.Tr("Open Scene List");
public static readonly string compilingMessage = L10n.Tr("Cannot build player while editor is importing assets or compiling scripts.");
public static readonly string invalidVirtualTexturingSettingMessage = L10n.Tr("Cannot build player because Virtual Texturing is enabled, but the target platform or graphics API does not support Virtual Texturing. Go to Player Settings to resolve the incompatibility.");
diff --git a/Modules/EditorToolbar/ToolbarElements/PreviewPackagesInUseDropdown.cs b/Modules/EditorToolbar/ToolbarElements/PreviewPackagesInUseDropdown.cs
index 3bfe03c644..94b2641425 100644
--- a/Modules/EditorToolbar/ToolbarElements/PreviewPackagesInUseDropdown.cs
+++ b/Modules/EditorToolbar/ToolbarElements/PreviewPackagesInUseDropdown.cs
@@ -31,6 +31,8 @@ sealed class PreviewPackagesInUseDropdown : ToolbarButton
private const string previewIcon = "unity-editor-toolbar-preview-package-in-use__icon";
private const string previewArrowIcon = "unity-icon-arrow-preview-packages-in-use";
+ private static readonly string k_ExpPackagesInUseText = L10n.Tr("Experimental Packages In Use");
+
public PreviewPackagesInUseDropdown()
{
m_ApplicationProxy = ServicesContainer.instance.Resolve();
@@ -41,7 +43,9 @@ public PreviewPackagesInUseDropdown()
AddToClassList("unity-toolbar-button-preview-packages-in-use");
- AddTextElement(this).text = L10n.Tr("Experimental Packages In Use");
+ tooltip = k_ExpPackagesInUseText;
+
+ AddTextElement(this).text = k_ExpPackagesInUseText;
AddIconElement(this);
AddArrowElement(this);
clicked += () => ShowUserMenu(worldBound);
diff --git a/Modules/IMGUI/TextSelectingUtilities.cs b/Modules/IMGUI/TextSelectingUtilities.cs
index 08020e26e6..7e77f22b5f 100644
--- a/Modules/IMGUI/TextSelectingUtilities.cs
+++ b/Modules/IMGUI/TextSelectingUtilities.cs
@@ -387,6 +387,15 @@ public void SelectParagraphForward()
{
ClearCursorPos();
bool wasBehind = cursorIndex < selectIndex;
+
+ if (textHandle.useAdvancedText)
+ {
+ int cursorTempIndex = cursorIndex;
+ textHandle.SelectToNextParagraph(ref cursorTempIndex);
+ cursorIndex = cursorTempIndex;
+ return;
+ }
+
if (cursorIndex < characterCount)
{
cursorIndex = IndexOfEndOfLine(cursorIndex + 1);
@@ -399,9 +408,19 @@ public void SelectParagraphBackward()
{
ClearCursorPos();
bool wasInFront = cursorIndex > selectIndex;
+
+ if (textHandle.useAdvancedText)
+ {
+ int cursorTempIndex = cursorIndex;
+ textHandle.SelectToPreviousParagraph(ref cursorTempIndex);
+ cursorIndex = cursorTempIndex;
+ return;
+ }
+
if (cursorIndex > 1)
{
cursorIndex = textHandle.LastIndexOf(kNewLineChar, cursorIndex - 2) + 1;
+
if (wasInFront && cursorIndex < selectIndex)
cursorIndex = selectIndex;
}
@@ -454,6 +473,16 @@ public void SelectCurrentParagraph()
ClearCursorPos();
int textLen = characterCount;
+ if (textHandle.useAdvancedText)
+ {
+ int cursorTempIndex = cursorIndex;
+ int selectTempIndex = selectIndex;
+ textHandle.SelectCurrentParagraph(ref cursorTempIndex, ref selectTempIndex);
+ cursorIndex = cursorTempIndex;
+ selectIndex = selectTempIndex;
+ return;
+ }
+
if (cursorIndex < textLen)
cursorIndex = IndexOfEndOfLine(cursorIndex);
if (selectIndex != 0)
@@ -583,6 +612,14 @@ public void MoveTextEnd()
/// Move to the next paragraph
public void MoveParagraphForward()
{
+ if (textHandle.useAdvancedText)
+ {
+ int cursorTempIndex = cursorIndex;
+ textHandle.SelectToNextParagraph(ref cursorTempIndex);
+ cursorIndex = selectIndex = cursorTempIndex;
+ return;
+ }
+
cursorIndex = cursorIndex > selectIndex ? cursorIndex : selectIndex;
if (cursorIndex < characterCount)
{
@@ -593,6 +630,14 @@ public void MoveParagraphForward()
/// Move to the previous paragraph
public void MoveParagraphBackward()
{
+ if (textHandle.useAdvancedText)
+ {
+ int cursorTempIndex = cursorIndex;
+ textHandle.SelectToPreviousParagraph(ref cursorTempIndex);
+ cursorIndex = selectIndex = cursorTempIndex;
+ return;
+ }
+
cursorIndex = cursorIndex < selectIndex ? cursorIndex : selectIndex;
if (cursorIndex > 1)
{
@@ -760,8 +805,16 @@ public void SelectToPosition(Vector2 cursorPosition)
} // paragraph
else
{
- if (p <= m_DblClickInitPosStart)
+ if ((!textHandle.useAdvancedText && p <= m_DblClickInitPosStart) || (textHandle.useAdvancedText && p < m_DblClickInitPosStart))
{
+ if (textHandle.useAdvancedText)
+ {
+ int selectTempIndex = p;
+ textHandle.SelectToStartOfParagraph(ref selectTempIndex);
+ selectIndex = selectTempIndex;
+ return;
+ }
+
if (p > 0)
cursorIndex = textHandle.LastIndexOf(kNewLineChar, Mathf.Max(0, p - 1)) + 1;
else
@@ -771,6 +824,14 @@ public void SelectToPosition(Vector2 cursorPosition)
}
else if (p >= m_DblClickInitPosEnd)
{
+ if (textHandle.useAdvancedText)
+ {
+ int cursorTempIndex = p;
+ textHandle.SelectToEndOfParagraph(ref cursorTempIndex);
+ cursorIndex = cursorTempIndex;
+ return;
+ }
+
if (p < characterCount)
{
cursorIndex = IndexOfEndOfLine(p);
@@ -782,8 +843,16 @@ public void SelectToPosition(Vector2 cursorPosition)
}
else
{
- cursorIndex = m_DblClickInitPosStart;
- selectIndex = m_DblClickInitPosEnd;
+ if (textHandle.useAdvancedText)
+ {
+ cursorIndex = m_DblClickInitPosEnd;
+ selectIndex = m_DblClickInitPosStart;
+ }
+ else
+ {
+ cursorIndex = m_DblClickInitPosStart;
+ selectIndex = m_DblClickInitPosEnd;
+ }
}
}
}
diff --git a/Modules/Marshalling/MarshallingTests.bindings.cs b/Modules/Marshalling/MarshallingTests.bindings.cs
index 5b50ced44b..6a8019eb19 100644
--- a/Modules/Marshalling/MarshallingTests.bindings.cs
+++ b/Modules/Marshalling/MarshallingTests.bindings.cs
@@ -901,6 +901,13 @@ internal class ValueTypeSpanTests
[NativeThrows] public static extern void ParameterCharReadOnlySpan(ReadOnlySpan param);
[NativeThrows] public static extern void ParameterEnumReadOnlySpan(ReadOnlySpan param);
[NativeThrows] public static extern void ParameterBlittableCornerCaseStructReadOnlySpan(ReadOnlySpan param);
+ public static extern Span ReturnsArrayRefWritableAsSpan(int val1, int val2, int val3);
+ public static extern Span ReturnsCoreVectorRefAsSpan(int val1, int val2, int val3);
+ public static extern Span ReturnsScriptingSpanAsSpan(int val1, int val2, int val3);
+ public static extern ReadOnlySpan ReturnsArrayRefWritableAsReadOnlySpan(int val1, int val2, int val3);
+ public static extern ReadOnlySpan ReturnsCoreVectorRefAsReadOnlySpan(int val1, int val2, int val3);
+ public static extern ReadOnlySpan ReturnsArrayRefAsReadOnlySpan(int val1, int val2, int val3);
+ public static extern ReadOnlySpan ReturnsScriptingReadOnlySpanAsSpan(int val1, int val2, int val3);
}
// --------------------------------------------------------------------
diff --git a/Modules/PackageManagerUI/Editor/External/SemVersionExtension.cs b/Modules/PackageManagerUI/Editor/External/SemVersionExtension.cs
index 55df745938..98eafd5d32 100644
--- a/Modules/PackageManagerUI/Editor/External/SemVersionExtension.cs
+++ b/Modules/PackageManagerUI/Editor/External/SemVersionExtension.cs
@@ -26,13 +26,13 @@ public static bool IsEqualOrPatchOf(this SemVersion version, SemVersion? olderVe
return version.Major == olderVersion?.Major && version.Minor == olderVersion?.Minor && version >= olderVersion;
}
- // The implementation here matches with the experimental version tagging check implemented in Package Validation Suite (US0017 CorrectPackageVersionTags)
- public static bool IsExperimental(this SemVersion version)
+ // The implementation to check for Experimental packages here matches with the experimental version tagging check implemented in Package Validation Suite (US0017 CorrectPackageVersionTags)
+ public static PackageTag GetExpOrPreOrReleaseTag(this SemVersion version)
{
if (string.IsNullOrEmpty(version.Prerelease))
- return false;
+ return PackageTag.Release;
var match = k_ExpRegex.Match(version.Prerelease);
- return match.Success && match.Groups["iteration"].Success && match.Groups["feature"].Value.Length <= 10;
+ return match.Success && match.Groups["iteration"].Success && match.Groups["feature"].Value.Length <= 10 ? PackageTag.Experimental : PackageTag.PreRelease;
}
}
}
diff --git a/Modules/PackageManagerUI/Editor/Services/Pages/PageManager.cs b/Modules/PackageManagerUI/Editor/Services/Pages/PageManager.cs
index 9b1645fed7..3424492f12 100644
--- a/Modules/PackageManagerUI/Editor/Services/Pages/PageManager.cs
+++ b/Modules/PackageManagerUI/Editor/Services/Pages/PageManager.cs
@@ -243,8 +243,6 @@ public IPage FindPage(IList packages)
if (packages.All(p => page.ShouldInclude(p)))
return page;
- if (!m_SettingsProxy.enablePreReleasePackages && packages.Any(p => p.versions.primary.version?.Prerelease.StartsWith("pre.") == true))
- Debug.Log(L10n.Tr("You must check \"Show Pre-release Package Versions\" in Project Settings > Package Manager in order to see this package."));
return null;
}
diff --git a/Modules/PackageManagerUI/Editor/Services/Upm/UpmClient.cs b/Modules/PackageManagerUI/Editor/Services/Upm/UpmClient.cs
index 55ddb71bf7..a0837f8c2c 100644
--- a/Modules/PackageManagerUI/Editor/Services/Upm/UpmClient.cs
+++ b/Modules/PackageManagerUI/Editor/Services/Upm/UpmClient.cs
@@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Linq;
using UnityEditor.PackageManager.Requests;
+using UnityEditor.Scripting.ScriptCompilation;
using UnityEngine;
namespace UnityEditor.PackageManager.UI.Internal
@@ -110,7 +111,7 @@ public UpmClient(IUpmCache upmCache,
public bool IsAnyExperimentalPackagesInUse()
{
- return PackageInfo.GetAllRegisteredPackages().Any(info => (info.version.Contains("-preview") || info.version.Contains("-exp.") || info.version.StartsWith("0.")) && IsUnityPackage(info));
+ return PackageInfo.GetAllRegisteredPackages().Any(info => SemVersionParser.TryParse(info.version, out var parsedVersion) && parsedVersion?.GetExpOrPreOrReleaseTag() == PackageTag.Experimental);
}
public void OnBeforeSerialize()
diff --git a/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackageVersion.cs b/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackageVersion.cs
index 241decdad3..53d0c8be13 100644
--- a/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackageVersion.cs
+++ b/Modules/PackageManagerUI/Editor/Services/Upm/UpmPackageVersion.cs
@@ -228,13 +228,10 @@ private void RefreshTags(PackageInfo packageInfo)
if (!HasTag(PackageTag.InstalledFromPath) && packageInfo.versions.deprecated.Contains(m_VersionString))
m_Tag |= PackageTag.Deprecated;
- if (isInvalidSemVerInManifest)
+ if (isInvalidSemVerInManifest || m_Version == null)
return;
- if (m_Version?.IsExperimental() == true)
- m_Tag |= PackageTag.Experimental;
- else
- m_Tag |= string.IsNullOrEmpty(m_Version?.Prerelease) ? PackageTag.Release : PackageTag.PreRelease;
+ m_Tag |= m_Version.Value.GetExpOrPreOrReleaseTag();
}
public override string GetDescriptor(bool isFirstLetterCapitalized = false)
diff --git a/Modules/PackageManagerUI/Editor/UI/PackageDetailsTabs/PackageDetailsVersionsTab.cs b/Modules/PackageManagerUI/Editor/UI/PackageDetailsTabs/PackageDetailsVersionsTab.cs
index 76cc9550e3..0170c82699 100644
--- a/Modules/PackageManagerUI/Editor/UI/PackageDetailsTabs/PackageDetailsVersionsTab.cs
+++ b/Modules/PackageManagerUI/Editor/UI/PackageDetailsTabs/PackageDetailsVersionsTab.cs
@@ -111,8 +111,7 @@ protected override void RefreshContent(IPackageVersion version)
return;
}
- var seeVersionsToolbar = versions.numUnloadedVersions > 0 && (m_SettingsProxy.seeAllPackageVersions || m_Version.availableRegistry != RegistryType.UnityRegistry || m_Version.package.versions.installed?.HasTag(PackageTag.Experimental) == true);
- UIUtils.SetElementDisplay(m_VersionsToolbar, seeVersionsToolbar);
+ UIUtils.SetElementDisplay(m_VersionsToolbar, versions.numUnloadedVersions > 0);
UIUtils.SetElementDisplay(m_LoadingLabel, false);
var primaryVersion = m_Version.package?.versions.primary;
diff --git a/Modules/PackageManagerUI/Editor/UI/PackageManagerWindowRoot.cs b/Modules/PackageManagerUI/Editor/UI/PackageManagerWindowRoot.cs
index 9ff2cdd56f..3fccba18ff 100644
--- a/Modules/PackageManagerUI/Editor/UI/PackageManagerWindowRoot.cs
+++ b/Modules/PackageManagerUI/Editor/UI/PackageManagerWindowRoot.cs
@@ -302,18 +302,18 @@ private bool TryApplyPackageAndPageSelection(PackageAndPageSelectionArgs args, b
// of this function to after the `My Assets` logic is done so that we don't break `My Assets` and the Entitlement Error checker.
if (!m_PageRefreshHandler.IsInitialFetchingDone(m_PageManager.activePage))
return false;
-
+
if (string.IsNullOrEmpty(args.packageToSelect))
{
m_PageManager.activePage = args.page;
return true;
}
-
+
m_PackageDatabase.GetPackageAndVersionByIdOrName(args.packageToSelect, out var package, out var version, true);
if (package == null && failIfPackageIsNotFoundInDatabase)
return false;
-
+
m_PageManager.activePage = args.page;
if (package != null)
{
@@ -380,11 +380,10 @@ public void SelectPackageAndPage(string packageToSelect = null, string pageId =
var packageToSelectSplit = packageToSelect.Split('@');
var versionString = packageToSelectSplit.Length == 2 ? packageToSelectSplit[1] : string.Empty;
- // Package is not found in PackageDatabase but we can determine if it's a preview package or not with it's version string.
- SemVersionParser.TryParse(versionString, out var semVersion);
- if (!m_SettingsProxy.enablePreReleasePackages && semVersion.HasValue && (semVersion.Value.Major == 0 || semVersion.Value.Prerelease.StartsWith("preview")))
+ // Package is not found in PackageDatabase, but we can determine if it's a prerelease package or not with its version string.
+ if (!m_SettingsProxy.enablePreReleasePackages && SemVersionParser.TryParse(versionString, out var semVersion) && semVersion?.GetExpOrPreOrReleaseTag() == PackageTag.PreRelease)
{
- Debug.Log("You must check \"Enable Preview Packages\" in Project Settings > Package Manager in order to see this package.");
+ Debug.Log("You must check \"Enable Pre-release Package Versions\" in Project Settings > Package Manager in order to see this package.");
args.packageToSelect = null;
}
args.page = m_PageManager.activePage;
diff --git a/Modules/QuickSearch/Editor/SearchService.cs b/Modules/QuickSearch/Editor/SearchService.cs
index 9f084a08e8..7cfe6a2d79 100644
--- a/Modules/QuickSearch/Editor/SearchService.cs
+++ b/Modules/QuickSearch/Editor/SearchService.cs
@@ -38,7 +38,7 @@ public class SearchActionsProviderAttribute : Attribute
public static class SearchService
{
private const int k_MaxFetchTimeMs = 50;
- private const int k_MaxSessionTimeMs = 60000;
+ private const int k_MaxSessionTimeMs = SearchSession.k_InfiniteSession;
static SearchProvider s_SearchServiceProvider;
///
diff --git a/Modules/QuickSearch/Editor/SearchSession.cs b/Modules/QuickSearch/Editor/SearchSession.cs
index 72920eec3f..25784bc6a6 100644
--- a/Modules/QuickSearch/Editor/SearchSession.cs
+++ b/Modules/QuickSearch/Editor/SearchSession.cs
@@ -200,11 +200,12 @@ class SearchSession : BaseAsyncIEnumerableHandler
/// This event is used to know when a search has finished fetching items.
///
public event Action sessionEnded;
+ public const int k_InfiniteSession = -1;
private SearchSessionContext m_Context;
private SearchProvider m_Provider;
private Stopwatch m_SessionTimer = new Stopwatch();
- private const long k_DefaultSessionTimeOut = 10000;
+ private const long k_DefaultSessionTimeOut = k_InfiniteSession;
private long m_SessionTimeOut = k_DefaultSessionTimeOut;
///
@@ -280,7 +281,7 @@ public override void Update(List newItems)
m_SessionTimer.Restart();
}
- if (m_SessionTimer.ElapsedMilliseconds > m_SessionTimeOut)
+ if (m_SessionTimeOut > 0 && m_SessionTimer.ElapsedMilliseconds > m_SessionTimeOut)
{
// Do this before stopping to get target IEnumerator
var timeOutError = BuildSessionContextTimeOutError();
diff --git a/Modules/QuickSearch/Editor/SearchUtils.cs b/Modules/QuickSearch/Editor/SearchUtils.cs
index 6da7e00741..0a86561552 100644
--- a/Modules/QuickSearch/Editor/SearchUtils.cs
+++ b/Modules/QuickSearch/Editor/SearchUtils.cs
@@ -1500,6 +1500,7 @@ internal static ISearchView OpenWithContextualProviders(params string[] provider
return OpenWithContextualProviders("", providerIds);
}
+ [Flags]
internal enum OpenWithContextualProvidersFlags
{
None = 0,
diff --git a/Modules/QuickSearch/Editor/UITK/SearchView.cs b/Modules/QuickSearch/Editor/UITK/SearchView.cs
index 064355092f..e2b8a28b9b 100644
--- a/Modules/QuickSearch/Editor/UITK/SearchView.cs
+++ b/Modules/QuickSearch/Editor/UITK/SearchView.cs
@@ -740,7 +740,7 @@ private void NotifySyncSearch(string groupId, UnityEditor.SearchService.SearchSe
syncViewId = typeof(SceneSearchEngine).FullName;
break;
}
- UnityEditor.SearchService.SearchService.NotifySyncSearchChanged(evt, syncViewId, context.searchText);
+ UnityEditor.SearchService.SearchService.NotifySyncSearchChanged(evt, syncViewId, context.searchQuery);
}
public void SetupColumns(IList fields)
diff --git a/Modules/TextCoreTextEngine/Managed/AssemblyInfo.cs b/Modules/TextCoreTextEngine/Managed/AssemblyInfo.cs
index 950726187e..66dddeeb84 100644
--- a/Modules/TextCoreTextEngine/Managed/AssemblyInfo.cs
+++ b/Modules/TextCoreTextEngine/Managed/AssemblyInfo.cs
@@ -34,3 +34,4 @@
[assembly: InternalsVisibleTo("Unity.TextCore.Editor.Tests")]
[assembly: InternalsVisibleTo("Unity.TextMeshPro.Editor")]
[assembly: InternalsVisibleTo("Unity.TextMeshPro.Editor.Tests")]
+[assembly: InternalsVisibleTo("Unity.UIElements.EditorTests")]
diff --git a/Modules/TextCoreTextEngine/Managed/NativeTextInfo.cs b/Modules/TextCoreTextEngine/Managed/NativeTextInfo.cs
index 08f6db045a..2f05cca2ad 100644
--- a/Modules/TextCoreTextEngine/Managed/NativeTextInfo.cs
+++ b/Modules/TextCoreTextEngine/Managed/NativeTextInfo.cs
@@ -7,6 +7,7 @@
namespace UnityEngine.TextCore.Text
{
+
[VisibleToOtherModules("UnityEngine.UIElementsModule", "UnityEngine.IMGUIModule")]
[StructLayout(LayoutKind.Sequential)]
[NativeHeader("Modules/TextCoreTextEngine/Native/TextInfo.h")]
diff --git a/Modules/TextCoreTextEngine/Managed/TextAssets/FontAsset.cs b/Modules/TextCoreTextEngine/Managed/TextAssets/FontAsset.cs
index 43b08a1a4d..4df9b7ecde 100644
--- a/Modules/TextCoreTextEngine/Managed/TextAssets/FontAsset.cs
+++ b/Modules/TextCoreTextEngine/Managed/TextAssets/FontAsset.cs
@@ -956,6 +956,7 @@ private void OnValidate()
// We need to update those lists to native in case they changed
UpdateFallbacks();
UpdateWeightFallbacks();
+ UpdateFaceInfo();
}
}
diff --git a/Modules/TextCoreTextEngine/Managed/TextAssets/FontAsset/NativeFontAsset.cs b/Modules/TextCoreTextEngine/Managed/TextAssets/FontAsset/NativeFontAsset.cs
index 7ee23f9dd5..71f97c9dd3 100644
--- a/Modules/TextCoreTextEngine/Managed/TextAssets/FontAsset/NativeFontAsset.cs
+++ b/Modules/TextCoreTextEngine/Managed/TextAssets/FontAsset/NativeFontAsset.cs
@@ -44,6 +44,11 @@ internal void UpdateWeightFallbacks()
UpdateWeightFallbacks(nativeFontAsset, weights.Item1, weights.Item2);
}
+ internal void UpdateFaceInfo()
+ {
+ UpdateFaceInfo(nativeFontAsset, faceInfo);
+ }
+
internal IntPtr[] GetFallbacks()
{
List fallbackList = new List();
@@ -177,6 +182,7 @@ private bool HasRecursionInternal(FontAsset fontAsset)
private static extern void UpdateWeightFallbacks(IntPtr ptr, IntPtr[] regularFallbacks, IntPtr[] italicFallbacks);
private static extern IntPtr Create(FaceInfo faceInfo, Font sourceFontFile, Font sourceFont_EditorRef, string sourceFontFilePath, int fontInstanceID, IntPtr[] fallbacks, IntPtr[] weightFallbacks, IntPtr[] italicFallbacks);
+ private static extern void UpdateFaceInfo(IntPtr ptr, FaceInfo faceInfo);
[FreeFunction("FontAsset::Destroy")]
private static extern void Destroy(IntPtr ptr);
diff --git a/Modules/TextCoreTextEngine/Managed/TextGenerator/NativeTextGenerationSettings.bindings.cs b/Modules/TextCoreTextEngine/Managed/TextGenerator/NativeTextGenerationSettings.bindings.cs
index 5c138ebf66..771a828471 100644
--- a/Modules/TextCoreTextEngine/Managed/TextGenerator/NativeTextGenerationSettings.bindings.cs
+++ b/Modules/TextCoreTextEngine/Managed/TextGenerator/NativeTextGenerationSettings.bindings.cs
@@ -7,6 +7,7 @@
using UnityEngine.Bindings;
using UnityEngine.Scripting;
using UnityEngine.TextCore.Text;
+using System.Collections.Generic;
namespace UnityEngine.TextCore
{
@@ -18,28 +19,68 @@ internal struct NativeTextGenerationSettings
{
public IntPtr fontAsset;
public IntPtr[] globalFontAssetFallbacks;
- public string text; // TODO: use RenderedText instead of string here
+ public string text; // Contains the parsed text, meaning the rich text tags have been removed.
public int screenWidth; // Encoded in Fixed Point.
public int screenHeight; // Encoded in Fixed Point.
- public int fontSize; // Encoded in Fixed Point.
public WhiteSpace wordWrap;
public LanguageDirection languageDirection;
-
+ public int vertexPadding; // Encoded in Fixed Point.
[VisibleToOtherModules("UnityEngine.UIElementsModule")]
internal HorizontalAlignment horizontalAlignment;
[VisibleToOtherModules("UnityEngine.UIElementsModule")]
internal VerticalAlignment verticalAlignment;
- public Color32 color;
+ public int fontSize; // Encoded in Fixed Point.
public FontStyles fontStyle;
public TextFontWeight fontWeight;
- public int vertexPadding; // Encoded in Fixed Point.
+
+ public TextSpan[] textSpans;
+ public Color32 color;
+
+ public bool hasLink => textSpans != null && Array.Exists(textSpans, span => span.linkID != -1);
+
+ public readonly TextSpan CreateTextSpan()
+ {
+ return new TextSpan()
+ {
+ fontAsset = this.fontAsset,
+ fontSize = this.fontSize,
+ color = this.color,
+ fontStyle = this.fontStyle,
+ fontWeight = this.fontWeight,
+ linkID = -1
+ };
+ }
+
+ // Used by automated tests
+ public string GetTextSpanContent(int spanIndex)
+ {
+ if (string.IsNullOrEmpty(text))
+ {
+ throw new InvalidOperationException("The text property is null or empty.");
+ }
+
+ if (textSpans == null || spanIndex < 0 || spanIndex >= textSpans.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(spanIndex), "Invalid span index.");
+ }
+
+ TextSpan span = textSpans[spanIndex];
+
+ if (span.startIndex < 0 || span.startIndex >= text.Length || span.startIndex + span.length > text.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(spanIndex), "Invalid startIndex or length for the current text.");
+ }
+
+ return text.Substring(span.startIndex, span.length);
+ }
public static NativeTextGenerationSettings Default => new ()
{
fontStyle = FontStyles.Normal,
- fontWeight = TextFontWeight.Regular
+ fontWeight = TextFontWeight.Regular,
+ color = Color.black,
};
// Used by automated tests
@@ -59,28 +100,73 @@ internal NativeTextGenerationSettings(NativeTextGenerationSettings tgs)
fontWeight = tgs.fontWeight;
languageDirection = tgs.languageDirection;
vertexPadding = tgs.vertexPadding;
+ textSpans = tgs.textSpans != null ? (TextSpan[])tgs.textSpans.Clone() : null;
}
public override string ToString()
{
string fallbacksString = globalFontAssetFallbacks != null
- ? $"{string.Join(", ", globalFontAssetFallbacks)}"
- : "null";
+ ? $"{string.Join(", ", globalFontAssetFallbacks)}"
+ : "null";
+
+ string textSpansString = "null";
+ if (textSpans != null)
+ {
+ System.Text.StringBuilder sb = new System.Text.StringBuilder();
+ sb.Append("[");
+ for (int i = 0; i < textSpans.Length; i++)
+ {
+ if (i > 0)
+ {
+ sb.Append(", ");
+ }
+ sb.Append(textSpans[i].ToString());
+ }
+ sb.Append("]");
+ textSpansString = sb.ToString();
+ }
return $"{nameof(fontAsset)}: {fontAsset}\n" +
- $"{nameof(globalFontAssetFallbacks)}: {fallbacksString}\n" +
- $"{nameof(text)}: {text}\n" +
- $"{nameof(screenWidth)}: {screenWidth}\n" +
- $"{nameof(screenHeight)}: {screenHeight}\n" +
- $"{nameof(fontSize)}: {fontSize}\n" +
- $"{nameof(wordWrap)}: {wordWrap}\n" +
- $"{nameof(languageDirection)}: {languageDirection}\n" +
- $"{nameof(horizontalAlignment)}: {horizontalAlignment}\n" +
- $"{nameof(verticalAlignment)}: {verticalAlignment}\n" +
- $"{nameof(color)}: {color}\n" +
- $"{nameof(fontStyle)}: {fontStyle}\n" +
- $"{nameof(fontWeight)}: {fontWeight}\n" +
- $"{nameof(vertexPadding)}: {vertexPadding}";
+ $"{nameof(globalFontAssetFallbacks)}: {fallbacksString}\n" +
+ $"{nameof(text)}: {text}\n" +
+ $"{nameof(screenWidth)}: {screenWidth}\n" +
+ $"{nameof(screenHeight)}: {screenHeight}\n" +
+ $"{nameof(fontSize)}: {fontSize}\n" +
+ $"{nameof(wordWrap)}: {wordWrap}\n" +
+ $"{nameof(languageDirection)}: {languageDirection}\n" +
+ $"{nameof(horizontalAlignment)}: {horizontalAlignment}\n" +
+ $"{nameof(verticalAlignment)}: {verticalAlignment}\n" +
+ $"{nameof(color)}: {color}\n" +
+ $"{nameof(fontStyle)}: {fontStyle}\n" +
+ $"{nameof(fontWeight)}: {fontWeight}\n" +
+ $"{nameof(vertexPadding)}: {vertexPadding}\n" +
+ $"{nameof(textSpans)}: {textSpansString}";
+ }
+ }
+
+ [VisibleToOtherModules( "UnityEngine.UIElementsModule")]
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct TextSpan
+ {
+ public int startIndex;
+ public int length;
+ public IntPtr fontAsset;
+ public int fontSize; // Encoded in Fixed Point.
+ public Color32 color;
+ public FontStyles fontStyle;
+ public TextFontWeight fontWeight;
+ public int linkID;
+
+ public override string ToString()
+ {
+ return $"{nameof(color)}: {color}\n" +
+ $"{nameof(fontStyle)}: {fontStyle}\n" +
+ $"{nameof(fontWeight)}: {fontWeight}\n" +
+ $"{nameof(linkID)}: {linkID}\n" +
+ $"{nameof(fontSize)}: {fontSize}\n" +
+ $"{nameof(fontAsset)}: {fontAsset}" +
+ $"{nameof(startIndex)}: {startIndex}\n" +
+ $"{nameof(length)}: {length}";
}
}
diff --git a/Modules/TextCoreTextEngine/Managed/TextGenerator/RichTextTagParser.cs b/Modules/TextCoreTextEngine/Managed/TextGenerator/RichTextTagParser.cs
new file mode 100644
index 0000000000..9d7b29092f
--- /dev/null
+++ b/Modules/TextCoreTextEngine/Managed/TextGenerator/RichTextTagParser.cs
@@ -0,0 +1,679 @@
+// 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;
+using System.Text;
+using UnityEngine.Bindings;
+
+#nullable enable
+
+namespace UnityEngine.TextCore
+{
+
+ [VisibleToOtherModules("UnityEngine.UIElementsModule")]
+ internal static class RichTextTagParser
+ {
+ public enum TagType
+ {
+ Hyperlink,
+ Align,
+ AllCaps,
+ Alpha,
+ Bold,
+ Br,
+ Color,
+ CSpace,
+ Font,
+ FontWeight,
+ Italic,
+ Indent,
+ LineHeight,
+ LineIndent,
+ Link,
+ Lowercase,
+ Mark,
+ Mspace,
+ NoBr,
+ NoParse,
+ Strikethrough,
+ Size,
+ SmallCaps,
+ Space,
+ Sprite,
+ Style,
+ Subscript,
+ Superscript,
+ Underline,
+ Uppercase,
+ Unknown // Not a real tag, used to indicate an error
+
+ //gradient: margin, pos, rotate , width, voffset will not be supported
+ }
+
+ internal record TagTypeInfo
+ {
+ internal TagTypeInfo(TagType tagType, string name, TagValueType valueType = TagValueType.None, TagUnitType unitType = TagUnitType.Pixels)
+ {
+ TagType = tagType;
+ this.name = name;
+ this.valueType = valueType;
+ this.unitType = unitType;
+ }
+
+ public TagType TagType;
+ public string name;
+ public TagValueType valueType;
+ public TagUnitType unitType;
+ }
+
+ internal readonly static TagTypeInfo[] TagsInfo =
+ {
+ new TagTypeInfo(TagType.Hyperlink, "a"),
+ new TagTypeInfo(TagType.Align,"align"), //"left", "center", "right", "justified", "flush"
+ new TagTypeInfo(TagType.AllCaps, "allcaps"), //none
+ new TagTypeInfo(TagType.Alpha, "alpha"), //FF CC AA 88 66 44 22 00
+ new TagTypeInfo(TagType.Bold,"b" ),
+ new TagTypeInfo(TagType.Br,"br"),
+ new TagTypeInfo(TagType.Color,"color",TagValueType.ColorValue), //Red Dark Green <#0000FF>Blue Semitransparent Red
+ new TagTypeInfo(TagType.CSpace,"cspace" ), //Spacing is just as important as timing.
+ new TagTypeInfo(TagType.Font,"font"), //a different font? or just a different material?
+ new TagTypeInfo(TagType.FontWeight,"font-weight"), //Thin
+ new TagTypeInfo(TagType.Italic,"i"),
+ new TagTypeInfo(TagType.Indent,"indent"), // pixels, font units, or percentages.
+ new TagTypeInfo(TagType.LineHeight,"line-height"), //pixels, font units, or percentages. Rather cozy.
+ new TagTypeInfo(TagType.LineIndent,"line-indent" ), //pixels, font units, or percentages.
+ new TagTypeInfo(TagType.Link, "link"), //my link
+ new TagTypeInfo(TagType.Lowercase,"lowercase"),//none
+ new TagTypeInfo(TagType.Mark,"mark" ), //
+ new TagTypeInfo(TagType.Mspace,"mspace" ), // monospace : pixels or font units.
+ new TagTypeInfo(TagType.NoBr,"nobr"), // none
+ new TagTypeInfo(TagType.NoParse,"noparse"), // none
+ new TagTypeInfo(TagType.Strikethrough,"s"), //striketrhough
+ new TagTypeInfo(TagType.Size,"size"), // // pixels, font units, or percentage. Pixel adjustments can be absolute (5px, 10px, and so on) or relative (+1 or -1, for example). Relative sizes are based on the original font size, so they're not cumulative.
+ new TagTypeInfo(TagType.SmallCaps,"smallcaps" ),//none
+ new TagTypeInfo(TagType.Space,"space" ), //pixels or font units.
+ new TagTypeInfo(TagType.Sprite,"sprite"),
+ // , or by name .
+ //tint=1 attribute to the tag tints the sprite with the TextMesh Pro object's Vertex Color. You can choose a different color by adding a color attribute to the tag (color=#FFFFFF).
+ new TagTypeInfo ( TagType.Style,"style"), //
+ new TagTypeInfo ( TagType.Subscript,"sub" ),
+ new TagTypeInfo ( TagType.Superscript,"sup" ),
+ new TagTypeInfo ( TagType.Underline,"u"),
+ new TagTypeInfo ( TagType.Uppercase,"uppercase"),//none
+ // page
+
+ };
+
+ internal enum TagValueType
+ {
+ None = 0x0,
+ NumericalValue = 0x1,
+ StringValue = 0x2,
+ ColorValue = 0x4,
+ }
+
+ internal enum TagUnitType
+ {
+ Pixels = 0x0,
+ FontUnits = 0x1,
+ Percentage = 0x2,
+ }
+
+ //TODO : change this for an union when development is over to save memory
+ // we possibly could remove the type check on getter unless in debug mode
+ //[StructLayout(LayoutKind.Explicit)]
+ internal record TagValue
+ {
+ internal TagValue(float value)
+ {
+ type = TagValueType.NumericalValue;
+ m_numericalValue = value;
+ }
+
+ internal TagValue(Color value)
+ {
+ type = TagValueType.ColorValue;
+ m_colorValue = value;
+ }
+
+ internal TagValue(string value)
+ {
+ type = TagValueType.StringValue;
+ m_stringValue = value;
+ }
+
+ //[FieldOffset(0)]
+ internal TagValueType type;
+
+ //[FieldOffset(4)]
+ //private TagUnitType unit;
+
+ //[FieldOffset(8)]
+ private string? m_stringValue;
+
+ //[FieldOffset(8)]
+ private float m_numericalValue;
+
+ //[FieldOffset(8)]
+ private Color m_colorValue;
+
+
+ internal string? StringValue
+ {
+ get
+ {
+ if (type != TagValueType.StringValue)
+ throw new InvalidOperationException("Not a string value");
+ return m_stringValue;
+ }
+ }
+
+ internal float NumericalValue
+ {
+ get
+ {
+ if (type != TagValueType.NumericalValue)
+ throw new InvalidOperationException("Not a numerical value");
+ return m_numericalValue;
+ }
+ }
+
+ internal Color ColorValue
+ {
+ get
+ {
+ if (type != TagValueType.ColorValue)
+ throw new InvalidOperationException("Not a color value");
+ return m_colorValue;
+ }
+ }
+
+
+ }
+
+
+ internal struct Tag
+ {
+ public TagType tagType;
+ public bool isClosing;
+ public int start; //position of the '<' character
+ public int end; //position of the '>' character
+ public TagValue? value; //could be replaced by a nullable struct?
+ }
+
+ public struct Segment
+ {
+ public List? tags;
+ public int start;
+ public int end;
+ }
+
+ internal record ParseError
+ {
+ internal ParseError(string message, int position)
+ {
+ this.message = message;
+ this.position = position;
+ }
+ public readonly int position;
+ public readonly string message;
+ }
+
+ static bool tagMatch(ReadOnlySpan tagCandidate, string tagName)
+ {
+ return tagCandidate.StartsWith(tagName.AsSpan()) && (tagCandidate.Length == tagName.Length || (!char.IsLetter(tagCandidate[tagName.Length]) && tagCandidate[tagName.Length] != '-'));
+ }
+
+ //Return true if there is a match
+ static bool SpanToEnum(ReadOnlySpan tagCandidate, out TagType tagType, out string? error, out ReadOnlySpan attribute)
+ {
+ for (int i = 0; i < TagsInfo.Length; i++)
+ {
+ string tagName = TagsInfo[i].name;
+ if (tagMatch(tagCandidate, tagName))
+ {
+ tagType = TagsInfo[i].TagType;
+ error = null;
+ attribute = tagCandidate.Slice(tagName.Length);//Support only one attribute for now
+ return true;
+ }
+ }
+
+ //Special case for color where there is no tag, just the attribute.
+ if(tagCandidate.Length > 4 &&tagCandidate[0] == '#')
+ {
+ tagType = TagType.Color;
+ error = null;
+ attribute = tagCandidate;
+ return true;
+ }
+
+ error = "Unknown tag: " + tagCandidate.ToString();
+ tagType = TagType.Unknown;
+ attribute = null;
+ return false;
+ }
+
+
+ internal static List FindTags(string inputStr, List? errors = null)
+ {
+ var input = inputStr.ToCharArray();
+ var result = new List();
+ int pos = 0;
+
+ while (true)
+ {
+ var start = Array.IndexOf(input, '<', pos);
+ if (start == -1) // no tag
+ break;
+
+ var end = Array.IndexOf(input, '>', start);
+ if (end == -1)
+ break;
+
+ bool isClosing = (input.Length > start + 1 && input[start + 1] == '/');
+
+ if (end == start + 1)
+ {
+ errors?.Add(new("Empty tag", start));
+ pos = end + 1;
+ continue;
+ }
+
+ pos = end + 1;
+
+ if (!isClosing)
+ {
+ var span = input.AsSpan(start + 1, end - start - 1);
+ if (SpanToEnum(span, out TagType tagType, out string? error, out var atributeSection))
+ {
+ // TODO Manual parsing of color need to be moved elsewhere
+ TagValue? value = null;
+ if (tagType == TagType.Color)
+ {
+ if (atributeSection.Length >= 2 && atributeSection[0] == '=')
+ atributeSection = atributeSection.Slice(1); // we should probably have a better way to do this
+
+ if (atributeSection.Length >= 4 && atributeSection[0] == '"' && atributeSection[atributeSection.Length - 1] == '"')
+ {
+ ColorUtility.TryParseHtmlString(atributeSection.Slice(1, atributeSection.Length - 2).ToString(), out Color color);
+ value = new TagValue(color);
+ }
+ else
+ {
+ ColorUtility.TryParseHtmlString(atributeSection.ToString(), out Color color);
+ value = new TagValue(color);
+ }
+
+ if (value is null)
+ {
+ errors?.Add(new("Invalid color value", start));
+ pos = start + 1; //malformed tag, skip the '<' character
+ continue;
+ }
+ }
+
+ if (tagType == TagType.Link || tagType == TagType.Hyperlink)
+ {
+ if (tagType == TagType.Hyperlink && atributeSection.StartsWith(" href="))
+ atributeSection = atributeSection.Slice(" href=".Length);
+
+ // strip the = for . The lenght need to be checked so that it is greater than 0
+ if (atributeSection.Length >= 1 && atributeSection[0] == '=')
+ atributeSection = atributeSection.Slice(1); // we should probably have a better way to do this
+
+ // strip the quotes for both and
+ // The length need to be checked so that it is greater than 0
+ // Quotes are not mandatory for link tag and for url it isn't problematic if they aren't there unless there is a <> in the url.
+ // We would need to stop parsing from the beginning of the quote until the second one (a bit like for the noparse tags) for supporting <> character in url
+ if (atributeSection.Length >= 2 && atributeSection[0] == '"' && atributeSection[atributeSection.Length - 1] == '"')
+ {
+ value = new TagValue(atributeSection.Slice(1, atributeSection.Length - 2).ToString());
+ }
+ else
+ {
+ value = new TagValue(atributeSection.ToString());
+ }
+ }
+
+ result.Add(new Tag { tagType = tagType, start = start, end = end, isClosing = isClosing, value = value });
+
+ if (tagType == TagType.NoParse)
+ {
+ //Not uisng the real loop to skip all "malformed tags" errors.
+ if ((start = input.AsSpan(pos).IndexOf("")) == -1)
+ {
+ break; // no closing noparse tag, no need to cleanup
+ }
+ start += pos; //The start index was relative to the span, we need to make it relative to the input
+ end = start + "".Length;
+ result.Add(new Tag { tagType = TagType.NoParse, start = start, end = end, isClosing = true });
+ pos = end + 1;
+ }
+ }
+ else
+ {
+ if (error is not null)
+ errors?.Add(new(error, start));
+
+ pos = start + 1; //malformed tag, skip the '<' character
+ }
+ }
+ else
+ {
+ if (SpanToEnum(input.AsSpan(start + 2, end - start - 2), out TagType tagType, out string? error, out var _))
+ {
+ result.Add(new Tag { tagType = tagType, start = start, end = end, isClosing = isClosing });
+ }
+ else
+ {
+ if (error is not null)
+ errors?.Add(new(error, start));
+
+ pos = start + 1; //malformed tag, skip the '<' character
+ }
+ }
+
+
+ }
+
+ return result;
+ }
+
+
+ // Return a list of tags that will be applied to the text
+ // previousTags can be empty to skip the allocation of a new list
+
+ internal static List PickResultingTags(List allTags, string input, int atPosition, List? applicableTags = null)
+ {
+ if (applicableTags == null)
+ {
+ applicableTags = new List();
+ }
+ else
+ {
+ applicableTags.Clear();
+ }
+
+ int startingPos = 0; //Assument the starting position is always 0 as we do not backup the stack infos..
+ // TODO incremental procesing, will have to review methods parameter and structure...
+ // TOOD clean the checks belos
+
+ Debug.Assert(string.IsNullOrEmpty(input) || (atPosition < input.Length && atPosition >= 0), "Invalid position");
+ Debug.Assert(startingPos <= atPosition && startingPos >= 0, "Invalid starting position");
+
+ int previousTagPosition = 0;
+ foreach(var tag in allTags)
+ {
+ Debug.Assert(tag.start >= previousTagPosition, "Tags are not sorted");
+ previousTagPosition = tag.end+1;
+ }
+
+ foreach(var tag in applicableTags)
+ {
+ Debug.Assert(tag.end <= startingPos, "Tag end pass the point where we should start parsing");
+ Debug.Assert(allTags.Contains(tag));
+ }
+ Span parents = stackalloc int?[allTags.Count];
+ Span lastTagOfType = stackalloc int?[TagsInfo.Length];
+
+ int i = -1;
+ foreach (var tag in allTags)
+ {
+ i++;
+ if (tag.end < startingPos)
+ {
+ continue;
+ }
+
+ if (tag.tagType == TagType.NoParse)
+ {
+ continue;
+ }
+
+ if (tag.start > atPosition)
+ {
+ break; // we are done.
+ }
+
+
+ if (tag.isClosing)
+ {
+ if (lastTagOfType[(int)tag.tagType].HasValue)
+ {
+ if (parents[i].HasValue)
+ {
+ lastTagOfType[(int)tag.tagType] = parents[i];
+ }
+ else
+ lastTagOfType[(int)tag.tagType] = null;
+ }
+ }
+ else
+ {
+ //New tag, set as last tag open and nest under parent if there was one already
+
+ var currentLastTagIndex = lastTagOfType[(int)tag.tagType];
+ if (currentLastTagIndex.HasValue)
+ {
+ parents[i] = currentLastTagIndex;
+ }
+
+ lastTagOfType[(int)tag.tagType] = i;
+
+ }
+
+ }
+
+ //The order in the resulting list is important: we cannot iterate only adding lastTagOfType
+ int currentTagIndex = 0;
+ foreach (var tag in allTags)
+ {
+ var lastTag = lastTagOfType[(int)tag.tagType];
+ if (lastTag.HasValue && currentTagIndex == lastTag.Value)
+ applicableTags.Add(tag);
+
+ currentTagIndex++;
+ }
+
+ return applicableTags;
+ }
+
+
+ //Return a list of text setgment that will share the same text generation settings between two tags
+ internal static Segment[] GenerateSegments(string input, List tags)
+ {
+ List segments = new List();
+ int afterPreviousTagEnd = 0;
+ for (int i = 0; i < tags.Count; i++)
+ {
+ Debug.Assert(tags[i].start >= afterPreviousTagEnd);
+ //If the tag is consecutive, no segment is generated
+ if (tags[i].start > afterPreviousTagEnd)
+ {
+ segments.Add(new Segment { start = afterPreviousTagEnd, end = tags[i].start - 1 }); // tags[i].start-1 wont be negative because afterPreviousTagEnd start at 0 and we are greater
+ }
+ afterPreviousTagEnd = tags[i].end + 1;
+ }
+
+ //Check if there is a segment after the last tag
+ if (afterPreviousTagEnd < input.Length)
+ {
+ segments.Add(new Segment { start = afterPreviousTagEnd, end = input.Length - 1 });
+ }
+
+ //This is ugly, need to be able to modify a reference in a loop later
+ return segments.ToArray();
+ }
+
+ internal static void ApplyStateToSegment(string input, List tags, Segment[] segments)
+ {
+
+ //tmp list = new List();
+ for (int i = 0; i < segments.Length; i++)
+ {
+ segments[i].tags = PickResultingTags(tags, input, segments[i].start);
+ // TODO change to the non alloc version
+ //segments[i].state.tags = PickResultingTags(tags, input, segments[i].start, i>0?segments[i-1].start :0, list).Copy? ;
+ //
+ }
+
+ }
+
+ static private int AddLink(TagType type, string value, List<(int, TagType, string)> links)
+ {
+ foreach (var (index, listType, listValue) in links)
+ {
+ if (type == listType && value == listValue)
+ return index;
+ }
+
+ int nextIndex = links.Count;
+ links.Add((nextIndex, type, value));
+
+ return nextIndex;
+ }
+
+ static TextSpan CreateTextSpan(Segment segment, ref NativeTextGenerationSettings tgs, List<(int, TagType, string)> links, Color hyperlinkColor )
+ {
+ var textSpan = tgs.CreateTextSpan();
+
+ if (segment.tags is null)
+ return textSpan;
+
+ for (int i = 0; i < segment.tags.Count; i++)
+ {
+ switch (segment.tags[i].tagType)
+ {
+ //Font Style
+ case TagType.Bold:
+ textSpan.fontWeight = TextCore.Text.TextFontWeight.Bold;
+ break;
+ case TagType.Italic:
+ textSpan.fontStyle |= TextCore.Text.FontStyles.Italic;
+ break;
+ case TagType.Underline:
+ textSpan.fontStyle |= TextCore.Text.FontStyles.Underline;
+ break;
+ case TagType.Strikethrough:
+ textSpan.fontStyle |= TextCore.Text.FontStyles.Strikethrough;
+ break;
+ case TagType.Subscript:
+ textSpan.fontStyle |= TextCore.Text.FontStyles.Subscript;
+ break;
+ case TagType.Superscript:
+ textSpan.fontStyle |= TextCore.Text.FontStyles.Superscript;
+ break;
+ case TagType.AllCaps:
+ case TagType.Uppercase:
+ textSpan.fontStyle |= TextCore.Text.FontStyles.UpperCase;
+ break;
+ case TagType.SmallCaps:
+ case TagType.Lowercase:
+ textSpan.fontStyle |= TextCore.Text.FontStyles.LowerCase;
+ break;
+
+ //Color and appeareance
+ case TagType.Color:
+ textSpan.color = segment.tags[i].value!.ColorValue;
+ break;
+ case TagType.Alpha:
+ //TODO tgs.color.a = segment.state.tags[i].value.NumericalValue;
+ break;
+ case TagType.Mark:
+ textSpan.fontStyle |= TextCore.Text.FontStyles.Highlight;
+ break;
+
+ //Asset required
+ case TagType.Style:
+ //TODO : Add support for style
+ break;
+ case TagType.Font:
+ //TODO : Add support for font
+ break;
+ case TagType.Hyperlink:
+ textSpan.linkID = AddLink(TagType.Hyperlink, segment.tags[i].value?.StringValue ?? "", links);
+ textSpan.color = hyperlinkColor;
+ textSpan.fontStyle |= TextCore.Text.FontStyles.Underline;
+ break;
+ case TagType.Link:
+ textSpan.linkID = AddLink(TagType.Link, segment.tags[i].value?.StringValue ?? "", links);
+ break;
+ case TagType.Sprite:
+ //TODO : Add support for sprite
+ break;
+
+ //Layout/Positioning
+ case TagType.Size:
+ textSpan.fontSize = (int)(segment.tags[i].value!.NumericalValue/64f);
+ break;
+ case TagType.CSpace:
+ //TODO : Add support for cspace
+ break;
+ case TagType.Br:
+ //TODO : Add support for br
+ break;
+ case TagType.Mspace:
+ //TODO : Add support for mspace
+ break;
+ case TagType.LineIndent:
+ //TODO : Add support for lineindent
+ break;
+ case TagType.Space:
+ //TODO : Add support for space
+ break;
+ case TagType.NoBr:
+ //TODO : Add support for nobr
+ break;
+ case TagType.Align:
+ //TODO : Add support for align
+ break;
+ case TagType.LineHeight:
+ //TODO : Add support for lineheight
+ break;
+
+
+ case TagType.NoParse://Noparse should not be reach here/Should be trimmed
+ case TagType.Unknown:
+ throw new InvalidOperationException("Invalid tag type" + segment.tags[i].tagType);
+
+ }
+ }
+
+
+ return textSpan;
+ }
+
+ [VisibleToOtherModules("UnityEngine.UIElementsModule")]
+ internal static void CreateTextGenerationSettingsArray(ref NativeTextGenerationSettings tgs, List<(int, TagType, string)> links, Color hyperlinkColor)
+ {
+ links.Clear();
+
+
+ var tags = FindTags(tgs.text);
+ var segments = GenerateSegments(tgs.text, tags);
+ ApplyStateToSegment(tgs.text, tags, segments);
+
+ var parsedTextBuilder = new StringBuilder(tgs.text.Length);
+ tgs.textSpans = new TextSpan[segments.Length];
+ int parsedIndex = 0;
+
+ for (int i = 0; i < segments.Length; i++)
+ {
+ var segment = segments[i];
+ string segmentText = tgs.text.Substring(segment.start, segment.end + 1 - segment.start);
+
+ var textSpan = CreateTextSpan(segment, ref tgs,links, hyperlinkColor );
+ textSpan.startIndex = parsedIndex;
+ textSpan.length = segmentText.Length;
+ tgs.textSpans[i] = textSpan;
+ parsedTextBuilder.Append(segmentText);
+ parsedIndex += segmentText.Length;
+ }
+
+ tgs.text = parsedTextBuilder.ToString();
+ }
+ }
+}
diff --git a/Modules/TextCoreTextEngine/Managed/TextGenerator/TextLib.bindings.cs b/Modules/TextCoreTextEngine/Managed/TextGenerator/TextLib.bindings.cs
index 11616dfdc7..ff1506f795 100644
--- a/Modules/TextCoreTextEngine/Managed/TextGenerator/TextLib.bindings.cs
+++ b/Modules/TextCoreTextEngine/Managed/TextGenerator/TextLib.bindings.cs
@@ -3,6 +3,7 @@
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License
using System;
+using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine.Bindings;
using UnityEngine.Scripting;
@@ -27,6 +28,7 @@ internal TextLib()
[VisibleToOtherModules("UnityEngine.UIElementsModule")]
internal NativeTextInfo GenerateText(NativeTextGenerationSettings settings, IntPtr textGenerationInfo)
{
+ Debug.Assert((settings.fontStyle & FontStyles.Bold) == 0);// Bold need to be set by the fontWeight only.
var textInfo = GenerateTextInternal(settings, textGenerationInfo);
foreach (ref var meshInfo in textInfo.meshInfos.AsSpan())
@@ -62,10 +64,12 @@ internal NativeTextInfo GenerateText(NativeTextGenerationSettings settings, IntP
bottomRightUV.x = topRightUV.x;
bottomRightUV.y = bottomLeftUV.y;
- textElementInfo.bottomLeft.uv0 = bottomLeftUV;
- textElementInfo.topLeft.uv0 = topLeftUV;
- textElementInfo.topRight.uv0 = topRightUV;
- textElementInfo.bottomRight.uv0 = bottomRightUV;
+ // The native code is not yet aware of the atlas, and glyphs for the underline+strikethrough have their UV manually eddited to
+ // be stretched without side effect. We need to combine the position in the atlas with the expected position in the source glyph.
+ textElementInfo.bottomLeft.uv0 = topRightUV * textElementInfo.bottomLeft.uv0 + bottomLeftUV * (new Vector2(1, 1) - textElementInfo.bottomLeft.uv0);
+ textElementInfo.topLeft.uv0 = topRightUV * textElementInfo.topLeft.uv0 + bottomLeftUV * (new Vector2(1, 1) - textElementInfo.topLeft.uv0); ;
+ textElementInfo.topRight.uv0 = topRightUV * textElementInfo.topRight.uv0 + bottomLeftUV * (new Vector2(1, 1) - textElementInfo.topRight.uv0); ;
+ textElementInfo.bottomRight.uv0 = topRightUV * textElementInfo.bottomRight.uv0 + bottomLeftUV * (new Vector2(1, 1) - textElementInfo.bottomRight.uv0); ;
}
}
return textInfo;
@@ -78,6 +82,10 @@ internal NativeTextInfo GenerateText(NativeTextGenerationSettings settings, IntP
[NativeMethod(Name = "TextLib::MeasureText")]
internal extern Vector2 MeasureText(NativeTextGenerationSettings settings, IntPtr textGenerationInfo);
+ [VisibleToOtherModules("UnityEngine.UIElementsModule")]
+ [NativeMethod(Name = "TextLib::FindIntersectingLink")]
+ static internal extern int FindIntersectingLink(Vector2 point, IntPtr textGenerationInfo);
+
internal static class BindingsMarshaller
{
public static IntPtr ConvertToNative(TextLib textLib) => textLib.m_Ptr;
@@ -134,10 +142,9 @@ internal static bool GetICUdata(Span data, int maxSize)
[VisibleToOtherModules("UnityEngine.UIElementsModule")]
internal static class TextGenerationInfo
{
- internal static extern IntPtr Create();
+ public static extern IntPtr Create();
[FreeFunction("TextGenerationInfo::Destroy")]
- [VisibleToOtherModules("UnityEngine.UIElementsModule")]
- internal static extern void Destroy(IntPtr ptr);
+ public static extern void Destroy(IntPtr ptr);
}
}
diff --git a/Modules/TextCoreTextEngine/Managed/TextGenerator/TextSelectionService.bindings.cs b/Modules/TextCoreTextEngine/Managed/TextGenerator/TextSelectionService.bindings.cs
index b1babeb8b4..3374ef37d4 100644
--- a/Modules/TextCoreTextEngine/Managed/TextGenerator/TextSelectionService.bindings.cs
+++ b/Modules/TextCoreTextEngine/Managed/TextGenerator/TextSelectionService.bindings.cs
@@ -58,5 +58,20 @@ internal class TextSelectionService
[NativeMethod(Name = "TextSelectionService::GetLineNumberFromLogicalIndex")]
internal static extern int GetLineNumber(IntPtr textGenerationInfo, int logicalIndex);
+
+ [NativeMethod(Name = "TextSelectionService::SelectToPreviousParagraph")]
+ internal static extern void SelectToPreviousParagraph(IntPtr textGenerationInfo, ref int cursorIndex);
+
+ [NativeMethod(Name = "TextSelectionService::SelectToStartOfParagraph")]
+ internal static extern void SelectToStartOfParagraph(IntPtr textGenerationInfo, ref int cursorIndex);
+
+ [NativeMethod(Name = "TextSelectionService::SelectToEndOfParagraph")]
+ internal static extern void SelectToEndOfParagraph(IntPtr textGenerationInfo, ref int cursorIndex);
+
+ [NativeMethod(Name = "TextSelectionService::SelectToNextParagraph")]
+ internal static extern void SelectToNextParagraph(IntPtr textGenerationInfo, ref int cursorIndex);
+
+ [NativeMethod(Name = "TextSelectionService::SelectCurrentParagraph")]
+ internal static extern void SelectCurrentParagraph(IntPtr textGenerationInfo, ref int cursorIndex, ref int selectIndex);
}
}
diff --git a/Modules/TextCoreTextEngine/Managed/TextHandle.cs b/Modules/TextCoreTextEngine/Managed/TextHandle.cs
index 3d3213c535..152d374554 100644
--- a/Modules/TextCoreTextEngine/Managed/TextHandle.cs
+++ b/Modules/TextCoreTextEngine/Managed/TextHandle.cs
@@ -4,12 +4,14 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Runtime.CompilerServices;
using Unity.Jobs.LowLevel.Unsafe;
using UnityEngine.Bindings;
namespace UnityEngine.TextCore.Text
{
+ [DebuggerDisplay("{settings.text}")]
[VisibleToOtherModules("UnityEngine.IMGUIModule", "UnityEngine.UIElementsModule")]
internal partial class TextHandle
{
@@ -181,7 +183,7 @@ public void RemoveTextInfoFromPermanentCache()
{
s_PermanentCache.RemoveTextInfoFromCache(this);
}
-
+
}
public static void UpdateCurrentFrame()
@@ -465,7 +467,7 @@ public LineInfo GetLineInfoFromCharacterIndex(int index)
Debug.LogError("Cannot use GetLineInfoFromCharacterIndex while using Advanced Text");
return new LineInfo();
}
-
+
return textInfo.GetLineInfoFromCharacterIndex(index);
}
@@ -602,6 +604,56 @@ public void SelectCurrentWord(int index, ref int cursorIndex, ref int selectInde
TextSelectionService.SelectCurrentWord(textGenerationInfo, index, ref cursorIndex, ref selectIndex);
}
+ public void SelectCurrentParagraph(ref int cursorIndex, ref int selectIndex)
+ {
+ if (!useAdvancedText)
+ {
+ Debug.LogError("Cannot use SelectCurrentParagraph while using Standard Text");
+ return;
+ }
+ TextSelectionService.SelectCurrentParagraph(textGenerationInfo, ref cursorIndex, ref selectIndex);
+ }
+
+ public void SelectToPreviousParagraph(ref int cursorIndex)
+ {
+ if (!useAdvancedText)
+ {
+ Debug.LogError("Cannot use SelectToPreviousParagraph while using Standard Text");
+ return;
+ }
+ TextSelectionService.SelectToPreviousParagraph(textGenerationInfo, ref cursorIndex);
+ }
+
+ public void SelectToNextParagraph(ref int cursorIndex)
+ {
+ if (!useAdvancedText)
+ {
+ Debug.LogError("Cannot use SelectToNextParagraph while using Standard Text");
+ return;
+ }
+ TextSelectionService.SelectToNextParagraph(textGenerationInfo, ref cursorIndex);
+ }
+
+ public void SelectToStartOfParagraph(ref int cursorIndex)
+ {
+ if (!useAdvancedText)
+ {
+ Debug.LogError("Cannot use SelectToStartOfParagraph while using Standard Text");
+ return;
+ }
+ TextSelectionService.SelectToStartOfParagraph(textGenerationInfo, ref cursorIndex);
+ }
+
+ public void SelectToEndOfParagraph(ref int cursorIndex)
+ {
+ if (!useAdvancedText)
+ {
+ Debug.LogError("Cannot use SelectToEndOfParagraph while using Standard Text");
+ return;
+ }
+ TextSelectionService.SelectToEndOfParagraph(textGenerationInfo, ref cursorIndex);
+ }
+
internal virtual bool IsAdvancedTextEnabledForElement() { return false; }
}
}
diff --git a/Modules/UIElements/Core/Controls/Slider.cs b/Modules/UIElements/Core/Controls/Slider.cs
index f2bc2796a3..d7c5adbe2e 100644
--- a/Modules/UIElements/Core/Controls/Slider.cs
+++ b/Modules/UIElements/Core/Controls/Slider.cs
@@ -51,8 +51,6 @@ public class Slider : BaseSlider
public override void Deserialize(object obj)
{
- base.Deserialize(obj);
-
var e = (Slider)obj;
if (ShouldWriteAttributeValue(lowValue_UxmlAttributeFlags))
e.lowValue = lowValue;
@@ -66,6 +64,9 @@ public override void Deserialize(object obj)
e.showInputField = showInputField;
if (ShouldWriteAttributeValue(inverted_UxmlAttributeFlags))
e.inverted = inverted;
+
+ // We need to apply the lowValue and highValue before the value to avoid incorrect clamping.
+ base.Deserialize(obj);
}
}
diff --git a/Modules/UIElements/Core/Panel.cs b/Modules/UIElements/Core/Panel.cs
index 0d6aa0d358..34d31dafbc 100644
--- a/Modules/UIElements/Core/Panel.cs
+++ b/Modules/UIElements/Core/Panel.cs
@@ -1356,6 +1356,8 @@ internal override IVisualTreeUpdater GetUpdater(VisualTreeUpdatePhase phase)
{
return m_VisualTreeUpdater.GetUpdater(phase);
}
+
+ internal virtual Color HyperlinkColor => Color.blue;
}
internal abstract class BaseRuntimePanel : Panel
@@ -1581,6 +1583,7 @@ internal void PointerEntersPanel(int pointerId, Vector2 position)
{
PointerDeviceState.SavePointerPosition(pointerId, position, this, contextType);
}
+
}
internal interface IRuntimePanelComponent
diff --git a/Modules/UIElements/Core/Renderer/UIRUtility.cs b/Modules/UIElements/Core/Renderer/UIRUtility.cs
index ca50407dda..87dd48242a 100644
--- a/Modules/UIElements/Core/Renderer/UIRUtility.cs
+++ b/Modules/UIElements/Core/Renderer/UIRUtility.cs
@@ -93,7 +93,7 @@ internal static void ComputeTransformMatrix(VisualElement ve, VisualElement ance
k_ComputeTransformMatrixMarker.Begin();
ve.GetPivotedMatrixWithLayout(out result);
- VisualElement currentAncestor = ve.parent;
+ VisualElement currentAncestor = ve.hierarchy.parent;
if ((currentAncestor == null) || (ancestor == currentAncestor))
{
k_ComputeTransformMatrixMarker.End();
@@ -112,7 +112,7 @@ internal static void ComputeTransformMatrix(VisualElement ve, VisualElement ance
else
VisualElement.MultiplyMatrix34(ref ancestorMatrix, ref temp, out result);
- currentAncestor = currentAncestor.parent;
+ currentAncestor = currentAncestor.hierarchy.parent;
destIsTemp = !destIsTemp;
} while ((currentAncestor != null) && (ancestor != currentAncestor));
diff --git a/Modules/UIElements/Core/Text/ATGTextEventHandler.cs b/Modules/UIElements/Core/Text/ATGTextEventHandler.cs
new file mode 100644
index 0000000000..6af9abb611
--- /dev/null
+++ b/Modules/UIElements/Core/Text/ATGTextEventHandler.cs
@@ -0,0 +1,263 @@
+// 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.TextCore.Text;
+using UnityEngine.UIElements;
+
+namespace UnityEngine.UIElements
+{
+ class ATGTextEventHandler
+ {
+ TextElement m_TextElement;
+
+ public ATGTextEventHandler(TextElement textElement)
+ {
+ Debug.Assert(textElement.uitkTextHandle.useAdvancedText);
+ m_TextElement = textElement;
+ }
+
+ EventCallback m_LinkTagOnPointerDown;
+ EventCallback m_LinkTagOnPointerUp;
+ EventCallback m_LinkTagOnPointerMove;
+ EventCallback m_LinkTagOnPointerOut;
+
+ EventCallback m_HyperlinkOnPointerUp;
+ EventCallback m_HyperlinkOnPointerMove;
+ EventCallback m_HyperlinkOnPointerOver;
+ EventCallback m_HyperlinkOnPointerOut;
+
+ bool HasAllocatedLinkCallbacks()
+ {
+ return m_LinkTagOnPointerDown != null;
+ }
+
+ void AllocateLinkCallbacks()
+ {
+ if (HasAllocatedLinkCallbacks())
+ return;
+
+ m_LinkTagOnPointerDown = LinkTagOnPointerDown;
+ m_LinkTagOnPointerUp = LinkTagOnPointerUp;
+ m_LinkTagOnPointerMove = LinkTagOnPointerMove;
+ m_LinkTagOnPointerOut = LinkTagOnPointerOut;
+ }
+
+ bool HasAllocatedHyperlinkCallbacks()
+ {
+ return m_HyperlinkOnPointerUp != null;
+ }
+
+ void AllocateHyperlinkCallbacks()
+ {
+ if (HasAllocatedHyperlinkCallbacks())
+ return;
+
+ m_HyperlinkOnPointerUp = HyperlinkOnPointerUp;
+ m_HyperlinkOnPointerMove = HyperlinkOnPointerMove;
+ m_HyperlinkOnPointerOver = HyperlinkOnPointerOver;
+ m_HyperlinkOnPointerOut = HyperlinkOnPointerOut;
+ }
+
+ void HyperlinkOnPointerUp(PointerUpEvent pue)
+ {
+ var pos = pue.localPosition - new Vector3(m_TextElement.contentRect.min.x, m_TextElement.contentRect.min.y);
+ var(type, link) = m_TextElement.uitkTextHandle.ATGFindIntersectingLink(pos);
+ if (link == null || type!= TextCore.RichTextTagParser.TagType.Hyperlink)
+ return;
+
+ if (Uri.IsWellFormedUriString(link, UriKind.Absolute))
+ Application.OpenURL(link);
+ }
+
+ internal bool isOverridingCursor;
+
+ void HyperlinkOnPointerOver(PointerOverEvent _)
+ {
+ isOverridingCursor = false;
+ }
+
+ void HyperlinkOnPointerMove(PointerMoveEvent pme)
+ {
+ var pos = pme.localPosition - new Vector3(m_TextElement.contentRect.min.x, m_TextElement.contentRect.min.y);
+ var (type, link) = m_TextElement.uitkTextHandle.ATGFindIntersectingLink(pos);
+
+ var cursorManager = (m_TextElement.panel as BaseVisualElementPanel)?.cursorManager;
+ if (link != null && type == TextCore.RichTextTagParser.TagType.Hyperlink)
+ {
+
+ if (!isOverridingCursor)
+ {
+ isOverridingCursor = true;
+
+ // defaultCursorId maps to the UnityEditor.MouseCursor enum where 4 is the link cursor.
+ cursorManager?.SetCursor(new Cursor { defaultCursorId = 4 });
+ }
+
+ return;
+ }
+
+ if (isOverridingCursor)
+ {
+ cursorManager?.SetCursor(m_TextElement.computedStyle.cursor);
+ isOverridingCursor = false;
+ }
+ }
+
+ void HyperlinkOnPointerOut(PointerOutEvent evt)
+ {
+ isOverridingCursor = false;
+ }
+
+
+ void LinkTagOnPointerDown(PointerDownEvent pde)
+ {
+ var pos = pde.localPosition - new Vector3(m_TextElement.contentRect.min.x, m_TextElement.contentRect.min.y);
+ // Convert UITK pos to ATG pos
+ var (type, link) = m_TextElement.uitkTextHandle.ATGFindIntersectingLink(pos);
+ if (link == null || type != TextCore.RichTextTagParser.TagType.Link)
+ return;
+
+ using (var e = Experimental.PointerDownLinkTagEvent.GetPooled(pde, link, "test" /* TODO we have no way of gettting the hilighted text*/ ))
+ {
+ e.elementTarget = m_TextElement;
+ m_TextElement.SendEvent(e);
+ }
+ }
+
+ void LinkTagOnPointerUp(PointerUpEvent pue)
+ {
+ var pos = pue.localPosition - new Vector3(m_TextElement.contentRect.min.x, m_TextElement.contentRect.min.y);
+ var (type, link) = m_TextElement.uitkTextHandle.ATGFindIntersectingLink(pos);
+ if (link == null || type != TextCore.RichTextTagParser.TagType.Link)
+ return;
+
+ using (var e = Experimental.PointerUpLinkTagEvent.GetPooled(pue, link, "test" /* TODO we have no way of gettting the hilighted text*/ ))
+ {
+ e.elementTarget = m_TextElement;
+ m_TextElement.SendEvent(e);
+ }
+ }
+
+ // Used in automated test
+ internal int currentLinkIDHash = -1;
+
+ void LinkTagOnPointerMove(PointerMoveEvent pme)
+ {
+ var pos = pme.localPosition - new Vector3(m_TextElement.contentRect.min.x, m_TextElement.contentRect.min.y);
+ // Convert UITK pos to ATG pos
+ var (type, link) = m_TextElement.uitkTextHandle.ATGFindIntersectingLink(pos);
+
+ if (link != null && type == TextCore.RichTextTagParser.TagType.Link)
+ {
+ // PointerOver
+ if (currentLinkIDHash == -1)
+ {
+ currentLinkIDHash = 0; // Placeholder for link.hashCode
+ using (var e = Experimental.PointerOverLinkTagEvent.GetPooled(pme, link, "test" /* TODO we have no way of gettting the hilighted text*/ ))
+ {
+ e.elementTarget = m_TextElement;
+ m_TextElement.SendEvent(e);
+ }
+
+ return;
+ }
+
+ // PointerMove
+ if (currentLinkIDHash == 0) // Placeholder for link.hashCode
+ {
+ using (var e = Experimental.PointerMoveLinkTagEvent.GetPooled(pme, link, "test" /* TODO we have no way of gettting the hilighted text*/ ))
+ {
+ e.elementTarget = m_TextElement;
+ m_TextElement.SendEvent(e);
+ }
+
+ return;
+ }
+ }
+
+ // PointerOut
+ if (currentLinkIDHash != -1)
+ {
+ currentLinkIDHash = -1;
+ using (var e = Experimental.PointerOutLinkTagEvent.GetPooled(pme, string.Empty))
+ {
+ e.elementTarget = m_TextElement;
+ m_TextElement.SendEvent(e);
+ }
+ }
+ }
+
+ void LinkTagOnPointerOut(PointerOutEvent poe)
+ {
+ if (currentLinkIDHash != -1)
+ {
+ using (var e = Experimental.PointerOutLinkTagEvent.GetPooled(poe, string.Empty))
+ {
+ e.elementTarget = m_TextElement;
+ m_TextElement.SendEvent(e);
+ }
+
+ currentLinkIDHash = -1;
+ }
+ }
+
+ internal void RegisterLinkTagCallbacks()
+ {
+ if (m_TextElement?.panel == null)
+ return;
+
+ AllocateLinkCallbacks();
+ m_TextElement.RegisterCallback(m_LinkTagOnPointerDown, TrickleDown.TrickleDown);
+ m_TextElement.RegisterCallback(m_LinkTagOnPointerUp, TrickleDown.TrickleDown);
+ m_TextElement.RegisterCallback(m_LinkTagOnPointerMove, TrickleDown.TrickleDown);
+ m_TextElement.RegisterCallback(m_LinkTagOnPointerOut, TrickleDown.TrickleDown);
+ }
+
+ internal void UnRegisterLinkTagCallbacks()
+ {
+ if (HasAllocatedLinkCallbacks())
+ {
+ m_TextElement.UnregisterCallback(m_LinkTagOnPointerDown, TrickleDown.TrickleDown);
+ m_TextElement.UnregisterCallback(m_LinkTagOnPointerUp, TrickleDown.TrickleDown);
+ m_TextElement.UnregisterCallback(m_LinkTagOnPointerMove, TrickleDown.TrickleDown);
+ m_TextElement.UnregisterCallback(m_LinkTagOnPointerOut, TrickleDown.TrickleDown);
+ }
+ }
+
+ internal void RegisterHyperlinkCallbacks()
+ {
+ if (m_TextElement?.panel == null)
+ return;
+
+ AllocateHyperlinkCallbacks();
+ m_TextElement.RegisterCallback(m_HyperlinkOnPointerUp, TrickleDown.TrickleDown);
+
+ // Switching the cursor to the Link cursor has been disable at runtime until OS cursor support is available at runtime.
+ if (m_TextElement.panel.contextType == ContextType.Editor)
+ {
+ m_TextElement.RegisterCallback(m_HyperlinkOnPointerMove, TrickleDown.TrickleDown);
+ m_TextElement.RegisterCallback(m_HyperlinkOnPointerOver, TrickleDown.TrickleDown);
+ m_TextElement.RegisterCallback(m_HyperlinkOnPointerOut, TrickleDown.TrickleDown);
+ }
+ }
+
+ internal void UnRegisterHyperlinkCallbacks()
+ {
+ if (m_TextElement?.panel == null)
+ return;
+
+ if (HasAllocatedHyperlinkCallbacks())
+ {
+ m_TextElement.UnregisterCallback(m_HyperlinkOnPointerUp, TrickleDown.TrickleDown);
+ if (m_TextElement.panel.contextType == ContextType.Editor)
+ {
+ m_TextElement.UnregisterCallback(m_HyperlinkOnPointerMove, TrickleDown.TrickleDown);
+ m_TextElement.UnregisterCallback(m_HyperlinkOnPointerOver, TrickleDown.TrickleDown);
+ m_TextElement.UnregisterCallback(m_HyperlinkOnPointerOut, TrickleDown.TrickleDown);
+ }
+ }
+ }
+ }
+}
diff --git a/Modules/UIElements/Core/Text/ATGTextHandle.cs b/Modules/UIElements/Core/Text/ATGTextHandle.cs
index 336b8bda93..e30f4ef63a 100644
--- a/Modules/UIElements/Core/Text/ATGTextHandle.cs
+++ b/Modules/UIElements/Core/Text/ATGTextHandle.cs
@@ -5,12 +5,21 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
+using UnityEngine.TextCore;
using UnityEngine.TextCore.Text;
+using static UnityEngine.TextCore.RichTextTagParser;
namespace UnityEngine.UIElements
{
internal partial class UITKTextHandle
{
+ ATGTextEventHandler m_ATGTextEventHandler;
+ // int LinkID: The identifier for the link.
+ // TagType: Specifies the type of tag (either Hyperlink or Link).
+ // string Attribute: For Hyperlink, this is the 'href' attribute; for Link, it's the associated attribute.
+ List<(int, TagType, string)> m_Links;//Not clearing links would result in a leak of strings and enum, but no class, so no consideration for clearing the list at the moment
+ private List<(int, TagType, string)> Links => m_Links ??= new();
+
public void ComputeNativeTextSize(in RenderedText textToMeasure, float width, float height)
{
ConvertUssToNativeTextGenerationSettings();
@@ -18,7 +27,18 @@ public void ComputeNativeTextSize(in RenderedText textToMeasure, float width, fl
nativeSettings.screenWidth = float.IsNaN(width) ? Int32.MaxValue : (int)(width * 64.0f);
nativeSettings.screenHeight = float.IsNaN(height) ? Int32.MaxValue : (int)(height * 64.0f);
- preferredSize = TextLib.MeasureText(nativeSettings, textGenerationInfo);
+ if (m_TextElement.enableRichText && !String.IsNullOrEmpty(nativeSettings.text))
+ {
+ Color hyperlinkColor = (m_TextElement.panel as Panel)?.HyperlinkColor ?? Color.blue;
+ RichTextTagParser.CreateTextGenerationSettingsArray(ref nativeSettings, Links, hyperlinkColor);
+ }
+ else
+ nativeSettings.textSpans = null;
+
+ // Passing a zero pointer instead of the cached textGenerationInfo because it is possible that calling the measure will not
+ // change the size, there will therefore be no full layout of the glyph and the textGenerationInfo will not have the final
+ // glyph position populated and it breaks TextLib.FindIntersectingLink
+ preferredSize = TextLib.MeasureText(nativeSettings, IntPtr.Zero);
}
public NativeTextInfo UpdateNative(ref bool success)
@@ -30,7 +50,87 @@ public NativeTextInfo UpdateNative(ref bool success)
}
success = true;
- return TextLib.GenerateText(nativeSettings, textGenerationInfo);
+
+ if (m_TextElement.enableRichText && !String.IsNullOrEmpty(nativeSettings.text))
+ {
+ Color hyperlinkColor = (m_TextElement.panel as Panel)?.HyperlinkColor ?? Color.blue;
+ RichTextTagParser.CreateTextGenerationSettingsArray(ref nativeSettings, Links, hyperlinkColor);
+ }
+ else
+ nativeSettings.textSpans = null;
+
+ if ((nativeSettings.hasLink) && textGenerationInfo == IntPtr.Zero)
+ {
+ textGenerationInfo = TextGenerationInfo.Create();
+ m_ATGTextEventHandler ??= new ATGTextEventHandler(m_TextElement);
+ }
+ var textInfo = TextLib.GenerateText(nativeSettings, textGenerationInfo);
+ UpdateATGTextEventHandler(nativeSettings);
+
+ return textInfo;
+ }
+
+ private (bool, bool) hasLinkAndHyperlink()
+ {
+ bool hasLink = false;
+ bool hasHyperlink = false;
+
+ if (m_Links != null) // Using member variable to not allocate if unused
+ {
+ foreach (var (_, type, _) in Links)
+ {
+ hasLink = hasLink || type == TagType.Link;
+ hasHyperlink = hasHyperlink || type == TagType.Hyperlink;
+
+ if (hasLink && hasHyperlink)
+ break;
+ }
+ }
+ return (hasLink, hasHyperlink);
+ }
+
+ internal (TagType, string) ATGFindIntersectingLink(Vector2 point)
+ {
+
+ //This should probably be public, but it would require exposing TagType
+ Debug.Assert(useAdvancedText);
+ if (textGenerationInfo == IntPtr.Zero)
+ {
+ Debug.LogError("TextGenerationInfo pointer is null.");
+ return(TagType.Unknown, null);
+ }
+
+ int id = TextLib.FindIntersectingLink(point, textGenerationInfo);
+
+ if (id == -1)
+ return (TagType.Unknown, null);
+
+ return (m_Links[id].Item2, m_Links[id].Item3);
+ }
+
+ private void UpdateATGTextEventHandler(NativeTextGenerationSettings setting)
+ {
+ if (m_ATGTextEventHandler == null)
+ return;
+
+ var (hasLink, hasHyperlink) = hasLinkAndHyperlink();
+ if (hasLink)
+ {
+ m_ATGTextEventHandler.RegisterLinkTagCallbacks();
+ }
+ else
+ {
+ m_ATGTextEventHandler.UnRegisterLinkTagCallbacks();
+ }
+
+ if (hasHyperlink)
+ {
+ m_ATGTextEventHandler.RegisterHyperlinkCallbacks();
+ }
+ else
+ {
+ m_ATGTextEventHandler.UnRegisterHyperlinkCallbacks();
+ }
}
internal bool ConvertUssToNativeTextGenerationSettings()
@@ -89,7 +189,12 @@ internal bool ConvertUssToNativeTextGenerationSettings()
}
}
nativeSettings.globalFontAssetFallbacks = globalFontAssetFallbacks.ToArray();
- nativeSettings.fontStyle = TextGeneratorUtilities.LegacyStyleToNewStyle(style.unityFontStyleAndWeight);
+
+ //Bold is not part of the font style in css and in text native, but it is in textCore/Uitk
+ var sourcefontStyle = TextGeneratorUtilities.LegacyStyleToNewStyle(style.unityFontStyleAndWeight);
+ nativeSettings.fontStyle = sourcefontStyle & ~FontStyles.Bold;
+ //Backward compatibility with text core
+ nativeSettings.fontWeight = (sourcefontStyle & FontStyles.Bold) == FontStyles.Bold ? TextFontWeight.Bold : TextFontWeight.Regular;
// The screenRect in TextCore is not properly implemented with regards to the offset part, so zero it out for now and we will add it ourselves later
var size = m_TextElement.contentRect.size;
diff --git a/Modules/UIElements/Core/Text/UITKTextHandle.cs b/Modules/UIElements/Core/Text/UITKTextHandle.cs
index abd9926e9f..5567784dd5 100644
--- a/Modules/UIElements/Core/Text/UITKTextHandle.cs
+++ b/Modules/UIElements/Core/Text/UITKTextHandle.cs
@@ -7,6 +7,8 @@
using System.Runtime.CompilerServices;
using Unity.Jobs.LowLevel.Unsafe;
using UnityEngine.TextCore.Text;
+using UnityEngine.UIElements.Internal;
+using UnityEngine.UIElements.UIR;
namespace UnityEngine.UIElements
{
diff --git a/Modules/UIElementsEditor/EditorPanel.cs b/Modules/UIElementsEditor/EditorPanel.cs
index e3e86fc327..3e672f0b26 100644
--- a/Modules/UIElementsEditor/EditorPanel.cs
+++ b/Modules/UIElementsEditor/EditorPanel.cs
@@ -59,7 +59,7 @@ public static void InitEditorUpdater(BaseVisualElementPanel panel, VisualTreeUpd
EditorWindow editorWindow => GetBackingScaleFactor(editorWindow?.m_Parent),
IEditorWindowModel ewm => GetBackingScaleFactor(ewm.window),
_ => null,
- } ;
+ };
}
private void CheckPanelScaling()
@@ -72,11 +72,11 @@ private void CheckPanelScaling()
var windowScaling = GetBackingScaleFactor();
if (windowScaling == null || windowScaling.Value == -1)
{
- Debug.Assert(windowScaling != null, "got -1 here!!" );
- // if we have -1, we were able to get to a GuiView, but the native call returned -1 because there is no containerWindow
- // if the windowScaling == null we were simply not able to get to a GuiView
- // in both cases, we want to update the scaling like the old behavior.
- pixelsPerPoint = GUIUtility.pixelsPerPoint;
+ Debug.Assert(windowScaling != null, "got -1 here!!");
+ // if we have -1, we were able to get to a GuiView, but the native call returned -1 because there is no containerWindow
+ // if the windowScaling == null we were simply not able to get to a GuiView
+ // in both cases, we want to update the scaling like the old behavior.
+ pixelsPerPoint = GUIUtility.pixelsPerPoint;
}
else
{
@@ -100,5 +100,15 @@ public override void Render()
CheckPanelScaling();
base.Render();
}
+
+ internal override Color HyperlinkColor
+ {
+ get
+ {
+ ColorUtility.TryParseHtmlString(EditorGUIUtility.GetHyperlinkColorForSkin(), out Color color);
+ return color;
+ }
+ }
+
}
}
diff --git a/Projects/CSharp/UnityEditor.csproj b/Projects/CSharp/UnityEditor.csproj
index 4d2d15eac2..0b9f3589b3 100644
--- a/Projects/CSharp/UnityEditor.csproj
+++ b/Projects/CSharp/UnityEditor.csproj
@@ -3927,12 +3927,54 @@
Editor\Mono\UnityConnect\CoppaCompliance.cs
+
+ Editor\Mono\UnityConnect\Network\UnityConnectWebRequestException.cs
+
+
+ Editor\Mono\UnityConnect\Network\UnityConnectWebRequestUtils.cs
+
+
+ Editor\Mono\UnityConnect\ServiceToken\Caching\GenesisAndServiceTokenCaching.cs
+
+
+ Editor\Mono\UnityConnect\ServiceToken\Caching\IGenesisAndServiceTokenCaching.cs
+
+
+ Editor\Mono\UnityConnect\ServiceToken\Caching\JsonWebToken.cs
+
+
+ Editor\Mono\UnityConnect\ServiceToken\Caching\Tokens.cs
+
+
+ Editor\Mono\UnityConnect\ServiceToken\ConfigurationProvider\CloudEnvironmentConfigProvider.cs
+
+
+ Editor\Mono\UnityConnect\ServiceToken\ConfigurationProvider\ICloudEnvironmentConfigProvider.cs
+
+
+ Editor\Mono\UnityConnect\ServiceToken\ServiceToken.cs
+
+
+ Editor\Mono\UnityConnect\ServiceToken\TokenExchange\ITokenExchange.cs
+
+
+ Editor\Mono\UnityConnect\ServiceToken\TokenExchange\Model\TokenExchangeRequest.cs
+
+
+ Editor\Mono\UnityConnect\ServiceToken\TokenExchange\Model\TokenExchangeResponse.cs
+
+
+ Editor\Mono\UnityConnect\ServiceToken\TokenExchange\TokenExchange.cs
+
Editor\Mono\UnityConnect\Services\EditorProjectAccess.bindings.cs
Editor\Mono\UnityConnect\UnityConnect.bindings.cs
+
+ Editor\Mono\UnityConnect\Utils\AsyncUtils.cs
+
Editor\Mono\UnityStats.bindings.cs
@@ -4434,6 +4476,9 @@
Modules\BuildPipeline\Editor\Ucbp\BuildPipelineContext.bindings.cs
+
+ Modules\BuildProfileEditor\ActiveBuildProfilerListener.cs
+
Modules\BuildProfileEditor\AssemblyInfo.cs
@@ -9378,9 +9423,6 @@
Modules\UnityConnectEditor\Common\UIElementsNotificationSubscriber.cs
-
- Modules\UnityConnectEditor\Common\UnityConnectWebRequestException.cs
-
Modules\UnityConnectEditor\ProjectSettings\AdsProjectSettings.cs
diff --git a/Projects/CSharp/UnityEngine.csproj b/Projects/CSharp/UnityEngine.csproj
index 9bb863c417..7ee1aadd59 100644
--- a/Projects/CSharp/UnityEngine.csproj
+++ b/Projects/CSharp/UnityEngine.csproj
@@ -1452,6 +1452,9 @@
Modules\TextCoreTextEngine\Managed\TextGenerator\NativeTextGenerationSettings.bindings.cs
+
+ Modules\TextCoreTextEngine\Managed\TextGenerator\RichTextTagParser.cs
+
Modules\TextCoreTextEngine\Managed\TextGenerator\TextGenerationSettings.cs
@@ -2844,6 +2847,9 @@
Modules\UIElements\Core\TextShadow.cs
+
+ Modules\UIElements\Core\Text\ATGTextEventHandler.cs
+
Modules\UIElements\Core\Text\ATGTextHandle.cs
diff --git a/README.md b/README.md
index 7a583118e3..7ef41e1fbd 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-## Unity 6000.0.21f1 C# reference source code
+## Unity 6000.0.22f1 C# reference source code
The C# part of the Unity engine and editor source code.
May be used for reference purposes only.
diff --git a/Runtime/Export/Graphics/GraphicsFormatUtility.bindings.cs b/Runtime/Export/Graphics/GraphicsFormatUtility.bindings.cs
index 40e27c1c30..60c1661c11 100644
--- a/Runtime/Export/Graphics/GraphicsFormatUtility.bindings.cs
+++ b/Runtime/Export/Graphics/GraphicsFormatUtility.bindings.cs
@@ -56,9 +56,9 @@ public static GraphicsFormat GetGraphicsFormat(RenderTextureFormat format, Rende
[FreeFunction(IsThreadSafe = true)]
extern private static GraphicsFormat GetDepthStencilFormatFromBitsLegacy_Native(int minimumDepthBits);
- internal static GraphicsFormat GetDepthStencilFormat(int minimumDepthBits)
+ public static GraphicsFormat GetDepthStencilFormat(int depthBits)
{
- return GetDepthStencilFormatFromBitsLegacy_Native(minimumDepthBits);
+ return GetDepthStencilFormatFromBitsLegacy_Native(depthBits);
}
[FreeFunction(IsThreadSafe = true)]
diff --git a/Runtime/Export/Graphics/Texture.cs b/Runtime/Export/Graphics/Texture.cs
index 7a49a83c90..0d1b469319 100644
--- a/Runtime/Export/Graphics/Texture.cs
+++ b/Runtime/Export/Graphics/Texture.cs
@@ -21,7 +21,7 @@ public struct RenderTextureDescriptor
public int volumeDepth { get; set; }
public int mipCount { get; set; }
- private GraphicsFormat _graphicsFormat;// { get; set; }
+ private GraphicsFormat _graphicsFormat;
public GraphicsFormat graphicsFormat
{
@@ -30,9 +30,7 @@ public GraphicsFormat graphicsFormat
set
{
_graphicsFormat = value;
- SetOrClearRenderTextureCreationFlag(GraphicsFormatUtility.IsSRGBFormat(value), RenderTextureCreationFlags.SRGB);
- //To avoid that the order of setting a property changes the end result, we need to update the depthbufferbits because the setter depends on the colorFormat.
- depthBufferBits = depthBufferBits;
+ SetOrClearRenderTextureCreationFlag(GraphicsFormatUtility.IsSRGBFormat(value), RenderTextureCreationFlags.SRGB);
}
}
@@ -55,16 +53,10 @@ public RenderTextureFormat colorFormat
}
set
{
- if (value == RenderTextureFormat.Shadowmap)
- {
- shadowSamplingMode = ShadowSamplingMode.CompareDepths;
- // 'shadowSamplingMode' must be set immediately as we otherwise are unable to track the fact
- // that a Shadowmap was requested. (+ see comment below regarding setting 'graphicsFormat' as well)
- }
- // Update 'graphicsFormat' last because it also updates 'depthBufferBits', which relies on the 'colorFormat',
- // which itself relies on the 'shadowSamplingMode' to make the distinction between RTF.Depth/RTF.Shadowmap.
+ shadowSamplingMode = RenderTexture.GetShadowSamplingModeForFormat(value);
GraphicsFormat requestedFormat = GraphicsFormatUtility.GetGraphicsFormat(value, sRGB);
graphicsFormat = SystemInfo.GetCompatibleFormat(requestedFormat, GraphicsFormatUsage.Render);
+ depthStencilFormat = RenderTexture.GetDepthStencilFormatLegacy(depthBufferBits, shadowSamplingMode);
}
}
@@ -83,10 +75,12 @@ public int depthBufferBits
{
get { return GraphicsFormatUtility.GetDepthBits(depthStencilFormat); }
//Ideally we deprecate the setter but keeping it for now because its a very commonly used api
- //It is very bad practice to use the colorFormat property here because that makes the result depend on the order of setting the properties
+ //It is very bad practice to use the shadowSamplingMode property here because that makes the result depend on the order of setting the properties
//However, it's the best what we can do to make sure this is functionally correct.
- //We now need to set depthBufferBits after we set graphicsFormat, see that property.
- set { depthStencilFormat = RenderTexture.GetDepthStencilFormatLegacy(value, colorFormat, true); }
+ //depthBufferBits and colorFormat are legacy APIs that can be used togther in any order to set a combination of the (modern) fields graphicsFormat, dephtStencilFormat and shadowSamplingMode.
+ //The use of these legacy APIs should not be combined with setting the modern fields directly, the order can change the results.
+ //There should be no "magic" when setting the modern fields, the desc will contain what the users sets, even if the combination is not valid (ie a depthStencilFormat with stencil and shadowSamplingMode CompareDepths).
+ set { depthStencilFormat = RenderTexture.GetDepthStencilFormatLegacy(value, shadowSamplingMode); }
}
public Rendering.TextureDimension dimension { get; set; }
diff --git a/Runtime/Export/iOS/iOSDevice.cs b/Runtime/Export/iOS/iOSDevice.cs
index be2d3a5394..718d949d7f 100644
--- a/Runtime/Export/iOS/iOSDevice.cs
+++ b/Runtime/Export/iOS/iOSDevice.cs
@@ -93,6 +93,10 @@ public enum DeviceGeneration
iPhone15Plus = 80,
iPhone15Pro = 81,
iPhone15ProMax = 82,
+ iPhone16 = 83,
+ iPhone16Plus = 84,
+ iPhone16Pro = 85,
+ iPhone16ProMax = 86,
iPhoneUnknown = 10001,
iPadUnknown = 10002,