Search Unity

TextMesh Pro Suggestion : TextMeshPro - Button

Discussion in 'UGUI & TextMesh Pro' started by Zennas, Nov 6, 2018.

  1. Zennas

    Zennas

    Joined:
    Oct 5, 2013
    Posts:
    15
    Hi

    When creating a Button with UGUI it will come with a basic UGUI Text object, yet since TMP is pretty much always the better option i always have to delete that text object and create a new TMP text, change its color (as default is white) and set the anchors to and font size to match the previous UGUI text before actually change its parameters to match my needs.

    The code bellow adds an option to the create menu for a TextMeshPro - Button. The code is a modified version of the default create Button code that just replease the Text component with a TextMeshPro UGUI text (and the code must be inside an Editor folder).

    I feel like having this option (create/TextMeshPro - Button) would speed up development time a bit, as deleting and creating text objects over and over do adds up.

    While making an asset store package would help to have this option available, i believe it would be much better if this could be added to TMP.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. using TMPro;
    6. using UnityEditor;
    7. using UnityEngine.EventSystems;
    8.  
    9. public class TMPExtensions : UnityEditor.UI.ButtonEditor
    10. {
    11.     private const string kUILayerName = "UI";
    12.     private const float kWidth = 160f;
    13.     private const float kThickHeight = 30f;
    14.     private const string kStandardSpritePath = "UI/Skin/UISprite.psd";
    15.  
    16.     private static Vector2 s_ThickGUIElementSize = new Vector2(kWidth, kThickHeight);
    17.     private static Color s_DefaultSelectableColor = new Color(1f, 1f, 1f, 1f);
    18.     private static Color s_TextColor = new Color(50f / 255f, 50f / 255f, 50f / 255f, 1f);
    19.     private static float s_TextFontSize = 14;
    20.  
    21.     private static void SetPositionVisibleinSceneView(RectTransform canvasRTransform, RectTransform itemTransform)
    22.     {
    23.         // Find the best scene view
    24.         SceneView sceneView = SceneView.lastActiveSceneView;
    25.         if (sceneView == null && SceneView.sceneViews.Count > 0)
    26.             sceneView = SceneView.sceneViews[0] as SceneView;
    27.  
    28.         // Couldn't find a SceneView. Don't set position.
    29.         if (sceneView == null || sceneView.camera == null)
    30.             return;
    31.  
    32.         // Create world space Plane from canvas position.
    33.         Vector2 localPlanePosition;
    34.         Camera camera = sceneView.camera;
    35.         Vector3 position = Vector3.zero;
    36.         if (RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRTransform, new Vector2(camera.pixelWidth / 2, camera.pixelHeight / 2), camera, out localPlanePosition))
    37.         {
    38.             // Adjust for canvas pivot
    39.             localPlanePosition.x = localPlanePosition.x + canvasRTransform.sizeDelta.x * canvasRTransform.pivot.x;
    40.             localPlanePosition.y = localPlanePosition.y + canvasRTransform.sizeDelta.y * canvasRTransform.pivot.y;
    41.  
    42.             localPlanePosition.x = Mathf.Clamp(localPlanePosition.x, 0, canvasRTransform.sizeDelta.x);
    43.             localPlanePosition.y = Mathf.Clamp(localPlanePosition.y, 0, canvasRTransform.sizeDelta.y);
    44.  
    45.             // Adjust for anchoring
    46.             position.x = localPlanePosition.x - canvasRTransform.sizeDelta.x * itemTransform.anchorMin.x;
    47.             position.y = localPlanePosition.y - canvasRTransform.sizeDelta.y * itemTransform.anchorMin.y;
    48.  
    49.             Vector3 minLocalPosition;
    50.             minLocalPosition.x = canvasRTransform.sizeDelta.x * (0 - canvasRTransform.pivot.x) + itemTransform.sizeDelta.x * itemTransform.pivot.x;
    51.             minLocalPosition.y = canvasRTransform.sizeDelta.y * (0 - canvasRTransform.pivot.y) + itemTransform.sizeDelta.y * itemTransform.pivot.y;
    52.  
    53.             Vector3 maxLocalPosition;
    54.             maxLocalPosition.x = canvasRTransform.sizeDelta.x * (1 - canvasRTransform.pivot.x) - itemTransform.sizeDelta.x * itemTransform.pivot.x;
    55.             maxLocalPosition.y = canvasRTransform.sizeDelta.y * (1 - canvasRTransform.pivot.y) - itemTransform.sizeDelta.y * itemTransform.pivot.y;
    56.  
    57.             position.x = Mathf.Clamp(position.x, minLocalPosition.x, maxLocalPosition.x);
    58.             position.y = Mathf.Clamp(position.y, minLocalPosition.y, maxLocalPosition.y);
    59.         }
    60.  
    61.         itemTransform.anchoredPosition = position;
    62.         itemTransform.localRotation = Quaternion.identity;
    63.         itemTransform.localScale = Vector3.one;
    64.     }
    65.  
    66.     private static GameObject CreateUIElementRoot(string name, MenuCommand menuCommand, Vector2 size)
    67.     {
    68.         GameObject parent = menuCommand.context as GameObject;
    69.         if (parent == null || parent.GetComponentInParent<Canvas>() == null)
    70.         {
    71.             parent = GetOrCreateCanvasGameObject();
    72.         }
    73.         GameObject child = new GameObject(name);
    74.  
    75.         Undo.RegisterCreatedObjectUndo(child, "Create " + name);
    76.         Undo.SetTransformParent(child.transform, parent.transform, "Parent " + child.name);
    77.         GameObjectUtility.SetParentAndAlign(child, parent);
    78.  
    79.         RectTransform rectTransform = child.AddComponent<RectTransform>();
    80.         rectTransform.sizeDelta = size;
    81.         if (parent != menuCommand.context) // not a context click, so center in sceneview
    82.         {
    83.             SetPositionVisibleinSceneView(parent.GetComponent<RectTransform>(), rectTransform);
    84.         }
    85.         Selection.activeGameObject = child;
    86.         return child;
    87.     }
    88.  
    89.     [MenuItem("GameObject/UI/TextMeshPro - Button", false, 2012)]
    90.     static public void AddButton(MenuCommand menuCommand)
    91.     {
    92.         GameObject buttonRoot = CreateUIElementRoot("TextMeshPro - Button", menuCommand, s_ThickGUIElementSize);
    93.  
    94.         GameObject childText = new GameObject("TextMeshPro - Text");
    95.         GameObjectUtility.SetParentAndAlign(childText, buttonRoot);
    96.  
    97.         Image image = buttonRoot.AddComponent<Image>();
    98.         image.sprite = AssetDatabase.GetBuiltinExtraResource<Sprite>(kStandardSpritePath);
    99.         image.type = Image.Type.Sliced;
    100.         image.color = s_DefaultSelectableColor;
    101.  
    102.         Button bt = buttonRoot.AddComponent<Button>();
    103.         SetDefaultColorTransitionValues(bt);
    104.  
    105.         TextMeshProUGUI text = childText.AddComponent<TextMeshProUGUI>();
    106.         text.text = "Button";
    107.         text.alignment = TextAlignmentOptions.Center;
    108.         SetDefaultTextValues(text);
    109.  
    110.         RectTransform textRectTransform = childText.GetComponent<RectTransform>();
    111.         textRectTransform.anchorMin = Vector2.zero;
    112.         textRectTransform.anchorMax = Vector2.one;
    113.         textRectTransform.sizeDelta = Vector2.zero;
    114.     }
    115.  
    116.     private static void SetDefaultTextValues(TextMeshProUGUI lbl)
    117.     {
    118.         // Set text values we want across UI elements in default controls.
    119.         // Don't set values which are the same as the default values for the Text component,
    120.         // since there's no point in that, and it's good to keep them as consistent as possible.
    121.         lbl.color = s_TextColor;
    122.         lbl.fontSize = s_TextFontSize;
    123.     }
    124.  
    125.     static GameObject CreateUIObject(string name, GameObject parent)
    126.     {
    127.         GameObject go = new GameObject(name);
    128.         go.AddComponent<RectTransform>();
    129.         GameObjectUtility.SetParentAndAlign(go, parent);
    130.         return go;
    131.     }
    132.  
    133.     private static void SetDefaultColorTransitionValues(Selectable slider)
    134.     {
    135.         ColorBlock colors = slider.colors;
    136.         colors.highlightedColor = new Color(0.882f, 0.882f, 0.882f);
    137.         colors.pressedColor = new Color(0.698f, 0.698f, 0.698f);
    138.         colors.disabledColor = new Color(0.521f, 0.521f, 0.521f);
    139.     }
    140.  
    141.     static public GameObject CreateNewUI()
    142.     {
    143.         // Root for the UI
    144.         var root = new GameObject("Canvas");
    145.         root.layer = LayerMask.NameToLayer(kUILayerName);
    146.         Canvas canvas = root.AddComponent<Canvas>();
    147.         canvas.renderMode = RenderMode.ScreenSpaceOverlay;
    148.         root.AddComponent<CanvasScaler>();
    149.         root.AddComponent<GraphicRaycaster>();
    150.         Undo.RegisterCreatedObjectUndo(root, "Create " + root.name);
    151.  
    152.         // if there is no event system add one...
    153.         CreateEventSystem(false);
    154.         return root;
    155.     }
    156.  
    157.     private static void CreateEventSystem(bool select)
    158.     {
    159.         CreateEventSystem(select, null);
    160.     }
    161.  
    162.     private static void CreateEventSystem(bool select, GameObject parent)
    163.     {
    164.         var esys = Object.FindObjectOfType<EventSystem>();
    165.         if (esys == null)
    166.         {
    167.             var eventSystem = new GameObject("EventSystem");
    168.             GameObjectUtility.SetParentAndAlign(eventSystem, parent);
    169.             esys = eventSystem.AddComponent<EventSystem>();
    170.             eventSystem.AddComponent<StandaloneInputModule>();
    171.             //eventSystem.AddComponent<TouchInputModule>();
    172.  
    173.             Undo.RegisterCreatedObjectUndo(eventSystem, "Create " + eventSystem.name);
    174.         }
    175.  
    176.         if (select && esys != null)
    177.         {
    178.             Selection.activeGameObject = esys.gameObject;
    179.         }
    180.     }
    181.  
    182.     // Helper function that returns a Canvas GameObject; preferably a parent of the selection, or other existing Canvas.
    183.     static public GameObject GetOrCreateCanvasGameObject()
    184.     {
    185.         GameObject selectedGo = Selection.activeGameObject;
    186.  
    187.         // Try to find a gameobject that is the selected GO or one if its parents.
    188.         Canvas canvas = (selectedGo != null) ? selectedGo.GetComponentInParent<Canvas>() : null;
    189.         if (canvas != null && canvas.gameObject.activeInHierarchy)
    190.             return canvas.gameObject;
    191.  
    192.         // No canvas in selection or its parents? Then use just any canvas..
    193.         canvas = Object.FindObjectOfType(typeof(Canvas)) as Canvas;
    194.         if (canvas != null && canvas.gameObject.activeInHierarchy)
    195.             return canvas.gameObject;
    196.  
    197.         // No canvas in the scene at all? Then create a new one.
    198.         return CreateNewUI();
    199.     }
    200. }
     
    mitsuhiro_koga likes this.
  2. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Thank you for taking the time to provide this solution.

    As luck would have it and given this is something I should have offered a long time ago, I actually decided to add this button last week :)

    upload_2018-11-6_12-14-47.png

    P.S. I also renamed the TMP options.
     
    Hosnkobf likes this.
  3. Zennas

    Zennas

    Joined:
    Oct 5, 2013
    Posts:
    15
    Thats great :D
     
  4. ZoidbergForPresident

    ZoidbergForPresident

    Joined:
    Dec 15, 2015
    Posts:
    157
    Cool script, thanks! Wish I understood it. :p

    One thing though, how do you make it so that a text mesh pro text fits nicely on the button, like maxed out size without overflowing?
     
    x1alphaz1 likes this.
  5. Drabantor

    Drabantor

    Joined:
    Dec 22, 2017
    Posts:
    4
    Hi, what type of file (for example *.cs) should this file have with and does it matter where in the Unity editor file hierarchy I put the file in order to make this work?
     
  6. ZoidbergForPresident

    ZoidbergForPresident

    Joined:
    Dec 15, 2015
    Posts:
    157
    Its extension should be "cs" and I think you have to put it in a Subfolder called Editor (anywhere in the hierarchy).
     
  7. Zennas

    Zennas

    Joined:
    Oct 5, 2013
    Posts:
    15
    @Drabantor
    ZoidbergForPresident is right, it should be a .cs script and as long as its in a folder called "Editor" (or in a subfolder of a folder called "Editor") it should work fine.

    Although, its looks like the "navite" create TMP Button was added a few versions ago, so my script isnt that relevant any more (still usefull when using older versions of TMP i guess). As far i can tell the only visible difference is the font size (14 on mine vs 24 on TMP)
     
  8. Drabantor

    Drabantor

    Joined:
    Dec 22, 2017
    Posts:
    4
    Thanks guys. I thought that by upgrading Unity to e.g. version 2018.3.7 that Text Mesh Pro would upgrade to include the Text Mesh button but that was no the case for me. Instead in order to make this work, I had to go to the Package Manager under the Window tab in Unity and then upgrade Text mesh Pro to version 1.4.0. Now I have the Text Mesh button.
     
  9. MrG

    MrG

    Joined:
    Oct 6, 2012
    Posts:
    368
    With Unity 2018.4.4 LTS, current TMP from Package Manager, TMP essentials imported, the doc PDF is from 2016 and while I have the "Button - TextMeshPro" in the UI menu, it's no different than the regular button, and has no text components. It just creates a canvas and a UGUI button.
     
  10. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Something must have gone wrong on the package import.

    Make sure you are using package version 1.4.1. The Button - TextMeshPro has the button as the parent and the text component as a child.
     
  11. VResearch

    VResearch

    Joined:
    Jan 6, 2021
    Posts:
    21
    Which version of TMP has the "Button - TextMeshPro" option? I am on Unity 2018.2.14f1 and my TMP is on 1.3.0 (highest possible from the package manager), but unfortunately I do not see it (only dropdown, text input field and text).. :'(
     
  12. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    I think that was added to version 1.4.x which is already fairly old. The latest for 2018.4 is 1.5.4.

    Any reason why you are still running on 2018.2?