diff --git a/com.unity.ugui/.buginfo b/com.unity.ugui/.buginfo index 8c0e302..73fe2d2 100644 --- a/com.unity.ugui/.buginfo +++ b/com.unity.ugui/.buginfo @@ -1,13 +1 @@ -default: - area: uGUI Controls - -ugui-framework: - when: - path: - - ^*Canvas.*$ - - ^*CanvasRenderer.*$ - - ^*Clipping.*$ - - ^*UnityEvent.*$ - - ^*EventSystem.*$ - - ^*Vertex.*$ - area: uGUI Framework +area: uGUI Controls diff --git a/com.unity.ugui/Documentation~/ProfilerUI.md b/com.unity.ugui/Documentation~/ProfilerUI.md index 0a987bb..b9fb649 100644 --- a/com.unity.ugui/Documentation~/ProfilerUI.md +++ b/com.unity.ugui/Documentation~/ProfilerUI.md @@ -2,40 +2,47 @@ uid: um-profiler-ui --- -# UI and UI Details Profiler +# UI and UI Details Profiler module reference -The UI and UI Details Profiler modules provide information on how much time and resources Unity spends laying out and rendering the user interface within your application. You can use this module to understand how Unity handles UI batching for your application, including why and how it batches objects. You can also use this module to find out which part of the UI is responsible for slow performance, or to preview the UI while you scrub the timeline. +>[!NOTE] +> The UI and UI Details Profiler module collects Editor-only data, and doesn't work in development builds. -To open the Profiler window, go to **Window > Analysis > Profiler**. For more information on how to use the Profiler window, refer to [The Profiler window](ProfilerWindow). +The UI and UI Details Profiler modules provide information on how much time and resources Unity spends laying out and rendering the user interface within your application. You can use this module to understand how Unity handles UI batching for your application, including why and how it batches objects. You can also use this module to find out which part of the UI has slow performance, or to preview the UI while you scrub the timeline. -![The UI and UI Details Profiler module](../uploads/Main/ui-profiler-module.png) +To open the Profiler window, go to **Window > Analysis > Profiler**. For more information on how to use the Profiler window, refer to [The Profiler window](xref:um-profiler-window). + +![The UI and UI Details Profiler module](images/ui-profiler-module.png) ## Chart categories -The UI and UI Details Profiler modules’ charts are divided into five categories. To change the order of the categories in the chart, you can drag and drop them in the chart’s legend. You can also click a category’s colored legend to toggle its display. +The UI and UI Details Profiler modules' charts contain the following categories. To change the order of the categories in the chart, you can drag and drop them in the chart's legend. You can also click a category's colored legend to toggle its display. + +### UI Profiler module +|**Chart**|**Description**| +|---|---| +|**Layout**|How much time Unity spent performing the layout pass for the UI. This includes calculations done by [`HorizontalLayoutGroup`](https://docs.unity3d.com/Packages/com.unity.ugui@latest/index.html?subfolder=/api/UnityEngine.UI.HorizontalLayoutGroup.html), [`VerticalLayoutGroup`](https://docs.unity3d.com/Packages/com.unity.ugui@latest/index.html?subfolder=/api/UnityEngine.UI.VerticalLayoutGroup.html), and [`GridLayoutGroup`](https://docs.unity3d.com/Packages/com.unity.ugui@latest/index.html?subfolder=/api/UnityEngine.UI.GridLayoutGroup.html).| +|**Render**|How much time the UI has spent doing its portion of rendering. This is either the time spent rendering directly to the graphics device or rendering to the main render queue.| -|**Chart**||**Description**| -|---|---|---| -|**UI Profiler module**||| -||Layout|How much time Unity has spent performing the layout pass for the UI. This includes calculations done by [HorizontalLayoutGroup](https://docs.unity3d.com/Packages/com.unity.ugui@latest/index.html?subfolder=/api/UnityEngine.UI.HorizontalLayoutGroup.html), [VerticalLayoutGroup](https://docs.unity3d.com/Packages/com.unity.ugui@latest/index.html?subfolder=/api/UnityEngine.UI.VerticalLayoutGroup.html), and [GridLayoutGroup](https://docs.unity3d.com/Packages/com.unity.ugui@latest/index.html?subfolder=/api/UnityEngine.UI.GridLayoutGroup.html).| -||Render|How much time the UI has spent doing its portion of rendering. This is either the cost of rendering directly to the graphics device or rendering to the main render queue.| -|**UI Details Profile module**||| -||Batches|Displays the total number of draw calls that are batched together. | -||Vertices|The total number of vertices that are used to render a section of UI. | -||Markers|Displays event markers. Unity records markers when the user interacts with the UI (for example, a button click, or a slider value change) and then draws them as vertical lines and labels on the chart.| +### UI Details Profile module + +|**Chart**|**Description**| +|---|---| +|**Batches**|Displays the total number of draw calls that are batched together. | +|**Vertices**|The total number of vertices that are used to render a section of UI. | +|**Markers**|Displays event markers. Unity records markers when the user interacts with the UI (for example, a button click, or a slider value change) and then draws them as vertical lines and labels on the chart.| ## Module details pane When you select the UI or the UI Details Profiler module, the module details pane at the bottom of the Profiler window displays more details on the UI in your application. You can use it to inspect the profiling information about the UI objects in your application. The pane is divided into the following columns: |**Column**|**Description**| |---|---| -|**Object**|A list of UI canvases your application used during the period profiled. Double click on a row to highlight the matching object in the Scene.| +|**Object**|A list of UI canvases your application used during the period profiled. Double click on a row to highlight the matching object in the scene.| |**Self Batch Count**|How many batches Unity generated for the canvas.| -|**Cumulative Batch Count**|How many batches Unity generated for the canvas and all of its nested canvases| +|**Cumulative Batch Count**|How many batches Unity generated for the canvas and all its nested canvases.| |**Self Vertex Count**|How many vertices this canvas is rendering.| -|**Cumulative Vertex Count**|How many vertices this canvas and nested canvases are rendering| -|**Batch Breaking Reason**|Why Unity split the batch. Sometimes Unity might not be able to batch objects together. Common reasons include:

**Not Coplanar With Canvas**, where the batching needs the object’s rect transform to be coplanar (unrotated) with the canvas.
**CanvasInjectionIndex**, where a CanvasGroup component is present and forces a new batch, such as when it displays the drop down list of a combo box on top of the rest.
**Different Material Instance, Rect clipping, Texture, or A8TextureUsage**, where Unity can only batch together objects with identical materials, masking, textures, and texture alpha channel usage.| -|**GameObject Count**|How many GameObjects are part of this batch| +|**Cumulative Vertex Count**|How many vertices this canvas and nested canvases are rendering.| +|**Batch Breaking Reason**|Why Unity split the batch. Sometimes Unity might not be able to batch objects together. Common reasons include: | +|**GameObject Count**|How many GameObjects are part of this batch.| |**GameObjects**|The list of GameObjects in the batch.| When you select a UI object from the list, a preview of it appears on the right hand side of the pane. Above the preview there are the following options in the toolbar: @@ -46,6 +53,6 @@ When you select a UI object from the list, a preview of it appears on the right ## Additional resources -* [Profiler window introduction](ProfilerWindow) -* [Profiling your application](profiler-profiling-applications) +* [Profiler window introduction](xref:um-profiler-window) +* [Profiling your application](xref:um-profiler-profiling-applications) diff --git a/com.unity.ugui/Documentation~/TableOfContents.md b/com.unity.ugui/Documentation~/TableOfContents.md index 5dd5cfe..cec3b5e 100644 --- a/com.unity.ugui/Documentation~/TableOfContents.md +++ b/com.unity.ugui/Documentation~/TableOfContents.md @@ -53,7 +53,6 @@ * [Graphic Raycaster](script-GraphicRaycaster.md) * [Panel Event Handler](script-PanelEventHandler) * [Panel Raycaster](script-PanelRaycaster) - * [World Document Raycaster](script-WorldDocumentRaycaster) * [Physics Raycaster](script-PhysicsRaycaster.md) * [Physics 2D Raycaster](script-Physics2DRaycaster.md) * [Standalone Input Module](script-StandaloneInputModule.md) diff --git a/com.unity.ugui/Documentation~/TextMeshPro/.buginfo b/com.unity.ugui/Documentation~/TextMeshPro/.buginfo index 8e99ac4..7e316a5 100644 --- a/com.unity.ugui/Documentation~/TextMeshPro/.buginfo +++ b/com.unity.ugui/Documentation~/TextMeshPro/.buginfo @@ -1 +1 @@ -area: Text (TextCore) +area: Text (TextCore) \ No newline at end of file diff --git a/com.unity.ugui/Documentation~/filter.yml b/com.unity.ugui/Documentation~/filter.yml deleted file mode 100644 index af83324..0000000 --- a/com.unity.ugui/Documentation~/filter.yml +++ /dev/null @@ -1,5 +0,0 @@ -apiRules: - - exclude: - uidRegex: ^TMPro\.ObjectUtilsBridge$ - type: Class - diff --git a/com.unity.ugui/Documentation~/images/ui-profiler-module.png b/com.unity.ugui/Documentation~/images/ui-profiler-module.png new file mode 100644 index 0000000..62bc7f9 Binary files /dev/null and b/com.unity.ugui/Documentation~/images/ui-profiler-module.png differ diff --git a/com.unity.ugui/Editor Resources/.buginfo b/com.unity.ugui/Editor Resources/.buginfo index 167481e..f640b3a 100644 --- a/com.unity.ugui/Editor Resources/.buginfo +++ b/com.unity.ugui/Editor Resources/.buginfo @@ -1 +1 @@ -area: Text (TextMeshPro) \ No newline at end of file +area: Text (TextMeshPro) diff --git a/com.unity.ugui/Runtime/InternalBridge.meta b/com.unity.ugui/Editor/Analytics.meta similarity index 77% rename from com.unity.ugui/Runtime/InternalBridge.meta rename to com.unity.ugui/Editor/Analytics.meta index 6c25e2a..7e349df 100644 --- a/com.unity.ugui/Runtime/InternalBridge.meta +++ b/com.unity.ugui/Editor/Analytics.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: bab3a29ab4f214742a44add41efffb7d +guid: c52726d811a9f66459355df6b0c3977b folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/com.unity.ugui/Tests/Runtime/UGUI/CanvasRenderer.meta b/com.unity.ugui/Editor/Analytics/IAnalytic.meta similarity index 77% rename from com.unity.ugui/Tests/Runtime/UGUI/CanvasRenderer.meta rename to com.unity.ugui/Editor/Analytics/IAnalytic.meta index 5a4067b..24e58e7 100644 --- a/com.unity.ugui/Tests/Runtime/UGUI/CanvasRenderer.meta +++ b/com.unity.ugui/Editor/Analytics/IAnalytic.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: a69b08936f9f74179ae333a4bebe7c54 +guid: 77417b0f4cf6d334f916c64448d24133 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/com.unity.ugui/Editor/Analytics/IAnalytic/UGUIBuildEvent.cs b/com.unity.ugui/Editor/Analytics/IAnalytic/UGUIBuildEvent.cs new file mode 100644 index 0000000..f841eea --- /dev/null +++ b/com.unity.ugui/Editor/Analytics/IAnalytic/UGUIBuildEvent.cs @@ -0,0 +1,45 @@ +using System; +using UnityEditor.Build.Reporting; +using UnityEngine; +using UnityEngine.Analytics; +using UnityEngine.Serialization; + +namespace UnityEditor.UI.Analytics +{ + [AnalyticInfo(eventName: "ugui_onBuild", vendorKey: "unity.ugui")] + internal class UGUIBuildEvent : IAnalytic + { + [Serializable] + internal struct Payload : IAnalytic.IData + { + public string build_guid; + public int build_type; + public Counter component_count; + } + + [Serializable] + internal struct Counter + { + public int WorldSpaceCanvas; + public int ScreenSpaceCanvas; + public int OverlayCanvas; + public int TMPElements; + } + + public string buildGuid; + public BuildType buildType; + public Counter counter; + + public bool TryGatherData(out IAnalytic.IData data, out Exception error) + { + error = null; + data = new Payload + { + build_guid = buildGuid, + build_type = (int)buildType, + component_count = counter, + }; + return data != null; + } + } +} diff --git a/com.unity.ugui/Editor/Analytics/IAnalytic/UGUIBuildEvent.cs.meta b/com.unity.ugui/Editor/Analytics/IAnalytic/UGUIBuildEvent.cs.meta new file mode 100644 index 0000000..aa2b24b --- /dev/null +++ b/com.unity.ugui/Editor/Analytics/IAnalytic/UGUIBuildEvent.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 24cda912b57cf784d9eebefb7c59111c \ No newline at end of file diff --git a/com.unity.ugui/Editor/Analytics/UGUIAnalytics.cs b/com.unity.ugui/Editor/Analytics/UGUIAnalytics.cs new file mode 100644 index 0000000..9543b68 --- /dev/null +++ b/com.unity.ugui/Editor/Analytics/UGUIAnalytics.cs @@ -0,0 +1,16 @@ +using UnityEngine; +using UnityEngine.Analytics; +using UnityEngine.UI; + +namespace UnityEditor.UI.Analytics +{ + internal static class UGUIAnalytics + { + public static void Send (IAnalytic analytic) + { + if (!EditorAnalytics.enabled) + return; + EditorAnalytics.SendAnalytic(analytic); + } + } +} diff --git a/com.unity.ugui/Editor/Analytics/UGUIAnalytics.cs.meta b/com.unity.ugui/Editor/Analytics/UGUIAnalytics.cs.meta new file mode 100644 index 0000000..ae377a9 --- /dev/null +++ b/com.unity.ugui/Editor/Analytics/UGUIAnalytics.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ff6d34f71e1a6af4290f9275ff94616c \ No newline at end of file diff --git a/com.unity.ugui/Editor/Analytics/UGUIBuildAnalytics.cs b/com.unity.ugui/Editor/Analytics/UGUIBuildAnalytics.cs new file mode 100644 index 0000000..927eeaa --- /dev/null +++ b/com.unity.ugui/Editor/Analytics/UGUIBuildAnalytics.cs @@ -0,0 +1,58 @@ +using System; +using TMPro; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.SceneManagement; +using UnityEngine.UI; + +namespace UnityEditor.UI.Analytics +{ + internal class UGUIBuildAnalytics : IPreprocessBuildWithReport, IProcessSceneWithReport, IPostprocessBuildWithReport + { + static UGUIBuildEvent buildEvent; + + public int callbackOrder { get; } + + public void OnPreprocessBuild (BuildReport report) + { + buildEvent = new UGUIBuildEvent(); + } + + public void OnPostprocessBuild (BuildReport report) + { + buildEvent.buildGuid = report.summary.guid.ToString(); + buildEvent.buildType = report.summary.buildType; + UGUIAnalytics.Send(buildEvent); + } + + public void OnProcessScene(Scene scene, BuildReport report) + { + //Not currently used in AssetBundle builds + if (buildEvent == null) { return; } + var gameObjects = scene.GetRootGameObjects(); + foreach (var gameObject in gameObjects) + { + var allCanvas = gameObject.GetComponentsInChildren(); + foreach (var canvas in allCanvas) + { + if (canvas.renderMode == RenderMode.WorldSpace) + buildEvent.counter.WorldSpaceCanvas++; + else if (canvas.renderMode == RenderMode.ScreenSpaceOverlay) + buildEvent.counter.OverlayCanvas++; + else if (canvas.renderMode == RenderMode.ScreenSpaceCamera) + buildEvent.counter.ScreenSpaceCanvas++; + } + + var allUI = gameObject.GetComponentsInChildren(); + foreach (var ui in allUI) + { + if (ui is TMP_Text or TMP_Dropdown or TMP_InputField or TMP_SubMeshUI or TMP_SelectionCaret) + buildEvent.counter.TMPElements++; + } + } + } + } + +} diff --git a/com.unity.ugui/Editor/Analytics/UGUIBuildAnalytics.cs.meta b/com.unity.ugui/Editor/Analytics/UGUIBuildAnalytics.cs.meta new file mode 100644 index 0000000..b15002b --- /dev/null +++ b/com.unity.ugui/Editor/Analytics/UGUIBuildAnalytics.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ea11495227d864849bdb4fa69e931cbc \ No newline at end of file diff --git a/com.unity.ugui/Editor/Analytics/UnityEditor.UI.Analytics.asmdef b/com.unity.ugui/Editor/Analytics/UnityEditor.UI.Analytics.asmdef new file mode 100644 index 0000000..c9338b9 --- /dev/null +++ b/com.unity.ugui/Editor/Analytics/UnityEditor.UI.Analytics.asmdef @@ -0,0 +1,19 @@ +{ + "name": "UnityEditor.UI.Analytics", + "rootNamespace": "", + "references": [ + "UnityEngine.UI", + "Unity.TextMeshPro" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/com.unity.ugui/Runtime/InternalBridge/Unity.InternalAPIEngineBridge.004.asmdef.meta b/com.unity.ugui/Editor/Analytics/UnityEditor.UI.Analytics.asmdef.meta similarity index 76% rename from com.unity.ugui/Runtime/InternalBridge/Unity.InternalAPIEngineBridge.004.asmdef.meta rename to com.unity.ugui/Editor/Analytics/UnityEditor.UI.Analytics.asmdef.meta index 5335c8f..0811204 100644 --- a/com.unity.ugui/Runtime/InternalBridge/Unity.InternalAPIEngineBridge.004.asmdef.meta +++ b/com.unity.ugui/Editor/Analytics/UnityEditor.UI.Analytics.asmdef.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 43b111c4a445f446abd2c02e77750ff5 +guid: 8394c018a82352f41ae354f7bdad54e3 AssemblyDefinitionImporter: externalObjects: {} userData: diff --git a/com.unity.ugui/Editor/TMP/.buginfo b/com.unity.ugui/Editor/TMP/.buginfo index 167481e..f640b3a 100644 --- a/com.unity.ugui/Editor/TMP/.buginfo +++ b/com.unity.ugui/Editor/TMP/.buginfo @@ -1 +1 @@ -area: Text (TextMeshPro) \ No newline at end of file +area: Text (TextMeshPro) diff --git a/com.unity.ugui/Editor/TMP/TMP_BaseEditorPanel.cs b/com.unity.ugui/Editor/TMP/TMP_BaseEditorPanel.cs index 5881c77..626df48 100644 --- a/com.unity.ugui/Editor/TMP/TMP_BaseEditorPanel.cs +++ b/com.unity.ugui/Editor/TMP/TMP_BaseEditorPanel.cs @@ -41,6 +41,7 @@ public abstract class TMP_BaseEditorPanel : Editor static readonly GUIContent k_SpacingOptionsLabel = new GUIContent("Spacing Options (em)", "Spacing adjustments between different elements of the text. Values are in font units where a value of 1 equals 1/100em."); static readonly GUIContent k_CharacterSpacingLabel = new GUIContent("Character"); + static readonly GUIContent k_CharacterHorizontalScaleLabel = new GUIContent("Character Horizontal Scale", "The horizontal scale of the characters. Value = 1.0 is normal. Values > 1.0 or < 1.0 respectively increases or decreases the horizontal scale or width of the characters."); static readonly GUIContent k_WordSpacingLabel = new GUIContent("Word"); static readonly GUIContent k_LineSpacingLabel = new GUIContent("Line"); static readonly GUIContent k_ParagraphSpacingLabel = new GUIContent("Paragraph"); @@ -129,6 +130,7 @@ protected struct Foldout protected SerializedProperty m_CharWidthMaxAdjProp; protected SerializedProperty m_CharacterSpacingProp; + private protected SerializedProperty m_CharacterHorizontalScaleProp; protected SerializedProperty m_WordSpacingProp; protected SerializedProperty m_LineSpacingProp; protected SerializedProperty m_ParagraphSpacingProp; @@ -213,6 +215,7 @@ protected virtual void OnEnable() m_OverrideHtmlColorProp = serializedObject.FindProperty("m_overrideHtmlColors"); m_CharacterSpacingProp = serializedObject.FindProperty("m_characterSpacing"); + m_CharacterHorizontalScaleProp = serializedObject.FindProperty("m_characterHorizontalScale"); m_WordSpacingProp = serializedObject.FindProperty("m_wordSpacing"); m_LineSpacingProp = serializedObject.FindProperty("m_lineSpacing"); m_ParagraphSpacingProp = serializedObject.FindProperty("m_paragraphSpacing"); @@ -275,7 +278,7 @@ protected virtual void OnEnable() // Get Styles from Style Sheet if (TMP_Settings.instance != null) m_StyleNames = GetStyleNames(); - + // Get list of font features for the primary font asset assigned to the text component // FontEngine.LoadFontFace(m_TextComponent.font.SourceFont_EditorRef); // OTL_Table gposTable = UnityEngine.TextCore.LowLevel.FontEngine.GetOpenTypeLayoutTable(OTL_TableType.GPOS); @@ -995,6 +998,8 @@ void DrawSpacing() EditorGUIUtility.labelWidth = currentLabelWidth; EditorGUI.indentLevel = oldIndent; + EditorGUILayout.PropertyField(m_CharacterHorizontalScaleProp, k_CharacterHorizontalScaleLabel, GUILayout.MaxWidth(EditorGUIUtility.labelWidth + 50f)); + if (EditorGUI.EndChangeCheck()) { m_HavePropertiesChanged = true; @@ -1266,7 +1271,7 @@ protected void DrawFontFeatures() for (int i = 0; i < featureCount; i++) { SerializedProperty activeFeatureProperty = m_FontFeaturesActiveProp.GetArrayElementAtIndex(i); - + for (int j = 0; j < k_FontFeatures.Length; j++) { if (activeFeatureProperty.intValue == k_FontFeatures[j].TagToInt()) @@ -1278,15 +1283,15 @@ protected void DrawFontFeatures() } EditorGUI.BeginChangeCheck(); - + int mask = EditorGUILayout.MaskField(k_FontFeaturesLabel, srcMask, k_FontFeatures); - + if (EditorGUI.EndChangeCheck()) { m_FontFeaturesActiveProp.ClearArray(); int writeIndex = 0; - + for (int i = 0; i < k_FontFeatures.Length; i++) { int bit = 0x1 << i; @@ -1301,7 +1306,7 @@ protected void DrawFontFeatures() } m_HavePropertiesChanged = true; - } + } } protected void DrawPadding() diff --git a/com.unity.ugui/Editor/TMP/TMP_EditorPanelUI.cs b/com.unity.ugui/Editor/TMP/TMP_EditorPanelUI.cs index 2b22a03..0d09d6b 100644 --- a/com.unity.ugui/Editor/TMP/TMP_EditorPanelUI.cs +++ b/com.unity.ugui/Editor/TMP/TMP_EditorPanelUI.cs @@ -51,7 +51,7 @@ protected override void DrawExtraSettings() DrawSpriteAsset(); DrawStyleSheet(); - + DrawFontFeatures(); DrawPadding(); @@ -83,12 +83,19 @@ protected void DrawMaskable() EditorGUILayout.PropertyField(m_MaskableProp, k_MaskableLabel); if (EditorGUI.EndChangeCheck()) { - m_TextComponent.maskable = m_MaskableProp.boolValue; + bool value = m_MaskableProp.boolValue; // Change needs to propagate to the child sub objects. MaskableGraphic[] maskableGraphics = m_TextComponent.GetComponentsInChildren(); - for (int i = 1; i < maskableGraphics.Length; i++) - maskableGraphics[i].maskable = m_MaskableProp.boolValue; + + foreach (var graphicComponent in maskableGraphics) + { + graphicComponent.maskable = value; + + // Force RectMask2D to re-evaluate clipping for this graphic + graphicComponent.RecalculateClipping(); + EditorUtility.SetDirty(graphicComponent); + } m_HavePropertiesChanged = true; } diff --git a/com.unity.ugui/Editor/TMP/TMP_FontAssetEditor.cs b/com.unity.ugui/Editor/TMP/TMP_FontAssetEditor.cs index 4b670cf..80aaee5 100644 --- a/com.unity.ugui/Editor/TMP/TMP_FontAssetEditor.cs +++ b/com.unity.ugui/Editor/TMP/TMP_FontAssetEditor.cs @@ -2445,7 +2445,7 @@ GlyphPairAdjustmentRecord GetGlyphPairAdjustmentRecord(SerializedProperty proper // TODO : Need to revise how Everything is handled in the event more enum values are added. int flagValue = property.FindPropertyRelative("m_FeatureLookupFlags").intValue; - //pairAdjustmentRecord.featureLookupFlags = flagValue == -1 ? FontFeatureLookupFlags.IgnoreLigatures | FontFeatureLookupFlags.IgnoreSpacingAdjustments : (FontFeatureLookupFlags) flagValue; + pairAdjustmentRecord.featureLookupFlags = (UnityEngine.TextCore.LowLevel.FontFeatureLookupFlags)flagValue; return pairAdjustmentRecord; } @@ -2599,6 +2599,22 @@ void CopyCharacterSerializedProperty(SerializedProperty source, ref SerializedPr } + static List ParseGlyphIndexList(string pattern) + { + var list = new List(); + if (string.IsNullOrWhiteSpace(pattern)) return list; + + var separators = new[] { ',', ';', '|', ' ', '\t' }; + var parts = pattern.Split(separators, StringSplitOptions.RemoveEmptyEntries); + foreach (var p in parts) + { + if (uint.TryParse(p.Trim(), out var id)) + list.Add(id); + } + return list; + } + + /// /// /// @@ -2666,38 +2682,44 @@ void SearchCharacterTable(string searchPattern, ref List searchResults) void SearchLigatureTable(string searchPattern, ref List searchResults) { if (searchResults == null) searchResults = new List(); - searchResults.Clear(); - // Lookup glyph index of potential characters contained in the search pattern. - uint firstGlyphIndex = 0; - TMP_Character firstCharacterSearch; + var requestedGlyphIndexes = ParseGlyphIndexList(searchPattern); + if (requestedGlyphIndexes.Count == 0) + return; - if (searchPattern.Length > 0 && m_fontAsset.characterLookupTable.TryGetValue(searchPattern[0], out firstCharacterSearch)) - firstGlyphIndex = firstCharacterSearch.glyphIndex; + int arraySize = m_LigatureSubstitutionRecords_prop.arraySize; - uint secondGlyphIndex = 0; - TMP_Character secondCharacterSearch; + for (int i = 0; i < arraySize; i++) + { + SerializedProperty record = m_LigatureSubstitutionRecords_prop.GetArrayElementAtIndex(i); - if (searchPattern.Length > 1 && m_fontAsset.characterLookupTable.TryGetValue(searchPattern[1], out secondCharacterSearch)) - secondGlyphIndex = secondCharacterSearch.glyphIndex; + // Build a set of all glyph IDs referenced by this ligature record. + var matches = new HashSet(); - int arraySize = m_MarkToBaseAdjustmentRecords_prop.arraySize; + SerializedProperty componentGlyphIDsProp = record.FindPropertyRelative("m_ComponentGlyphIDs"); + int componentCount = componentGlyphIDsProp.arraySize; + for (int j = 0; j < componentCount; j++) + { + uint componentGlyphID = (uint)componentGlyphIDsProp.GetArrayElementAtIndex(j).intValue; + matches.Add(componentGlyphID); + } - for (int i = 0; i < arraySize; i++) - { - SerializedProperty record = m_MarkToBaseAdjustmentRecords_prop.GetArrayElementAtIndex(i); + uint ligatureGlyphID = (uint)record.FindPropertyRelative("m_LigatureGlyphID").intValue; + matches.Add(ligatureGlyphID); - int baseGlyphIndex = record.FindPropertyRelative("m_BaseGlyphID").intValue; - int markGlyphIndex = record.FindPropertyRelative("m_MarkGlyphID").intValue; + // Require that all requested IDs are present somewhere in this record. + bool hasAll = true; + for (int n = 0; n < requestedGlyphIndexes.Count; n++) + { + if (!matches.Contains(requestedGlyphIndexes[n])) + { + hasAll = false; + break; + } + } - if (firstGlyphIndex == baseGlyphIndex && secondGlyphIndex == markGlyphIndex) - searchResults.Add(i); - else if (searchPattern.Length == 1 && (firstGlyphIndex == baseGlyphIndex || firstGlyphIndex == markGlyphIndex)) - searchResults.Add(i); - else if (baseGlyphIndex.ToString().Contains(searchPattern)) - searchResults.Add(i); - else if (markGlyphIndex.ToString().Contains(searchPattern)) + if (hasAll) searchResults.Add(i); } } diff --git a/com.unity.ugui/Editor/TMP/TMPro_EditorShaderUtilities.cs b/com.unity.ugui/Editor/TMP/TMPro_EditorShaderUtilities.cs index 4f14623..3d63900 100644 --- a/com.unity.ugui/Editor/TMP/TMPro_EditorShaderUtilities.cs +++ b/com.unity.ugui/Editor/TMP/TMPro_EditorShaderUtilities.cs @@ -24,22 +24,22 @@ public static void CopyMaterialProperties(Material source, Material destination) int property_ID = Shader.PropertyToID(source_prop[i].name); if (destination.HasProperty(property_ID)) { - //Debug.Log(source_prop[i].name + " Type:" + source.shader.GetPropertyType(i)); - switch (source.shader.GetPropertyType(i)) + //Debug.Log(source_prop[i].name + " Type:" + ShaderUtil.GetPropertyType(source.shader, i)); + switch (ShaderUtil.GetPropertyType(source.shader, i)) { - case UnityEngine.Rendering.ShaderPropertyType.Color: + case ShaderUtil.ShaderPropertyType.Color: destination.SetColor(property_ID, source.GetColor(property_ID)); break; - case UnityEngine.Rendering.ShaderPropertyType.Float: + case ShaderUtil.ShaderPropertyType.Float: destination.SetFloat(property_ID, source.GetFloat(property_ID)); break; - case UnityEngine.Rendering.ShaderPropertyType.Range: + case ShaderUtil.ShaderPropertyType.Range: destination.SetFloat(property_ID, source.GetFloat(property_ID)); break; - case UnityEngine.Rendering.ShaderPropertyType.Texture: + case ShaderUtil.ShaderPropertyType.TexEnv: destination.SetTexture(property_ID, source.GetTexture(property_ID)); break; - case UnityEngine.Rendering.ShaderPropertyType.Vector: + case ShaderUtil.ShaderPropertyType.Vector: destination.SetVector(property_ID, source.GetVector(property_ID)); break; } diff --git a/com.unity.ugui/Editor/TMP/Unity.TextMeshPro.Editor.asmdef b/com.unity.ugui/Editor/TMP/Unity.TextMeshPro.Editor.asmdef index 73cf18f..9f2e256 100644 --- a/com.unity.ugui/Editor/TMP/Unity.TextMeshPro.Editor.asmdef +++ b/com.unity.ugui/Editor/TMP/Unity.TextMeshPro.Editor.asmdef @@ -39,4 +39,4 @@ } ], "noEngineReferences": false -} +} \ No newline at end of file diff --git a/com.unity.ugui/Editor/UGUI/EventSystem/EventSystemEditor.cs b/com.unity.ugui/Editor/UGUI/EventSystem/EventSystemEditor.cs index 762e9d9..c78c806 100644 --- a/com.unity.ugui/Editor/UGUI/EventSystem/EventSystemEditor.cs +++ b/com.unity.ugui/Editor/UGUI/EventSystem/EventSystemEditor.cs @@ -18,13 +18,6 @@ public override void OnInspectorGUI() if (eventSystem == null) return; - if (eventSystem.isOverridingUIToolkitEvents) - { - EditorGUILayout.HelpBox(L10n.Tr( - "This EventSystem will be used to drive UI Toolkit input.\nYou can use the Panel Input Configuration component " + - "to configure how UI Toolkit will interact with this EventSystem."), MessageType.Info); - } - if (eventSystem.GetComponent() != null) return; diff --git a/com.unity.ugui/Editor/UGUI/UI/HorizontalOrVerticalLayoutGroupEditor.cs b/com.unity.ugui/Editor/UGUI/UI/HorizontalOrVerticalLayoutGroupEditor.cs index 40b2a6b..0ec8a22 100644 --- a/com.unity.ugui/Editor/UGUI/UI/HorizontalOrVerticalLayoutGroupEditor.cs +++ b/com.unity.ugui/Editor/UGUI/UI/HorizontalOrVerticalLayoutGroupEditor.cs @@ -41,17 +41,15 @@ protected virtual void OnEnable() public override void OnInspectorGUI() { serializedObject.Update(); - EditorGUIUtility.labelWidth = 130; EditorGUILayout.PropertyField(m_Padding, true); EditorGUILayout.PropertyField(m_Spacing, true); EditorGUILayout.PropertyField(m_ChildAlignment, true); EditorGUILayout.PropertyField(m_ReverseArrangement, true); - EditorGUIUtility.labelWidth = 0; Rect rect = EditorGUILayout.GetControlRect(); rect = EditorGUI.PrefixLabel(rect, -1, EditorGUIUtility.TrTextContent("Control Child Size")); - rect.width = Mathf.Max(60, (rect.width - 4) / 3); - EditorGUIUtility.labelWidth = 60; + rect.width = Mathf.Max(50, (rect.width - 4) / 3); + EditorGUIUtility.labelWidth = 50; ToggleLeft(rect, m_ChildControlWidth, EditorGUIUtility.TrTextContent("Width")); rect.x += rect.width + 2; ToggleLeft(rect, m_ChildControlHeight, EditorGUIUtility.TrTextContent("Height")); @@ -59,8 +57,8 @@ public override void OnInspectorGUI() rect = EditorGUILayout.GetControlRect(); rect = EditorGUI.PrefixLabel(rect, -1, EditorGUIUtility.TrTextContent("Use Child Scale")); - rect.width = Mathf.Max(60, (rect.width - 4) / 3); - EditorGUIUtility.labelWidth = 60; + rect.width = Mathf.Max(50, (rect.width - 4) / 3); + EditorGUIUtility.labelWidth = 50; ToggleLeft(rect, m_ChildScaleWidth, EditorGUIUtility.TrTextContent("Width")); rect.x += rect.width + 2; ToggleLeft(rect, m_ChildScaleHeight, EditorGUIUtility.TrTextContent("Height")); @@ -68,8 +66,8 @@ public override void OnInspectorGUI() rect = EditorGUILayout.GetControlRect(); rect = EditorGUI.PrefixLabel(rect, -1, EditorGUIUtility.TrTextContent("Child Force Expand")); - rect.width = Mathf.Max(60, (rect.width - 4) / 3); - EditorGUIUtility.labelWidth = 60; + rect.width = Mathf.Max(50, (rect.width - 4) / 3); + EditorGUIUtility.labelWidth = 50; ToggleLeft(rect, m_ChildForceExpandWidth, EditorGUIUtility.TrTextContent("Width")); rect.x += rect.width + 2; ToggleLeft(rect, m_ChildForceExpandHeight, EditorGUIUtility.TrTextContent("Height")); diff --git a/com.unity.ugui/Editor/UGUI/UI/InterceptedEventsPreview.cs b/com.unity.ugui/Editor/UGUI/UI/InterceptedEventsPreview.cs index 4ea4eef..9b92d9c 100644 --- a/com.unity.ugui/Editor/UGUI/UI/InterceptedEventsPreview.cs +++ b/com.unity.ugui/Editor/UGUI/UI/InterceptedEventsPreview.cs @@ -27,25 +27,7 @@ class Styles public Styles() { - Color fontColor = new Color(0.7f, 0.7f, 0.7f); labelStyle.padding.right += 20; - labelStyle.normal.textColor = fontColor; - labelStyle.active.textColor = fontColor; - labelStyle.focused.textColor = fontColor; - labelStyle.hover.textColor = fontColor; - labelStyle.onNormal.textColor = fontColor; - labelStyle.onActive.textColor = fontColor; - labelStyle.onFocused.textColor = fontColor; - labelStyle.onHover.textColor = fontColor; - - componentName.normal.textColor = fontColor; - componentName.active.textColor = fontColor; - componentName.focused.textColor = fontColor; - componentName.hover.textColor = fontColor; - componentName.onNormal.textColor = fontColor; - componentName.onActive.textColor = fontColor; - componentName.onFocused.textColor = fontColor; - componentName.onHover.textColor = fontColor; } } diff --git a/com.unity.ugui/Package Resources/.buginfo b/com.unity.ugui/Package Resources/.buginfo index 167481e..f640b3a 100644 --- a/com.unity.ugui/Package Resources/.buginfo +++ b/com.unity.ugui/Package Resources/.buginfo @@ -1 +1 @@ -area: Text (TextMeshPro) \ No newline at end of file +area: Text (TextMeshPro) diff --git a/com.unity.ugui/Package Resources/TMP Examples & Extras.unitypackage b/com.unity.ugui/Package Resources/TMP Examples & Extras.unitypackage index b137d08..d4fa3a8 100644 Binary files a/com.unity.ugui/Package Resources/TMP Examples & Extras.unitypackage and b/com.unity.ugui/Package Resources/TMP Examples & Extras.unitypackage differ diff --git a/com.unity.ugui/README.md b/com.unity.ugui/README.md index 223c383..730b6d5 100644 --- a/com.unity.ugui/README.md +++ b/com.unity.ugui/README.md @@ -1,5 +1,9 @@ # Unity UI The Unity UI package allows you to create in-game user interfaces fast and intuitively. +## Prerequisites +### Unity 2019.2 +This package is in development, and requires Unity 2019.2. + ## Getting Started The Unity UI user manual can be found [here](https://docs.unity3d.com/Manual/UISystem.html). diff --git a/com.unity.ugui/Runtime/InternalBridge/ObjectUtilsBridge.cs b/com.unity.ugui/Runtime/InternalBridge/ObjectUtilsBridge.cs deleted file mode 100644 index 50032a8..0000000 --- a/com.unity.ugui/Runtime/InternalBridge/ObjectUtilsBridge.cs +++ /dev/null @@ -1,14 +0,0 @@ -using UnityEngine; -using System.ComponentModel; - -namespace TMPro -{ - public static class ObjectUtilsBridge - { - [EditorBrowsable(EditorBrowsableState.Never)] - public static void MarkDirty(this UnityEngine.Object obj) - { - obj.MarkDirty(); - } - } -} diff --git a/com.unity.ugui/Runtime/InternalBridge/ObjectUtilsBridge.cs.meta b/com.unity.ugui/Runtime/InternalBridge/ObjectUtilsBridge.cs.meta deleted file mode 100644 index 7519af6..0000000 --- a/com.unity.ugui/Runtime/InternalBridge/ObjectUtilsBridge.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 0ce5fe4db14be432cab1bc46704eef4c \ No newline at end of file diff --git a/com.unity.ugui/Runtime/InternalBridge/Unity.InternalAPIEngineBridge.004.asmdef b/com.unity.ugui/Runtime/InternalBridge/Unity.InternalAPIEngineBridge.004.asmdef deleted file mode 100644 index cab9d1b..0000000 --- a/com.unity.ugui/Runtime/InternalBridge/Unity.InternalAPIEngineBridge.004.asmdef +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "Unity.InternalAPIEngineBridge.004" -} diff --git a/com.unity.ugui/Runtime/TMP/.buginfo b/com.unity.ugui/Runtime/TMP/.buginfo index 167481e..f640b3a 100644 --- a/com.unity.ugui/Runtime/TMP/.buginfo +++ b/com.unity.ugui/Runtime/TMP/.buginfo @@ -1 +1 @@ -area: Text (TextMeshPro) \ No newline at end of file +area: Text (TextMeshPro) diff --git a/com.unity.ugui/Runtime/TMP/TMP_InputField.cs b/com.unity.ugui/Runtime/TMP/TMP_InputField.cs index b25f891..ab06a34 100644 --- a/com.unity.ugui/Runtime/TMP/TMP_InputField.cs +++ b/com.unity.ugui/Runtime/TMP/TMP_InputField.cs @@ -1940,9 +1940,9 @@ IEnumerator MouseDragOutsideRect(PointerEventData eventData) if (multiLine) { if (localMousePos.y > rect.yMax) - MoveUp(true, true); + MoveUp(true, false); else if (localMousePos.y < rect.yMin) - MoveDown(true, true); + MoveDown(true, false); } else { @@ -3902,7 +3902,6 @@ private void GenerateCaret(VertexHelper vbo, Vector2 roundingOffset) TMP_FontAsset fontAsset = m_TextComponent.font; float baseScale = (m_TextComponent.fontSize / fontAsset.m_FaceInfo.pointSize * fontAsset.m_FaceInfo.scale); float width = m_CaretWidth * fontAsset.faceInfo.lineHeight * baseScale * 0.05f; - width = Mathf.Max(width, 1.0f); m_CursorVerts[0].position = new Vector3(startPosition.x, bottom, 0.0f); m_CursorVerts[1].position = new Vector3(startPosition.x, top, 0.0f); @@ -4476,6 +4475,12 @@ public virtual void OnSubmit(BaseEventData eventData) m_ShouldActivateNextUpdate = true; SendOnSubmit(); +#if PLATFORM_TVOS + // When a keyboard is open in tvOS, the submit button is used for typing. + // Only actually close the keyboard on tvOS if "Done" was pressed in the soft keyboard. + if (m_SoftKeyboard != null && m_SoftKeyboard.status == TouchScreenKeyboard.Status.Visible) + return; +#endif DeactivateInputField(); eventData?.Use(); } diff --git a/com.unity.ugui/Runtime/TMP/TMP_InputValidator.cs b/com.unity.ugui/Runtime/TMP/TMP_InputValidator.cs index 2fff19f..026766a 100644 --- a/com.unity.ugui/Runtime/TMP/TMP_InputValidator.cs +++ b/com.unity.ugui/Runtime/TMP/TMP_InputValidator.cs @@ -10,13 +10,6 @@ namespace TMPro [System.Serializable] public abstract class TMP_InputValidator : ScriptableObject { - /// - /// Customs text input validation function. - /// - /// The original text - /// The position in the string to add the caharcter - /// The character to add - /// The character added public abstract char Validate(ref string text, ref int pos, char ch); } } diff --git a/com.unity.ugui/Runtime/TMP/TMP_SubMeshUI.cs b/com.unity.ugui/Runtime/TMP/TMP_SubMeshUI.cs index 9aa238b..610d891 100644 --- a/com.unity.ugui/Runtime/TMP/TMP_SubMeshUI.cs +++ b/com.unity.ugui/Runtime/TMP/TMP_SubMeshUI.cs @@ -238,14 +238,19 @@ public static TMP_SubMeshUI AddSubTextObject(TextMeshProUGUI textComponent, Mate subMesh.m_fontAsset = materialReference.fontAsset; subMesh.m_spriteAsset = materialReference.spriteAsset; subMesh.m_isDefaultMaterial = materialReference.isDefaultMaterial; - subMesh.maskable = textComponent.maskable; subMesh.SetSharedMaterial(materialReference.material); + // Only recalculate clipping if it is set to false on the parent text object + if (!textComponent.maskable) + { + subMesh.maskable = false; + subMesh.RecalculateClipping(); + } + return subMesh; } - /// /// /// diff --git a/com.unity.ugui/Runtime/TMP/TMP_Text.cs b/com.unity.ugui/Runtime/TMP/TMP_Text.cs index 2d1be86..26ca675 100644 --- a/com.unity.ugui/Runtime/TMP/TMP_Text.cs +++ b/com.unity.ugui/Runtime/TMP/TMP_Text.cs @@ -663,6 +663,18 @@ public float characterSpacing protected float m_monoSpacing = 0; protected bool m_duoSpace; + /// + /// The horizontal scale of characters. + /// A value of 1.0 is normal. Value greater than 1.0 increases the width. Value less than 1.0 decreases the width. + /// + public float characterHorizontalScale + { + get { return m_characterHorizontalScale; } + set { if (m_characterHorizontalScale == value) return; m_havePropertiesChanged = true; m_characterHorizontalScale = value; SetVerticesDirty(); SetLayoutDirty(); } + } + [SerializeField] + private protected float m_characterHorizontalScale = 1.0f; + /// /// The amount of additional spacing between words. /// @@ -1100,10 +1112,6 @@ public bool isTextObjectScaleStatic { m_IsTextObjectScaleStatic = value; - // UUM-92041. RegisterTextObjectForUpdate is not called until OnEnable. - if (!isActiveAndEnabled) - return; - if (m_IsTextObjectScaleStatic) TMP_UpdateManager.UnRegisterTextObjectForUpdate(this); else @@ -2018,6 +2026,17 @@ protected void ParseInputText() break; } + // Used to output the character sequence for debugging + /*for (int i = 0; i < m_TextProcessingArray.Length; i++) + { + uint c = m_TextProcessingArray[i].unicode; + + if (c == 0) + break; + + Debug.Log("Unicode: " + c.ToString("X8")); + }*/ + SetArraySizes(m_TextProcessingArray); k_ParseTextMarker.End(); @@ -3885,9 +3904,10 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m // Calculate the scale of the font based on selected font size and sampling point size. // baseScale is calculated using the font asset assigned to the text object. - float baseScale = (fontSize / m_fontAsset.faceInfo.pointSize * m_fontAsset.faceInfo.scale * (m_isOrthographic ? 1 : 0.1f)); + float orthographicMultiplier = m_isOrthographic ? 1 : 0.1f; + float baseScale = fontSize / m_fontAsset.faceInfo.pointSize * m_fontAsset.faceInfo.scale * orthographicMultiplier; float currentElementScale = baseScale; - float currentEmScale = fontSize * 0.01f * (m_isOrthographic ? 1 : 0.1f); + float currentEmScale = fontSize * 0.01f * orthographicMultiplier; m_fontScaleMultiplier = 1; m_currentFontSize = fontSize; @@ -3930,6 +3950,10 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m m_IsDrivenLineSpacing = false; m_LastBaseGlyphIndex = int.MinValue; + bool kerning = m_ActiveFontFeatures.Contains(OTL_FeatureTag.kern); + bool markToBase = m_ActiveFontFeatures.Contains(OTL_FeatureTag.mark); + bool markToMark = m_ActiveFontFeatures.Contains(OTL_FeatureTag.mkmk); + float marginWidth = marginSize.x; float marginHeight = marginSize.y; m_marginLeft = 0; @@ -3947,6 +3971,7 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m // Tracking of the highest Ascender m_maxCapHeight = 0; m_maxTextAscender = 0; + float maxTextDescender = 0; m_ElementDescender = 0; float maxVisibleDescender = 0; bool isMaxVisibleDescenderSet = false; @@ -4094,36 +4119,41 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m float baselineOffset = 0; float elementAscentLine = 0; float elementDescentLine = 0; + + FaceInfo fontFace = m_currentFontAsset.faceInfo; + if (m_textElementType == TMP_TextElementType.Sprite) { // If a sprite is used as a fallback then get a reference to it and set the color to white. TMP_SpriteCharacter sprite = (TMP_SpriteCharacter)m_textInfo.characterInfo[m_characterCount].textElement; + if (sprite == null) continue; + m_currentSpriteAsset = sprite.textAsset as TMP_SpriteAsset; m_spriteIndex = (int)sprite.glyphIndex; - if (sprite == null) continue; - // Sprites are assigned in the E000 Private Area + sprite Index if (charCode == 60) - charCode = 57344 + (uint)m_spriteIndex; + charCode = 0xE000 + (uint)m_spriteIndex; - // The sprite scale calculations are based on the font asset assigned to the text object. - if (m_currentSpriteAsset.faceInfo.pointSize > 0) + FaceInfo spriteFace = m_currentSpriteAsset.faceInfo; + + // Use sprite asset's own metrics when available. Otherwise, scale sprite based on current font asset face metrics. + if (spriteFace.pointSize > 0) { - float spriteScale = (m_currentFontSize / m_currentSpriteAsset.faceInfo.pointSize * m_currentSpriteAsset.faceInfo.scale * (m_isOrthographic ? 1 : 0.1f)); + float spriteScale = m_currentFontSize / spriteFace.pointSize * spriteFace.scale * orthographicMultiplier; currentElementScale = sprite.scale * sprite.glyph.scale * spriteScale; - elementAscentLine = m_currentSpriteAsset.faceInfo.ascentLine; + elementAscentLine = spriteFace.ascentLine; //baselineOffset = m_currentSpriteAsset.faceInfo.baseline * m_fontScale * m_fontScaleMultiplier * m_currentSpriteAsset.faceInfo.scale; - elementDescentLine = m_currentSpriteAsset.faceInfo.descentLine; + elementDescentLine = spriteFace.descentLine; } else { - float spriteScale = (m_currentFontSize / m_currentFontAsset.faceInfo.pointSize * m_currentFontAsset.faceInfo.scale * (m_isOrthographic ? 1 : 0.1f)); - currentElementScale = m_currentFontAsset.faceInfo.ascentLine / sprite.glyph.metrics.height * sprite.scale * sprite.glyph.scale * spriteScale; - float scaleDelta = spriteScale / currentElementScale; - elementAscentLine = m_currentFontAsset.faceInfo.ascentLine * scaleDelta; + float spriteScale = m_currentFontSize / fontFace.pointSize * fontFace.scale * orthographicMultiplier; + currentElementScale = fontFace.ascentLine / sprite.glyph.metrics.height * sprite.scale * sprite.glyph.scale * spriteScale; + float scaleDelta = currentElementScale != 0 ? spriteScale / currentElementScale : 0; + elementAscentLine = fontFace.ascentLine * scaleDelta; //baselineOffset = m_currentFontAsset.faceInfo.baseline * m_fontScale * m_fontScaleMultiplier * m_currentFontAsset.faceInfo.scale; - elementDescentLine = m_currentFontAsset.faceInfo.descentLine * scaleDelta; + elementDescentLine = fontFace.descentLine * scaleDelta; } m_cached_TextElement = sprite; @@ -4142,9 +4172,9 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m float adjustedScale; if (isInjectedCharacter && m_TextProcessingArray[i].unicode == 0x0A && m_characterCount != m_firstCharacterOfLine) - adjustedScale = m_textInfo.characterInfo[m_characterCount - 1].pointSize * smallCapsMultiplier / m_currentFontAsset.m_FaceInfo.pointSize * m_currentFontAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f); + adjustedScale = m_textInfo.characterInfo[m_characterCount - 1].pointSize * smallCapsMultiplier / fontFace.pointSize * fontFace.scale * orthographicMultiplier; else - adjustedScale = m_currentFontSize * smallCapsMultiplier / m_currentFontAsset.m_FaceInfo.pointSize * m_currentFontAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f); + adjustedScale = m_currentFontSize * smallCapsMultiplier / fontFace.pointSize * fontFace.scale * orthographicMultiplier; // Special handling for injected Ellipsis if (isInjectedCharacter && charCode == 0x2026) @@ -4154,11 +4184,11 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m } else { - elementAscentLine = m_currentFontAsset.m_FaceInfo.ascentLine; - elementDescentLine = m_currentFontAsset.m_FaceInfo.descentLine; + elementAscentLine = fontFace.ascentLine; + elementDescentLine = fontFace.descentLine; } - currentElementScale = adjustedScale * m_fontScaleMultiplier * m_cached_TextElement.scale; + currentElementScale = adjustedScale * m_fontScaleMultiplier * m_cached_TextElement.scale * m_cached_TextElement.m_Glyph.scale; m_internalCharacterInfo[m_characterCount].elementType = TMP_TextElementType.Character; } @@ -4188,8 +4218,7 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m #region Handle Kerning GlyphValueRecord glyphAdjustments = new GlyphValueRecord(); float characterSpacingAdjustment = m_characterSpacing; - // Make sure the current character and the next are Characters (not Sprite). - if (m_enableKerning && m_textElementType == TMP_TextElementType.Character) + if (kerning && m_textElementType == TMP_TextElementType.Character) { GlyphPairAdjustmentRecord adjustmentPair; uint baseGlyphIndex = m_cached_TextElement.m_GlyphIndex; @@ -4225,7 +4254,7 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m // Handle Diacritical Marks #region Handle Diacritical Marks - bool isBaseGlyph = TMP_TextParsingUtilities.IsBaseGlyph((uint)charCode); + bool isBaseGlyph = TMP_TextParsingUtilities.IsBaseGlyph(charCode); if (isBaseGlyph) m_LastBaseGlyphIndex = m_characterCount; @@ -4233,7 +4262,7 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m if (m_characterCount > 0 && !isBaseGlyph) { // Check for potential Mark-to-Base lookup if previous glyph was a base glyph - if (m_LastBaseGlyphIndex != int.MinValue && m_LastBaseGlyphIndex == m_characterCount - 1) + if (markToBase && m_LastBaseGlyphIndex != int.MinValue && m_LastBaseGlyphIndex == m_characterCount - 1) { Glyph baseGlyph = m_textInfo.characterInfo[m_LastBaseGlyphIndex].textElement.glyph; uint baseGlyphIndex = baseGlyph.index; @@ -4256,31 +4285,34 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m bool wasLookupApplied = false; // Check for any potential Mark-to-Mark lookups - for (int characterLookupIndex = m_characterCount - 1; characterLookupIndex >= 0 && characterLookupIndex != m_LastBaseGlyphIndex; characterLookupIndex--) + if (markToMark) { - // Handle any potential Mark-to-Mark lookup - Glyph baseMarkGlyph = m_textInfo.characterInfo[characterLookupIndex].textElement.glyph; - uint baseGlyphIndex = baseMarkGlyph.index; - uint combiningMarkGlyphIndex = m_cached_TextElement.glyphIndex; - uint key = combiningMarkGlyphIndex << 16 | baseGlyphIndex; - - if (m_currentFontAsset.fontFeatureTable.m_MarkToMarkAdjustmentRecordLookup.TryGetValue(key, out MarkToMarkAdjustmentRecord glyphAdjustmentRecord)) + for (int characterLookupIndex = m_characterCount - 1; characterLookupIndex >= 0 && characterLookupIndex != m_LastBaseGlyphIndex; characterLookupIndex--) { - float baseMarkOrigin = (m_textInfo.characterInfo[characterLookupIndex].origin - m_xAdvance) / currentElementScale; - float currentBaseline = baselineOffset - m_lineOffset + m_baselineOffset; - float baseMarkBaseline = (m_internalCharacterInfo[characterLookupIndex].baseLine - currentBaseline) / currentElementScale; + // Handle any potential Mark-to-Mark lookup + Glyph baseMarkGlyph = m_textInfo.characterInfo[characterLookupIndex].textElement.glyph; + uint baseGlyphIndex = baseMarkGlyph.index; + uint combiningMarkGlyphIndex = m_cached_TextElement.glyphIndex; + uint key = combiningMarkGlyphIndex << 16 | baseGlyphIndex; - glyphAdjustments.xPlacement = baseMarkOrigin + glyphAdjustmentRecord.baseMarkGlyphAnchorPoint.xCoordinate - glyphAdjustmentRecord.combiningMarkPositionAdjustment.xPositionAdjustment; - glyphAdjustments.yPlacement = baseMarkBaseline + glyphAdjustmentRecord.baseMarkGlyphAnchorPoint.yCoordinate - glyphAdjustmentRecord.combiningMarkPositionAdjustment.yPositionAdjustment; + if (m_currentFontAsset.fontFeatureTable.m_MarkToMarkAdjustmentRecordLookup.TryGetValue(key, out MarkToMarkAdjustmentRecord glyphAdjustmentRecord)) + { + float baseMarkOrigin = (m_textInfo.characterInfo[characterLookupIndex].origin - m_xAdvance) / currentElementScale; + float currentBaseline = baselineOffset - m_lineOffset + m_baselineOffset; + float baseMarkBaseline = (m_internalCharacterInfo[characterLookupIndex].baseLine - currentBaseline) / currentElementScale; - characterSpacingAdjustment = 0; - wasLookupApplied = true; - break; + glyphAdjustments.xPlacement = baseMarkOrigin + glyphAdjustmentRecord.baseMarkGlyphAnchorPoint.xCoordinate - glyphAdjustmentRecord.combiningMarkPositionAdjustment.xPositionAdjustment; + glyphAdjustments.yPlacement = baseMarkBaseline + glyphAdjustmentRecord.baseMarkGlyphAnchorPoint.yCoordinate - glyphAdjustmentRecord.combiningMarkPositionAdjustment.yPositionAdjustment; + + characterSpacingAdjustment = 0; + wasLookupApplied = true; + break; + } } } // If no Mark-to-Mark lookups were applied, check for potential Mark-to-Base lookup. - if (m_LastBaseGlyphIndex != int.MinValue && !wasLookupApplied) + if (markToBase && m_LastBaseGlyphIndex != int.MinValue && !wasLookupApplied) { // Handle lookup for Mark-to-Base Glyph baseGlyph = m_textInfo.characterInfo[m_LastBaseGlyphIndex].textElement.glyph; @@ -4324,7 +4356,7 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m float monoAdvance = 0; if (m_monoSpacing != 0) { - monoAdvance = (m_monoSpacing / 2 - (m_cached_TextElement.glyph.metrics.width / 2 + m_cached_TextElement.glyph.metrics.horizontalBearingX) * currentElementScale) * (1 - m_charWidthAdjDelta); + monoAdvance = (m_monoSpacing / 2 - (m_cached_TextElement.glyph.metrics.width / 2 + m_cached_TextElement.glyph.metrics.horizontalBearingX) * currentElementScale) * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; m_xAdvance += monoAdvance; } #endregion @@ -4368,7 +4400,7 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m m_maxLineAscender = Mathf.Max(adjustedAscender, m_maxLineAscender); m_maxLineDescender = Mathf.Min(adjustedDescender, m_maxLineDescender); - } + } // Element Ascender and Descender in object space if (isFirstCharacterOfLine || isWhiteSpace == false) @@ -4398,6 +4430,9 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m } } + // Max text Ascender and Descender + maxTextDescender = Mathf.Min(maxTextDescender, m_ElementDescender); + // Page ascender if (m_lineOffset == 0) { @@ -4425,7 +4460,7 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m widthOfTextArea = m_width != -1 ? Mathf.Min(marginWidth + 0.0001f - m_marginLeft - m_marginRight, m_width) : marginWidth + 0.0001f - m_marginLeft - m_marginRight; // Calculate the line breaking width of the text. - textWidth = Mathf.Abs(m_xAdvance) + currentGlyphMetrics.horizontalAdvance * (1 - m_charWidthAdjDelta) * (charCode == 0xAD ? currentElementUnmodifiedScale : currentElementScale); + textWidth = Mathf.Abs(m_xAdvance) + currentGlyphMetrics.horizontalAdvance * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale * (charCode == 0xAD ? currentElementUnmodifiedScale : currentElementScale); int testedCharacterCount = m_characterCount; @@ -4505,7 +4540,6 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m float baselineAdjustmentDelta = m_maxLineAscender - m_startOfLineAscender; if (m_lineOffset > 0 && Math.Abs(baselineAdjustmentDelta) > 0.01f && m_IsDrivenLineSpacing == false && !m_isNewPage) { - //AdjustLineOffset(m_firstCharacterOfLine, m_characterCount, baselineAdjustmentDelta); m_ElementDescender -= baselineAdjustmentDelta; m_lineOffset += baselineAdjustmentDelta; } @@ -4559,7 +4593,7 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m // Compute Preferred Width & Height m_RenderedWidth = Mathf.Max(m_RenderedWidth, textWidth + m_marginLeft + m_marginRight); - m_RenderedHeight = Mathf.Max(m_RenderedHeight, m_maxTextAscender - m_ElementDescender); + m_RenderedHeight = Mathf.Max(m_RenderedHeight, m_maxTextAscender - maxTextDescender); } #endregion Handle Visible Characters @@ -4570,9 +4604,9 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m if (m_lineOffset > 0 && !TMP_Math.Approximately(m_maxLineAscender, m_startOfLineAscender) && m_IsDrivenLineSpacing == false && !m_isNewPage) { float offsetDelta = m_maxLineAscender - m_startOfLineAscender; - //AdjustLineOffset(m_firstCharacterOfLine, m_characterCount, offsetDelta); m_ElementDescender -= offsetDelta; m_lineOffset += offsetDelta; + m_RenderedHeight += offsetDelta; m_startOfLineAscender += offsetDelta; internalWordWrapState.lineOffset = m_lineOffset; @@ -4591,14 +4625,14 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m } else if (m_monoSpacing != 0) { - m_xAdvance += (m_monoSpacing - monoAdvance + ((m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment) * currentEmScale) + m_cSpacing) * (1 - m_charWidthAdjDelta); + m_xAdvance += (m_monoSpacing - monoAdvance + ((m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment) * currentEmScale) + m_cSpacing) * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; if (isWhiteSpace || charCode == 0x200B) m_xAdvance += m_wordSpacing * currentEmScale; } else { - m_xAdvance += ((currentGlyphMetrics.horizontalAdvance * m_FXScale.x + glyphAdjustments.xAdvance) * currentElementScale + (m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment + boldSpacingAdjustment) * currentEmScale + m_cSpacing) * (1 - m_charWidthAdjDelta); + m_xAdvance += ((currentGlyphMetrics.horizontalAdvance * m_FXScale.x + glyphAdjustments.xAdvance) * currentElementScale + (m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment + boldSpacingAdjustment) * currentEmScale + m_cSpacing) * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; if (isWhiteSpace || charCode == 0x200B) m_xAdvance += m_wordSpacing * currentEmScale; @@ -4645,6 +4679,7 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m m_lineNumber += 1; m_firstCharacterOfLine = m_characterCount + 1; + isFirstWordOfLine = true; float ascender = m_internalCharacterInfo[m_characterCount].adjustedAscender; @@ -4684,6 +4719,7 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m { bool shouldSaveHardLineBreak = false; bool shouldSaveSoftLineBreak = false; + uint nextChar = m_characterCount + 1 < totalCharacterCount ? m_textInfo.characterInfo[m_characterCount + 1].character : 0u; if ((isWhiteSpace || charCode == 0x200B || charCode == 0x2D || charCode == 0xAD) && (!m_isNonBreakingSpace || ignoreNonBreakingSpace) && charCode != 0xA0 && charCode != 0x2007 && charCode != 0x2011 && charCode != 0x202F && charCode != 0x2060) { @@ -4701,7 +4737,7 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m else if (m_isNonBreakingSpace == false && (TMP_TextParsingUtilities.IsHangul(charCode) && TMP_Settings.useModernHangulLineBreakingRules == false || TMP_TextParsingUtilities.IsCJK(charCode))) { bool isCurrentLeadingCharacter = TMP_Settings.linebreakingRules.leadingCharacters.Contains(charCode); - bool isNextFollowingCharacter = m_characterCount < totalCharacterCount - 1 && TMP_Settings.linebreakingRules.followingCharacters.Contains(m_internalCharacterInfo[m_characterCount + 1].character); + bool isNextFollowingCharacter = m_characterCount < totalCharacterCount - 1 && TMP_Settings.linebreakingRules.followingCharacters.Contains(nextChar); if (isCurrentLeadingCharacter == false) { @@ -4732,14 +4768,10 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m } } } - // Special handling for Latin characters followed by a CJK character. - else if (!m_isNonBreakingSpace && (m_characterCount + 1) < totalCharacterCount && TMP_TextParsingUtilities.IsCJK(m_textInfo.characterInfo[m_characterCount + 1].character)) + // Handling for Latin characters followed by a CJK character that is not a following character. + else if (m_isNonBreakingSpace == false && TMP_TextParsingUtilities.IsCJK(nextChar) && !TMP_Settings.linebreakingRules.followingCharacters.Contains(nextChar)) { - uint nextChar = m_textInfo.characterInfo[m_characterCount + 1].character; - bool prevIsLeading = TMP_Settings.linebreakingRules.leadingCharacters.Contains(charCode); - bool nextIsFollowing = TMP_Settings.linebreakingRules.followingCharacters.Contains(nextChar); - if (!prevIsLeading && !nextIsFollowing) - shouldSaveHardLineBreak = true; + shouldSaveHardLineBreak = true; } else if (isFirstWordOfLine) { @@ -4999,7 +5031,7 @@ internal void InsertNewLine(int i, float baseScale, float currentElementScale, f m_textInfo.lineInfo[m_lineNumber].width = width; float glyphAdjustment = m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].adjustedHorizontalAdvance; - float maxAdvanceOffset = (glyphAdjustment * currentElementScale + (m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment + boldSpacingAdjustment) * currentEmScale + m_cSpacing) * (1 - m_charWidthAdjDelta); + float maxAdvanceOffset = (glyphAdjustment * currentElementScale + (m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment + boldSpacingAdjustment) * currentEmScale + m_cSpacing) * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; float adjustedHorizontalAdvance = m_textInfo.lineInfo[m_lineNumber].maxAdvance = m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].xAdvance + (m_isRightToLeft ? maxAdvanceOffset : - maxAdvanceOffset); m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].xAdvance = adjustedHorizontalAdvance; @@ -7146,7 +7178,6 @@ internal bool ValidateHtmlTag(TextProcessingElement[] chars, int startIndex, out case MarkupTag.SUBSCRIPT: m_fontScaleMultiplier *= m_currentFontAsset.faceInfo.subscriptSize > 0 ? m_currentFontAsset.faceInfo.subscriptSize : 1; m_baselineOffsetStack.Push(m_baselineOffset); - m_materialReferenceStack.Push(m_materialReferences[m_currentMaterialIndex]); fontScale = (m_currentFontSize / m_currentFontAsset.faceInfo.pointSize * m_currentFontAsset.faceInfo.scale * (m_isOrthographic ? 1 : 0.1f)); m_baselineOffset += m_currentFontAsset.faceInfo.subscriptOffset * fontScale * m_fontScaleMultiplier; @@ -7156,11 +7187,10 @@ internal bool ValidateHtmlTag(TextProcessingElement[] chars, int startIndex, out case MarkupTag.SLASH_SUBSCRIPT: if ((m_FontStyleInternal & FontStyles.Subscript) == FontStyles.Subscript) { - var previousFontAsset = m_materialReferenceStack.Pop().fontAsset; if (m_fontScaleMultiplier < 1) { m_baselineOffset = m_baselineOffsetStack.Pop(); - m_fontScaleMultiplier /= previousFontAsset.faceInfo.subscriptSize > 0 ? previousFontAsset.faceInfo.subscriptSize : 1; + m_fontScaleMultiplier /= m_currentFontAsset.faceInfo.subscriptSize > 0 ? m_currentFontAsset.faceInfo.subscriptSize : 1; } if (m_fontStyleStack.Remove(FontStyles.Subscript) == 0) @@ -7170,7 +7200,6 @@ internal bool ValidateHtmlTag(TextProcessingElement[] chars, int startIndex, out case MarkupTag.SUPERSCRIPT: m_fontScaleMultiplier *= m_currentFontAsset.faceInfo.superscriptSize > 0 ? m_currentFontAsset.faceInfo.superscriptSize : 1; m_baselineOffsetStack.Push(m_baselineOffset); - m_materialReferenceStack.Push(m_materialReferences[m_currentMaterialIndex]); fontScale = (m_currentFontSize / m_currentFontAsset.faceInfo.pointSize * m_currentFontAsset.faceInfo.scale * (m_isOrthographic ? 1 : 0.1f)); m_baselineOffset += m_currentFontAsset.faceInfo.superscriptOffset * fontScale * m_fontScaleMultiplier; @@ -7180,11 +7209,10 @@ internal bool ValidateHtmlTag(TextProcessingElement[] chars, int startIndex, out case MarkupTag.SLASH_SUPERSCRIPT: if ((m_FontStyleInternal & FontStyles.Superscript) == FontStyles.Superscript) { - var previousFontAsset = m_materialReferenceStack.Pop().fontAsset; if (m_fontScaleMultiplier < 1) { m_baselineOffset = m_baselineOffsetStack.Pop(); - m_fontScaleMultiplier /= previousFontAsset.faceInfo.superscriptSize > 0 ? previousFontAsset.faceInfo.superscriptSize : 1; + m_fontScaleMultiplier /= m_currentFontAsset.faceInfo.superscriptSize > 0 ? m_currentFontAsset.faceInfo.superscriptSize : 1; } if (m_fontStyleStack.Remove(FontStyles.Superscript) == 0) diff --git a/com.unity.ugui/Runtime/TMP/TMP_TextParsingUtilities.cs b/com.unity.ugui/Runtime/TMP/TMP_TextParsingUtilities.cs index 3ff3754..3bc5ad4 100644 --- a/com.unity.ugui/Runtime/TMP/TMP_TextParsingUtilities.cs +++ b/com.unity.ugui/Runtime/TMP/TMP_TextParsingUtilities.cs @@ -168,6 +168,15 @@ internal static bool IsBaseGlyph(uint c) ); } + + internal static bool IsIgnorableForLigature(uint cp) + { + // ZWJ + VS1..VS16 + (optional) Variation Selectors Supplement + return cp == 0x200D // ZWJ + || (cp >= 0xFE00 && cp <= 0xFE0F) // VS1..VS16 + || (cp >= 0xE0100 && cp <= 0xE01EF); // VS17..VS256 (supplement) + } + /// /// HashSet that contains all known Emojis as per Unicode Emoji 16.0 https://unicode.org/Public/draft/UCD/ucd/emoji/emoji-data.txt /// This HashSet is generated by using the following project https://drive.google.com/file/d/1iWEKEPvqkNoH_iPq3fhPZ5CTCyJETqMd and copy / pasting the resulting string. @@ -266,32 +275,59 @@ internal static bool IsHangul(uint c) internal static bool IsCJK(uint c) { - return c >= 0x3100 && c <= 0x312F || /* Bopomofo */ - c >= 0x31A0 && c <= 0x31BF || /* Bopomofo Extended */ - c >= 0x4E00 && c <= 0x9FFF || /* CJK Unified Ideographs (Han) */ - c >= 0x3400 && c <= 0x4DBF || /* CJK Extension A */ - c >= 0x20000 && c <= 0x2A6DF || /* CJK Extension B */ - c >= 0x2A700 && c <= 0x2B73F || /* CJK Extension C */ - c >= 0x2B740 && c <= 0x2B81F || /* CJK Extension D */ - c >= 0x2B820 && c <= 0x2CEAF || /* CJK Extension E */ - c >= 0x2CEB0 && c <= 0x2EBE0 || /* CJK Extension F */ - c >= 0x30000 && c <= 0x3134A || /* CJK Extension G */ - c >= 0xF900 && c <= 0xFAFF || /* CJK Compatibility Ideographs */ - c >= 0x2F800 && c <= 0x2FA1F || /* CJK Compatibility Ideographs Supplement */ - c >= 0x2F00 && c <= 0x2FDF || /* CJK Radicals / Kangxi Radicals */ - c >= 0x2E80 && c <= 0x2EFF || /* CJK Radicals Supplement */ - c >= 0x31C0 && c <= 0x31EF || /* CJK Strokes */ - c >= 0x2FF0 && c <= 0x2FFF || /* Ideographic Description Characters */ - c >= 0x3040 && c <= 0x309F || /* Hiragana */ - c >= 0x1B100 && c <= 0x1B12F || /* Kana Extended-A */ - c >= 0x1AFF0 && c <= 0x1AFFF || /* Kana Extended-B */ - c >= 0x1B000 && c <= 0x1B0FF || /* Kana Supplement */ - c >= 0x1B130 && c <= 0x1B16F || /* Small Kana Extension */ - c >= 0x3190 && c <= 0x319F || /* Kanbun */ - c >= 0x30A0 && c <= 0x30FF || /* Katakana */ - c >= 0x31F0 && c <= 0x31FF || /* Katakana Phonetic Extensions */ - c >= 0xFF65 && c <= 0xFF9F; /* Halfwidth Katakana */ + // Han Ideographs and Extensions (Unified CJK) + if ((c >= 0x4E00 && c <= 0x9FFF) || // CJK Unified Ideographs + (c >= 0x3400 && c <= 0x4DBF) || // CJK Extension A + (c >= 0x20000 && c <= 0x2A6DF) || // CJK Extension B + (c >= 0x2A700 && c <= 0x2B73F) || // CJK Extension C + (c >= 0x2B740 && c <= 0x2B81F) || // CJK Extension D + (c >= 0x2B820 && c <= 0x2CEAF) || // CJK Extension E + (c >= 0x2CEB0 && c <= 0x2EBE0) || // CJK Extension F + (c >= 0x30000 && c <= 0x3134A) || // CJK Extension G + (c >= 0x31350 && c <= 0x323AF)) // CJK Extension H + return true; + + // CJK Compatibility Ideographs + if ((c >= 0xF900 && c <= 0xFAFF) || // Compatibility Ideographs + (c >= 0x2F800 && c <= 0x2FA1F)) // Compatibility Ideographs Supplement + return true; + + // Japanese + if ((c >= 0x3040 && c <= 0x309F) || // Hiragana + (c >= 0x30A0 && c <= 0x30FF) || // Katakana + (c >= 0x31F0 && c <= 0x31FF) || // Katakana Phonetic Extensions + (c >= 0x1B000 && c <= 0x1B0FF) || // Kana Supplement + (c >= 0x1B100 && c <= 0x1B12F) || // Kana Extended-A + (c >= 0x1AFF0 && c <= 0x1AFFF) || // Kana Extended-B + (c >= 0x1B130 && c <= 0x1B16F)) // Small Kana Extension + return true; + + // Chinese phonetics (Zhuyin/Bopomofo) + if ((c >= 0x3100 && c <= 0x312F) || // Bopomofo + (c >= 0x31A0 && c <= 0x31BF)) // Bopomofo Extended + return true; + + // Radicals, Description Characters, Strokes + if ((c >= 0x2E80 && c <= 0x2EFF) || // CJK Radicals Supplement + (c >= 0x2F00 && c <= 0x2FDF) || // Kangxi Radicals + (c >= 0x2FF0 && c <= 0x2FFF) || // Ideographic Description Characters + (c >= 0x31C0 && c <= 0x31EF)) // CJK Strokes + return true; + + // Punctuation and Symbols + if ((c >= 0x3000 && c <= 0x303F) || // CJK Symbols and Punctuation + (c >= 0x3190 && c <= 0x319F) || // Kanbun + (c >= 0x16FE0 && c <= 0x16FFF)) // Ideographic Symbols and Punctuation + return true; + + // Compatibility Forms and Halfwidth / Fullwidth + if ((c >= 0xFE10 && c <= 0xFE1F) || // Vertical Forms + (c >= 0xFE30 && c <= 0xFE4F) || // CJK Compatibility Forms + (c >= 0xFE50 && c <= 0xFE6F) || // Small Form Variants + (c >= 0xFF65 && c <= 0xFF9F)) // Halfwidth Katakana (JP) + return true; + + return false; } - } } diff --git a/com.unity.ugui/Runtime/TMP/TMP_TextUtilities.cs b/com.unity.ugui/Runtime/TMP/TMP_TextUtilities.cs index ed68801..6a1b701 100644 --- a/com.unity.ugui/Runtime/TMP/TMP_TextUtilities.cs +++ b/com.unity.ugui/Runtime/TMP/TMP_TextUtilities.cs @@ -321,7 +321,6 @@ public static bool IsIntersectingRectTransform(RectTransform rectTransform, Vect /// /// Function returning the index of the character at the given position (if any). - /// Returns @@-1@@ if no character is found at the specified position. /// /// A reference to the TextMeshPro component. /// Position to check for intersection. diff --git a/com.unity.ugui/Runtime/TMP/TextMeshPro.cs b/com.unity.ugui/Runtime/TMP/TextMeshPro.cs index dec8b7e..f575337 100644 --- a/com.unity.ugui/Runtime/TMP/TextMeshPro.cs +++ b/com.unity.ugui/Runtime/TMP/TextMeshPro.cs @@ -225,8 +225,6 @@ public override void SetVerticesDirty() return; TMP_UpdateManager.RegisterTextElementForGraphicRebuild(this); - - ObjectUtilsBridge.MarkDirty(this); } @@ -479,9 +477,6 @@ public override void UpdateVertexData() } } - /// - /// Loads either the default font or a newly assigned font asset, and assigns the appropriate material to the renderer. - /// public void UpdateFontAsset() { LoadFontAsset(); @@ -1431,7 +1426,6 @@ void SetPerspectiveCorrection() m_sharedMaterial.SetFloat(ShaderUtilities.ID_PerspectiveFilter, 0.875f); } - Dictionary materialIndexPairs = new Dictionary(); // This function parses through the Char[] to determine how many characters will be visible. It then makes sure the arrays are large enough for all those characters. internal override int SetArraySizes(TextProcessingElement[] textProcessingArray) @@ -1453,6 +1447,7 @@ internal override int SetArraySizes(TextProcessingElement[] textProcessingArray) m_currentFontAsset = m_fontAsset; m_currentMaterial = m_sharedMaterial; m_currentMaterialIndex = 0; + materialIndexPairs.Clear(); m_materialReferenceStack.SetDefault(new MaterialReference(m_currentMaterialIndex, m_currentFontAsset, null, m_currentMaterial, m_padding)); @@ -1741,46 +1736,62 @@ internal override int SetArraySizes(TextProcessingElement[] textProcessingArray) { LigatureSubstitutionRecord record = records[j]; + uint[] componentGlyphIDs = record.componentGlyphIDs; int componentCount = record.componentGlyphIDs.Length; uint ligatureGlyphID = record.ligatureGlyphID; - // - for (int k = 1; k < componentCount; k++) + int idx = i + 1; // current input index + int componentIndex = 1; // next component index we still need to match in record.componentGlyphIDs + + // Try to match remaining components, skipping ignorable code points when they aren't the expected component. + while (ligatureGlyphID != 0 && componentIndex < componentCount) { - uint componentUnicode = (uint)textProcessingArray[i + k].unicode; + if (idx >= textProcessingArray.Length) { ligatureGlyphID = 0; break; } - // Special Handling for Zero Width Joiner (ZWJ) - //if (componentUnicode == 0x200D) - // continue; + uint codepoint = textProcessingArray[idx].unicode; + uint glyphIndex = m_currentFontAsset.GetGlyphIndex(codepoint); + uint expect = componentGlyphIDs[componentIndex]; - uint glyphIndex = m_currentFontAsset.GetGlyphIndex(componentUnicode); + // If codepoint is ignorable for ligature matching *and* it is not the expected component, skip it. + if (glyphIndex != expect && TMP_TextParsingUtilities.IsIgnorableForLigature(codepoint)) + { + idx++; + continue; + } - if (glyphIndex == record.componentGlyphIDs[k]) + // Otherwise, we require an exact glyph match for this component. + if (glyphIndex == expect) + { + componentIndex++; + idx++; continue; + } + // Mismatch. ligatureGlyphID = 0; break; } - if (ligatureGlyphID != 0) + // If we matched all components, perform the substitution. + if (ligatureGlyphID != 0 && componentIndex == componentCount) { + // Check for potential trailing variant selector that should also be consumed in the sequence. + if (idx < textProcessingArray.Length && TMP_TextParsingUtilities.IsIgnorableForLigature(textProcessingArray[idx].unicode)) + idx++; + + int spanLen = idx - i; if (m_currentFontAsset.TryAddGlyphInternal(ligatureGlyphID, out Glyph glyph)) { m_textInfo.characterInfo[m_totalCharacterCount].alternativeGlyph = glyph; + textProcessingArray[i].length = spanLen; - // Update text processing array - for (int c = 0; c < componentCount; c++) + // Collapse the entire consumed span into the ligature. + for (int c = 1; c < spanLen; c++) { - if (c == 0) - { - textProcessingArray[i + c].length = componentCount; - continue; - } - - textProcessingArray[i + c].unicode = 0x1A; + textProcessingArray[i + c].unicode = 0x1A; // mark as consumed } - i += componentCount - 1; + i += spanLen - 1; // advance past the cluster break; } } @@ -1831,9 +1842,11 @@ internal override int SetArraySizes(TextProcessingElement[] textProcessingArray) } // Handle Multi Atlas Texture support - if (character != null && character.glyph.atlasIndex > 0) + int atlasIndex = m_textInfo.characterInfo[m_totalCharacterCount].alternativeGlyph != null ? m_textInfo.characterInfo[m_totalCharacterCount].alternativeGlyph.atlasIndex : character == null ? 0 : character.glyph.atlasIndex; + + if (character != null && atlasIndex > 0) { - m_currentMaterial = TMP_MaterialManager.GetFallbackMaterial(m_currentFontAsset, m_currentMaterial, character.glyph.atlasIndex); + m_currentMaterial = TMP_MaterialManager.GetFallbackMaterial(m_currentFontAsset, m_currentMaterial, atlasIndex); m_currentMaterialIndex = MaterialReference.AddMaterialReference(m_currentMaterial, m_currentFontAsset, ref m_materialReferences, m_materialReferenceIndexLookup); @@ -2133,7 +2146,7 @@ void OnPreRenderObject() m_maxFontSize = m_fontSizeMax; m_minFontSize = m_fontSizeMin; m_lineSpacingDelta = 0; - m_charWidthAdjDelta = 0; + m_charWidthAdjDelta = 0.0f; m_isTextTruncated = false; @@ -2211,9 +2224,10 @@ protected virtual void GenerateTextMesh() // Calculate the scale of the font based on selected font size and sampling point size. // baseScale is calculated using the font asset assigned to the text object. - float baseScale = (m_fontSize / m_fontAsset.m_FaceInfo.pointSize * m_fontAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f)); + float orthographicMultiplier = m_isOrthographic ? 1 : 0.1f; + float baseScale = m_fontSize / m_fontAsset.m_FaceInfo.pointSize * m_fontAsset.m_FaceInfo.scale * orthographicMultiplier; float currentElementScale = baseScale; - float currentEmScale = m_fontSize * 0.01f * (m_isOrthographic ? 1 : 0.1f); + float currentEmScale = m_fontSize * 0.01f * orthographicMultiplier; m_fontScaleMultiplier = 1; m_currentFontSize = m_fontSize; @@ -2509,44 +2523,48 @@ protected virtual void GenerateTextMesh() float baselineOffset = 0; float elementAscentLine = 0; float elementDescentLine = 0; + + FaceInfo fontFace = m_currentFontAsset.m_FaceInfo; + if (m_textElementType == TMP_TextElementType.Sprite) { // If a sprite is used as a fallback then get a reference to it and set the color to white. TMP_SpriteCharacter sprite = (TMP_SpriteCharacter)textInfo.characterInfo[m_characterCount].textElement; - m_currentSpriteAsset = sprite.textAsset as TMP_SpriteAsset; - m_spriteIndex = (int)sprite.glyphIndex; - if (sprite == null) { k_CharacterLookupMarker.End(); continue; } + m_currentSpriteAsset = sprite.textAsset as TMP_SpriteAsset; + m_spriteIndex = (int)sprite.glyphIndex; + // Sprites are assigned in the E000 Private Area + sprite Index if (charCode == '<') - charCode = 57344 + (uint)m_spriteIndex; + charCode = 0xE000 + (uint)m_spriteIndex; else m_spriteColor = s_colorWhite; - float fontScale = (m_currentFontSize / m_currentFontAsset.faceInfo.pointSize * m_currentFontAsset.faceInfo.scale * (m_isOrthographic ? 1 : 0.1f)); + float fontScale = m_currentFontSize / fontFace.pointSize * fontFace.scale * orthographicMultiplier; + FaceInfo spriteFace = m_currentSpriteAsset.m_FaceInfo; - // The sprite scale calculations are based on the font asset assigned to the text object. - if (m_currentSpriteAsset.m_FaceInfo.pointSize > 0) + // Use sprite asset's own metrics when available. Otherwise, scale sprite based on current font asset face metrics. + if (spriteFace.pointSize > 0) { - float spriteScale = m_currentFontSize / m_currentSpriteAsset.m_FaceInfo.pointSize * m_currentSpriteAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f); + float spriteScale = m_currentFontSize / spriteFace.pointSize * spriteFace.scale * orthographicMultiplier; currentElementScale = sprite.m_Scale * sprite.m_Glyph.scale * spriteScale; - elementAscentLine = m_currentSpriteAsset.m_FaceInfo.ascentLine; - baselineOffset = m_currentSpriteAsset.m_FaceInfo.baseline * fontScale * m_fontScaleMultiplier * m_currentSpriteAsset.m_FaceInfo.scale; - elementDescentLine = m_currentSpriteAsset.m_FaceInfo.descentLine; + elementAscentLine = spriteFace.ascentLine; + baselineOffset = spriteFace.baseline * fontScale * m_fontScaleMultiplier * spriteFace.scale; + elementDescentLine = spriteFace.descentLine; } else { - float spriteScale = m_currentFontSize / m_currentFontAsset.m_FaceInfo.pointSize * m_currentFontAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f); - currentElementScale = m_currentFontAsset.m_FaceInfo.ascentLine / sprite.m_Glyph.metrics.height * sprite.m_Scale * sprite.m_Glyph.scale * spriteScale; - float scaleDelta = spriteScale / currentElementScale; - elementAscentLine = m_currentFontAsset.m_FaceInfo.ascentLine * scaleDelta; - baselineOffset = m_currentFontAsset.m_FaceInfo.baseline * fontScale * m_fontScaleMultiplier * m_currentFontAsset.m_FaceInfo.scale; - elementDescentLine = m_currentFontAsset.m_FaceInfo.descentLine * scaleDelta; + float spriteScale = m_currentFontSize / fontFace.pointSize * fontFace.scale * orthographicMultiplier; + currentElementScale = fontFace.ascentLine / sprite.m_Glyph.metrics.height * sprite.m_Scale * sprite.m_Glyph.scale * spriteScale; + float scaleDelta = currentElementScale != 0 ? spriteScale / currentElementScale : 0; + elementAscentLine = fontFace.ascentLine * scaleDelta; + baselineOffset = fontFace.baseline * fontScale * m_fontScaleMultiplier * fontFace.scale; + elementDescentLine = fontFace.descentLine * scaleDelta; } m_cached_TextElement = sprite; @@ -2576,9 +2594,9 @@ protected virtual void GenerateTextMesh() // Special handling if replaced character was a line feed where in this case we have to use the scale of the previous character. float adjustedScale; if (isInjectedCharacter && m_TextProcessingArray[i].unicode == 0x0A && m_characterCount != m_firstCharacterOfLine) - adjustedScale = m_textInfo.characterInfo[m_characterCount - 1].pointSize * smallCapsMultiplier / m_currentFontAsset.m_FaceInfo.pointSize * m_currentFontAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f); + adjustedScale = m_textInfo.characterInfo[m_characterCount - 1].pointSize * smallCapsMultiplier / fontFace.pointSize * fontFace.scale * orthographicMultiplier; else - adjustedScale = m_currentFontSize * smallCapsMultiplier / m_currentFontAsset.m_FaceInfo.pointSize * m_currentFontAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f); + adjustedScale = m_currentFontSize * smallCapsMultiplier / fontFace.pointSize * fontFace.scale * orthographicMultiplier; // Special handling for injected Ellipsis if (isInjectedCharacter && charCode == 0x2026) @@ -2588,12 +2606,12 @@ protected virtual void GenerateTextMesh() } else { - elementAscentLine = m_currentFontAsset.m_FaceInfo.ascentLine; - elementDescentLine = m_currentFontAsset.m_FaceInfo.descentLine; + elementAscentLine = fontFace.ascentLine; + elementDescentLine = fontFace.descentLine; } currentElementScale = adjustedScale * m_fontScaleMultiplier * m_cached_TextElement.m_Scale * m_cached_TextElement.m_Glyph.scale; - baselineOffset = m_currentFontAsset.m_FaceInfo.baseline * adjustedScale * m_fontScaleMultiplier * m_currentFontAsset.m_FaceInfo.scale; + baselineOffset = fontFace.baseline * adjustedScale * m_fontScaleMultiplier * fontFace.scale; m_textInfo.characterInfo[m_characterCount].elementType = TMP_TextElementType.Character; m_textInfo.characterInfo[m_characterCount].scale = currentElementScale; @@ -2761,7 +2779,7 @@ protected virtual void GenerateTextMesh() #region Handle Right-to-Left if (m_isRightToLeft) { - m_xAdvance -= currentGlyphMetrics.horizontalAdvance * (1 - m_charWidthAdjDelta) * currentElementScale; + m_xAdvance -= currentGlyphMetrics.horizontalAdvance * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale * currentElementScale; if (isWhiteSpace || charCode == 0x200B) m_xAdvance -= m_wordSpacing * currentEmScale; @@ -2775,9 +2793,9 @@ protected virtual void GenerateTextMesh() if (m_monoSpacing != 0) { if (m_duoSpace && (charCode == '.' || charCode == ':' || charCode == ',')) - monoAdvance = (m_monoSpacing / 4 - (currentGlyphMetrics.width / 2 + currentGlyphMetrics.horizontalBearingX) * currentElementScale) * (1 - m_charWidthAdjDelta); + monoAdvance = (m_monoSpacing / 4 - (currentGlyphMetrics.width / 2 + currentGlyphMetrics.horizontalBearingX) * currentElementScale) * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; else - monoAdvance = (m_monoSpacing / 2 - (currentGlyphMetrics.width / 2 + currentGlyphMetrics.horizontalBearingX) * currentElementScale) * (1 - m_charWidthAdjDelta); + monoAdvance = (m_monoSpacing / 2 - (currentGlyphMetrics.width / 2 + currentGlyphMetrics.horizontalBearingX) * currentElementScale) * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; m_xAdvance += monoAdvance; } @@ -2786,39 +2804,26 @@ protected virtual void GenerateTextMesh() // Set Padding based on selected font style #region Handle Style Padding - float boldSpacingAdjustment; - float style_padding; - if (m_textElementType == TMP_TextElementType.Character && !isUsingAltTypeface && ((m_FontStyleInternal & FontStyles.Bold) == FontStyles.Bold)) // Checks for any combination of Bold Style. - { - if (m_currentMaterial != null && m_currentMaterial.HasProperty(ShaderUtilities.ID_GradientScale)) - { - float gradientScale = m_currentMaterial.GetFloat(ShaderUtilities.ID_GradientScale); - style_padding = m_currentFontAsset.boldStyle / 4.0f * gradientScale * m_currentMaterial.GetFloat(ShaderUtilities.ID_ScaleRatio_A); + float boldSpacingAdjustment = 0; + float style_padding = 0; - // Clamp overall padding to Gradient Scale size. - if (style_padding + padding > gradientScale) - padding = gradientScale - style_padding; - } - else - style_padding = 0; - - boldSpacingAdjustment = m_currentFontAsset.boldSpacing; - } - else + if (m_textElementType == TMP_TextElementType.Character) { + bool isBold = !isUsingAltTypeface && (m_FontStyleInternal & FontStyles.Bold) == FontStyles.Bold; + boldSpacingAdjustment = isBold ? m_currentFontAsset.boldSpacing : 0; + if (m_currentMaterial != null && m_currentMaterial.HasProperty(ShaderUtilities.ID_GradientScale) && m_currentMaterial.HasProperty(ShaderUtilities.ID_ScaleRatio_A)) { float gradientScale = m_currentMaterial.GetFloat(ShaderUtilities.ID_GradientScale); - style_padding = m_currentFontAsset.normalStyle / 4.0f * gradientScale * m_currentMaterial.GetFloat(ShaderUtilities.ID_ScaleRatio_A); + float scaleRatioA = m_currentMaterial.GetFloat(ShaderUtilities.ID_ScaleRatio_A); + float styleModifier = isBold ? m_currentFontAsset.boldStyle : m_currentFontAsset.normalStyle; + + style_padding = styleModifier * 0.25f * gradientScale * scaleRatioA; // Clamp overall padding to Gradient Scale size. if (style_padding + padding > gradientScale) padding = gradientScale - style_padding; } - else - style_padding = 0; - - boldSpacingAdjustment = 0; } #endregion Handle Style Padding @@ -2827,7 +2832,7 @@ protected virtual void GenerateTextMesh() #region Calculate Vertices Position k_CalculateVerticesPositionMarker.Begin(); Vector3 top_left; - top_left.x = m_xAdvance + ((currentGlyphMetrics.horizontalBearingX * m_FXScale.x - padding - style_padding + glyphAdjustments.xPlacement) * currentElementScale * (1 - m_charWidthAdjDelta)); + top_left.x = m_xAdvance + (currentGlyphMetrics.horizontalBearingX * m_FXScale.x - padding - style_padding + glyphAdjustments.xPlacement) * currentElementScale * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; top_left.y = baselineOffset + (currentGlyphMetrics.horizontalBearingY + padding + glyphAdjustments.yPlacement) * currentElementScale - m_lineOffset + m_baselineOffset; top_left.z = 0; @@ -2837,7 +2842,7 @@ protected virtual void GenerateTextMesh() bottom_left.z = 0; Vector3 top_right; - top_right.x = bottom_left.x + ((currentGlyphMetrics.width * m_FXScale.x + padding * 2 + style_padding * 2) * currentElementScale * (1 - m_charWidthAdjDelta)); + top_right.x = bottom_left.x + (currentGlyphMetrics.width * m_FXScale.x + padding * 2 + style_padding * 2) * currentElementScale * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; top_right.y = top_left.y; top_right.z = 0; @@ -3024,7 +3029,7 @@ protected virtual void GenerateTextMesh() widthOfTextArea = m_width != -1 ? Mathf.Min(marginWidth + 0.0001f - marginLeft - marginRight, m_width) : marginWidth + 0.0001f - marginLeft - marginRight; // Calculate the line breaking width of the text. - float textWidth = Mathf.Abs(m_xAdvance) + (!m_isRightToLeft ? currentGlyphMetrics.horizontalAdvance : 0) * (1 - m_charWidthAdjDelta) * (charCode == 0xAD ? currentElementUnmodifiedScale : currentElementScale); + float textWidth = Mathf.Abs(m_xAdvance) + (!m_isRightToLeft ? currentGlyphMetrics.horizontalAdvance : 0) * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale * (charCode == 0xAD ? currentElementUnmodifiedScale : currentElementScale); float textHeight = m_maxTextAscender - (m_maxLineDescender - m_lineOffset) + (m_lineOffset > 0 && m_IsDrivenLineSpacing == false ? m_maxLineAscender - m_startOfLineAscender : 0); int testedCharacterCount = m_characterCount; @@ -3763,7 +3768,7 @@ protected virtual void GenerateTextMesh() #region Track Potential Insertion Location for Ellipsis if (m_overflowMode == TextOverflowModes.Ellipsis && (isInjectedCharacter == false || charCode == 0x2D)) { - float fontScale = m_currentFontSize / m_Ellipsis.fontAsset.m_FaceInfo.pointSize * m_Ellipsis.fontAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f); + float fontScale = m_currentFontSize / m_Ellipsis.fontAsset.m_FaceInfo.pointSize * m_Ellipsis.fontAsset.m_FaceInfo.scale * orthographicMultiplier; float scale = fontScale * m_fontScaleMultiplier * m_Ellipsis.character.m_Scale * m_Ellipsis.character.m_Glyph.scale; float marginLeft = m_marginLeft; float marginRight = m_marginRight; @@ -3771,14 +3776,14 @@ protected virtual void GenerateTextMesh() // Use the scale and margins of the previous character if Line Feed (LF) is not the first character of a line. if (charCode == 0x0A && m_characterCount != m_firstCharacterOfLine) { - fontScale = m_textInfo.characterInfo[m_characterCount - 1].pointSize / m_Ellipsis.fontAsset.m_FaceInfo.pointSize * m_Ellipsis.fontAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f); + fontScale = m_textInfo.characterInfo[m_characterCount - 1].pointSize / m_Ellipsis.fontAsset.m_FaceInfo.pointSize * m_Ellipsis.fontAsset.m_FaceInfo.scale * orthographicMultiplier; scale = fontScale * m_fontScaleMultiplier * m_Ellipsis.character.m_Scale * m_Ellipsis.character.m_Glyph.scale; marginLeft = m_textInfo.lineInfo[m_lineNumber].marginLeft; marginRight = m_textInfo.lineInfo[m_lineNumber].marginRight; } float textHeight = m_maxTextAscender - (m_maxLineDescender - m_lineOffset) + (m_lineOffset > 0 && m_IsDrivenLineSpacing == false ? m_maxLineAscender - m_startOfLineAscender : 0); - float textWidth = Mathf.Abs(m_xAdvance) + (!m_isRightToLeft ? m_Ellipsis.character.m_Glyph.metrics.horizontalAdvance : 0) * (1 - m_charWidthAdjDelta) * scale; + float textWidth = Mathf.Abs(m_xAdvance) + (!m_isRightToLeft ? m_Ellipsis.character.m_Glyph.metrics.horizontalAdvance : 0) * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale * scale; float widthOfTextAreaForEllipsis = m_width != -1 ? Mathf.Min(marginWidth + 0.0001f - marginLeft - marginRight, m_width) : marginWidth + 0.0001f - marginLeft - marginRight; if (textWidth < widthOfTextAreaForEllipsis * (isJustifiedOrFlush ? 1.05f : 1.0f) && textHeight < marginHeight + 0.0001f) @@ -3826,21 +3831,21 @@ protected virtual void GenerateTextMesh() else monoAdjustment = m_monoSpacing - monoAdvance; - m_xAdvance += (monoAdjustment + ((m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment) * currentEmScale) + m_cSpacing) * (1 - m_charWidthAdjDelta); + m_xAdvance += (monoAdjustment + ((m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment) * currentEmScale) + m_cSpacing) * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; if (isWhiteSpace || charCode == 0x200B) m_xAdvance += m_wordSpacing * currentEmScale; } else if (m_isRightToLeft) { - m_xAdvance -= ((glyphAdjustments.xAdvance * currentElementScale + (m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment + boldSpacingAdjustment) * currentEmScale + m_cSpacing) * (1 - m_charWidthAdjDelta)); + m_xAdvance -= (glyphAdjustments.xAdvance * currentElementScale + (m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment + boldSpacingAdjustment) * currentEmScale + m_cSpacing) * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; if (isWhiteSpace || charCode == 0x200B) m_xAdvance -= m_wordSpacing * currentEmScale; } else { - m_xAdvance += ((currentGlyphMetrics.horizontalAdvance * m_FXScale.x + glyphAdjustments.xAdvance) * currentElementScale + (m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment + boldSpacingAdjustment) * currentEmScale + m_cSpacing) * (1 - m_charWidthAdjDelta); + m_xAdvance += ((currentGlyphMetrics.horizontalAdvance * m_FXScale.x + glyphAdjustments.xAdvance) * currentElementScale + (m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment + boldSpacingAdjustment) * currentEmScale + m_cSpacing) * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; if (isWhiteSpace || charCode == 0x200B) m_xAdvance += m_wordSpacing * currentEmScale; @@ -3945,7 +3950,7 @@ protected virtual void GenerateTextMesh() if (m_textInfo.lineInfo[m_lineNumber].characterCount == 1) m_textInfo.lineInfo[m_lineNumber].alignment = m_lineJustification; - float maxAdvanceOffset = ((m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment + boldSpacingAdjustment) * currentEmScale + m_cSpacing) * (1 - m_charWidthAdjDelta); + float maxAdvanceOffset = ((m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment + boldSpacingAdjustment) * currentEmScale + m_cSpacing) * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; if (m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].isVisible) m_textInfo.lineInfo[m_lineNumber].maxAdvance = m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].xAdvance + (m_isRightToLeft ? maxAdvanceOffset : - maxAdvanceOffset); else @@ -4042,6 +4047,7 @@ protected virtual void GenerateTextMesh() bool shouldSaveHardLineBreak = false; bool shouldSaveSoftLineBreak = false; + uint nextChar = m_characterCount + 1 < totalCharacterCount ? m_textInfo.characterInfo[m_characterCount + 1].character : 0u; if ((isWhiteSpace || charCode == 0x200B || charCode == 0x2D || charCode == 0xAD) && (!m_isNonBreakingSpace || ignoreNonBreakingSpace) && charCode != 0xA0 && charCode != 0x2007 && charCode != 0x2011 && charCode != 0x202F && charCode != 0x2060) { @@ -4059,7 +4065,7 @@ protected virtual void GenerateTextMesh() else if (m_isNonBreakingSpace == false && (TMP_TextParsingUtilities.IsHangul(charCode) && TMP_Settings.useModernHangulLineBreakingRules == false || TMP_TextParsingUtilities.IsCJK(charCode))) { bool isCurrentLeadingCharacter = TMP_Settings.linebreakingRules.leadingCharacters.Contains(charCode); - bool isNextFollowingCharacter = m_characterCount < totalCharacterCount - 1 && TMP_Settings.linebreakingRules.followingCharacters.Contains(m_textInfo.characterInfo[m_characterCount + 1].character); + bool isNextFollowingCharacter = m_characterCount < totalCharacterCount - 1 && TMP_Settings.linebreakingRules.followingCharacters.Contains(nextChar); if (isCurrentLeadingCharacter == false) { @@ -4090,14 +4096,10 @@ protected virtual void GenerateTextMesh() } } } - // Special handling for Latin characters followed by a CJK character. - else if (!m_isNonBreakingSpace && (m_characterCount + 1) < totalCharacterCount && TMP_TextParsingUtilities.IsCJK(m_textInfo.characterInfo[m_characterCount + 1].character)) + // Handling for Latin characters followed by a CJK character that is not a following character. + else if (m_isNonBreakingSpace == false && TMP_TextParsingUtilities.IsCJK(nextChar) && !TMP_Settings.linebreakingRules.followingCharacters.Contains(nextChar)) { - uint nextChar = m_textInfo.characterInfo[m_characterCount + 1].character; - bool prevIsLeading = TMP_Settings.linebreakingRules.leadingCharacters.Contains(charCode); - bool nextIsFollowing = TMP_Settings.linebreakingRules.followingCharacters.Contains(nextChar); - if (!prevIsLeading && !nextIsFollowing) - shouldSaveHardLineBreak = true; + shouldSaveHardLineBreak = true; } else if (isFirstWordOfLine) { @@ -4328,7 +4330,7 @@ protected virtual void GenerateTextMesh() { float gap = !m_isRightToLeft ? lineInfo.width - lineInfo.maxAdvance : lineInfo.width + lineInfo.maxAdvance; int visibleCount = lineInfo.visibleCharacterCount - 1 + lineInfo.controlCharacterCount; - int spaces = lineInfo.spaceCount - lineInfo.controlCharacterCount; + int spaces = lineInfo.visibleSpaceCount - lineInfo.controlCharacterCount; if (isFirstSeperator) { spaces -= 1; visibleCount += 1; } @@ -4491,7 +4493,7 @@ protected virtual void GenerateTextMesh() // Pack UV's so that we can pass Xscale needed for Shader to maintain 1:1 ratio. #region Pack Scale into UV2 - xScale = characterInfos[i].scale * Mathf.Abs(lossyScale) * (1 - m_charWidthAdjDelta); + xScale = characterInfos[i].scale * Mathf.Abs(lossyScale) * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; if (!characterInfos[i].isUsingAlternateTypeface && (characterInfos[i].style & FontStyles.Bold) == FontStyles.Bold) xScale *= -1; // Set SDF Scale diff --git a/com.unity.ugui/Runtime/TMP/TextMeshProUGUI.cs b/com.unity.ugui/Runtime/TMP/TextMeshProUGUI.cs index 74e4b75..f456e34 100644 --- a/com.unity.ugui/Runtime/TMP/TextMeshProUGUI.cs +++ b/com.unity.ugui/Runtime/TMP/TextMeshProUGUI.cs @@ -117,8 +117,6 @@ public override void SetVerticesDirty() CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this); - ObjectUtilsBridge.MarkDirty(this); - if (m_OnDirtyVertsCallback != null) m_OnDirtyVertsCallback(); } @@ -1753,6 +1751,7 @@ internal override int SetArraySizes(TextProcessingElement[] textProcessingArray) m_currentFontAsset = m_fontAsset; m_currentMaterial = m_sharedMaterial; m_currentMaterialIndex = 0; + materialIndexPairs.Clear(); m_materialReferenceStack.SetDefault(new MaterialReference(m_currentMaterialIndex, m_currentFontAsset, null, m_currentMaterial, m_padding)); @@ -2053,46 +2052,62 @@ internal override int SetArraySizes(TextProcessingElement[] textProcessingArray) { LigatureSubstitutionRecord record = records[j]; + uint[] componentGlyphIDs = record.componentGlyphIDs; int componentCount = record.componentGlyphIDs.Length; uint ligatureGlyphID = record.ligatureGlyphID; - // - for (int k = 1; k < componentCount; k++) + int idx = i + 1; // current input index + int componentIndex = 1; // next component index we still need to match in record.componentGlyphIDs + + // Try to match remaining components, skipping ignorable code points when they aren't the expected component. + while (ligatureGlyphID != 0 && componentIndex < componentCount) { - uint componentUnicode = (uint)textProcessingArray[i + k].unicode; + if (idx >= textProcessingArray.Length) { ligatureGlyphID = 0; break; } - // Special Handling for Zero Width Joiner (ZWJ) - //if (componentUnicode == 0x200D) - // continue; + uint codepoint = textProcessingArray[idx].unicode; + uint glyphIndex = m_currentFontAsset.GetGlyphIndex(codepoint); + uint expect = componentGlyphIDs[componentIndex]; - uint glyphIndex = m_currentFontAsset.GetGlyphIndex(componentUnicode); + // If codepoint is ignorable for ligature matching *and* it is not the expected component, skip it. + if (glyphIndex != expect && TMP_TextParsingUtilities.IsIgnorableForLigature(codepoint)) + { + idx++; + continue; + } - if (glyphIndex == record.componentGlyphIDs[k]) + // Otherwise, we require an exact glyph match for this component. + if (glyphIndex == expect) + { + componentIndex++; + idx++; continue; + } + // Mismatch. ligatureGlyphID = 0; break; } - if (ligatureGlyphID != 0) + // If we matched all components, perform the substitution. + if (ligatureGlyphID != 0 && componentIndex == componentCount) { + // Check for potential trailing variant selector that should also be consumed in the sequence. + if (idx < textProcessingArray.Length && TMP_TextParsingUtilities.IsIgnorableForLigature(textProcessingArray[idx].unicode)) + idx++; + + int spanLen = idx - i; if (m_currentFontAsset.TryAddGlyphInternal(ligatureGlyphID, out Glyph glyph)) { m_textInfo.characterInfo[m_totalCharacterCount].alternativeGlyph = glyph; + textProcessingArray[i].length = spanLen; - // Update text processing array - for (int c = 0; c < componentCount; c++) + // Collapse the entire consumed span into the ligature. + for (int c = 1; c < spanLen; c++) { - if (c == 0) - { - textProcessingArray[i + c].length = componentCount; - continue; - } - - textProcessingArray[i + c].unicode = 0x1A; + textProcessingArray[i + c].unicode = 0x1A; // mark as consumed } - i += componentCount - 1; + i += spanLen - 1; // advance past the cluster break; } } @@ -2143,9 +2158,11 @@ internal override int SetArraySizes(TextProcessingElement[] textProcessingArray) } // Handle Multi Atlas Texture support - if (character != null && character.glyph.atlasIndex > 0) + int atlasIndex = m_textInfo.characterInfo[m_totalCharacterCount].alternativeGlyph != null ? m_textInfo.characterInfo[m_totalCharacterCount].alternativeGlyph.atlasIndex : character == null ? 0 : character.glyph.atlasIndex; + + if (character != null && atlasIndex > 0) { - m_currentMaterial = TMP_MaterialManager.GetFallbackMaterial(m_currentFontAsset, m_currentMaterial, character.glyph.atlasIndex); + m_currentMaterial = TMP_MaterialManager.GetFallbackMaterial(m_currentFontAsset, m_currentMaterial, atlasIndex); m_currentMaterialIndex = MaterialReference.AddMaterialReference(m_currentMaterial, m_currentFontAsset, ref m_materialReferences, m_materialReferenceIndexLookup); @@ -2559,9 +2576,10 @@ protected virtual void GenerateTextMesh() // Calculate the scale of the font based on selected font size and sampling point size. // baseScale is calculated using the font asset assigned to the text object. - float baseScale = (m_fontSize / m_fontAsset.m_FaceInfo.pointSize * m_fontAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f)); + float orthographicMultiplier = m_isOrthographic ? 1 : 0.1f; + float baseScale = m_fontSize / m_fontAsset.m_FaceInfo.pointSize * m_fontAsset.m_FaceInfo.scale * orthographicMultiplier; float currentElementScale = baseScale; - float currentEmScale = m_fontSize * 0.01f * (m_isOrthographic ? 1 : 0.1f); + float currentEmScale = m_fontSize * 0.01f * orthographicMultiplier; m_fontScaleMultiplier = 1; m_currentFontSize = m_fontSize; @@ -2857,44 +2875,48 @@ protected virtual void GenerateTextMesh() float baselineOffset = 0; float elementAscentLine = 0; float elementDescentLine = 0; + + FaceInfo fontFace = m_currentFontAsset.m_FaceInfo; + if (m_textElementType == TMP_TextElementType.Sprite) { // If a sprite is used as a fallback then get a reference to it and set the color to white. TMP_SpriteCharacter sprite = (TMP_SpriteCharacter)textInfo.characterInfo[m_characterCount].textElement; - m_currentSpriteAsset = sprite.textAsset as TMP_SpriteAsset; - m_spriteIndex = (int)sprite.glyphIndex; - if (sprite == null) { k_CharacterLookupMarker.End(); continue; } + m_currentSpriteAsset = sprite.textAsset as TMP_SpriteAsset; + m_spriteIndex = (int)sprite.glyphIndex; + // Sprites are assigned in the E000 Private Area + sprite Index if (charCode == '<') - charCode = 57344 + (uint)m_spriteIndex; + charCode = 0xE000 + (uint)m_spriteIndex; else m_spriteColor = s_colorWhite; - float fontScale = (m_currentFontSize / m_currentFontAsset.faceInfo.pointSize * m_currentFontAsset.faceInfo.scale * (m_isOrthographic ? 1 : 0.1f)); + float fontScale = m_currentFontSize / fontFace.pointSize * fontFace.scale * orthographicMultiplier; + FaceInfo spriteFace = m_currentSpriteAsset.m_FaceInfo; - // The sprite scale calculations are based on the font asset assigned to the text object. - if (m_currentSpriteAsset.m_FaceInfo.pointSize > 0) + // Use sprite asset's own metrics when available. Otherwise, scale sprite based on current font asset face metrics. + if (spriteFace.pointSize > 0) { - float spriteScale = m_currentFontSize / m_currentSpriteAsset.m_FaceInfo.pointSize * m_currentSpriteAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f); + float spriteScale = m_currentFontSize / spriteFace.pointSize * spriteFace.scale * orthographicMultiplier; currentElementScale = sprite.m_Scale * sprite.m_Glyph.scale * spriteScale; - elementAscentLine = m_currentSpriteAsset.m_FaceInfo.ascentLine; - baselineOffset = m_currentSpriteAsset.m_FaceInfo.baseline * fontScale * m_fontScaleMultiplier * m_currentSpriteAsset.m_FaceInfo.scale; - elementDescentLine = m_currentSpriteAsset.m_FaceInfo.descentLine; + elementAscentLine = spriteFace.ascentLine; + baselineOffset = spriteFace.baseline * fontScale * m_fontScaleMultiplier * spriteFace.scale; + elementDescentLine = spriteFace.descentLine; } else { - float spriteScale = m_currentFontSize / m_currentFontAsset.m_FaceInfo.pointSize * m_currentFontAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f); - currentElementScale = m_currentFontAsset.m_FaceInfo.ascentLine / sprite.m_Glyph.metrics.height * sprite.m_Scale * sprite.m_Glyph.scale * spriteScale; - float scaleDelta = spriteScale / currentElementScale; - elementAscentLine = m_currentFontAsset.m_FaceInfo.ascentLine * scaleDelta; - baselineOffset = m_currentFontAsset.m_FaceInfo.baseline * fontScale * m_fontScaleMultiplier * m_currentFontAsset.m_FaceInfo.scale; - elementDescentLine = m_currentFontAsset.m_FaceInfo.descentLine * scaleDelta; + float spriteScale = m_currentFontSize / fontFace.pointSize * fontFace.scale * orthographicMultiplier; + currentElementScale = fontFace.ascentLine / sprite.m_Glyph.metrics.height * sprite.m_Scale * sprite.m_Glyph.scale * spriteScale; + float scaleDelta = currentElementScale != 0 ? spriteScale / currentElementScale : 0; + elementAscentLine = fontFace.ascentLine * scaleDelta; + baselineOffset = fontFace.baseline * fontScale * m_fontScaleMultiplier * fontFace.scale; + elementDescentLine = fontFace.descentLine * scaleDelta; } m_cached_TextElement = sprite; @@ -2924,9 +2946,9 @@ protected virtual void GenerateTextMesh() // Special handling if replaced character was a line feed where in this case we have to use the scale of the previous character. float adjustedScale; if (isInjectedCharacter && m_TextProcessingArray[i].unicode == 0x0A && m_characterCount != m_firstCharacterOfLine) - adjustedScale = m_textInfo.characterInfo[m_characterCount - 1].pointSize * smallCapsMultiplier / m_currentFontAsset.m_FaceInfo.pointSize * m_currentFontAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f); + adjustedScale = m_textInfo.characterInfo[m_characterCount - 1].pointSize * smallCapsMultiplier / fontFace.pointSize * fontFace.scale * orthographicMultiplier; else - adjustedScale = m_currentFontSize * smallCapsMultiplier / m_currentFontAsset.m_FaceInfo.pointSize * m_currentFontAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f); + adjustedScale = m_currentFontSize * smallCapsMultiplier / fontFace.pointSize * fontFace.scale * orthographicMultiplier; // Special handling for injected Ellipsis if (isInjectedCharacter && charCode == 0x2026) @@ -2936,12 +2958,12 @@ protected virtual void GenerateTextMesh() } else { - elementAscentLine = m_currentFontAsset.m_FaceInfo.ascentLine; - elementDescentLine = m_currentFontAsset.m_FaceInfo.descentLine; + elementAscentLine = fontFace.ascentLine; + elementDescentLine = fontFace.descentLine; } currentElementScale = adjustedScale * m_fontScaleMultiplier * m_cached_TextElement.m_Scale * m_cached_TextElement.m_Glyph.scale; - baselineOffset = m_currentFontAsset.m_FaceInfo.baseline * adjustedScale * m_fontScaleMultiplier * m_currentFontAsset.m_FaceInfo.scale; + baselineOffset = fontFace.baseline * adjustedScale * m_fontScaleMultiplier * fontFace.scale; m_textInfo.characterInfo[m_characterCount].elementType = TMP_TextElementType.Character; m_textInfo.characterInfo[m_characterCount].scale = currentElementScale; @@ -2987,7 +3009,7 @@ protected virtual void GenerateTextMesh() GlyphPairAdjustmentRecord adjustmentPair; uint baseGlyphIndex = m_cached_TextElement.m_GlyphIndex; - if (m_characterCount < totalCharacterCount - 1 && textInfo.characterInfo[m_characterCount + 1].elementType == TMP_TextElementType.Character) + if (m_characterCount < totalCharacterCount - 1 && m_textInfo.characterInfo[m_characterCount + 1].elementType == TMP_TextElementType.Character) { uint nextGlyphIndex = m_textInfo.characterInfo[m_characterCount + 1].textElement.m_GlyphIndex; uint key = nextGlyphIndex << 16 | baseGlyphIndex; @@ -3109,7 +3131,7 @@ protected virtual void GenerateTextMesh() #region Handle Right-to-Left if (m_isRightToLeft) { - m_xAdvance -= currentGlyphMetrics.horizontalAdvance * (1 - m_charWidthAdjDelta) * currentElementScale; + m_xAdvance -= currentGlyphMetrics.horizontalAdvance * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale * currentElementScale; if (isWhiteSpace || charCode == 0x200B) m_xAdvance -= m_wordSpacing * currentEmScale; @@ -3123,9 +3145,9 @@ protected virtual void GenerateTextMesh() if (m_monoSpacing != 0) { if (m_duoSpace && (charCode == '.' || charCode == ':' || charCode == ',')) - monoAdvance = (m_monoSpacing / 4 - (currentGlyphMetrics.width / 2 + currentGlyphMetrics.horizontalBearingX) * currentElementScale) * (1 - m_charWidthAdjDelta); + monoAdvance = (m_monoSpacing / 4 - (currentGlyphMetrics.width / 2 + currentGlyphMetrics.horizontalBearingX) * currentElementScale) * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; else - monoAdvance = (m_monoSpacing / 2 - (currentGlyphMetrics.width / 2 + currentGlyphMetrics.horizontalBearingX) * currentElementScale) * (1 - m_charWidthAdjDelta); + monoAdvance = (m_monoSpacing / 2 - (currentGlyphMetrics.width / 2 + currentGlyphMetrics.horizontalBearingX) * currentElementScale) * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; m_xAdvance += monoAdvance; } @@ -3134,39 +3156,26 @@ protected virtual void GenerateTextMesh() // Set Padding based on selected font style #region Handle Style Padding - float boldSpacingAdjustment; - float style_padding; - if (m_textElementType == TMP_TextElementType.Character && !isUsingAltTypeface && ((m_FontStyleInternal & FontStyles.Bold) == FontStyles.Bold)) // Checks for any combination of Bold Style. - { - if (m_currentMaterial != null && m_currentMaterial.HasProperty(ShaderUtilities.ID_GradientScale)) - { - float gradientScale = m_currentMaterial.GetFloat(ShaderUtilities.ID_GradientScale); - style_padding = m_currentFontAsset.boldStyle / 4.0f * gradientScale * m_currentMaterial.GetFloat(ShaderUtilities.ID_ScaleRatio_A); - - // Clamp overall padding to Gradient Scale size. - if (style_padding + padding > gradientScale) - padding = gradientScale - style_padding; - } - else - style_padding = 0; + float boldSpacingAdjustment = 0; + float style_padding = 0; - boldSpacingAdjustment = m_currentFontAsset.boldSpacing; - } - else + if (m_textElementType == TMP_TextElementType.Character) { + bool isBold = !isUsingAltTypeface && (m_FontStyleInternal & FontStyles.Bold) == FontStyles.Bold; + boldSpacingAdjustment = isBold ? m_currentFontAsset.boldSpacing : 0; + if (m_currentMaterial != null && m_currentMaterial.HasProperty(ShaderUtilities.ID_GradientScale) && m_currentMaterial.HasProperty(ShaderUtilities.ID_ScaleRatio_A)) { float gradientScale = m_currentMaterial.GetFloat(ShaderUtilities.ID_GradientScale); - style_padding = m_currentFontAsset.normalStyle / 4.0f * gradientScale * m_currentMaterial.GetFloat(ShaderUtilities.ID_ScaleRatio_A); + float scaleRatioA = m_currentMaterial.GetFloat(ShaderUtilities.ID_ScaleRatio_A); + float styleModifier = isBold ? m_currentFontAsset.boldStyle : m_currentFontAsset.normalStyle; + + style_padding = styleModifier * 0.25f * gradientScale * scaleRatioA; // Clamp overall padding to Gradient Scale size. if (style_padding + padding > gradientScale) padding = gradientScale - style_padding; } - else - style_padding = 0; - - boldSpacingAdjustment = 0; } #endregion Handle Style Padding @@ -3175,7 +3184,7 @@ protected virtual void GenerateTextMesh() #region Calculate Vertices Position k_CalculateVerticesPositionMarker.Begin(); Vector3 top_left; - top_left.x = m_xAdvance + ((currentGlyphMetrics.horizontalBearingX * m_FXScale.x - padding - style_padding + glyphAdjustments.xPlacement) * currentElementScale * (1 - m_charWidthAdjDelta)); + top_left.x = m_xAdvance + (currentGlyphMetrics.horizontalBearingX * m_FXScale.x - padding - style_padding + glyphAdjustments.xPlacement) * currentElementScale * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; top_left.y = baselineOffset + (currentGlyphMetrics.horizontalBearingY + padding + glyphAdjustments.yPlacement) * currentElementScale - m_lineOffset + m_baselineOffset; top_left.z = 0; @@ -3185,7 +3194,7 @@ protected virtual void GenerateTextMesh() bottom_left.z = 0; Vector3 top_right; - top_right.x = bottom_left.x + ((currentGlyphMetrics.width * m_FXScale.x + padding * 2 + style_padding * 2) * currentElementScale * (1 - m_charWidthAdjDelta)); + top_right.x = bottom_left.x + (currentGlyphMetrics.width * m_FXScale.x + padding * 2 + style_padding * 2) * currentElementScale * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; top_right.y = top_left.y; top_right.z = 0; @@ -3372,7 +3381,7 @@ protected virtual void GenerateTextMesh() widthOfTextArea = m_width != -1 ? Mathf.Min(marginWidth + 0.0001f - marginLeft - marginRight, m_width) : marginWidth + 0.0001f - marginLeft - marginRight; // Calculate the line breaking width of the text. - float textWidth = Mathf.Abs(m_xAdvance) + (!m_isRightToLeft ? currentGlyphMetrics.horizontalAdvance : 0) * (1 - m_charWidthAdjDelta) * (charCode == 0xAD ? currentElementUnmodifiedScale : currentElementScale); + float textWidth = Mathf.Abs(m_xAdvance) + (!m_isRightToLeft ? currentGlyphMetrics.horizontalAdvance : 0) * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale * (charCode == 0xAD ? currentElementUnmodifiedScale : currentElementScale); float textHeight = m_maxTextAscender - (m_maxLineDescender - m_lineOffset) + (m_lineOffset > 0 && m_IsDrivenLineSpacing == false ? m_maxLineAscender - m_startOfLineAscender : 0); int testedCharacterCount = m_characterCount; @@ -4111,7 +4120,7 @@ protected virtual void GenerateTextMesh() #region Track Potential Insertion Location for Ellipsis if (m_overflowMode == TextOverflowModes.Ellipsis && (isInjectedCharacter == false || charCode == 0x2D)) { - float fontScale = m_currentFontSize / m_Ellipsis.fontAsset.m_FaceInfo.pointSize * m_Ellipsis.fontAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f); + float fontScale = m_currentFontSize / m_Ellipsis.fontAsset.m_FaceInfo.pointSize * m_Ellipsis.fontAsset.m_FaceInfo.scale * orthographicMultiplier; float scale = fontScale * m_fontScaleMultiplier * m_Ellipsis.character.m_Scale * m_Ellipsis.character.m_Glyph.scale; float marginLeft = m_marginLeft; float marginRight = m_marginRight; @@ -4119,14 +4128,14 @@ protected virtual void GenerateTextMesh() // Use the scale and margins of the previous character if Line Feed (LF) is not the first character of a line. if (charCode == 0x0A && m_characterCount != m_firstCharacterOfLine) { - fontScale = m_textInfo.characterInfo[m_characterCount - 1].pointSize / m_Ellipsis.fontAsset.m_FaceInfo.pointSize * m_Ellipsis.fontAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f); + fontScale = m_textInfo.characterInfo[m_characterCount - 1].pointSize / m_Ellipsis.fontAsset.m_FaceInfo.pointSize * m_Ellipsis.fontAsset.m_FaceInfo.scale * orthographicMultiplier; scale = fontScale * m_fontScaleMultiplier * m_Ellipsis.character.m_Scale * m_Ellipsis.character.m_Glyph.scale; marginLeft = m_textInfo.lineInfo[m_lineNumber].marginLeft; marginRight = m_textInfo.lineInfo[m_lineNumber].marginRight; } float textHeight = m_maxTextAscender - (m_maxLineDescender - m_lineOffset) + (m_lineOffset > 0 && m_IsDrivenLineSpacing == false ? m_maxLineAscender - m_startOfLineAscender : 0); - float textWidth = Mathf.Abs(m_xAdvance) + (!m_isRightToLeft ? m_Ellipsis.character.m_Glyph.metrics.horizontalAdvance : 0) * (1 - m_charWidthAdjDelta) * scale; + float textWidth = Mathf.Abs(m_xAdvance) + (!m_isRightToLeft ? m_Ellipsis.character.m_Glyph.metrics.horizontalAdvance : 0) * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale * scale; float widthOfTextAreaForEllipsis = m_width != -1 ? Mathf.Min(marginWidth + 0.0001f - marginLeft - marginRight, m_width) : marginWidth + 0.0001f - marginLeft - marginRight; if (textWidth < widthOfTextAreaForEllipsis * (isJustifiedOrFlush ? 1.05f : 1.0f) && textHeight < marginHeight + 0.0001f) @@ -4174,21 +4183,21 @@ protected virtual void GenerateTextMesh() else monoAdjustment = m_monoSpacing - monoAdvance; - m_xAdvance += (monoAdjustment + ((m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment) * currentEmScale) + m_cSpacing) * (1 - m_charWidthAdjDelta); + m_xAdvance += (monoAdjustment + ((m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment) * currentEmScale) + m_cSpacing) * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; if (isWhiteSpace || charCode == 0x200B) m_xAdvance += m_wordSpacing * currentEmScale; } else if (m_isRightToLeft) { - m_xAdvance -= ((glyphAdjustments.xAdvance * currentElementScale + (m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment + boldSpacingAdjustment) * currentEmScale + m_cSpacing) * (1 - m_charWidthAdjDelta)); + m_xAdvance -= (glyphAdjustments.xAdvance * currentElementScale + (m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment + boldSpacingAdjustment) * currentEmScale + m_cSpacing) * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; if (isWhiteSpace || charCode == 0x200B) m_xAdvance -= m_wordSpacing * currentEmScale; } else { - m_xAdvance += ((currentGlyphMetrics.horizontalAdvance * m_FXScale.x + glyphAdjustments.xAdvance) * currentElementScale + (m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment + boldSpacingAdjustment) * currentEmScale + m_cSpacing) * (1 - m_charWidthAdjDelta); + m_xAdvance += ((currentGlyphMetrics.horizontalAdvance * m_FXScale.x + glyphAdjustments.xAdvance) * currentElementScale + (m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment + boldSpacingAdjustment) * currentEmScale + m_cSpacing) * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; if (isWhiteSpace || charCode == 0x200B) m_xAdvance += m_wordSpacing * currentEmScale; @@ -4293,7 +4302,7 @@ protected virtual void GenerateTextMesh() if (m_textInfo.lineInfo[m_lineNumber].characterCount == 1) m_textInfo.lineInfo[m_lineNumber].alignment = m_lineJustification; - float maxAdvanceOffset = ((m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment + boldSpacingAdjustment) * currentEmScale + m_cSpacing) * (1 - m_charWidthAdjDelta); + float maxAdvanceOffset = ((m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment + boldSpacingAdjustment) * currentEmScale + m_cSpacing) * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; if (m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].isVisible) m_textInfo.lineInfo[m_lineNumber].maxAdvance = m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].xAdvance + (m_isRightToLeft ? maxAdvanceOffset : - maxAdvanceOffset); else @@ -4390,6 +4399,7 @@ protected virtual void GenerateTextMesh() bool shouldSaveHardLineBreak = false; bool shouldSaveSoftLineBreak = false; + uint nextChar = m_characterCount + 1 < totalCharacterCount ? m_textInfo.characterInfo[m_characterCount + 1].character : 0u; if ((isWhiteSpace || charCode == 0x200B || charCode == 0x2D || charCode == 0xAD) && (!m_isNonBreakingSpace || ignoreNonBreakingSpace) && charCode != 0xA0 && charCode != 0x2007 && charCode != 0x2011 && charCode != 0x202F && charCode != 0x2060) { @@ -4407,7 +4417,7 @@ protected virtual void GenerateTextMesh() else if (m_isNonBreakingSpace == false && (TMP_TextParsingUtilities.IsHangul(charCode) && TMP_Settings.useModernHangulLineBreakingRules == false || TMP_TextParsingUtilities.IsCJK(charCode))) { bool isCurrentLeadingCharacter = TMP_Settings.linebreakingRules.leadingCharacters.Contains(charCode); - bool isNextFollowingCharacter = m_characterCount < totalCharacterCount - 1 && TMP_Settings.linebreakingRules.followingCharacters.Contains(m_textInfo.characterInfo[m_characterCount + 1].character); + bool isNextFollowingCharacter = m_characterCount < totalCharacterCount - 1 && TMP_Settings.linebreakingRules.followingCharacters.Contains(nextChar); if (isCurrentLeadingCharacter == false) { @@ -4438,14 +4448,10 @@ protected virtual void GenerateTextMesh() } } } - // Special handling for Latin characters followed by a CJK character. - else if (!m_isNonBreakingSpace && (m_characterCount + 1) < totalCharacterCount && TMP_TextParsingUtilities.IsCJK(m_textInfo.characterInfo[m_characterCount + 1].character)) + // Handling for Latin characters followed by a CJK character that is not a following character. + else if (m_isNonBreakingSpace == false && TMP_TextParsingUtilities.IsCJK(nextChar) && !TMP_Settings.linebreakingRules.followingCharacters.Contains(nextChar)) { - uint nextChar = m_textInfo.characterInfo[m_characterCount + 1].character; - bool prevIsLeading = TMP_Settings.linebreakingRules.leadingCharacters.Contains(charCode); - bool nextIsFollowing = TMP_Settings.linebreakingRules.followingCharacters.Contains(nextChar); - if (!prevIsLeading && !nextIsFollowing) - shouldSaveHardLineBreak = true; + shouldSaveHardLineBreak = true; } else if (isFirstWordOfLine) { @@ -4843,7 +4849,7 @@ protected virtual void GenerateTextMesh() // Pack UV's so that we can pass Xscale needed for Shader to maintain 1:1 ratio. #region Pack Scale into UV2 - xScale = characterInfos[i].scale * (1 - m_charWidthAdjDelta); + xScale = characterInfos[i].scale * (1 - m_charWidthAdjDelta) * m_characterHorizontalScale; if (!characterInfos[i].isUsingAlternateTypeface && (characterInfos[i].style & FontStyles.Bold) == FontStyles.Bold) xScale *= -1; switch (canvasRenderMode) @@ -5406,31 +5412,38 @@ protected virtual void GenerateTextMesh() // Clear unused vertices m_textInfo.meshInfo[i].ClearUnusedVertices(); - if (m_subTextObjects[i] == null) continue; + TMP_SubMeshUI subTextObject = m_subTextObjects[i]; + + if (subTextObject == null) continue; // Sort the geometry of the sub-text objects if needed. if (m_geometrySortingOrder != VertexSortingOrder.Normal) m_textInfo.meshInfo[i].SortGeometry(VertexSortingOrder.Reverse); - //m_subTextObjects[i].mesh.MarkDynamic(); - m_subTextObjects[i].mesh.vertices = m_textInfo.meshInfo[i].vertices; - m_subTextObjects[i].mesh.SetUVs(0, m_textInfo.meshInfo[i].uvs0); - m_subTextObjects[i].mesh.uv2 = m_textInfo.meshInfo[i].uvs2; - //m_subTextObjects[i].mesh.uv4 = m_textInfo.meshInfo[i].uvs4; - m_subTextObjects[i].mesh.colors32 = m_textInfo.meshInfo[i].colors32; + subTextObject.mesh.vertices = m_textInfo.meshInfo[i].vertices; + subTextObject.mesh.SetUVs(0, m_textInfo.meshInfo[i].uvs0); + subTextObject.mesh.uv2 = m_textInfo.meshInfo[i].uvs2; + subTextObject.mesh.colors32 = m_textInfo.meshInfo[i].colors32; - m_subTextObjects[i].mesh.RecalculateBounds(); + subTextObject.mesh.RecalculateBounds(); - m_subTextObjects[i].canvasRenderer.SetMesh(m_subTextObjects[i].mesh); + subTextObject.canvasRenderer.SetMesh(subTextObject.mesh); // Set CanvasRenderer color to match the parent text object. - m_subTextObjects[i].canvasRenderer.SetColor(parentBaseColor); + subTextObject.canvasRenderer.SetColor(parentBaseColor); // Make sure Cull Transparent Mesh of the sub objects matches the parent - m_subTextObjects[i].canvasRenderer.cullTransparentMesh = isCullTransparentMeshEnabled; + subTextObject.canvasRenderer.cullTransparentMesh = isCullTransparentMeshEnabled; // Sync RaycastTarget property with parent text object - m_subTextObjects[i].raycastTarget = this.raycastTarget; + subTextObject.raycastTarget = this.raycastTarget; + + // Sync Maskable property with parent text object (if needed) + if (subTextObject.maskable != this.maskable) + { + subTextObject.maskable = this.maskable; + subTextObject.RecalculateClipping(); + } } } diff --git a/com.unity.ugui/Runtime/TMP/Unity.TextMeshPro.asmdef b/com.unity.ugui/Runtime/TMP/Unity.TextMeshPro.asmdef index 31e8cc4..400e2be 100644 --- a/com.unity.ugui/Runtime/TMP/Unity.TextMeshPro.asmdef +++ b/com.unity.ugui/Runtime/TMP/Unity.TextMeshPro.asmdef @@ -1,9 +1,7 @@ { "name": "Unity.TextMeshPro", "rootNamespace": "", - "references": [ - "GUID:43b111c4a445f446abd2c02e77750ff5" - ], + "references": [], "includePlatforms": [], "excludePlatforms": [], "allowUnsafeCode": true, diff --git a/com.unity.ugui/Runtime/UGUI/AssemblyInfo.cs b/com.unity.ugui/Runtime/UGUI/AssemblyInfo.cs deleted file mode 100644 index 8f57e62..0000000 --- a/com.unity.ugui/Runtime/UGUI/AssemblyInfo.cs +++ /dev/null @@ -1,4 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("UnityEditor.UI")] diff --git a/com.unity.ugui/Runtime/UGUI/AssemblyInfo.cs.meta b/com.unity.ugui/Runtime/UGUI/AssemblyInfo.cs.meta deleted file mode 100644 index 333d6c4..0000000 --- a/com.unity.ugui/Runtime/UGUI/AssemblyInfo.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 58c9792f08de21b4194eb2f9d862dc22 \ No newline at end of file diff --git a/com.unity.ugui/Runtime/UGUI/EventSystem/EventData/PointerEventData.cs b/com.unity.ugui/Runtime/UGUI/EventSystem/EventData/PointerEventData.cs index 38f2c64..84f052f 100644 --- a/com.unity.ugui/Runtime/UGUI/EventSystem/EventData/PointerEventData.cs +++ b/com.unity.ugui/Runtime/UGUI/EventSystem/EventData/PointerEventData.cs @@ -229,7 +229,7 @@ public enum FramePressState /// public float twist { get; set; } /// - /// Specifies the angle of the pen relative to the X & Y axis, in radians. + /// Specifies the angle of the pen relative to the X & Y axis, in radians. /// /// public Vector2 tilt { get; set; } diff --git a/com.unity.ugui/Runtime/UGUI/EventSystem/EventSystem.cs b/com.unity.ugui/Runtime/UGUI/EventSystem/EventSystem.cs index 32e5145..68dc473 100644 --- a/com.unity.ugui/Runtime/UGUI/EventSystem/EventSystem.cs +++ b/com.unity.ugui/Runtime/UGUI/EventSystem/EventSystem.cs @@ -324,50 +324,37 @@ public bool IsPointerOverGameObject(int pointerId) return m_CurrentInputModule != null && m_CurrentInputModule.IsPointerOverGameObject(pointerId); } - // This code is disabled unless the com.unity.modules.uielements module is present. + // This code is disabled unless the UI Toolkit package or the com.unity.modules.uielements module are present. // The UIElements module is always present in the Editor but it can be stripped from a project build if unused. #if PACKAGE_UITOOLKIT - [SerializeField, HideInInspector] private UIToolkitInteroperabilityBridge m_UIToolkitInterop = new (); - - /// - /// Use this property to initialize UI Toolkit interoperability with uGUI events. - /// - internal UIToolkitInteroperabilityBridge uiToolkitInterop => m_UIToolkitInterop; -#endif - - /// - /// Returns true if UI Toolkit interoperability is enabled and - /// there is some active UI Toolkit content in the scene. - /// - internal bool isOverridingUIToolkitEvents - { - get - { -#if PACKAGE_UITOOLKIT - return uiToolkitInterop.overrideUIToolkitEvents && UIDocument.EnabledDocumentCount > 0; -#else - return false; -#endif - } - } - -#if PACKAGE_UITOOLKIT - private struct UIToolkitOverrideConfigOld + private struct UIToolkitOverrideConfig { public EventSystem activeEventSystem; public bool sendEvents; public bool createPanelGameObjectsOnStart; } - private static UIToolkitOverrideConfigOld? s_UIToolkitOverrideConfigOld = null; + + private static UIToolkitOverrideConfig s_UIToolkitOverride = new UIToolkitOverrideConfig + { + activeEventSystem = null, + sendEvents = true, + createPanelGameObjectsOnStart = true + }; + + private bool isUIToolkitActiveEventSystem => + s_UIToolkitOverride.activeEventSystem == this || s_UIToolkitOverride.activeEventSystem == null; + + private bool sendUIToolkitEvents => + s_UIToolkitOverride.sendEvents && isUIToolkitActiveEventSystem; + + private bool createUIToolkitPanelGameObjectsOnStart => + s_UIToolkitOverride.createPanelGameObjectsOnStart && isUIToolkitActiveEventSystem; #endif /// /// Sets how UI Toolkit runtime panels receive events and handle selection /// when interacting with other objects that use the EventSystem, such as components from the Unity UI package. /// - /// - /// This method is obsolete. Use the PanelInputConfiguration component instead. - /// /// /// The EventSystem used to override UI Toolkit panel events and selection. /// If activeEventSystem is null, UI Toolkit panels will use current enabled EventSystem @@ -381,27 +368,72 @@ private struct UIToolkitOverrideConfigOld /// If true, UI Toolkit panels' unassigned selectableGameObject will be automatically initialized /// with children GameObjects of this EventSystem on Start. /// - [Obsolete("Use PanelInputConfiguration component instead.")] public static void SetUITookitEventSystemOverride(EventSystem activeEventSystem, bool sendEvents = true, bool createPanelGameObjectsOnStart = true) { #if PACKAGE_UITOOLKIT - s_UIToolkitOverrideConfigOld = activeEventSystem == null && sendEvents && createPanelGameObjectsOnStart ? null : new UIToolkitOverrideConfigOld + UIElementsRuntimeUtility.UnregisterEventSystem(UIElementsRuntimeUtility.activeEventSystem); + + s_UIToolkitOverride = new UIToolkitOverrideConfig { activeEventSystem = activeEventSystem, sendEvents = sendEvents, createPanelGameObjectsOnStart = createPanelGameObjectsOnStart, }; - var eventSystem = activeEventSystem != null ? activeEventSystem : EventSystem.current; - if (UIElementsRuntimeUtility.activeEventSystem != null && UIElementsRuntimeUtility.activeEventSystem != eventSystem) + if (sendEvents) { - ((EventSystem)UIElementsRuntimeUtility.activeEventSystem).uiToolkitInterop.overrideUIToolkitEvents = false; + var eventSystem = activeEventSystem != null ? activeEventSystem : EventSystem.current; + if (eventSystem.isActiveAndEnabled) + UIElementsRuntimeUtility.RegisterEventSystem(activeEventSystem); } - if (eventSystem != null && eventSystem.isActiveAndEnabled) +#endif + } + +#if PACKAGE_UITOOLKIT + private bool m_Started; + private bool m_IsTrackingUIToolkitPanels; + + private void StartTrackingUIToolkitPanels() + { + if (createUIToolkitPanelGameObjectsOnStart) { - eventSystem.uiToolkitInterop.overrideUIToolkitEvents = sendEvents; - eventSystem.uiToolkitInterop.handlerTypes = createPanelGameObjectsOnStart ? (UIToolkitInteroperabilityBridge.EventHandlerTypes)~0 : 0; + foreach (BaseRuntimePanel panel in UIElementsRuntimeUtility.GetSortedPlayerPanels()) + { + CreateUIToolkitPanelGameObject(panel); + } + UIElementsRuntimeUtility.onCreatePanel += CreateUIToolkitPanelGameObject; + m_IsTrackingUIToolkitPanels = true; } + } + + private void StopTrackingUIToolkitPanels() + { + if (m_IsTrackingUIToolkitPanels) + { + UIElementsRuntimeUtility.onCreatePanel -= CreateUIToolkitPanelGameObject; + m_IsTrackingUIToolkitPanels = false; + } + } + + private void CreateUIToolkitPanelGameObject(BaseRuntimePanel panel) + { + if (panel.selectableGameObject == null) + { + var go = new GameObject(panel.name, typeof(PanelEventHandler), typeof(PanelRaycaster)); + go.transform.SetParent(transform); + panel.selectableGameObject = go; + panel.destroyed += () => DestroyImmediate(go); + } + } +#endif + + protected override void Start() + { + base.Start(); + +#if PACKAGE_UITOOLKIT + m_Started = true; + StartTrackingUIToolkitPanels(); #endif } @@ -411,24 +443,22 @@ protected override void OnEnable() m_EventSystems.Add(this); #if PACKAGE_UITOOLKIT - if (s_UIToolkitOverrideConfigOld != null) + if (m_Started && !m_IsTrackingUIToolkitPanels) { - m_UIToolkitInterop = new(); - if (!s_UIToolkitOverrideConfigOld.Value.sendEvents) - m_UIToolkitInterop.overrideUIToolkitEvents = false; - if (!s_UIToolkitOverrideConfigOld.Value.createPanelGameObjectsOnStart) - m_UIToolkitInterop.handlerTypes = 0; + StartTrackingUIToolkitPanels(); + } + if (sendUIToolkitEvents) + { + UIElementsRuntimeUtility.RegisterEventSystem(this); } - - m_UIToolkitInterop.eventSystem = this; - m_UIToolkitInterop.OnEnable(); #endif } protected override void OnDisable() { #if PACKAGE_UITOOLKIT - m_UIToolkitInterop.OnDisable(); + StopTrackingUIToolkitPanels(); + UIElementsRuntimeUtility.UnregisterEventSystem(this); #endif if (m_CurrentInputModule != null) @@ -442,15 +472,6 @@ protected override void OnDisable() base.OnDisable(); } - protected override void Start() - { - base.Start(); - -#if PACKAGE_UITOOLKIT - m_UIToolkitInterop.Start(); -#endif - } - private void TickModules() { var systemInputModulesCount = m_SystemInputModules.Count; @@ -470,13 +491,8 @@ protected virtual void OnApplicationFocus(bool hasFocus) protected virtual void Update() { -#if PACKAGE_UITOOLKIT - m_UIToolkitInterop.Update(); -#endif - if (current != this) return; - TickModules(); bool changedModule = false; diff --git a/com.unity.ugui/Runtime/UGUI/EventSystem/InputModules/BaseInputModule.cs b/com.unity.ugui/Runtime/UGUI/EventSystem/InputModules/BaseInputModule.cs index cfcdf03..9d87756 100644 --- a/com.unity.ugui/Runtime/UGUI/EventSystem/InputModules/BaseInputModule.cs +++ b/com.unity.ugui/Runtime/UGUI/EventSystem/InputModules/BaseInputModule.cs @@ -406,59 +406,5 @@ public virtual Vector2 ConvertPointerEventScrollDeltaToTicks(Vector2 scrollDelta { return scrollDelta / input.mouseScrollDeltaPerTick; } - - /// - /// Returns the type of device that generated this event. - /// - /// - /// This method is used by UI Toolkit's TextField to allow navigation in and out - /// of the TextField when using a device other than a keyboard. - /// - /// If we don't know what the source of the event is, this method should return Unknown. - /// Input Modules with more information on the event source should override this method - /// to reflect that information. - /// - /// This method's behavior is undefined for events other than Move, Submit, or Cancel. - /// - /// An event generated by this input module. - /// The type of device that generated this event. - public virtual NavigationDeviceType GetNavigationEventDeviceType(BaseEventData eventData) - { - return NavigationDeviceType.Unknown; - } - } - - /// - /// The type of device that generated a navigation event. - /// - /// - /// This can help avoid duplicated treatment of events when some controls react to keyboard input - /// and navigation events at the same time. - /// - public enum NavigationDeviceType - { - /// - /// Indicates that no specific information is known about this device. - /// - /// - /// Controls reacting to navigation events from an unknown device should react conservatively. - /// For example, if there is a conflict between a keyboard event and a subsequent navigation event, - /// a control could assume that the device type is a keyboard and conservatively block the navigation event. - /// - Unknown = 0, - /// - /// Indicates that this device is known to be a keyboard. - /// - /// - /// This device sends keyboard events along with any navigation event it generates. - /// - Keyboard, - /// - /// Indicates that this device is anything else than a keyboard (it could be a Gamepad, for example). - /// - /// - /// This device never sends keyboard events along with any navigation event it generates. - /// - NonKeyboard } } diff --git a/com.unity.ugui/Runtime/UGUI/EventSystem/InputModules/StandaloneInputModule.cs b/com.unity.ugui/Runtime/UGUI/EventSystem/InputModules/StandaloneInputModule.cs index 1fae484..b60c92f 100644 --- a/com.unity.ugui/Runtime/UGUI/EventSystem/InputModules/StandaloneInputModule.cs +++ b/com.unity.ugui/Runtime/UGUI/EventSystem/InputModules/StandaloneInputModule.cs @@ -1,5 +1,6 @@ using System; -using UnityEngine; +using System.Collections.Generic; +using UnityEngine.Pool; using UnityEngine.Serialization; namespace UnityEngine.EventSystems @@ -22,7 +23,7 @@ public class StandaloneInputModule : PointerInputModule private GameObject m_CurrentFocusedGameObject; - private PointerEventData m_InputPointerEvent; + private readonly Dictionary m_InputPointerEvents = new(); private const float doubleClickTime = 0.3f; @@ -166,18 +167,40 @@ public override void UpdateModule() { if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus()) { - if (m_InputPointerEvent != null && m_InputPointerEvent.pointerDrag != null && m_InputPointerEvent.dragging) + ReleasePointerDrags(); + return; + } + + m_LastMousePosition = m_MousePosition; + m_MousePosition = input.mousePosition; + } + + private void ReleasePointerDrags() + { + using (ListPool.Get(out var pointerIds)) + { + // Copy all current pointer IDs into a temporary list so we can iterate over them safely. + // We cannot iterate m_InputPointerEvents directly because ReleaseMouse() modifies + // m_InputPointerEvents, which would invalidate any active enumeration. + foreach (var key in m_InputPointerEvents.Keys) { - ReleaseMouse(m_InputPointerEvent, m_InputPointerEvent.pointerCurrentRaycast.gameObject); + pointerIds.Add(key); } - m_InputPointerEvent = null; + // Release drag events for all pointers + foreach (var pointerId in pointerIds) + { + if (!m_InputPointerEvents.TryGetValue(pointerId, out var inputPointerEvent)) + continue; - return; + if (inputPointerEvent != null && inputPointerEvent.pointerDrag != null && inputPointerEvent.dragging) + { + ReleaseMouse(inputPointerEvent, inputPointerEvent.pointerCurrentRaycast.gameObject); + } + } } - m_LastMousePosition = m_MousePosition; - m_MousePosition = input.mousePosition; + m_InputPointerEvents.Clear(); } private void ReleaseMouse(PointerEventData pointerEvent, GameObject currentOverGo) @@ -217,7 +240,7 @@ private void ReleaseMouse(PointerEventData pointerEvent, GameObject currentOverG HandlePointerExitAndEnter(pointerEvent, currentOverGo); } - m_InputPointerEvent = pointerEvent; + m_InputPointerEvents[pointerEvent.pointerId] = pointerEvent; } public override bool ShouldActivateModule() @@ -436,7 +459,7 @@ protected void ProcessTouchPress(PointerEventData pointerEvent, bool pressed, bo pointerEvent.pointerEnter = null; } - m_InputPointerEvent = pointerEvent; + m_InputPointerEvents[pointerEvent.pointerId] = pointerEvent; } /// @@ -645,7 +668,7 @@ protected void ProcessMousePress(MouseButtonEventData data) if (pointerEvent.pointerDrag != null) ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.initializePotentialDrag); - m_InputPointerEvent = pointerEvent; + m_InputPointerEvents[pointerEvent.pointerId] = pointerEvent; } // PointerUp notification diff --git a/com.unity.ugui/Runtime/UGUI/EventSystem/RaycastResult.cs b/com.unity.ugui/Runtime/UGUI/EventSystem/RaycastResult.cs index 35c2266..bee85c0 100644 --- a/com.unity.ugui/Runtime/UGUI/EventSystem/RaycastResult.cs +++ b/com.unity.ugui/Runtime/UGUI/EventSystem/RaycastResult.cs @@ -1,5 +1,3 @@ -using UnityEngine.UIElements; - namespace UnityEngine.EventSystems { /// @@ -78,11 +76,6 @@ public GameObject gameObject /// public int sortingOrder; - /// - /// The world position of the raycast ray origin. - /// - public Vector3 origin; - /// /// The world position of the where the raycast has hit. /// @@ -111,24 +104,6 @@ public bool isValid get { return module != null && gameObject != null; } } - // This code is disabled unless the com.unity.modules.uielements module is present. - // The UIElements module is always present in the Editor but it can be stripped from a project build if unused. -#if PACKAGE_UITOOLKIT - /// - /// The UIToolkit Document that was intersected by this raycast, if any. - /// - /// This is only useful in the context of EventSystem UI Toolkit interoperability. - /// - public UIDocument document; - - /// - /// The UIToolkit Visual Element that was intersected by this raycast, if any. - /// - /// This is only useful in the context of EventSystem UI Toolkit interoperability. - /// - public VisualElement element; -#endif - /// /// Reset the result. /// @@ -141,15 +116,9 @@ public void Clear() depth = 0; sortingLayer = 0; sortingOrder = 0; - origin = Vector3.zero; worldNormal = Vector3.up; worldPosition = Vector3.zero; screenPosition = Vector3.zero; - displayIndex = 0; -#if PACKAGE_UITOOLKIT - document = null; - element = null; -#endif } public override string ToString() diff --git a/com.unity.ugui/Runtime/UGUI/EventSystem/UIElements/PanelEventHandler.cs b/com.unity.ugui/Runtime/UGUI/EventSystem/UIElements/PanelEventHandler.cs index bb6ce08..88494eb 100644 --- a/com.unity.ugui/Runtime/UGUI/EventSystem/UIElements/PanelEventHandler.cs +++ b/com.unity.ugui/Runtime/UGUI/EventSystem/UIElements/PanelEventHandler.cs @@ -1,10 +1,9 @@ using UnityEngine.EventSystems; using UnityEngine.UI; -using System.Collections.Generic; namespace UnityEngine.UIElements { - // This code is disabled unless the com.unity.modules.uielements module is present. + // This code is disabled unless the UI Toolkit package or the com.unity.modules.uielements module are present. // The UIElements module is always present in the Editor but it can be stripped from a project build if unused. #if PACKAGE_UITOOLKIT /// @@ -45,7 +44,6 @@ public IPanel panel private Focusable currentFocusedElement => m_Panel?.focusController.GetLeafFocusedElement(); private readonly PointerEvent m_PointerEvent = new PointerEvent(); - private readonly List m_ContainedPointers = new(); private float m_LastClickTime = 0; @@ -121,58 +119,53 @@ public void OnDeselect(BaseEventData eventData) public void OnPointerMove(PointerEventData eventData) { - if (!ReadPointerData(m_PointerEvent, eventData)) + if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData)) return; using (var e = PointerMoveEvent.GetPooled(m_PointerEvent)) { - UpdatePointerEventTarget(e, m_PointerEvent); SendEvent(e, eventData); } } public void OnPointerUp(PointerEventData eventData) { - if (!ReadPointerData(m_PointerEvent, eventData, PointerEventType.Up)) + if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData, PointerEventType.Up)) return; using (var e = PointerUpEvent.GetPooled(m_PointerEvent)) { - UpdatePointerEventTarget(e, m_PointerEvent); SendEvent(e, eventData); if (e.pressedButtons == 0) - PointerDeviceState.SetElementWithSoftPointerCapture(e.pointerId, null, null); + PointerDeviceState.SetPlayerPanelWithSoftPointerCapture(e.pointerId, null); } } public void OnPointerDown(PointerEventData eventData) { - if (!ReadPointerData(m_PointerEvent, eventData, PointerEventType.Down)) + if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData, PointerEventType.Down)) return; + // Allow KeyDown/KeyUp events to be processed before pointer events. + var target = currentFocusedElement ?? m_Panel.visualTree; + ProcessImguiEvents(target); + if (eventSystem != null) eventSystem.SetSelectedGameObject(selectableGameObject); using (var e = PointerDownEvent.GetPooled(m_PointerEvent)) { - UpdatePointerEventTarget(e, m_PointerEvent); SendEvent(e, eventData); - PointerDeviceState.SetElementWithSoftPointerCapture(e.pointerId, e.elementTarget, eventData.pressEventCamera); + PointerDeviceState.SetPlayerPanelWithSoftPointerCapture(e.pointerId, m_Panel); } } public void OnPointerExit(PointerEventData eventData) { - m_ContainedPointers.Remove(eventData); - - if (!ReadPointerData(m_PointerEvent, eventData)) - { - if (m_Panel != null && !m_Panel.isFlat) - m_Panel.PointerLeavesPanel(m_PointerEvent.pointerId); + if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData)) return; - } // If a pointer exit is called while the pointer is still on top of this object, it means // there's something else removing the pointer, so we might need to send a PointerCancelEvent. @@ -184,23 +177,21 @@ public void OnPointerExit(PointerEventData eventData) { using (var e = PointerCancelEvent.GetPooled(m_PointerEvent)) { - UpdatePointerEventTarget(e, m_PointerEvent); SendEvent(e, eventData); if (e.pressedButtons == 0) - PointerDeviceState.SetElementWithSoftPointerCapture(e.pointerId, null, null); + PointerDeviceState.SetPlayerPanelWithSoftPointerCapture(e.pointerId, null); } } - m_Panel.PointerLeavesPanel(m_PointerEvent.pointerId); + m_Panel.PointerLeavesPanel(m_PointerEvent.pointerId, m_PointerEvent.position); } public void OnPointerEnter(PointerEventData eventData) { - if (!ReadPointerData(m_PointerEvent, eventData)) + if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData)) return; - m_ContainedPointers.Add(eventData); m_Panel.PointerEntersPanel(m_PointerEvent.pointerId, m_PointerEvent.position); } @@ -218,7 +209,7 @@ public void OnSubmit(BaseEventData eventData) var target = currentFocusedElement ?? m_Panel.visualTree; ProcessImguiEvents(target); - using (var e = NavigationSubmitEvent.GetPooled(GetDeviceType(eventData), s_Modifiers)) + using (var e = NavigationSubmitEvent.GetPooled(s_Modifiers)) { e.target = target; SendEvent(e, eventData); @@ -234,7 +225,7 @@ public void OnCancel(BaseEventData eventData) var target = currentFocusedElement ?? m_Panel.visualTree; ProcessImguiEvents(target); - using (var e = NavigationCancelEvent.GetPooled(GetDeviceType(eventData), s_Modifiers)) + using (var e = NavigationCancelEvent.GetPooled(s_Modifiers)) { e.target = target; SendEvent(e, eventData); @@ -250,7 +241,7 @@ public void OnMove(AxisEventData eventData) var target = currentFocusedElement ?? m_Panel.visualTree; ProcessImguiEvents(target); - using (var e = NavigationMoveEvent.GetPooled(eventData.moveVector, GetDeviceType(eventData), s_Modifiers)) + using (var e = NavigationMoveEvent.GetPooled(eventData.moveVector, s_Modifiers)) { e.target = target; SendEvent(e, eventData); @@ -261,7 +252,7 @@ public void OnMove(AxisEventData eventData) public void OnScroll(PointerEventData eventData) { - if (!ReadPointerData(m_PointerEvent, eventData)) + if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData)) return; var uguiScrollDelta = eventData.scrollDelta; @@ -294,16 +285,10 @@ private void SendEvent(EventBase e, Event sourceEvent) // See UGUIEventSystemTests.KeyDownStoppedDoesntPreventNavigationEvents for a test requires this. } - /// - /// This method is automatically called on every frame. - /// It can also be called manually to force some queued events to be processed. - /// - public void Update() + internal void Update() { if (isCurrentFocusedPanel) ProcessImguiEvents(currentFocusedElement ?? m_Panel.visualTree); - - UpdateWorldSpacePointers(); } void LateUpdate() @@ -394,52 +379,19 @@ private void SendKeyDownEvent(Event e, Focusable target) private bool ReadPointerData(PointerEvent pe, PointerEventData eventData, PointerEventType eventType = PointerEventType.Default) { - if (m_Panel == null || eventSystem == null || eventSystem.currentInputModule == null) + if (eventSystem == null || eventSystem.currentInputModule == null) return false; pe.Read(this, eventData, eventType); - if (!pe.ComputeTarget(m_Panel)) - return false; + // PointerEvents making it this far have been validated by PanelRaycaster already + m_Panel.ScreenToPanel(pe.position, pe.deltaPosition, + out var panelPosition, out var panelDelta, allowOutside:true); + pe.SetPosition(panelPosition, panelDelta); return true; } - private void UpdatePointerEventTarget(TPointerEvent e, PointerEvent eventData) - where TPointerEvent : PointerEventBase, new() - { - e.target = eventData.elementTarget; - - if (!m_Panel.isFlat) - { - // World-space panels set their top element manually instead of using RecomputeElementUnderPointer. - m_Panel.SetTopElementUnderPointer(eventData.pointerId, eventData.elementUnderPointer, e); - } - } - - private UIElements.NavigationDeviceType GetDeviceType(BaseEventData eventData) - { - if (eventSystem == null || eventSystem.currentInputModule == null) - return NavigationDeviceType.Unknown; - return (UIElements.NavigationDeviceType)eventSystem.currentInputModule.GetNavigationEventDeviceType( - eventData); - } - - private void UpdateWorldSpacePointers() - { - if (m_Panel == null || m_Panel.isFlat || eventSystem == null || eventSystem.currentInputModule == null) - return; - - foreach (var eventData in m_ContainedPointers) - { - if (!ReadPointerData(m_PointerEvent, eventData)) - continue; - - m_Panel.SetTopElementUnderPointer(m_PointerEvent.pointerId, m_PointerEvent.elementUnderPointer, m_PointerEvent.position); - m_Panel.CommitElementUnderPointers(); - } - } - enum PointerEventType { Default, Down, Up @@ -447,7 +399,6 @@ enum PointerEventType class PointerEvent : IPointerEvent { - public int pointerId { get; private set; } public string pointerType { get; private set; } public bool isPrimary { get; private set; } @@ -479,13 +430,6 @@ class PointerEvent : IPointerEvent ? commandKey : ctrlKey; - public Vector3 screenPosition { get; private set; } - public Vector3 screenDelta { get; private set; } - public Ray worldRay { get; private set; } - public UIDocument document { get; private set; } - public VisualElement elementTarget { get; private set; } - public VisualElement elementUnderPointer { get; private set; } - public void Read(PanelEventHandler self, PointerEventData eventData, PointerEventType eventType) { pointerId = self.eventSystem.currentInputModule.ConvertUIToolkitPointerId(eventData); @@ -507,22 +451,15 @@ public void Read(PanelEventHandler self, PointerEventData eventData, PointerEven Vector3 eventPosition = MultipleDisplayUtilities.GetRelativeMousePositionForRaycast(eventData); int eventDisplayIndex = (int)eventPosition.z; - if (UnityEngineInternal.DisplayInternal.IsASecondaryDisplayIndex(eventDisplayIndex)) - { -#if UNITY_ANDROID - // Changed for UITK to be coherent for Android which passes display-relative rendering coordinates - h = Display.displays[eventDisplayIndex].renderingHeight; -#else + if (eventDisplayIndex > 0 && eventDisplayIndex < Display.displays.Length) h = Display.displays[eventDisplayIndex].systemHeight; -#endif - } var delta = eventData.delta; eventPosition.y = h - eventPosition.y; delta.y = -delta.y; - screenPosition = eventPosition; - screenDelta = delta; + localPosition = position = eventPosition; + deltaPosition = delta; deltaTime = 0; //TODO: find out what's expected here. Time since last frame? Since last sent event? pressure = eventData.pressure; @@ -568,47 +505,12 @@ public void Read(PanelEventHandler self, PointerEventData eventData, PointerEven } pressedButtons = PointerDeviceState.GetPressedButtons(pointerId); - - var origin = eventData.pointerCurrentRaycast.origin; - worldRay = new Ray(origin, eventData.pointerCurrentRaycast.worldPosition - origin); - document = eventData.pointerCurrentRaycast.document; - elementUnderPointer = eventData.pointerCurrentRaycast.element; - } - - public bool ComputeTarget(BaseRuntimePanel panel) - { - Vector3 panelPosition; - if (panel.isFlat) - { - // PointerEvents making it this far have been validated by PanelRaycaster already - panel.ScreenToPanel(screenPosition, screenDelta, - out panelPosition, allowOutside:true); - elementTarget = null; - } - else - { - if (document == null) - return false; - - var capturingElement = RuntimePanel.s_EventDispatcher.pointerState.GetCapturingElement(pointerId) as VisualElement; - if (capturingElement != null && capturingElement.panel != panel) - return false; - - elementTarget = capturingElement ?? elementUnderPointer ?? document.rootVisualElement; - panelPosition = GetPanelPosition(elementTarget, document, worldRay); - } - - localPosition = position = panelPosition; - deltaPosition = PointerDeviceState.GetPointerDeltaPosition(pointerId, ContextType.Player, position); - return true; } - Vector3 GetPanelPosition(VisualElement pickedElement, UIDocument document, Ray worldRay) + public void SetPosition(Vector3 positionOverride, Vector3 deltaOverride) { - var documentRay = document.transform.worldToLocalMatrix.TransformRay(worldRay); - pickedElement.IntersectWorldRay(documentRay, out var distanceWithinDocument, out _); - var documentPoint = documentRay.origin + documentRay.direction * distanceWithinDocument; - return documentPoint; + localPosition = position = positionOverride; + deltaPosition = deltaOverride; } } } diff --git a/com.unity.ugui/Runtime/UGUI/EventSystem/UIElements/PanelRaycaster.cs b/com.unity.ugui/Runtime/UGUI/EventSystem/UIElements/PanelRaycaster.cs index f022211..c1352e7 100644 --- a/com.unity.ugui/Runtime/UGUI/EventSystem/UIElements/PanelRaycaster.cs +++ b/com.unity.ugui/Runtime/UGUI/EventSystem/UIElements/PanelRaycaster.cs @@ -6,7 +6,7 @@ namespace UnityEngine.UIElements { - // This code is disabled unless the com.unity.modules.uielements module is present. + // This code is disabled unless the UI Toolkit package or the com.unity.modules.uielements module are present. // The UIElements module is always present in the Editor but it can be stripped from a project build if unused. #if PACKAGE_UITOOLKIT /// @@ -62,41 +62,54 @@ void OnPanelDestroyed() public override int sortOrderPriority => Mathf.FloorToInt(m_Panel?.sortingPriority ?? 0f); public override int renderOrderPriority => int.MaxValue - (UIElementsRuntimeUtility.s_ResolvedSortingIndexMax - (m_Panel?.resolvedSortingIndex ?? 0)); - private static ScreenOverlayPanelPicker panelPicker = new ScreenOverlayPanelPicker(); - public override void Raycast(PointerEventData eventData, List resultAppendList) { - if (m_Panel == null || !m_Panel.isFlat) + if (m_Panel == null) return; var displayIndex = m_Panel.targetDisplay; Vector3 eventPosition = MultipleDisplayUtilities.GetRelativeMousePositionForRaycast(eventData); + + // Discard events that are not part of this display so the user does not interact with multiple displays at once. + if ((int) eventPosition.z != displayIndex) + return; + var position = eventPosition; var delta = eventData.delta; float h = Screen.height; - if (UnityEngineInternal.DisplayInternal.IsASecondaryDisplayIndex(displayIndex)) + if (displayIndex > 0 && displayIndex < Display.displays.Length) { -#if UNITY_ANDROID - // Changed for UITK to be coherent for Android which passes display-relative rendering coordinates - h = Display.displays[displayIndex].renderingHeight; -#else - h = Display.displays[displayIndex].systemHeight; -#endif + h = Display.displays[displayIndex].systemHeight; } position.y = h - position.y; delta.y = -delta.y; - var currentInputModule = eventData.currentInputModule; - if (currentInputModule == null) + var eventSystem = UIElementsRuntimeUtility.activeEventSystem as EventSystem; + if (eventSystem == null || eventSystem.currentInputModule == null) return; - var pointerId = currentInputModule.ConvertUIToolkitPointerId(eventData); + var pointerId = eventSystem.currentInputModule.ConvertUIToolkitPointerId(eventData); - if (!panelPicker.TryPick((RuntimePanel)m_Panel, pointerId, position, delta, (int)eventPosition.z, out _)) + var capturingElement = m_Panel.GetCapturingElement(pointerId); + if (capturingElement is VisualElement ve && ve.panel != m_Panel && ve.panel != null) return; + var capturingPanel = PointerDeviceState.GetPlayerPanelWithSoftPointerCapture(pointerId); + if (capturingPanel != null && capturingPanel != m_Panel) + return; + + if (capturingElement == null && capturingPanel == null) + { + if (!m_Panel.ScreenToPanel(position, delta, out var panelPosition, out _)) + return; + + var pick = m_Panel.Pick(panelPosition); + if (pick == null) + return; + } + resultAppendList.Add(new RaycastResult { gameObject = selectableGameObject, diff --git a/com.unity.ugui/Runtime/UGUI/EventSystem/UIElements/UIToolkitInteroperabilityBridge.cs b/com.unity.ugui/Runtime/UGUI/EventSystem/UIElements/UIToolkitInteroperabilityBridge.cs deleted file mode 100644 index ae3a2e7..0000000 --- a/com.unity.ugui/Runtime/UGUI/EventSystem/UIElements/UIToolkitInteroperabilityBridge.cs +++ /dev/null @@ -1,365 +0,0 @@ -using System; -using System.Collections.Generic; -using UnityEngine.EventSystems; - -namespace UnityEngine.UIElements -{ - // This code is disabled unless the com.unity.modules.uielements module is present. - // The UIElements module is always present in the Editor but it can be stripped from a project build if unused. -#if PACKAGE_UITOOLKIT - /// - /// Enables UI Toolkit interoperability with uGUI events. - /// - internal class UIToolkitInteroperabilityBridge - { - [Flags] - public enum EventHandlerTypes - { - ScreenOverlay = 1, - WorldSpace = 2, - } - - private EventSystem m_EventSystem; - internal EventSystem eventSystem - { - get => m_EventSystem; - set - { - if (m_EventSystem == value) return; - Debug.Assert(!m_Started && !m_Enabled, "Expect EventSystem set before OnEnable and Start"); - m_EventSystem = value; - } - } - - private bool m_OverrideUIToolkitEvents = true; - private EventHandlerTypes m_HandlerTypes = EventHandlerTypes.ScreenOverlay | EventHandlerTypes.WorldSpace; - private LayerMask m_WorldPickingLayers = Physics.DefaultRaycastLayers; - private float m_WorldPickingMaxDistance = Mathf.Infinity; - private bool m_CreateDefaultPanelComponents = true; - - private bool m_Started; - private bool m_Enabled; - private bool m_IsTrackingPanels; - private GameObject m_WorldSpaceGo; - - public bool overrideUIToolkitEvents - { - get => m_OverrideUIToolkitEvents; - internal set - { - m_OverrideUIToolkitEvents = value; - ApplyOverrideUIToolkitEvents(); - } - } - - public EventHandlerTypes handlerTypes - { - get => m_HandlerTypes; - internal set - { - m_HandlerTypes = value; - ApplyOtherProperties(); - } - } - - public int worldPickingLayers - { - get => m_WorldPickingLayers; - internal set => m_WorldPickingLayers = value; - } - - public float worldPickingMaxDistance - { - get => m_WorldPickingMaxDistance; - internal set => m_WorldPickingMaxDistance = value; - } - - public bool createDefaultPanelComponents - { - get => m_CreateDefaultPanelComponents; - internal set - { - m_CreateDefaultPanelComponents = value; - ApplyOtherProperties(); - } - } - - private bool shouldTrackPanels => overrideUIToolkitEvents && createDefaultPanelComponents && - m_Started && m_Enabled; - - private void StartTrackingUIToolkitPanels() - { - if (m_IsTrackingPanels || !shouldTrackPanels) return; - - foreach (var panel in UIElementsRuntimeUtility.GetSortedPlayerPanels()) - { - StartTrackingPanel(panel); - } - - UIElementsRuntimeUtility.onCreatePanel += StartTrackingPanel; - m_IsTrackingPanels = true; - } - - private readonly HashSet trackedPanels = new(); - private void StartTrackingPanel(BaseRuntimePanel panel) - { - trackedPanels.Add(panel); - } - - private void StopTrackingUIToolkitPanels() - { - if (!m_IsTrackingPanels) return; - - UIElementsRuntimeUtility.onCreatePanel -= StartTrackingPanel; - m_IsTrackingPanels = false; - - foreach (var panel in trackedPanels) - { - DestroyPanelGameObject(panel); - } - trackedPanels.Clear(); - - DestroyWorldSpacePanelGameObject(); - } - - private void UpdatePanelGameObject(BaseRuntimePanel panel) - { - var handlerType = panel.isFlat ? EventHandlerTypes.ScreenOverlay : EventHandlerTypes.WorldSpace; - if ((m_HandlerTypes & handlerType) != 0) - { - CreatePanelGameObject(panel); - } - else - { - DestroyPanelGameObject(panel); - } - } - - private readonly Dictionary destroyedActions = new(); - private void CreatePanelGameObject(BaseRuntimePanel panel) - { - if (panel.selectableGameObject == null) - { - var go = new GameObject(panel.name, typeof(PanelEventHandler), typeof(PanelRaycaster)); - go.transform.SetParent(m_EventSystem.transform); - panel.selectableGameObject = go; - var destroyed = destroyedActions[panel] = () => UIRUtility.Destroy(go); - panel.destroyed += destroyed; - } - } - - private void DestroyPanelGameObject(BaseRuntimePanel panel) - { - var go = panel.selectableGameObject; - if (go != null) - { - if (!destroyedActions.Remove(panel, out var destroyed)) - return; // this object wasn't created by us, so leave it untouched - panel.destroyed -= destroyed; - panel.selectableGameObject = null; - UIRUtility.Destroy(go); - } - } - - private void CreateWorldSpacePanelGameObject() - { - // This will destroy m_WorldSpaceGo if raycaster components need to change. - ApplyCameraProperties(); - - if (m_WorldSpaceGo == null) - { - var go = new GameObject("WorldDocumentRaycaster"); - go.transform.SetParent(m_EventSystem.transform); - - if (m_InputSettings.defaultEventCameraIsMainCamera) - { - go.AddComponent(); - } - else - { - foreach (var cam in m_InputSettings.eventCameras) - { - go.AddComponent().camera = cam; - } - } - - m_WorldSpaceGo = go; - } - } - - private void DestroyWorldSpacePanelGameObject() - { - var go = m_WorldSpaceGo; - m_WorldSpaceGo = null; - UIRUtility.Destroy(go); - } - - public void Start() - { - m_Started = true; - StartTrackingUIToolkitPanels(); - } - - public void OnEnable() - { - if (m_Enabled) return; - m_Enabled = true; - - if (PanelInputConfiguration.current != null) - Apply(PanelInputConfiguration.current); - PanelInputConfiguration.onApply += Apply; - - if (m_Started) - StartTrackingUIToolkitPanels(); - - if (m_OverrideUIToolkitEvents) - UIElementsRuntimeUtility.RegisterEventSystem(m_EventSystem); - } - - public void OnDisable() - { - if (!m_Enabled) return; - m_Enabled = false; - - PanelInputConfiguration.onApply -= Apply; - - StopTrackingUIToolkitPanels(); - UIElementsRuntimeUtility.UnregisterEventSystem(m_EventSystem); - } - - public void Update() - { - UpdatePanelGameObjects(); - } - - private PanelInputConfiguration.Settings m_InputSettings = PanelInputConfiguration.Settings.Default; - void Apply(PanelInputConfiguration input) - { - m_InputSettings = input != null ? input.settings : PanelInputConfiguration.Settings.Default; - m_OverrideUIToolkitEvents = - m_InputSettings.panelInputRedirection != PanelInputConfiguration.PanelInputRedirection.Never; - m_HandlerTypes = EventHandlerTypes.ScreenOverlay | - (m_InputSettings.processWorldSpaceInput ? EventHandlerTypes.WorldSpace : 0); - m_WorldPickingLayers = m_InputSettings.interactionLayers; - m_WorldPickingMaxDistance = m_InputSettings.maxInteractionDistance; - m_CreateDefaultPanelComponents = m_InputSettings.autoCreatePanelComponents; - - ApplyOverrideUIToolkitEvents(); - ApplyCameraProperties(); - ApplyOtherProperties(); - } - - private bool m_OldOverrideUIToolkitEvents = true; - private EventHandlerTypes m_OldHandlerTypes = EventHandlerTypes.ScreenOverlay | EventHandlerTypes.WorldSpace; - private bool m_OldCreateDefaultPanelComponents = true; - private bool m_OldDefaultEventCameraIsMainCamera = true; - private long m_OldEventCamerasHash = 0; - - private void ApplyOverrideUIToolkitEvents() - { - if (m_OldOverrideUIToolkitEvents == m_OverrideUIToolkitEvents) return; - m_OldOverrideUIToolkitEvents = m_OverrideUIToolkitEvents; - - if (!m_Enabled) return; - - if (m_OverrideUIToolkitEvents) - { - UIElementsRuntimeUtility.RegisterEventSystem(m_EventSystem); - } - else - { - UIElementsRuntimeUtility.UnregisterEventSystem(m_EventSystem); - } - - UpdatePanelTracking(); - } - - private void ApplyCameraProperties() - { - bool dirty = false; - - if (m_OldDefaultEventCameraIsMainCamera != m_InputSettings.defaultEventCameraIsMainCamera) - { - m_OldDefaultEventCameraIsMainCamera = m_InputSettings.defaultEventCameraIsMainCamera; - dirty = true; - } - - if (!m_InputSettings.defaultEventCameraIsMainCamera) - { - var hashCode = 0; - foreach (var cam in m_InputSettings.eventCameras) - { - hashCode = (hashCode * 397) ^ cam.GetHashCode(); - } - - if (m_OldEventCamerasHash != hashCode) - { - m_OldEventCamerasHash = hashCode; - dirty = true; - } - } - else - { - m_OldEventCamerasHash = 0; - } - - if (dirty) - DestroyWorldSpacePanelGameObject(); - } - - private void ApplyOtherProperties() - { - bool dirty = false; - - if (m_OldHandlerTypes != m_HandlerTypes) - { - m_OldHandlerTypes = m_HandlerTypes; - dirty = true; - } - - if (m_OldCreateDefaultPanelComponents != m_CreateDefaultPanelComponents) - { - m_OldCreateDefaultPanelComponents = m_CreateDefaultPanelComponents; - dirty = true; - } - - if (dirty) - UpdatePanelTracking(); - } - - private void UpdatePanelTracking() - { - if (shouldTrackPanels) - { - StartTrackingUIToolkitPanels(); - } - else - { - StopTrackingUIToolkitPanels(); - } - } - - private void UpdatePanelGameObjects() - { - if (!m_IsTrackingPanels) return; - - bool isWorldSpaceActive = false; - - foreach (var panel in trackedPanels) - { - UpdatePanelGameObject(panel); - isWorldSpaceActive |= !panel.isFlat; - } - - if (isWorldSpaceActive && (m_HandlerTypes & EventHandlerTypes.WorldSpace) != 0) - { - CreateWorldSpacePanelGameObject(); - } - else - { - DestroyWorldSpacePanelGameObject(); - } - } - } -#endif -} diff --git a/com.unity.ugui/Runtime/UGUI/EventSystem/UIElements/UIToolkitInteroperabilityBridge.cs.meta b/com.unity.ugui/Runtime/UGUI/EventSystem/UIElements/UIToolkitInteroperabilityBridge.cs.meta deleted file mode 100644 index 90a0015..0000000 --- a/com.unity.ugui/Runtime/UGUI/EventSystem/UIElements/UIToolkitInteroperabilityBridge.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: ba923e2b58fea044bb1f8839376a1118 diff --git a/com.unity.ugui/Runtime/UGUI/EventSystem/UIElements/WorldDocumentRaycaster.cs b/com.unity.ugui/Runtime/UGUI/EventSystem/UIElements/WorldDocumentRaycaster.cs deleted file mode 100644 index a617a5b..0000000 --- a/com.unity.ugui/Runtime/UGUI/EventSystem/UIElements/WorldDocumentRaycaster.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System.Collections.Generic; -using UnityEngine.EventSystems; -using UnityEngine.UI; - -namespace UnityEngine.UIElements -{ - // This code is disabled unless the com.unity.modules.uielements module is present. - // The UIElements module is always present in the Editor but it can be stripped from a project build if unused. -#if PACKAGE_UITOOLKIT - /// - /// A derived BaseRaycaster to raycast against UI Toolkit world-space document instances at runtime. - /// - [AddComponentMenu("UI Toolkit/World Document Raycaster (UI Toolkit)")] - public class WorldDocumentRaycaster : BaseRaycaster - { - [SerializeField] private Camera m_EventCamera; - - /// - /// The camera that will generate rays for this raycaster. - /// - public override Camera eventCamera => m_EventCamera; - - /// - /// The camera used by this Raycaster to convert screen coordinates to Rays. - /// If empty, Camera.main is going to be used. - /// - public new Camera camera - { - get => m_EventCamera; - set => m_EventCamera = value; - } - - private static PhysicsDocumentPicker worldPicker = new(); - - /// - /// Raycast against the scene. - /// - /// Current event data. - /// List of hit Objects. - public override void Raycast(PointerEventData eventData, List resultAppendList) - { - var currentInputModule = EventSystem.current != null ? EventSystem.current.currentInputModule : null; - if (currentInputModule == null) - return; - - if (!GetWorldRay(eventData, out var worldRay, out var maxDistance, out var layerMask)) - return; - - maxDistance = Mathf.Min(maxDistance, EventSystem.current.uiToolkitInterop.worldPickingMaxDistance); - layerMask &= EventSystem.current.uiToolkitInterop.worldPickingLayers; - - var pointerId = currentInputModule.ConvertUIToolkitPointerId(eventData); - - var capturingCamera = PointerDeviceState.GetCameraWithSoftPointerCapture(pointerId); - if (capturingCamera != null) - { - var cam = m_EventCamera != null ? m_EventCamera : Camera.main; - if (capturingCamera != cam) - return; - } - - if (!worldPicker.TryPickWithCapture(pointerId, worldRay, maxDistance, layerMask, out _, - out var document, out var elementUnderPointer, out var distance, out var captured)) - return; - - resultAppendList.Add(new RaycastResult - { - // Discard hits against non-UI objects. They should block UI but not hide the PhysicsRaycaster results. - gameObject = document == null ? gameObject : document.containerPanel.selectableGameObject, - origin = worldRay.origin, - worldPosition = worldRay.origin + distance * worldRay.direction, - document = document, - element = elementUnderPointer, - module = this, - distance = distance, - sortingOrder = captured ? int.MaxValue : 0, - }); - } - - /// - /// Creates a ray from a given pointer event, according to this raycaster's properties. - /// Also allows the raycaster to constrain the distance and layers that the ray can use. - /// - /// Current event data. - /// The created ray. - /// A distance constraint for the created ray. - /// A layer constraint for the created ray. - /// Returns true if a valid ray could be created. - protected virtual bool GetWorldRay(PointerEventData eventData, out Ray worldRay, out float maxDistance, out int layerMask) - { - var cam = m_EventCamera != null ? m_EventCamera : Camera.main; - if (cam == null) - { - worldRay = default; - maxDistance = 0; - layerMask = 0; - return false; - } - - maxDistance = cam.farClipPlane; - layerMask = cam.cullingMask; - - Vector3 eventPosition = MultipleDisplayUtilities.GetRelativeMousePositionForRaycast(eventData); - - // Discard events that are not part of this display so the user does not interact with multiple displays at once. - if ((int)eventPosition.z != cam.targetDisplay) - { - worldRay = default; - return false; - } - - worldRay = cam.ScreenPointToRay(eventPosition); - - return true; - } - } -#endif -} diff --git a/com.unity.ugui/Runtime/UGUI/EventSystem/UIElements/WorldDocumentRaycaster.cs.meta b/com.unity.ugui/Runtime/UGUI/EventSystem/UIElements/WorldDocumentRaycaster.cs.meta deleted file mode 100644 index 36cf62a..0000000 --- a/com.unity.ugui/Runtime/UGUI/EventSystem/UIElements/WorldDocumentRaycaster.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 2c727e4c005e5114fb3348a38c4cebbf diff --git a/com.unity.ugui/Runtime/UGUI/UI/Core/Graphic.cs b/com.unity.ugui/Runtime/UGUI/UI/Core/Graphic.cs index 8a1fe9a..6bddd68 100644 --- a/com.unity.ugui/Runtime/UGUI/UI/Core/Graphic.cs +++ b/com.unity.ugui/Runtime/UGUI/UI/Core/Graphic.cs @@ -858,52 +858,81 @@ protected bool Raycast(Vector2 sp, Camera eventCamera, bool ignoreMasks) bool ignoreParentGroups = false; bool continueTraversal = true; + bool isParent = false; while (t != null) { + bool raycastValid = true; + bool hasMask = false; + bool maskableGraphicRaycastValid = true; + t.GetComponents(components); for (var i = 0; i < components.Count; i++) { - var canvas = components[i] as Canvas; + var component = components[i]; + var canvas = component as Canvas; if (canvas != null && canvas.overrideSorting) continueTraversal = false; - var filter = components[i] as ICanvasRaycastFilter; - + var filter = component as ICanvasRaycastFilter; // Image, Mask, RectMask2D, CanvasGroup if (filter == null) continue; - if (ignoreMasks && components[i] is Mask or RectMask2D) + if (ignoreMasks && component is Mask or RectMask2D) continue; - var raycastValid = true; - - var group = components[i] as CanvasGroup; - if (group != null) + if (component is CanvasGroup group) { if (!group.enabled) continue; - if (ignoreParentGroups == false && group.ignoreParentGroups) + if (ignoreParentGroups == false) { - ignoreParentGroups = true; + if (group.ignoreParentGroups) + ignoreParentGroups = true; + raycastValid = filter.IsRaycastLocationValid(sp, eventCamera); + if (!raycastValid) + break; } - else if (!ignoreParentGroups) - raycastValid = filter.IsRaycastLocationValid(sp, eventCamera); } else { + if (isParent && component is Graphic graphic && !graphic.raycastTarget) + continue; + + hasMask |= component is Mask; + raycastValid = filter.IsRaycastLocationValid(sp, eventCamera); - } - if (!raycastValid) - { - ListPool.Release(components); - return false; + // Try to early-out if the raycast is not valid + if (!raycastValid) + { + // Cache the raycast result for parent MaskableGraphics and continue processing components + // unless we already found a Mask and are not ignoring masks + if (isParent && component is MaskableGraphic) + { + maskableGraphicRaycastValid = raycastValid; + if (ignoreMasks || !hasMask) + { + raycastValid = true; + continue; + } + } + + break; + } } } + + if (!raycastValid || (hasMask && !maskableGraphicRaycastValid)) + { + ListPool.Release(components); + return false; + } + t = continueTraversal ? t.parent : null; + isParent = true; } ListPool.Release(components); return true; diff --git a/com.unity.ugui/Runtime/UGUI/UI/Core/GraphicRaycaster.cs b/com.unity.ugui/Runtime/UGUI/UI/Core/GraphicRaycaster.cs index 552d0b2..d591c2e 100644 --- a/com.unity.ugui/Runtime/UGUI/UI/Core/GraphicRaycaster.cs +++ b/com.unity.ugui/Runtime/UGUI/UI/Core/GraphicRaycaster.cs @@ -155,16 +155,10 @@ public override void Raycast(PointerEventData eventData, List res // so we use the standard none multiple display method. (case 741751) float w = Screen.width; float h = Screen.height; - if (UnityEngineInternal.DisplayInternal.IsASecondaryDisplayIndex(displayIndex)) + if (displayIndex > 0 && displayIndex < Display.displays.Length) { -#if UNITY_ANDROID - // Changed to be coherent for Android which passes display-relative rendering coordinates - w = Display.displays[displayIndex].renderingWidth; - h = Display.displays[displayIndex].renderingHeight; -#else w = Display.displays[displayIndex].systemWidth; h = Display.displays[displayIndex].systemHeight; -#endif } pos = new Vector2(eventPosition.x / w, eventPosition.y / h); } diff --git a/com.unity.ugui/Runtime/UGUI/UI/Core/Image.cs b/com.unity.ugui/Runtime/UGUI/UI/Core/Image.cs index 3733bb8..79d04ca 100644 --- a/com.unity.ugui/Runtime/UGUI/UI/Core/Image.cs +++ b/com.unity.ugui/Runtime/UGUI/UI/Core/Image.cs @@ -285,7 +285,7 @@ public Sprite sprite if (m_Sprite != value) { m_SkipLayoutUpdate = m_Sprite.rect.size.Equals(value ? value.rect.size : Vector2.zero); - m_SkipMaterialUpdate = (m_Sprite.texture == (value ? value.texture : null)) && !CheckSecondaryTexturesChanged(value); + m_SkipMaterialUpdate = m_Sprite.texture == (value ? value.texture : null); m_Sprite = value; ResetAlphaHitThresholdIfNeeded(); @@ -296,7 +296,7 @@ public Sprite sprite else if (value != null) { m_SkipLayoutUpdate = value.rect.size == Vector2.zero; - m_SkipMaterialUpdate = (value.texture == null) && (value.GetSecondaryTextureCount() == 0); + m_SkipMaterialUpdate = value.texture == null; m_Sprite = value; ResetAlphaHitThresholdIfNeeded(); @@ -930,96 +930,6 @@ protected override void OnDisable() UnTrackImage(this); } - static SecondarySpriteTexture[] s_TempNewSecondaryTextures = {}; - SecondarySpriteTexture [] m_SecondaryTextures; - - internal SecondarySpriteTexture [] secondaryTextures => m_SecondaryTextures; // Internal for testing only - - static void ClearArray(ref SecondarySpriteTexture[] array) - { - array = Array.Empty(); - } - - bool CheckSecondaryTexturesChanged(Sprite sprite) - { - var changed = CheckSecondaryTexturesChanged(sprite, ref s_TempNewSecondaryTextures); - ClearArray(ref s_TempNewSecondaryTextures); - return changed; - } - - bool CheckSecondaryTexturesChanged(Sprite sprite, ref SecondarySpriteTexture [] newSecondaryTextures) - { - newSecondaryTextures ??= new SecondarySpriteTexture[0]; - - bool Compare(SecondarySpriteTexture[] array1, SecondarySpriteTexture[] array2) - { - if (array1.Length != array2.Length) - return false; - - for (var i = 0; i < array1.Length; ++i) - { - if (array1[i] != array2[i]) - return false; - } - - return true; - } - - var oldSecondaryTexCount = m_SecondaryTextures == null ? 0 : m_SecondaryTextures.Length; - var newSecondaryTexCount = sprite != null ? sprite.GetSecondaryTextureCount() : 0; - - // If the secondary texture count remained 0 then return - if (oldSecondaryTexCount == 0 && newSecondaryTexCount == 0) - return false; - - if (sprite != null) - { - Array.Resize(ref newSecondaryTextures, newSecondaryTexCount); - sprite.GetSecondaryTextures(newSecondaryTextures); - } - else - { - ClearArray(ref newSecondaryTextures); - } - - // If the list of secondary textures has not changed then return - if (m_SecondaryTextures != null && Compare(m_SecondaryTextures, newSecondaryTextures)) - return false; - - return true; - } - - internal void SetSecondaryTextures(CanvasRenderer renderer) - { - if (CheckSecondaryTexturesChanged(activeSprite, ref s_TempNewSecondaryTextures)) - { - if (s_TempNewSecondaryTextures.Length == 0) - m_SecondaryTextures = null; - else - { - if (m_SecondaryTextures == null) - m_SecondaryTextures = new SecondarySpriteTexture[s_TempNewSecondaryTextures.Length]; - else - Array.Resize(ref m_SecondaryTextures, s_TempNewSecondaryTextures.Length); - Array.Copy(s_TempNewSecondaryTextures, m_SecondaryTextures, s_TempNewSecondaryTextures.Length); - } - } - - renderer.SetSecondaryTextureCount(m_SecondaryTextures?.Length ?? 0); - - if (m_SecondaryTextures != null) - { - for (var i = 0; i < m_SecondaryTextures.Length; ++i) - { - var secondaryTex = m_SecondaryTextures[i]; - - renderer.SetSecondaryTexture(i, secondaryTex.name, secondaryTex.texture); - } - } - - ClearArray(ref s_TempNewSecondaryTextures); - } - /// /// Update the renderer's material. /// @@ -1042,8 +952,6 @@ protected override void UpdateMaterial() { canvasRenderer.SetAlphaTexture(alphaTex); } - - SetSecondaryTextures(canvasRenderer); } protected override void OnCanvasHierarchyChanged() @@ -1925,12 +1833,6 @@ public virtual bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCame float x = local.x / activeSprite.texture.width; float y = local.y / activeSprite.texture.height; - // Locations outside the image are always considered valid. - // This guarantees that the behavior remains consistent with the case where alphaHitTestMinimumThreshold <= 0. - // Without this check, we would continue to sample a pixel outside the texture. - if (x < 0 || x > 1 || y < 0 || y > 1) - return true; - try { return activeSprite.texture.GetPixelBilinear(x, y).a >= alphaHitTestMinimumThreshold; @@ -2024,6 +1926,7 @@ protected override void OnValidate() base.OnValidate(); m_PixelsPerUnitMultiplier = Mathf.Max(0.01f, m_PixelsPerUnitMultiplier); } + #endif } } diff --git a/com.unity.ugui/Runtime/UGUI/UI/Core/Layout/LayoutGroup.cs b/com.unity.ugui/Runtime/UGUI/UI/Core/Layout/LayoutGroup.cs index d5480b9..6c14ff9 100644 --- a/com.unity.ugui/Runtime/UGUI/UI/Core/Layout/LayoutGroup.cs +++ b/com.unity.ugui/Runtime/UGUI/UI/Core/Layout/LayoutGroup.cs @@ -134,6 +134,7 @@ protected LayoutGroup() protected override void OnEnable() { base.OnEnable(); + rectTransform.sendChildDimensionsChange = true; SetDirty(); } @@ -141,6 +142,7 @@ protected override void OnDisable() { m_Tracker.Clear(); LayoutRebuilder.MarkLayoutForRebuild(rectTransform); + rectTransform.sendChildDimensionsChange = false; base.OnDisable(); } @@ -337,6 +339,15 @@ protected virtual void OnTransformChildrenChanged() SetDirty(); } + /// + /// Callback sent from native code whenever the RectTransform dimensions of a direct child are changed. + /// + protected virtual void OnChildRectTransformDimensionsChange() + { + if (!CanvasUpdateRegistry.IsRebuildingLayout()) + SetDirty(); + } + /// /// Helper method used to set a given property if it has changed. /// diff --git a/com.unity.ugui/Runtime/UGUI/UI/Core/Layout/LayoutRebuilder.cs b/com.unity.ugui/Runtime/UGUI/UI/Core/Layout/LayoutRebuilder.cs index 48db463..b003bcf 100644 --- a/com.unity.ugui/Runtime/UGUI/UI/Core/Layout/LayoutRebuilder.cs +++ b/com.unity.ugui/Runtime/UGUI/UI/Core/Layout/LayoutRebuilder.cs @@ -195,6 +195,28 @@ public static void MarkLayoutForRebuild(RectTransform rect) parent = parent.parent as RectTransform; } +#if UNITY_EDITOR + //Child ILayoutGroup components from the root need to be marked for layout rebuild. + //UUM-100091: Ideally, we'd want a broader fix. This change narrowly addresses the bug. + if (!UnityEditor.EditorApplication.isPlaying) + { + using (ListPool.Get(out var layoutComps)) + { + layoutRoot.GetComponentsInChildren(layoutComps); + foreach (var layout in layoutComps) + { + if (layout is Behaviour behaviour && behaviour.isActiveAndEnabled) + { + if (ReferenceEquals(behaviour.gameObject, layoutRoot.gameObject)) + continue; + + MarkLayoutRootForRebuild(behaviour.transform as RectTransform); + } + } + } + } +#endif + // We know the layout root is valid if it's not the same as the rect, // since we checked that above. But if they're the same we still need to check. if (layoutRoot == rect && !ValidController(layoutRoot, comps)) diff --git a/com.unity.ugui/Runtime/UGUI/UI/Core/MultipleDisplayUtilities.cs b/com.unity.ugui/Runtime/UGUI/UI/Core/MultipleDisplayUtilities.cs index 3e605bf..575b022 100644 --- a/com.unity.ugui/Runtime/UGUI/UI/Core/MultipleDisplayUtilities.cs +++ b/com.unity.ugui/Runtime/UGUI/UI/Core/MultipleDisplayUtilities.cs @@ -66,45 +66,30 @@ internal static Vector3 GetRelativeMousePositionForRaycast(PointerEventData even public static Vector3 RelativeMouseAtScaled(Vector2 position, int displayIndex) { #if !UNITY_EDITOR && !UNITY_WSA - // For most platforms, if the main display is not the same resolution as the system then we will have to scale the mouse position. (case 1141732) - var display = Display.main; -#if ENABLE_INPUT_SYSTEM && PACKAGE_INPUTSYSTEM - // With the new input system, passed positions are always relative to a surface and scaled accordingly to the rendering resolution. - - // If not in fullscreen, assume UaaL multi-view multi-screen multi-touch scenario, where the position is already in the correct scaled coordinates for the displayIndex - if (!Screen.fullScreen) - { - return new Vector3(position.x, position.y, displayIndex); - } - - // Otherwise, in full screen, we add some padding if rendering and system resolution differs, as for other platforms' main display. (So behavior is unchanged for Android main display, untested for non-main displays) - if (displayIndex >= Display.displays.Length) - displayIndex = 0; // use position relative to first display if displayIndex is out of bounds - display = Display.displays[displayIndex]; -#endif - if (display.renderingWidth != display.systemWidth || display.renderingHeight != display.systemHeight) + // If the main display is now the same resolution as the system then we need to scale the mouse position. (case 1141732) + if (Display.main.renderingWidth != Display.main.systemWidth || Display.main.renderingHeight != Display.main.systemHeight) { // The system will add padding when in full-screen and using a non-native aspect ratio. (case UUM-7893) // For example Rendering 1920x1080 with a systeem resolution of 3440x1440 would create black bars on each side that are 330 pixels wide. // we need to account for this or it will offset our coordinates when we are not on the main display. - var systemAspectRatio = display.systemWidth / (float)display.systemHeight; + var systemAspectRatio = Display.main.systemWidth / (float)Display.main.systemHeight; - var sizePlusPadding = new Vector2(display.renderingWidth, display.renderingHeight); + var sizePlusPadding = new Vector2(Display.main.renderingWidth, Display.main.renderingHeight); var padding = Vector2.zero; if (Screen.fullScreen) { - var aspectRatio = Screen.width / (float)Screen.height; // This assumes aspectRatio is the same for all displays - if (display.systemHeight * aspectRatio < display.systemWidth) + var aspectRatio = Screen.width / (float)Screen.height; + if (Display.main.systemHeight * aspectRatio < Display.main.systemWidth) { // Horizontal padding - sizePlusPadding.x = display.renderingHeight * systemAspectRatio; - padding.x = (sizePlusPadding.x - display.renderingWidth) * 0.5f; + sizePlusPadding.x = Display.main.renderingHeight * systemAspectRatio; + padding.x = (sizePlusPadding.x - Display.main.renderingWidth) * 0.5f; } else { // Vertical padding - sizePlusPadding.y = display.renderingWidth / systemAspectRatio; - padding.y = (sizePlusPadding.y - display.renderingHeight) * 0.5f; + sizePlusPadding.y = Display.main.renderingWidth / systemAspectRatio; + padding.y = (sizePlusPadding.y - Display.main.renderingHeight) * 0.5f; } } @@ -120,20 +105,20 @@ public static Vector3 RelativeMouseAtScaled(Vector2 position, int displayIndex) if (!Screen.fullScreen) { // When in windowed mode, the window will be centered with the 0,0 coordinate at the top left, we need to adjust so it is relative to the screen instead. - adjustedPosition.x -= (display.renderingWidth - display.systemWidth) * 0.5f; - adjustedPosition.y -= (display.renderingHeight - display.systemHeight) * 0.5f; + adjustedPosition.x -= (Display.main.renderingWidth - Display.main.systemWidth) * 0.5f; + adjustedPosition.y -= (Display.main.renderingHeight - Display.main.systemHeight) * 0.5f; } else { // Scale the mouse position to account for the black bars when in a non-native aspect ratio. adjustedPosition += padding; - adjustedPosition.x *= display.systemWidth / sizePlusPadding.x; - adjustedPosition.y *= display.systemHeight / sizePlusPadding.y; + adjustedPosition.x *= Display.main.systemWidth / sizePlusPadding.x; + adjustedPosition.y *= Display.main.systemHeight / sizePlusPadding.y; } - // fix for UUM-63551: Use the display index provided to this method. Display.RelativeMouseAt( ) no longer works starting with 2021 LTS and new input system - // as the Pointer position is reported in Window coordinates rather than relative to the primary window as Display.RelativeMouseAt( ) expects. -#if ENABLE_INPUT_SYSTEM && PACKAGE_INPUTSYSTEM && (UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_ANDROID || UNITY_EMBEDDED_LINUX || UNITY_QNX) + // fix for UUM-63551: Use the display index provided to this method. Display.RelativeMouseAt( ) no longer works starting with 2021 LTS and new input system + // as the Pointer position is reported in Window coordinates rather than relative to the primary window as Display.RelativeMouseAt( ) expects. +#if ENABLE_INPUT_SYSTEM && PACKAGE_INPUTSYSTEM && (UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_EMBEDDED_LINUX || UNITY_QNX) var relativePos = new Vector3(adjustedPosition.x, adjustedPosition.y, displayIndex); #else var relativePos = Display.RelativeMouseAt(adjustedPosition); @@ -145,8 +130,7 @@ public static Vector3 RelativeMouseAtScaled(Vector2 position, int displayIndex) } // We are using the main display. -#if ENABLE_INPUT_SYSTEM && PACKAGE_INPUTSYSTEM && (UNITY_ANDROID || UNITY_EMBEDDED_LINUX || UNITY_QNX) - // On Android, in all cases, it is a surface associated to a given displayIndex, so we need to use the display index +#if ENABLE_INPUT_SYSTEM && PACKAGE_INPUTSYSTEM && (UNITY_EMBEDDED_LINUX || UNITY_QNX) return new Vector3(position.x, position.y, displayIndex); #else return new Vector3(position.x, position.y, 0); @@ -154,9 +138,9 @@ public static Vector3 RelativeMouseAtScaled(Vector2 position, int displayIndex) } #endif - // fix for UUM-63551: Use the display index provided to this method. Display.RelativeMouseAt( ) no longer works starting with 2021 LTS and new input system - // as the Pointer position is reported in Window coordinates rather than relative to the primary window as Display.RelativeMouseAt( ) expects. -#if ENABLE_INPUT_SYSTEM && PACKAGE_INPUTSYSTEM && (UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_ANDROID || UNITY_EMBEDDED_LINUX || UNITY_QNX) + // fix for UUM-63551: Use the display index provided to this method. Display.RelativeMouseAt( ) no longer works starting with 2021 LTS and new input system + // as the Pointer position is reported in Window coordinates rather than relative to the primary window as Display.RelativeMouseAt( ) expects. +#if ENABLE_INPUT_SYSTEM && PACKAGE_INPUTSYSTEM && (UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_EMBEDDED_LINUX || UNITY_QNX) return new Vector3(position.x, position.y, displayIndex); #else return Display.RelativeMouseAt(position); diff --git a/com.unity.ugui/Runtime/UGUI/UI/Core/RawImage.cs b/com.unity.ugui/Runtime/UGUI/UI/Core/RawImage.cs index fb33060..bd6fd67 100644 --- a/com.unity.ugui/Runtime/UGUI/UI/Core/RawImage.cs +++ b/com.unity.ugui/Runtime/UGUI/UI/Core/RawImage.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using UnityEngine.Serialization; namespace UnityEngine.UI @@ -42,7 +40,13 @@ public override Texture mainTexture return s_WhiteTexture; } - return m_Texture; + // Patch fix for UUM-117371. + // If m_Texture is set as a non-matching type from native side this will ensure that dependent code will be able to null-check. + // Proposed fix involved safe-casting to Texture2D, but RawImage must support any Texture subclass. + // We can't safe-cast m_Texture to Texture directly, as the managed runtime assumes it's already a Texture and does nothing. + // Instead, cast to a System.Object and check that. + object objReference = m_Texture; + return objReference as Texture; } } @@ -138,20 +142,21 @@ protected override void OnPopulateMesh(VertexHelper vh) vh.Clear(); if (tex != null) { - var r = GetPixelAdjustedRect(); + Rect r = GetPixelAdjustedRect(); var v = new Vector4(r.x, r.y, r.x + r.width, r.y + r.height); - var scaleX = tex.width * tex.texelSize.x; - var scaleY = tex.height * tex.texelSize.y; - { - var color32 = color; - vh.AddVert(new Vector3(v.x, v.y), color32, new Vector2(m_UVRect.xMin * scaleX, m_UVRect.yMin * scaleY)); - vh.AddVert(new Vector3(v.x, v.w), color32, new Vector2(m_UVRect.xMin * scaleX, m_UVRect.yMax * scaleY)); - vh.AddVert(new Vector3(v.z, v.w), color32, new Vector2(m_UVRect.xMax * scaleX, m_UVRect.yMax * scaleY)); - vh.AddVert(new Vector3(v.z, v.y), color32, new Vector2(m_UVRect.xMax * scaleX, m_UVRect.yMin * scaleY)); - vh.AddTriangle(0, 1, 2); - vh.AddTriangle(2, 3, 0); - } + Vector2 texTexelSize = tex.texelSize; + float scaleX = tex.width * texTexelSize.x; + float scaleY = tex.height * texTexelSize.y; + + Color32 color32 = color; + vh.AddVert(new Vector3(v.x, v.y), color32, new Vector4(m_UVRect.xMin * scaleX, m_UVRect.yMin * scaleY)); + vh.AddVert(new Vector3(v.x, v.w), color32, new Vector4(m_UVRect.xMin * scaleX, m_UVRect.yMax * scaleY)); + vh.AddVert(new Vector3(v.z, v.w), color32, new Vector4(m_UVRect.xMax * scaleX, m_UVRect.yMax * scaleY)); + vh.AddVert(new Vector3(v.z, v.y), color32, new Vector4(m_UVRect.xMax * scaleX, m_UVRect.yMin * scaleY)); + + vh.AddTriangle(0, 1, 2); + vh.AddTriangle(2, 3, 0); } } diff --git a/com.unity.ugui/Runtime/UGUI/UI/Core/ScrollRect.cs b/com.unity.ugui/Runtime/UGUI/UI/Core/ScrollRect.cs index ab676fb..ea5e819 100644 --- a/com.unity.ugui/Runtime/UGUI/UI/Core/ScrollRect.cs +++ b/com.unity.ugui/Runtime/UGUI/UI/Core/ScrollRect.cs @@ -1051,8 +1051,8 @@ public float verticalNormalizedPosition } } - private void SetHorizontalNormalizedPosition(float value) { SetNormalizedPosition(value, 0); } - private void SetVerticalNormalizedPosition(float value) { SetNormalizedPosition(value, 1); } + private void SetHorizontalNormalizedPosition(float value) { if(horizontalNormalizedPosition != value) SetNormalizedPosition(value, 0); } + private void SetVerticalNormalizedPosition(float value) { if(verticalNormalizedPosition != value) SetNormalizedPosition(value, 1); } /// /// >Set the horizontal or vertical scroll position as a value between 0 and 1, with 0 being at the left or at the bottom. diff --git a/com.unity.ugui/Runtime/UGUI/UI/Core/Slider.cs b/com.unity.ugui/Runtime/UGUI/UI/Core/Slider.cs index 27c44ec..9145dd7 100644 --- a/com.unity.ugui/Runtime/UGUI/UI/Core/Slider.cs +++ b/com.unity.ugui/Runtime/UGUI/UI/Core/Slider.cs @@ -2,6 +2,10 @@ using UnityEngine.Events; using UnityEngine.EventSystems; +#if UNITY_EDITOR +using UnityEditor; +#endif + namespace UnityEngine.UI { [AddComponentMenu("UI/Slider", 34)] @@ -428,10 +432,17 @@ protected override void OnEnable() Set(m_Value, false); // Update rects since they need to be initialized correctly. UpdateVisuals(); +#if UNITY_EDITOR + Undo.undoRedoEvent -= OnUndoRedoEvent; + Undo.undoRedoEvent += OnUndoRedoEvent; +#endif } protected override void OnDisable() { +#if UNITY_EDITOR + Undo.undoRedoEvent -= OnUndoRedoEvent; +#endif m_Tracker.Clear(); base.OnDisable(); } @@ -533,7 +544,6 @@ protected virtual void Set(float input, bool sendCallback = true) return; m_Value = newValue; - MarkDirty(); UpdateVisuals(); if (sendCallback) { @@ -553,6 +563,13 @@ protected override void OnRectTransformDimensionsChange() UpdateVisuals(); } +#if UNITY_EDITOR + void OnUndoRedoEvent(in UndoRedoInfo undo) + { + UpdateVisuals(); + } +#endif + enum Axis { Horizontal = 0, diff --git a/com.unity.ugui/Tests/Editor/TMP/.buginfo b/com.unity.ugui/Tests/Editor/TMP/.buginfo index 167481e..f640b3a 100644 --- a/com.unity.ugui/Tests/Editor/TMP/.buginfo +++ b/com.unity.ugui/Tests/Editor/TMP/.buginfo @@ -1 +1 @@ -area: Text (TextMeshPro) \ No newline at end of file +area: Text (TextMeshPro) diff --git a/com.unity.ugui/Tests/Editor/TMP/FontEngineTests.cs b/com.unity.ugui/Tests/Editor/TMP/FontEngineTests.cs index c3d0528..64a3fe1 100644 --- a/com.unity.ugui/Tests/Editor/TMP/FontEngineTests.cs +++ b/com.unity.ugui/Tests/Editor/TMP/FontEngineTests.cs @@ -3,11 +3,10 @@ using NUnit.Framework; using UnityEngine.TextCore.LowLevel; - namespace TMPro { [Category("Text Parsing & Layout")] - class FontEngineTests + internal class FontEngineTests { [OneTimeSetUp] public void Setup() diff --git a/com.unity.ugui/Tests/Editor/TMP/TMP_ControlTests.cs b/com.unity.ugui/Tests/Editor/TMP/TMP_ControlTests.cs index b216b6f..81feba1 100644 --- a/com.unity.ugui/Tests/Editor/TMP/TMP_ControlTests.cs +++ b/com.unity.ugui/Tests/Editor/TMP/TMP_ControlTests.cs @@ -5,7 +5,7 @@ namespace TMPro { - public class TMP_ControlTests + internal class TMP_ControlTests { Scene scene; [SetUp] diff --git a/com.unity.ugui/Tests/Editor/TMP/TMP_EditorTests.cs b/com.unity.ugui/Tests/Editor/TMP/TMP_EditorTests.cs index 132a62d..3982db8 100644 --- a/com.unity.ugui/Tests/Editor/TMP/TMP_EditorTests.cs +++ b/com.unity.ugui/Tests/Editor/TMP/TMP_EditorTests.cs @@ -3,11 +3,10 @@ using NUnit.Framework; using System.IO; - namespace TMPro { [Category("Text Parsing & Layout")] - class TMP_EditorTests + internal class TMP_EditorTests { private TextMeshPro m_TextComponent; diff --git a/com.unity.ugui/Tests/Editor/UGUI/Canvas/CanvasUseReflectionProbes.cs b/com.unity.ugui/Tests/Editor/UGUI/Canvas/CanvasUseReflectionProbes.cs deleted file mode 100644 index 3b8e904..0000000 --- a/com.unity.ugui/Tests/Editor/UGUI/Canvas/CanvasUseReflectionProbes.cs +++ /dev/null @@ -1,39 +0,0 @@ -using NUnit.Framework; -using UnityEngine; - -[Category("Canvas")] -internal class CanvasUseReflectionProbes : TestBehaviourBase -{ - [Test] - public void OnlyWorldSpaceCanvasCanUseReflectionProbes() - { - m_TestObject.useReflectionProbes = true; - - m_TestObject.renderMode = RenderMode.ScreenSpaceOverlay; - Assert.False(m_TestObject.useReflectionProbes); - - m_TestObject.renderMode = RenderMode.ScreenSpaceCamera; - Assert.False(m_TestObject.useReflectionProbes); - - m_TestObject.renderMode = RenderMode.WorldSpace; - Assert.True(m_TestObject.useReflectionProbes); - } - - [Test] - public void ProvidesNormals() - { - var dummyChannels = AdditionalCanvasShaderChannels.TexCoord1 | AdditionalCanvasShaderChannels.Tangent; - - m_TestObject.renderMode = RenderMode.WorldSpace; - m_TestObject.additionalShaderChannels = dummyChannels; - - m_TestObject.useReflectionProbes = false; - Assert.AreEqual(m_TestObject.additionalShaderChannels, dummyChannels); - - m_TestObject.useReflectionProbes = true; - Assert.AreEqual(m_TestObject.additionalShaderChannels, dummyChannels | AdditionalCanvasShaderChannels.Normal); - - m_TestObject.useReflectionProbes = false; - Assert.AreEqual(m_TestObject.additionalShaderChannels, dummyChannels); - } -} diff --git a/com.unity.ugui/Tests/Editor/UGUI/Canvas/CanvasUseReflectionProbes.cs.meta b/com.unity.ugui/Tests/Editor/UGUI/Canvas/CanvasUseReflectionProbes.cs.meta deleted file mode 100644 index 9408581..0000000 --- a/com.unity.ugui/Tests/Editor/UGUI/Canvas/CanvasUseReflectionProbes.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 3cbc4280d63720c448a3c6fee4b1ec88 \ No newline at end of file diff --git a/com.unity.ugui/Tests/Editor/UGUI/Canvas/RootCanvasTests.cs b/com.unity.ugui/Tests/Editor/UGUI/Canvas/RootCanvasTests.cs index 3306e25..a650761 100644 --- a/com.unity.ugui/Tests/Editor/UGUI/Canvas/RootCanvasTests.cs +++ b/com.unity.ugui/Tests/Editor/UGUI/Canvas/RootCanvasTests.cs @@ -78,54 +78,4 @@ public void ChildOfDisabledCanvasCantReceiveClicks() Assert.IsTrue(raycasts.Count == 0); } - - [Test] - public void ChildrenInheritRootCanvasReflectionProbeProperties() - { - m_TestObject.useReflectionProbes = true; - m_TestObject.renderMode = RenderMode.WorldSpace; - - // rootCanvasChild inherits properties from root - Assert.AreEqual( - rootCanvasChild.useReflectionProbes, - m_TestObject.useReflectionProbes); - Assert.AreEqual( - rootCanvasChild.additionalShaderChannels & AdditionalCanvasShaderChannels.Normal, - m_TestObject.additionalShaderChannels & AdditionalCanvasShaderChannels.Normal); - - // baseCanvas inherits properties from root - Assert.AreEqual( - baseCanvas.useReflectionProbes, - m_TestObject.useReflectionProbes); - Assert.AreEqual( - baseCanvas.additionalShaderChannels & AdditionalCanvasShaderChannels.Normal, - m_TestObject.additionalShaderChannels & AdditionalCanvasShaderChannels.Normal); - } - - [Test] - public void CannotOverrideRootCanvasReflectionProbeProperties() - { - m_TestObject.useReflectionProbes = true; - m_TestObject.renderMode = RenderMode.WorldSpace; - - rootCanvasChild.useReflectionProbes = false; - rootCanvasChild.renderMode = RenderMode.ScreenSpaceOverlay; - rootCanvasChild.additionalShaderChannels = AdditionalCanvasShaderChannels.None; - - // rootCanvasChild inherits properties from root - Assert.AreEqual( - rootCanvasChild.useReflectionProbes, - m_TestObject.useReflectionProbes); - Assert.AreEqual( - rootCanvasChild.additionalShaderChannels & AdditionalCanvasShaderChannels.Normal, - m_TestObject.additionalShaderChannels & AdditionalCanvasShaderChannels.Normal); - - // baseCanvas inherits properties from root - Assert.AreEqual( - baseCanvas.useReflectionProbes, - m_TestObject.useReflectionProbes); - Assert.AreEqual( - baseCanvas.additionalShaderChannels & AdditionalCanvasShaderChannels.Normal, - m_TestObject.additionalShaderChannels & AdditionalCanvasShaderChannels.Normal); - } } diff --git a/com.unity.ugui/Tests/Runtime/TMP/.buginfo b/com.unity.ugui/Tests/Runtime/TMP/.buginfo index 167481e..f640b3a 100644 --- a/com.unity.ugui/Tests/Runtime/TMP/.buginfo +++ b/com.unity.ugui/Tests/Runtime/TMP/.buginfo @@ -1 +1 @@ -area: Text (TextMeshPro) \ No newline at end of file +area: Text (TextMeshPro) diff --git a/com.unity.ugui/Tests/Runtime/TMP/TMP_CanvasTests.cs b/com.unity.ugui/Tests/Runtime/TMP/TMP_CanvasTests.cs index 3078d15..67e2c69 100644 --- a/com.unity.ugui/Tests/Runtime/TMP/TMP_CanvasTests.cs +++ b/com.unity.ugui/Tests/Runtime/TMP/TMP_CanvasTests.cs @@ -5,10 +5,9 @@ using UnityEngine.UI; using UnityEngine.EventSystems; - namespace TMPro { - public class TMP_CanvasTests + internal class TMP_CanvasTests { [OneTimeSetUp] public void Setup() diff --git a/com.unity.ugui/Tests/Runtime/TMP/TMP_RuntimeTests.cs b/com.unity.ugui/Tests/Runtime/TMP/TMP_RuntimeTests.cs index ac4ec00..fb0e2b1 100644 --- a/com.unity.ugui/Tests/Runtime/TMP/TMP_RuntimeTests.cs +++ b/com.unity.ugui/Tests/Runtime/TMP/TMP_RuntimeTests.cs @@ -4,12 +4,11 @@ using System.Collections.Generic; using UnityEngine.UI; using UnityEngine.EventSystems; -using UnityEngine.TestTools; namespace TMPro { [Category("Text Parsing & Layout")] - class TMP_RuntimeTests + internal class TMP_RuntimeTests { private TextMeshPro m_TextComponent; @@ -275,57 +274,5 @@ public void MultiLineNewLine_NegativeOrZeroLineLimit_AddsNewLine(int lineLimitVa // } //} - [Test] - public void SettingIsTextObjectScaleStatic_OnDisabledObject_DoesntProduceErrors() - { - // Reset the text component to before its OnEnable - var textObject = m_TextComponent.gameObject; - Object.DestroyImmediate(m_TextComponent); - textObject.SetActive(false); - m_TextComponent = textObject.AddComponent(); - - // UUM-92041: verify that we only register if enabled - m_TextComponent.isTextObjectScaleStatic = true; // Make sure we're not starting false - m_TextComponent.isTextObjectScaleStatic = false; - Canvas.ForceUpdateCanvases(); - - LogAssert.NoUnexpectedReceived(); - } - - [Test] - public void SettingIsTextObjectScaleStatic_OnDisabledObject_IsAppliedCorrectlyAfterObjectIsEnabled() - { - m_TextComponent.text = "Test"; - - // Reset the text component to before its OnEnable - var textObject = m_TextComponent.gameObject; - Object.DestroyImmediate(m_TextComponent); - textObject.SetActive(false); - m_TextComponent = textObject.AddComponent(); - - // Set isTextObjectScaleStatic to false while the object is disabled - m_TextComponent.isTextObjectScaleStatic = true; - m_TextComponent.isTextObjectScaleStatic = false; // Make sure we're not starting false - Canvas.ForceUpdateCanvases(); - - m_TextComponent.gameObject.SetActive(true); - Canvas.ForceUpdateCanvases(); - var w = m_TextComponent.textInfo.meshInfo[0].uvs0[0].w; - - // When isTextObjectScaleStatic is false, scale is updated in mesh uv - m_TextComponent.transform.localScale = Vector3.one * 2; - Canvas.ForceUpdateCanvases(); - Assert.AreEqual(w * 2, m_TextComponent.textInfo.meshInfo[0].uvs0[0].w); - - // Setting isTextObjectScaleStatic to true shouldn't change mesh - m_TextComponent.isTextObjectScaleStatic = true; - Canvas.ForceUpdateCanvases(); - Assert.AreEqual(w * 2, m_TextComponent.textInfo.meshInfo[0].uvs0[0].w); - - // When isTextObjectScaleStatic is true, scale is NOT updated in mesh - m_TextComponent.transform.localScale = Vector3.one; - Canvas.ForceUpdateCanvases(); - Assert.AreEqual(w * 2, m_TextComponent.textInfo.meshInfo[0].uvs0[0].w); - } } } diff --git a/com.unity.ugui/Tests/Runtime/UGUI/Canvas/CheckMeshColorsAndColors32Match.cs b/com.unity.ugui/Tests/Runtime/UGUI/Canvas/CheckMeshColorsAndColors32Match.cs index 940b843..0e810ec 100644 --- a/com.unity.ugui/Tests/Runtime/UGUI/Canvas/CheckMeshColorsAndColors32Match.cs +++ b/com.unity.ugui/Tests/Runtime/UGUI/Canvas/CheckMeshColorsAndColors32Match.cs @@ -2,7 +2,6 @@ using UnityEngine.TestTools; using NUnit.Framework; using System.Collections; -using UnityEngine.Rendering; using UnityEngine.TestTools.Utils; [TestFixture] @@ -11,28 +10,15 @@ internal class CheckMeshColorsAndColors32Match GameObject m_CanvasGO; GameObject m_ColorMeshGO; GameObject m_Color32MeshGO; - GameObject m_CameraGO; Texture2D m_ScreenTexture; - RenderTexture m_ScreenRenderTexture; - Camera m_ScreenCamera; [SetUp] public void TestSetup() { - // Create Camera - m_ScreenRenderTexture = new RenderTexture(Screen.width, Screen.height, 16, RenderTextureFormat.ARGB32); - m_ScreenRenderTexture.Create(); - m_CameraGO = new GameObject("Camera"); - m_ScreenCamera = m_CameraGO.AddComponent(); - m_ScreenCamera.orthographic = true; - m_ScreenCamera.orthographicSize = 1; - m_ScreenCamera.targetTexture = m_ScreenRenderTexture; - // Create Canvas m_CanvasGO = new GameObject("Canvas"); Canvas canvas = m_CanvasGO.AddComponent(); - canvas.renderMode = RenderMode.ScreenSpaceCamera; - canvas.worldCamera = m_ScreenCamera; + canvas.renderMode = RenderMode.ScreenSpaceOverlay; // Create Color UI GameObject m_ColorMeshGO = new GameObject("ColorMesh"); @@ -40,7 +26,6 @@ public void TestSetup() RectTransform colorMeshRectTransform = m_ColorMeshGO.AddComponent(); colorMeshRectTransform.pivot = colorMeshRectTransform.anchorMin = colorMeshRectTransform.anchorMax = Vector2.zero; m_ColorMeshGO.transform.SetParent(m_CanvasGO.transform); - m_ColorMeshGO.transform.localPosition = Vector3.zero; // Create Color32 UI GameObject m_Color32MeshGO = new GameObject("Color32Mesh"); @@ -48,13 +33,12 @@ public void TestSetup() RectTransform color32MeshRectTransform = m_Color32MeshGO.AddComponent(); color32MeshRectTransform.pivot = color32MeshRectTransform.anchorMin = color32MeshRectTransform.anchorMax = Vector2.zero; m_Color32MeshGO.transform.SetParent(m_CanvasGO.transform); - m_Color32MeshGO.transform.localPosition = Vector3.zero; Material material = new Material(Shader.Find("UI/Default")); // Setup Color mesh and add it to Color CanvasRenderer Mesh meshColor = new Mesh(); - meshColor.vertices = new Vector3[3] { new Vector3(0, 0, 0), new Vector3(0, -3, 0), new Vector3(-3, 0, 0) }; + meshColor.vertices = new Vector3[3] { new Vector3(0, 0, 0), new Vector3(0, 10, 0), new Vector3(10, 0, 0) }; meshColor.triangles = new int[3] { 0, 1, 2 }; meshColor.normals = new Vector3[3] { Vector3.zero, Vector3.zero, Vector3.zero }; meshColor.colors = new Color[3] { Color.white, Color.white, Color.white }; @@ -65,7 +49,7 @@ public void TestSetup() // Setup Color32 mesh and add it to Color32 CanvasRenderer Mesh meshColor32 = new Mesh(); - meshColor32.vertices = new Vector3[3] { new Vector3(0, 0, 0), new Vector3(0, 3, 0), new Vector3(3, 0, 0) }; + meshColor32.vertices = new Vector3[3] { new Vector3(10, 0, 0), new Vector3(10, 10, 0), new Vector3(20, 0, 0) }; meshColor32.triangles = new int[3] { 0, 1, 2 }; meshColor32.normals = new Vector3[3] { Vector3.zero, Vector3.zero, Vector3.zero }; meshColor32.colors32 = new Color32[3] { Color.white, Color.white, Color.white }; @@ -79,28 +63,23 @@ public void TestSetup() public void TearDown() { GameObject.DestroyImmediate(m_CanvasGO); - m_ScreenCamera.targetTexture = null; - GameObject.DestroyImmediate(m_ScreenRenderTexture); - GameObject.DestroyImmediate(m_CameraGO); + GameObject.DestroyImmediate(m_ScreenTexture); } + [Ignore("UnityTest yielded WaitForEndOfFrame, which is not evoked in batchmode.")] [UnityTest] public IEnumerator CheckMeshColorsAndColors32Matches() { - Assert.That(m_ScreenRenderTexture.IsCreated(), "RenderTexture is not created"); - - Texture2D screenTexture = new Texture2D(m_ScreenRenderTexture.width, m_ScreenRenderTexture.height); - RenderTexture.active = m_ScreenRenderTexture; - screenTexture.ReadPixels(new Rect(0, 0, m_ScreenRenderTexture.width, m_ScreenRenderTexture.height), 0, 0); - screenTexture.Apply(); + yield return new WaitForEndOfFrame(); - yield return null; + // Create a Texture2D + m_ScreenTexture = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false); + m_ScreenTexture.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0); + m_ScreenTexture.Apply(); - Color screenPixelColorForMeshColor = screenTexture.GetPixel(Screen.width / 2 + 100, Screen.height / 2 + 100); - Color screenPixelColorForMesh32Color = screenTexture.GetPixel(100, 100); - Color screenPixelOutsideMesh = screenTexture.GetPixel(Screen.width / 2 + 100, 100); + Color screenPixelColorForMeshColor = m_ScreenTexture.GetPixel(1, 0); + Color screenPixelColorForMesh32Color = m_ScreenTexture.GetPixel(11, 0); Assert.That(screenPixelColorForMesh32Color, Is.EqualTo(screenPixelColorForMeshColor).Using(new ColorEqualityComparer(0.0f)), "UI Mesh with Colors does not match UI Mesh with Colors32"); - Assert.That(screenPixelOutsideMesh, Is.Not.EqualTo(screenPixelColorForMeshColor).Using(new ColorEqualityComparer(0.0f)), "Empty space should not be matching the color of the UI Mesh"); } } diff --git a/com.unity.ugui/Tests/Runtime/UGUI/Canvas/RectTransformValidAfterEnable.cs b/com.unity.ugui/Tests/Runtime/UGUI/Canvas/RectTransformValidAfterEnable.cs index 199f4f2..fef23a1 100644 --- a/com.unity.ugui/Tests/Runtime/UGUI/Canvas/RectTransformValidAfterEnable.cs +++ b/com.unity.ugui/Tests/Runtime/UGUI/Canvas/RectTransformValidAfterEnable.cs @@ -30,6 +30,7 @@ public void Setup() #endif } + [Ignore("UnityTest yielded WaitForEndOfFrame, which is not evoked in batchmode.")] [UnityTest] public IEnumerator CheckRectTransformValidAfterEnable() { @@ -52,6 +53,8 @@ public IEnumerator CheckRectTransformValidAfterEnable() RectTransform rectTransform = canvasGameObject.GetComponent(); canvasGameObject.SetActive(true); + yield return new WaitForEndOfFrame(); + Rect rect = rectTransform.rect; Assert.Greater(rect.width, 0); Assert.Greater(rect.height, 0); diff --git a/com.unity.ugui/Tests/Runtime/UGUI/Canvas/SiblingOrderChangesLayout.cs b/com.unity.ugui/Tests/Runtime/UGUI/Canvas/SiblingOrderChangesLayout.cs index 5e449c6..b1c9297 100644 --- a/com.unity.ugui/Tests/Runtime/UGUI/Canvas/SiblingOrderChangesLayout.cs +++ b/com.unity.ugui/Tests/Runtime/UGUI/Canvas/SiblingOrderChangesLayout.cs @@ -26,6 +26,7 @@ public void TestSetup() #endif } + [Ignore("UnityTest yielded WaitForEndOfFrame, which is not evoked in batchmode.")] [UnityTest] public IEnumerator ReorderingSiblingChangesLayout() { @@ -38,7 +39,7 @@ public IEnumerator ReorderingSiblingChangesLayout() m_ParentGO.AddComponent(); m_ParentGO.AddComponent(); - yield return null; + yield return new WaitForEndOfFrame(); Vector2 child1Pos = m_Child1GO.GetComponent().anchoredPosition; Vector2 child2Pos = m_Child2GO.GetComponent().anchoredPosition; diff --git a/com.unity.ugui/Tests/Runtime/UGUI/CanvasRenderer/CanvasRendererTests.cs b/com.unity.ugui/Tests/Runtime/UGUI/CanvasRenderer/CanvasRendererTests.cs deleted file mode 100644 index 324d2fe..0000000 --- a/com.unity.ugui/Tests/Runtime/UGUI/CanvasRenderer/CanvasRendererTests.cs +++ /dev/null @@ -1,119 +0,0 @@ -using UnityEngine; -using NUnit.Framework; - -internal class CanvasRendererTests -{ - private const int Width = 32; - private const int Height = 32; - GameObject m_GraphicObj; - CanvasRenderer m_CanvasRenderer; - private Texture2D m_DefaultTexture; - static readonly string k_MaskTexPropName = "_MaskTex"; - static readonly string k_GlowTexPropName = "_GlowTex"; - Texture2D m_MaskTex; - Texture2D m_GlowTex; - - [SetUp] - public void SetUp() - { - m_GraphicObj = new GameObject("Graphic"); - m_CanvasRenderer = m_GraphicObj.AddComponent(); - m_MaskTex = CreateTexture(Color.red); - m_GlowTex = CreateTexture(Color.yellow); - } - - Texture2D CreateTexture(Color color) - { - var tex = new Texture2D(Width, Height); - Color[] colors = new Color[Width * Height]; - for (int i = 0; i < Width * Height; i++) - colors[i] = color; - tex.SetPixels(colors); - tex.Apply(); - return tex; - } - - [Test] - public void InitialData() - { - Assert.AreEqual(0, m_CanvasRenderer.GetSecondaryTextureCount()); - } - - [Test] - public void AddSecondaryTextures() - { - m_CanvasRenderer.SetSecondaryTextureCount(2); - - Assert.AreEqual(2, m_CanvasRenderer.GetSecondaryTextureCount()); - Assert.True(string.IsNullOrEmpty(m_CanvasRenderer.GetSecondaryTextureName(0))); - Assert.Null(m_CanvasRenderer.GetSecondaryTexture(0)); - Assert.True(string.IsNullOrEmpty(m_CanvasRenderer.GetSecondaryTextureName(1))); - Assert.Null(m_CanvasRenderer.GetSecondaryTexture(1)); - - m_CanvasRenderer.SetSecondaryTexture(0, k_MaskTexPropName, m_MaskTex); - m_CanvasRenderer.SetSecondaryTexture(1, k_GlowTexPropName, m_GlowTex); - - Assert.AreEqual(k_MaskTexPropName, m_CanvasRenderer.GetSecondaryTextureName(0)); - Assert.AreEqual(m_MaskTex, m_CanvasRenderer.GetSecondaryTexture(0)); - Assert.AreEqual(k_GlowTexPropName, m_CanvasRenderer.GetSecondaryTextureName(1)); - Assert.AreEqual(m_GlowTex, m_CanvasRenderer.GetSecondaryTexture(1)); - } - - [Test] - public void RemoveSecondaryTextures() - { - m_CanvasRenderer.SetSecondaryTextureCount(2); - m_CanvasRenderer.SetSecondaryTexture(0, k_MaskTexPropName, m_MaskTex); - m_CanvasRenderer.SetSecondaryTexture(1, k_GlowTexPropName, m_GlowTex); - - // The last secondary texture - m_CanvasRenderer.SetSecondaryTextureCount(1); - - Assert.AreEqual(1, m_CanvasRenderer.GetSecondaryTextureCount()); - Assert.AreEqual(k_MaskTexPropName, m_CanvasRenderer.GetSecondaryTextureName(0)); - Assert.AreEqual(m_MaskTex, m_CanvasRenderer.GetSecondaryTexture(0)); - } - - [Test] - public void SetSecondaryTextureCount() - { - m_CanvasRenderer.SetSecondaryTextureCount(2); - m_CanvasRenderer.SetSecondaryTexture(0, k_MaskTexPropName, m_MaskTex); - m_CanvasRenderer.SetSecondaryTexture(1, k_GlowTexPropName, m_GlowTex); - - m_CanvasRenderer.SetSecondaryTextureCount(1); - - Assert.AreEqual(1, m_CanvasRenderer.GetSecondaryTextureCount()); - Assert.AreEqual(k_MaskTexPropName, m_CanvasRenderer.GetSecondaryTextureName(0)); - Assert.AreEqual(m_MaskTex, m_CanvasRenderer.GetSecondaryTexture(0)); - - // Increase the number of secondary textures and verify that the new entries are empty - m_CanvasRenderer.SetSecondaryTextureCount(3); - - Assert.AreEqual(3, m_CanvasRenderer.GetSecondaryTextureCount()); - Assert.AreEqual(k_MaskTexPropName, m_CanvasRenderer.GetSecondaryTextureName(0)); - Assert.AreEqual(m_MaskTex, m_CanvasRenderer.GetSecondaryTexture(0)); - Assert.True(string.IsNullOrEmpty(m_CanvasRenderer.GetSecondaryTextureName(1))); - Assert.Null(m_CanvasRenderer.GetSecondaryTexture(1)); - Assert.True(string.IsNullOrEmpty(m_CanvasRenderer.GetSecondaryTextureName(2))); - Assert.Null(m_CanvasRenderer.GetSecondaryTexture(2)); - - // Clear all the secondary textures - m_CanvasRenderer.SetSecondaryTextureCount(0); - - Assert.AreEqual(0, m_CanvasRenderer.GetSecondaryTextureCount()); - - // Add an element again and verify that it is empty - m_CanvasRenderer.SetSecondaryTextureCount(1); - - Assert.True(string.IsNullOrEmpty(m_CanvasRenderer.GetSecondaryTextureName(0))); - Assert.Null(m_CanvasRenderer.GetSecondaryTexture(0)); - } - - [TearDown] - public void TearDown() - { - GameObject.DestroyImmediate(m_GraphicObj); - } -} - diff --git a/com.unity.ugui/Tests/Runtime/UGUI/CanvasRenderer/CanvasRendererTests.cs.meta b/com.unity.ugui/Tests/Runtime/UGUI/CanvasRenderer/CanvasRendererTests.cs.meta deleted file mode 100644 index 558f822..0000000 --- a/com.unity.ugui/Tests/Runtime/UGUI/CanvasRenderer/CanvasRendererTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 6fac6d65d2fb7472d94e95ddb8b8798c \ No newline at end of file diff --git a/com.unity.ugui/Tests/Runtime/UGUI/Dropdown/DropdownTests.cs b/com.unity.ugui/Tests/Runtime/UGUI/Dropdown/DropdownTests.cs index 7154ab5..7eb09c2 100644 --- a/com.unity.ugui/Tests/Runtime/UGUI/Dropdown/DropdownTests.cs +++ b/com.unity.ugui/Tests/Runtime/UGUI/Dropdown/DropdownTests.cs @@ -155,7 +155,7 @@ public void OneTimeTearDown() SerializedObject tagManager = new SerializedObject(AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/TagManager.asset")[0]); SerializedProperty sortingLayers = tagManager.FindProperty("m_SortingLayers"); - sortingLayers.DeleteArrayElementAtIndex(sortingLayers.arraySize - 1); + sortingLayers.DeleteArrayElementAtIndex(sortingLayers.arraySize); tagManager.ApplyModifiedProperties(); #endif } diff --git a/com.unity.ugui/Tests/Runtime/UGUI/EventSystem/GraphicRaycasterButtonTests.cs b/com.unity.ugui/Tests/Runtime/UGUI/EventSystem/GraphicRaycasterButtonTests.cs new file mode 100644 index 0000000..28e0727 --- /dev/null +++ b/com.unity.ugui/Tests/Runtime/UGUI/EventSystem/GraphicRaycasterButtonTests.cs @@ -0,0 +1,114 @@ +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.TestTools; +using UnityEngine.UI; + +internal class GraphicRaycasterButtonTests +{ + Camera m_Camera; + EventSystem m_EventSystem; + Canvas m_Canvas; + Button m_ParentButton; + Button m_ChildButton; + Sprite m_Sprite; + + const int TextureSize = 64; + readonly Texture2D texture = new Texture2D(TextureSize, TextureSize); + + [UnitySetUp] + public IEnumerator TestSetup() + { + m_Camera = new GameObject("Camera").AddComponent(); + m_Camera.transform.position = new Vector3(0, 0, -10); + + m_Canvas = new GameObject("Canvas").AddComponent(); + m_Canvas.renderMode = RenderMode.ScreenSpaceOverlay; + m_Canvas.gameObject.AddComponent(); + + m_EventSystem = new GameObject("Event System").AddComponent(); + + Color[] colors = new Color[TextureSize * TextureSize]; + for (int y = 24; y < 40; y++) + for (int x = 0; x < TextureSize; x++) + colors[y + TextureSize * x] = colors[x + TextureSize * y] = Color.red; + texture.SetPixels(colors); + texture.Apply(); + + m_Sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), 100); + + var parentImage = new GameObject("ParentButton", typeof(RectTransform)).AddComponent(); + parentImage.transform.SetParent(m_Canvas.transform); + parentImage.rectTransform.anchoredPosition = new Vector2(0, 0); + parentImage.sprite = m_Sprite; + parentImage.SetNativeSize(); + m_ParentButton = parentImage.gameObject.AddComponent