Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Feature Request UXML Preprocessing

Discussion in 'UI Toolkit' started by SevenSolaris, Apr 6, 2020.

  1. SevenSolaris

    SevenSolaris

    Joined:
    Jan 14, 2019
    Posts:
    20
    It would be incredibly useful if we could write scripts to process the UXML during the import process.

    Perhaps a class that could be inherited from that would have functions that could be overridden.

    Some example overridable functions that I'm thinking of are things like:

    string PreprocessText(string source)

    PreprocessText would be used to read the source string, then output a new string that will be passed on to the next step of the process.

    void PreprocessXml(XmlDocument document)

    PreprocessXml would be used to make changes to the XmlDocument before it is passed on to UI Toolkit.

    void PreprocessTree(VisualElement root)

    PreprocessTree would be used to make changes to the tree before it is returned from the asset importer as a VisualTreeAsset.

    Thoughts?
     
    recursive likes this.
  2. benoitd_unity

    benoitd_unity

    Unity Technologies

    Joined:
    Jan 2, 2018
    Posts:
    331
    Agreed and took note of your request.

    Thanks for your feedback!
     
    SevenSolaris likes this.
  3. SevenSolaris

    SevenSolaris

    Joined:
    Jan 14, 2019
    Posts:
    20
    I wrote a mockup class as an example of how this feature could be used. I'm sure there is more functionality that could be implemented, but this is what I was able to think of.

    Code (CSharp):
    1. //      ╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
    2. //      ║   Source File: source.sample.uxml                                                                                                   ║
    3. //      ╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
    4. //      ║<!--This is a comment.-->                                                                                                     ║
    5. //      ║<?xml version="1.0" encoding="utf-8"?>                                                                                        ║
    6. //      ║<UXML                                                                                                                         ║
    7. //      ║    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"                                                                     ║
    8. //      ║    xmlns="UnityEngine.UIElements"                                                                                            ║
    9. //      ║    xmlns:e="UnityEditor.UIElements"                                                                                          ║
    10. //      ║    xsi:noNamespaceSchemaLocation="../../../../UIElementsSchema/UIElements.xsd">                                              ║
    11. //      ║                                                                                                                              ║
    12. //      ║    <VisualElement name="#REPLACE_ME#">                                                                                       ║
    13. //      ║        <row>                                                                                                                 ║
    14. //      ║            <Label>Column1</Label>                                                                                            ║
    15. //      ║            <Label>Column2</Label>                                                                                            ║
    16. //      ║            <Label>Column3</Label>                                                                                            ║
    17. //      ║        </row>                                                                                                                ║
    18. //      ║    </VisualElement>                                                                                                          ║
    19. //      ║                                                                                                                              ║
    20. //      ║</UXML>                                                                                                                       ║
    21. //      ╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
    22.  
    23. //  When defining a UxmlPreprocessor, you can apply a filter to what files are affected.
    24. //  This allows you to define a naming convention for files that you would like this preprocessor to be applied to.
    25. //  By default, the filter would be "*" to apply to all files.
    26. //  You could also filter by folder, for example: "Assets/UI/Templates/Sample/*.uxml"
    27. //  You can apply multiple filters by using the '|'(Pipe) seperator.
    28. //  The `priority` determines which UxmlPreprocessors are considered first if one of their filters match.
    29. //  For example, let's say you have one UxmlPreprocessor that has a filter like "Assets/UI/One/*.uxml"
    30. //  then you have another that has a filter like "*.debug.uxml". You would want the "*.debug.uxml" filter to have a higher priority
    31. //  because it is more specific. That means that you would want to set the priority for "*.debug.uxml" to a number
    32. //  that is less than the priority for "Assrts/Ui/One/*.uxml".
    33. [UxmlPreprocessor(filter = "*.sample.uxml|*.demo.uxml", priority = 0)]
    34. public class UxmlPreprocessorExample : UxmlPreprocessor
    35. {
    36.  
    37.     //  Rather than passing in a string as a source, there are other options such as
    38.     //  StringReader, Stream, or some other way of sharing the source code.
    39.     //  This function should return valid XML source code.
    40.     public override string PreprocessSource(string src)
    41.     {
    42.         //  This is just a very basic example where you could modify the source string of a UXML file
    43.         //  before passing it on to the importer.
    44.         string result = src;
    45.         //  First we extract the first line from source.sample.uxml, which will be "<!--This is a comment.-->".
    46.         string firstLine = result.Substring(0, result.IndexOf(Environment.NewLine));
    47.         //  Next, we extract the remainder of the text from the source.uxml file.
    48.         //  This is achieved by getting the substrng starting after the index of the first new line.
    49.         result = result.Substring(result.IndexOf(Environment.NewLine) + 1);
    50.         //  Lastly, we added "#REPLACE_ME#" into our source file. We're going to replace that with a
    51.         //  proper name.
    52.         result = result.Replace("#REPLACE_ME#", "proper-name");
    53.         return result;
    54.     }
    55.  
    56.     //  After modifying the source code in PreprocessSource, we are now ready to modify the actual XML document.
    57.     public override void PreprocessXml(XDocument doc)
    58.     {
    59.         //  Get the elements with the tag "row".
    60.         var rows = doc.Descendants("row");
    61.         //  Iterate through the elements.
    62.         foreach (var row in rows)
    63.         {
    64.             //  Change the name of the tag to VisualElement.
    65.             row.Name = "VisualElement";
    66.             //  Get the style attribute if it has one.
    67.             var style = row.Attribute("style");
    68.             if (style != null)
    69.                 //  If the style is not null, append our new style.
    70.                 style.Value = style.Value + "flex-direction:row;";
    71.             else
    72.                 row.SetAttributeValue("style", "flex-direction:row;");
    73.         }
    74.     }
    75.  
    76.     //  After modifying the Xml Document, the importer would build the Uxml tree from the modified Xml document.
    77.     //  This function is clled just before the VisualElement is passed back from the Importer.
    78.     //  This is for the final touches that you would like to apply to the VisualElement tree, including adding callbacks and bindings.
    79.     public override void PreprocessTree(VisualElement root)
    80.     {
    81.         var node = root.Q<VisualElement>("proper-name");
    82.         node.style.display = DisplayStyle.None;
    83.     }
    84.  
    85. }