Search Unity

TextMesh Pro How to break up TextAlignmentOptions between horizontal and vertical?

Discussion in 'UGUI & TextMesh Pro' started by Spidyy, Sep 5, 2017.

  1. Spidyy

    Spidyy

    Joined:
    Mar 6, 2011
    Posts:
    184
    Hey there.

    I'm having some issues understanding how the alignment options works. As it is, the TextAlignmentOptions enum is a mess. It possess the 36 possible horizontal and vertical combinations, but there is no explicit way to get only the horizontal OR the vertical alignments.

    I want to have two enum popups to setup the text alignment in a custom editor, but the way it is done, I just cannot split them without passing by some huge comparison tables.

    Is there a way to split and merge back the TextAlignmentOptions to manage separately the horizontal alignment and vertical alignment?

    Spidyy
     
  2. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,596
    The TextAlignmentOptions are made up of the following.

    Code (csharp):
    1.  
    2. /// <summary>
    3. /// Internal horizontal text alignment options.
    4. /// </summary>
    5. public enum _HorizontalAlignmentOptions
    6. {
    7.     Left = 0x1, Center = 0x2, Right = 0x4, Justified = 0x8, Flush = 0x10, Geometry = 0x20
    8. }
    9.  
    10. /// <summary>
    11. /// Internal vertical text alignment options.
    12. /// </summary>
    13. public enum _VerticalAlignmentOptions
    14. {
    15.     Top = 0x100, Middle = 0x200, Bottom = 0x400, Baseline = 0x800, Geometry = 0x1000, Capline = 0x2000,
    16. }
    17.  
    So the TopLeft for example is as follows:

    Code (csharp):
    1. TopLeft = _HorizontalAlignmentOptions.Left | _VerticalAlignmentOptions.Top
     
    Last edited: Sep 5, 2017
    Faisalimran and Protozoaire like this.
  3. Protozoaire

    Protozoaire

    Joined:
    Apr 14, 2015
    Posts:
    61
    As it's the first result in Google

    For those searching it, you can write something like that:

    Code (CSharp):
    1.         switch(align_text)
    2.         {
    3.             case "left" :
    4.                 GetComponent<TextMeshProUGUI>().alignment = TextAlignmentOptions.TopLeft;
    5.             break;
    6.  
    7.             case "center" :
    8.                 GetComponent<TextMeshProUGUI>().alignment = TextAlignmentOptions.Center; //First
    9.                 GetComponent<TextMeshProUGUI>().alignment = TextAlignmentOptions.Top; //Second
    10.             break;
    11.  
    12.             case "right" :
    13.                 GetComponent<TextMeshProUGUI>().alignment = TextAlignmentOptions.TopRight;
    14.             break;
    15.             break;
    16.         }
    => Unfortunalty, there is no TextAlignmentOptions.TopCenter ATM (`TMPro.TextAlignmentOptions' does not contain a definition for `TopCenter').

    Link: https://docs.unity3d.com/Packages/com.unity.textmeshpro@1.3/api/TMPro.TextAlignmentOptions.html
     
  4. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,596
    These are the TextAlignmentOptions

    Code (csharp):
    1.  
    2. public enum TextAlignmentOptions
    3.     {
    4.         TopLeft = _HorizontalAlignmentOptions.Left | _VerticalAlignmentOptions.Top,
    5.         Top = _HorizontalAlignmentOptions.Center | _VerticalAlignmentOptions.Top,
    6.         TopRight = _HorizontalAlignmentOptions.Right | _VerticalAlignmentOptions.Top,
    7.         TopJustified = _HorizontalAlignmentOptions.Justified | _VerticalAlignmentOptions.Top,
    8.         TopFlush = _HorizontalAlignmentOptions.Flush | _VerticalAlignmentOptions.Top,
    9.         TopGeoAligned = _HorizontalAlignmentOptions.Geometry | _VerticalAlignmentOptions.Top,
    10.         Left = _HorizontalAlignmentOptions.Left | _VerticalAlignmentOptions.Middle,
    11.         Center = _HorizontalAlignmentOptions.Center | _VerticalAlignmentOptions.Middle,
    12.         Right = _HorizontalAlignmentOptions.Right | _VerticalAlignmentOptions.Middle,
    13.         Justified = _HorizontalAlignmentOptions.Justified | _VerticalAlignmentOptions.Middle,
    14.         Flush = _HorizontalAlignmentOptions.Flush | _VerticalAlignmentOptions.Middle,
    15.         CenterGeoAligned = _HorizontalAlignmentOptions.Geometry | _VerticalAlignmentOptions.Middle,
    16.         BottomLeft = _HorizontalAlignmentOptions.Left | _VerticalAlignmentOptions.Bottom,
    17.         Bottom = _HorizontalAlignmentOptions.Center | _VerticalAlignmentOptions.Bottom,
    18.         BottomRight = _HorizontalAlignmentOptions.Right | _VerticalAlignmentOptions.Bottom,
    19.         BottomJustified = _HorizontalAlignmentOptions.Justified | _VerticalAlignmentOptions.Bottom,
    20.         BottomFlush = _HorizontalAlignmentOptions.Flush | _VerticalAlignmentOptions.Bottom,
    21.         BottomGeoAligned = _HorizontalAlignmentOptions.Geometry | _VerticalAlignmentOptions.Bottom,
    22.         BaselineLeft = _HorizontalAlignmentOptions.Left | _VerticalAlignmentOptions.Baseline,
    23.         Baseline = _HorizontalAlignmentOptions.Center | _VerticalAlignmentOptions.Baseline,
    24.         BaselineRight = _HorizontalAlignmentOptions.Right | _VerticalAlignmentOptions.Baseline,
    25.         BaselineJustified = _HorizontalAlignmentOptions.Justified | _VerticalAlignmentOptions.Baseline,
    26.         BaselineFlush = _HorizontalAlignmentOptions.Flush | _VerticalAlignmentOptions.Baseline,
    27.         BaselineGeoAligned = _HorizontalAlignmentOptions.Geometry | _VerticalAlignmentOptions.Baseline,
    28.         MidlineLeft = _HorizontalAlignmentOptions.Left | _VerticalAlignmentOptions.Geometry,
    29.         Midline = _HorizontalAlignmentOptions.Center | _VerticalAlignmentOptions.Geometry,
    30.         MidlineRight = _HorizontalAlignmentOptions.Right | _VerticalAlignmentOptions.Geometry,
    31.         MidlineJustified = _HorizontalAlignmentOptions.Justified | _VerticalAlignmentOptions.Geometry,
    32.         MidlineFlush = _HorizontalAlignmentOptions.Flush | _VerticalAlignmentOptions.Geometry,
    33.         MidlineGeoAligned = _HorizontalAlignmentOptions.Geometry | _VerticalAlignmentOptions.Geometry,
    34.         CaplineLeft = _HorizontalAlignmentOptions.Left | _VerticalAlignmentOptions.Capline,
    35.         Capline = _HorizontalAlignmentOptions.Center | _VerticalAlignmentOptions.Capline,
    36.         CaplineRight = _HorizontalAlignmentOptions.Right | _VerticalAlignmentOptions.Capline,
    37.         CaplineJustified = _HorizontalAlignmentOptions.Justified | _VerticalAlignmentOptions.Capline,
    38.         CaplineFlush = _HorizontalAlignmentOptions.Flush | _VerticalAlignmentOptions.Capline,
    39.         CaplineGeoAligned = _HorizontalAlignmentOptions.Geometry | _VerticalAlignmentOptions.Capline
    40.     };
    41.  
    Top center is Top.
     
  5. jashan

    jashan

    Joined:
    Mar 9, 2007
    Posts:
    3,307
    So, how can I change the horizontal alignment without changing the vertical alignment? Do I have to AND with all vertical alignment options, and then OR with the new horizontal alignment? While possible, that seems like a rather cumbersome approach (especially because I'd first have to create the "all vertical alignment options" by ORing all of them).
     
  6. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,596
    Can you provide more context?

    To preserve whatever the Vertical alignment and change only the horizontal, you could use the following:
    Code (csharp):
    1. TextComponent.alignment = (TextAlignmentOptions) ((int)TextComponent.alignment & 0xFF00 | (int)_HorizontalAlignmentOptions.Right);
     
  7. jashan

    jashan

    Joined:
    Mar 9, 2007
    Posts:
    3,307
    Use case for this is a text field in a list with several entries (it's kind of a tree-table with each entry being a button that has several text fields, for things like a name, a start time OR timespan). Depending on the content that is displayed / the level in the hierarchy, one of those text fields is either left aligned, or right aligned, so the code loading up the values needs to change the horizontal alignment. And I wanted that alignment change to work without assuming what the vertical alignment is.

    Ah, yeah, cool, that makes sense. I guess what I'm asking for is to add two more entries to those enums, then:

    Code (CSharp):
    1.     public enum _HorizontalAlignmentOptions {
    2.         All = 0xFF,
    3.         Left = 1,
    4.         Center = 2,
    5.         Right = 4,
    6.         Justified = 8,
    7.         Flush = 16,
    8.         Geometry = 32
    9.     }
    10.  
    11.     public enum _VerticalAlignmentOptions {
    12.         All = 0xFF00,
    13.         Top = 256,
    14.         Middle = 512,
    15.         Bottom = 1024,
    16.         Baseline = 2048,
    17.         Geometry = 4096,
    18.         Capline = 8192
    19.     }
    20.  
    I'm not sure what the best name is for those, but "All" seems reasonably easy to understand.

    What's the rationale behind putting both horizontal and vertical into the same attribute?

    It seems to me that this would only have disadvantages. Like, aside of the obvious issue that I ran into: When using text fields in prefabs, you also might want to override the horizontal alignment on some, but not the vertical alignment. It seems like a very likely use case to want to change the vertical alignment for all instances, then (so it would be changed on the main prefab) but this probably won't work if both are in the same field because then, even though only horizontal was overridden, technically, the prefab system would think both are overridden because there really is only one.

    I don't see any case where vertical and horizontal alignment depend upon each other, so to me it seems like those should be separate. But I might be missing something.
     
  8. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,596
    Do you mean TMP_Text.alignment using TextAlignmentOptions which combines both horizontal and vertical options?

    I guess what you are looking for is perhaps two extra properties which would be TMP_Text.horizontalAlignment and TMP_Text.verticalAlignment which would make it easier to change one of the other.

    Update
    Since it makes sense, I went ahead and added the following:

    Code (csharp):
    1. public HorizontalAlignmentOptions horizontalAlignment
    Code (csharp):
    1. public VerticalAlignmentOptions verticalAlignment
    These new properties allow independent control over both horizontal and vertical alignment options.

    Note that I renamed these two enum types to remove the underscore in front of these names.
     
    Last edited: Jun 2, 2019
    burningwipf and jashan like this.
  9. jashan

    jashan

    Joined:
    Mar 9, 2007
    Posts:
    3,307
    Exactly. I just wondered why those were combined.

    Exactly. With the &-ing, and the "All" (0xFF / 0xFF00), it's not too hard to do but I don't see the benefit in having to jump through that hoop, and I also see the issue with prefabs.

    Awesome! So those will also store these values now, right? That's relevant for the potential prefab-issue. Also, you'll probably need some migration for this when it replaces the old approach.
     
  10. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,596
    I am not replacing the TMP_Text.alignment. I simply added two new properties to provide an alternative way to control horizontal and vertical alignment. Internally nothing changes as only the m_Alignment property remains serialized.
     
    jashan likes this.
  11. jashan

    jashan

    Joined:
    Mar 9, 2007
    Posts:
    3,307
    Ok, that does solve the issue of making it easy to change those independently. What are your thoughts concerning prefab overrides?
     
  12. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,596
    I think it does as you will be able to simply use :

    Code (csharp):
    1. // Only change Horizontal Alignment
    2. TextComponent.horizontalAlignment = HorizontalAlignmentOptions.Flush;
    3.  
    4. // Only change Vertical Alignment
    5. TextComponent.verticalAlignment = VerticalAlignmentOptions.Top;
    6.  
    7. // Instead of previously having to do something like this to only change Horizontal Alignment.
    8. TextComponent.alignment = (TextAlignmentOptions) ((int)TextComponent.alignment & 0xFF00 | (int)_HorizontalAlignmentOptions.Flush);
    9.  
    With regards to prefab overrides, this won't have any affect as changing the alignment which is a serialized property will show up as an override. Not sure we have any options there.
     
  13. jashan

    jashan

    Joined:
    Mar 9, 2007
    Posts:
    3,307
    You do, but it does come with a migration / update requirement, so I understand it's not a change made easily without deeper consideration: But with the current approach of keeping both horizontal and vertical alignment in the same serialized property, TextMesh Pro severely limits how the prefab system can be used.

    Use case: You have a prefab for text that appears in a table. Some cells are left, some centered, some right aligned. These could either be prefab variants or just horizontal overrides in the instances. Now, let's say you had vertical alignment set to top and want to change it to middle or bottom vertical alignment.

    With horizontal and vertical alignment stored in two separate properties, that's trivial: They are independent, as they should be. You simply change the vertical alignment in the main prefab, and all instances / variants inherit the new vertical alignment while keeping their horizontal override.

    With horizontal and vertical in one and the same property, vertical is overridden simply by changing horizontal. And worse: This is not even obvious from the UI because the UI makes it look like those are two different properties (which is also what people would expect). So you change the vertical alignment in the prefab, assuming the instances will inherit that change because you didn't override vertical ... but that doesn't happen.

    That's actually quite likely to cause major headaches for some people.

    Solution: Make verticalAlignment and horizontalAlignment the actual serialized properties. Add a new option OptionWasConvertedToSeparateAlignments option to TextAlignmentOptions (that's a long name but it will appear at some point in time). Rename alignment to alignmentAlignedOld with FormerlySerializedAs("alignment"), introduce a new property alignment with get/set mapping properly to verticalAlignment combined with horizontalAlignment.

    On Awake(), if alignmentAlignedOldhas any other value than OptionWasConvertedToSeparateAlignments, grab that value and push it into verticalAlignment and horizontalAlignment (which is trivial because we have the new property alignment, so that's basically alignment = alignmentAlignedOld). Make sure that value gets stored.

    Problem solved.

    Over time, convert any usage of alignment into verticalAlignment or horizontalAlignment. Probably, at some point, alignment can be made obsolete and later be removed.
     
  14. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,596
    I never got around to providing feedback on this. So I just wanted you to know that I will be most likely revising the implementation to split up as you suggested the alignment modes. No ETA yet but it will happen.
     
    Alverik and jashan like this.
  15. ledbetterMRI

    ledbetterMRI

    Joined:
    May 29, 2019
    Posts:
    25
  16. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,596
    I agree. There are folks at Unity working on updating the documentation which is long overdue.
     
    Zullar and ledbetterMRI like this.
  17. ledbetterMRI

    ledbetterMRI

    Joined:
    May 29, 2019
    Posts:
    25
    Glad to hear! Thanks for following up.