Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Official Introducing the Runtime Bindings API in Unity 2023.2

Discussion in 'UI Toolkit' started by martinpa_unity, Jun 28, 2023.

  1. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    484
    Starting with the 2023.2.a18 alpha release of Unity, we have added the runtime bindings feature to UI Toolkit. We have offered a solution to bind against serialized data for a long while now, however it was only available inside the editor and you could only bind a restricted set of data to the
    value
    property of a bindable control.

    While porting the bindings system to be available at runtime as well, we decided to generalize it and to give you more control over how the bindings are registered and updated. This new system was designed with customizability and extensibility in mind in order to enable a myriad of use cases.

    One use case that comes out of the box is the ability to create a link between your data and different parts of the UI. For example, provided you have the following data type:
    Code (CSharp):
    1.  
    2. public class MyDataSource
    3. {
    4.     [CreateProperty]
    5.     public string Name { get; set; }
    6.     [CreateProperty]
    7.     public int Level { get; set; }
    8. }
    You can now create a binding between an instance of
    MyDataSource
    and your UI by doing:
    Code (CSharp):
    1.  
    2. var element = new VisualElement();
    3.  
    4. // Sets a data source that will be available to binding instances on this element and its children.
    5. element.dataSource = new MyDataSource
    6. {
    7.     Name = "Peter",
    8.     Level = 9001
    9. };
    10.  
    11. var nameLabel = new Label();
    12. element.Add(nameLabel);
    13.  
    14. // Create a one-way binding from the source to the "text" property of the label.
    15. nameLabel.SetBinding(nameof(Label.text), new DataBinding
    16. {
    17.     dataSourcePath = new PropertyPath(nameof(MyDataSource.Name)),
    18.     bindingMode = BindingMode.ToTarget
    19. });
    20.  
    21. var levelField = new IntegerField();
    22. element.Add(levelField);
    23.  
    24. // Create a two-way binding from the source to the "value" property of the integer field. Changes in the UI will be propagated back to the source.
    25. levelField.SetBinding(nameof(IntegerField.value), new DataBinding
    26. {
    27.     dataSourcePath = new PropertyPath(nameof(MyDataSource.Level)),
    28.     bindingMode = BindingMode.TwoWay
    29. });
    30.  
    31. rootVisualElement.Add(element);
    32.  
    This example, while very simple, should help you get started quickly on using the new runtime binding system.

    In order to keep this announcement post as short as possible, we have written a series of articles that will provide an in-depth guide into the new runtime bindings system:

    These articles are meant to be live documents and will be updated in the weeks to come to provide more details and more insights.

    We hope this feature will enable new use cases for you!

    As always, we are open to feedback and questions. :)
     
    Last edited: Aug 15, 2023
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,342
    This looks cool!

    Questions!

    1: In part 3, you're writing that
    Isn't using a value type as a data source kinda meaningless? It's a value type, so we'de be passing the ui a copy of the value, so it won't be possible to reflect any changes. So it's essentially equivalent to an extremely expensive way of just setting the display value of the visual element. I don't think the boxing is the biggest problem here!

    2: Also in part 3:
    I don't quite understand that! Are you saying that the binding will work on subtypes of the type the property is on, or that the properties will accept subtypes for the bindings, or?

    3: I don't understand why we would ever want to be "Implementing both IDataSourceViewHashProvider and INotifyBindablePropertyChanged". The example doesn't make any sense to me - why would we want the system to calculate a hash when we know exactly when the data have changed and the UI needs to update?
    Is the idea that when you combine the interfaces, the UI is only updated when you both invoke the PropertyChangedEventHandler and the hash code has changed?
     
    Lautaro-Arino and tatoforever like this.
  3. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    484
    Hi @Baste

    Yes, you would pass in a copy. If your value type only contain value types or primitives, then you are correct, it won't reflect any changes. However, if it contains reference types as field/properties, then it is still possible to have the changes be reflected (of course, as long as those references hold).

    I've encountered a lot of handle-type structs that hide references to non-public classes. Usually, those structs don't hold any data and acts only as a wrapper to the data. Those would still work.

    We certainly don't recommend to use value types as data sources, I'll take a note to review this part. Thank you!

    For this, I am saying that the binding will work on subtypes of the type of the property. So, in the property bag system, pretty much every is more akin to
    [SerializeReference]
    than to
    [SerializeField]
    , if that makes sense. We'll review that part as well.

    So the use case here is admittedly a bit niche, but I've certainly used it in the past. If you have a lot of very volatile data, but you don't want the UI to be constantly updated for performance reasons, you could delay it using the
    IDataSourceViewHashProvider
    with a custom made "hash" instead of reflecting the changes directly. That way, you get to decide when the UI is updated.. and when it is updated, only the parts that have changed will be updated.

    You are correct that the example doesn't provide any value, we'll update it.

    Hope this helps,
    Thank you.
     
    Last edited: Jun 29, 2023
  4. YourWaifu

    YourWaifu

    Joined:
    Oct 4, 2015
    Posts:
    45
    UnityAction to Button binding still not available or am i stupid blind?

    And yet another question about IDataSourceViewHashProvider, why not use IEquatable<>?
     
  5. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    484
    You can do this by creating a
    CustomBinding
    and un/setting the callbacks from the
    OnDataSourceChanged
    method.

    So the
    IDataSourceViewHashProvider
    interface is not about checking for equality. The idea is to tell the binding system when to update the bindings that are related to a specific data source. You can have some data that changes every frame without wanting the UI to be updated every frame.

    We also needed something that we could store the result from for comparisons,
    IEquatable<>
    doesn't really fit the bill.

    The example provided where we implement both interfaces treats it as a "normal hash code", which doesn't really indicates what it can do. We'll update it soon.
     
    YourWaifu and mariandev like this.
  6. Thygrrr

    Thygrrr

    Joined:
    Sep 23, 2013
    Posts:
    705
    Uhh, wow - that's a huge amount of boilerplate required over and over again to bind each individual value.

    I'm scratching my head and perhaps asking a quite sad "How about no?"

    The SetBinding calls could and should be automatic, taking all schema information from the structure, field visibility, and annotations in the code.

    I guess the ship has sailed on suggesting a pure codegen solution, like Qt provides, where UI assets are transpiled into C# code, that is then compiled. (so UI changes lead to compile time errors, which beats finding them in your bug reports inbox the next week)

    There's merit to data driven UI. But then Unity's solution has to be less verbose than these transpiled workflows, not more verbose.

    Basic Idea
    Something that could work in your Architecture right now, with just a little bit of codegen:
    Code (CSharp):
    1. nameLabel.SetBinding(nameof(Label.text), ref myDataSource.name); //default parameter mode:BindingMode.TwoWay
    (you might do the PropertyPath stuff inside the code of SetBinding, for example via a Type Parameter to SetBinding, especially with Roslyn by your side to annotate/populate MyDataSource appropriately, but it could be easier to do it in the getters and setters of MyDataSource.name)

    Possibly it could some day boil down to something like (sorry I'm from the C++ UI framework world):
    Code (CSharp):
    1. myDataSource.name = ref nameLabel.text;
    (and automatic traversing mappers of this for larger trees of VisualElements and more complex data sources - however, this could force UI structure to mirror Code structure, which seldom works out)

    There's much more to be gained from a slightly different approach; copied this here from my thread on Unity.Properties:

    Blue Sky Counterexample
    Game UI often consists of small packs of data that go together, like start & end dates, bullets & clip capacity, wood & gold & stone, x and y values, first names and last names, emails and passwords, HP and mana, "all difficulty settings", but the layouts these are shown in are often moved around a lot in the UI and its layout during editing, and oftentimes at runtime, too.

    That means not the roots of paths, but instead the ends (suffixes) of property paths often stay relatively similar, e.g. if I move a DatePicker to a different parent element, it's still a DatePicker there, with the same Year, Day, Month, properties, just a different path leading there from the root.

    There are also plenty of infixes that can just be skipped over - e.g., the boxes around the gold price and the silver price in a price tag.

    The UX designer doesn't want approval from the developer want to name each of these roots, paths and infixes.

    The developer doesn't want to ask the name of a new path from the UX designer whenever an item moves.

    They both just need an agreement that there's a DatePicker somewhere, named "birthday", and what the small-scale, local relative property names are. Unnamed elements intuitively count as empty infixes.

    I believe a ideal world example could look kind of like this:
    (XContainer and XYContainer here are what MyDataSource is in the original post)
    Code (CSharp):
    1. partial struct XContainer //NB: absence of even [Serializable], use only if desired
    2. {
    3.    [CreateProperty(/*optionalpath*/)] public int X {get; set;}
    4. }
    5.  
    6. //Just get and set X as you always would.
    7. XContainer a = new {X=42};
    8.  
    9. //Generated code resolves the path, which you may override via optionalpath
    10. //In the default case the path is derived from its name by codegen
    11. //X declares its path, not an outside constant in another class.
    In a data binding scenario, the user-facing boilerplate should be limited to the type declarations, property attributes, and single UXML elements.
    Code (CSharp):
    1. //UXML declaration for the associate UI element
    2. <IntegerField name="X" label="Answer" value="0" />
    3.  
    4. // All "X" of type int in all bound containers are bound to this IntegerField
    5. // matching by longest common suffix. If you had a ParentContainer with
    6. //    [CreateProperty]XContainer child{get;set;}
    7. //    [CreateProperty]XContainer friend{get;set;}
    8. // then "X" would match both child.X and friend.X
    9. // while "child.X" and "friend.X" would only match the respective fields.
    ...and in C#-Land...
    Code (CSharp):
    1. //2nd container is a surprise tool that will help us later
    2. partial struct XYZContainer
    3. {
    4.    [CreateProperty] public int X {get; set;} = 69;
    5.    [CreateProperty] public int Y {get; private set;} //one-way, can't be set by visitor (UI)
    6.    [CreateProperty] public int Z {private get; set;} //one-way, write-only for visitor (UI)
    7. }
    8.  
    9. //Binding some datas now! Finally. Ohhh baby!
    10. XYZContainer ab = default;
    11.  
    12. //The Visitor would also be automatically generated, and used by the Binder
    13. rootVisualElement.Bind(ref a, ref ab, ...); //NB: for most cases, you can bind directly to root!
    14.  
    15. //UI now shows 69, as bindings apply in param order
    16. //Should you prefer to take the initial value from UXML or the element's
    17. //state instead, imagine the appropriate overloads.
    18.  
    19. //User types "123" in UI
    20. Debug.Log($"{a.x}=={ab.x}"}); // >"123==123"
    21. //all bound containers receive the forward-propagated data from UI
    22.  
    23. //Code path executes this somewhere: ab.x = 9000+1;
    24. Debug.Log($"{a.x}=={ab.x}"}); // >"9001==9001"
    25. // use style/property on IntegerField in UXML to limit back-propagation if desired
    26. // accessors and propagation always synchronous, rendering always asynchronous
     
    Last edited: Aug 1, 2023
  7. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    484
    Hi @Thygrrr!

    Are you talking about calls such as this one?
    Code (CSharp):
    1. nameLabel.SetBinding(nameof(Label.text), new DataBinding
    2. {
    3.     dataSourcePath = new PropertyPath(nameof(MyDataSource.Name)),
    4.     bindingMode = BindingMode.ToTarget
    5. });
    If so, then there are reasons for each individual piece here:
    * The first parameter of the
    SetBinding
    method is the binding Id. In the case of a
    DataBinding
    , it is the targeted property. I used the
    nameof
    version in the example instead of a naked string, but a string would work or a wpf-like static property definition (i.e.
    Label.textProperty
    , which we have internally, but it missed the cutoff). In the case other binding types, you get to define your own ids.
    * The second parameter of the
    SetBinding
    method is the binding object itself. Now, the reason it is an object is because we support multiple types of bindings and not just data bindings. You could define a binding object that will add/remove uss classes on your element; you could define a binding object that will localize your content (which we will also provide in the localization package); you could define a binding object that will bind to a manipulator/logic/events, etc. If you only care about data bindings in your own code, you can easily create an extension method that will remove the boilerplate.
    * The parameters we construct the binding instances with are completely optional and depend on your setup and the type of binding you are using.

    If you are using uxml files, you can also create most of your data bindings directly inside the UI Builder, which will allow you to add bindings without writing any code.

    First, I want to address the code generation bit. Code generation can add to the compilation time and the domain reload time as well. This might be a cost that you are willing to pay, but we can't generalize this sentiment. We've already added code generation (through source generators) to remove the boilerplate associated with an element's factory/traits classes and we've added code generation for the property bags we used for data bindings. This has an impact on both the compilation and domain reload times. As such, they are both opt-in through the use of attributes.

    We could add an on-demand code generation of a UI schema of a Uxml file from the UI Builder (and this is something that's been experimented by some folks), but this would need to come with an integrated workflow, which is not in our priorities at the moment.

    Second, we have decided to not to make
    SetBinding
    calls automatic based on the schema information because we found that rather limiting and, in our testing, resulted in writing more boilerplate. We instead decided to add ways to configure how the data bindings work so that you could re-use the same source in different contexts. We also decided that we should support different kinds of data sources. Some examples:
    • Want to use the exact same source for in-editor authoring/tooling (editable) and in-game display (not editable)? You can set the binding mode to be
      TwoWay
      on the tooling side and
      ToTarget
      for the game.
    • Want to process the changes from the UI in a command pattern? You can set the binding mode to
      ToTarget
      and register callbacks on the element to create commands (or define a custom binding to do it).
    • Want to have a dynamic data source where bindable properties are generated on the fly from one or many other pieces of data? You can create a property bag manually, gather the data and generate properties on the fly. One example of this is the
      EntityContainer
      in ECS where the
      Entity
      type is a handle and the component data is stored somewhere else. A manually crafted property bag was created to gather the different kinds of components an entity can have.
    The idea behind these decisions is that we want to offer a solution that can used across a variety of use-cases and not force one in particular. That usually leads to more options, more ways to configure and/or more boilerplate. I personally don't see that as a problem. We can build something simpler on top of the system if need be. In my experience, making something automatic by default has usually led to more problems down the road.

    I'm not sure I'm following you here.

    Sure, you can set any arbitrary C# object as your data source. You can set a data source on any given visual element inside your hierarchy and alternatively, you can set a data source on the binding object directly. There are plenty of options to ensure that designers would only need to care about leaf properties and ensure that moving elements around won't break the bindings.

    This is the case with the current system. If a visual element does not override the data source or the data source path, it is skipped.

    Again, all doable with the current system.

    The only caveat is that you can't set arbitrary C# objects as data sources inside the UI Builder because we need to save the uxml files as assets and be able to reload them. So at the moment, we only support putting assets as the data source, but in code, there is no such limitation. For authoring purposes, you can set the targeted type of the source and you will have access to some form of auto-completion even though the source is not actually set.

    Hope this helps!
     
    PaulMDev and Kirsche like this.
  8. Thygrrr

    Thygrrr

    Joined:
    Sep 23, 2013
    Posts:
    705
    Yes. That's a lot of boilerplate. Am I alone in this feeling?

    Let's focus on the runtime (non UXML) API here, since that was the OP example.

    Imagine hooking up 75 UI fields like that, scattered across a project. Maintaining all this.
    Imagine passing a method on a Label the name of its own text property.

    There are, if we're generous, 38 characters of information in there (nameLabel,text,MyDataSource.Name,ToTarget), padded with 3x that in boilerplate. Signal-to-noise of 38:167.

    Assume most of these defaults are usually the same, (LabelField usually wants to bind to text, and ToTarget is decent default) the SNR drops to 26:167.

    These are worse code-to-boilerplate ratios than Hello World in Java. But Java's ratio gets better when you add a second line of output; but in UI Toolkit, the ratio stays constant if you add a second binding.

    Like I said, an ideal syntax goal for this iteration could be (coincidentally in ~38 characters of information):
    Code (CSharp):
    1. nameLabel.text = (ref)anyObject.Name;
    I don't see how that kind of clear syntax and intent wouldn't be possible, as the set accessor behind Label.text can surely do the necessary binding work. (even if just replicating, almost line by line, the boilerplate you were showing)

    Or, if you prefer to make it an explicit method on the Label type itself, the same applies:
    Code (CSharp):
    1. nameLabel.SetBinding(in anyObject.Name); //hey, also ~38 chars
    2. //in, out, ref express binding mode much better than that enum
    3. //a cool pattern from DOTS; but if too sus, this is also good:
    4. nameLabel.SetBinding(ref anyObject.Name, BindingMode.ToTarget);
    Why we as users would need to care about PropertyPaths and (a little less so) Binding IDs, I would never know. We also don't want or need a specific DataSource object that wraps our values, we usually want to bind an extant object's values, which the C# Language perfectly supports. No need to be declaring accompanying dataSourcePaths or bindingModes for each field when we avoid that forced facade pattern.

    No DataSource facade means no sources to re-use to begin with. The data binding instead is already the second (or more) use for anyObject.
     
    Last edited: Aug 2, 2023
    BTStone and Sluggy like this.
  9. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    484
    text
    is one of many properties that you can bind to for a
    Label
    . For example, you can also bind
    enableRichTextProperty
    ,
    selectable
    ,
    tooltip
    , one of the style properties and others. They can all be bound using the same syntax.

    If you want a dedicated path for the
    text
    property of a
    Label
    , then you can easily define an extension method that will remove the boilerplate for you.

    The binding mode is a property of the binding instance. You may still want to detect changes made from code to the
    Label.text
    property and not just from the UI. In the UI Builder, we're looking into setting a different binding mode when you add a binding based on what you would most likely want.

    We have access to two forms of code generation:
    • IL Post Processors, where you can re-write the IL after its been compiled.
    • Source Generators, where you can add new code to complement or to complete existing code.
    In the case of IL Post Processors, as far as I'm aware, the input have to be valid C# code. In the case of Source Generators, we cannot re-write existing code and the resulting code must be valid C# code.

    Code (CSharp):
    1. nameLabel.text = (ref)myDataSource.name;
    With the example data source that I've provided in the original post and the version of C# we have access to at the moment, this snippet gives two compilation errors:
    1. error CS8373: The left-hand side of a ref assignment must be a ref variable.
    2. error CS1510: A ref or out value must be an assignable variable
    I can fix the data source side to return a ref property, but that removes only one compilation error and there are other considerations with using ref properties as well.

    Perhaps I'm not understanding you correctly. Can you make a custom
    VisualElement
    that would define a
    text
    property that would allow this?

    Two reasons why we expose
    PropertyPath
    :
    1. You can bind to sub-properties of a data source (i.e.
      source.path.to.my.value
      .
    2. You can construct a
      PropertyPath
      using different part types. There are three part types: name, index (for lists/arrays) and keys (for dictionaries and hashsets).
     
    TheSmokingGnu, Kirsche and mariandev like this.
  10. AliAlbarrak

    AliAlbarrak

    Joined:
    Jan 24, 2018
    Posts:
    15
    I know this might be going off topic but I'd like to know if you are considering implementing my suggestion here. Basically, specifying the target type for binding in UXML will help tooling developers, for IDEs and Unity Assets Store alike.

    I'd really like to see compile-time / static checking for binding. All XML based UI frameworks I worked with supports static checking, including but not limited to Android UI, WPF, UWP, and even Angular
     
  11. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    484
    Hi @AliAlbarrak!

    That is a good feature to have and something we have on our radar as well. At the moment, we've added the
    data-source-type
    as an attribute that the UI Builder can use to drive the auto-complete when setting the
    data-source-path
    attribute.

    It doesn't do static validation, but it's something we can build on. Not perfect, but it's a start!

    [Edit] For the editor bindings, it's already using the new system under the hood, but we intend to expose public dedicated binding types to deal with serialization. Once we expose these, they will inherit the workflows as well.
     
    AliAlbarrak likes this.
  12. Pot

    Pot

    Joined:
    Apr 16, 2020
    Posts:
    2
    Hey guys. appreciate your work a lot, and thank you for the examples on how to use this API.
    However, I am sorry to say but I find it still very hard to understand from your examples how to use the new binding on a list with a ListView, something that I imagine should be very simple given how common of a use case it is in UI development (i.e. A simple inventory system, a list of characters etc...)

    In the 2022 LTS version there was this page
    https://docs.unity3d.com/2022.3/Documentation/Manual/UIE-bind-to-list.html
    Which clarified some things, but considering it only worked in the editor and not in runtime, I would really appreciate a new simple tutorial like this with the new system

    The only reason I upgraded my simple 2d UI-heavy idle game to 2023.2 was to use this new binding system, as I thought it would save me a lot of time.
    So I would really appreciate any help in the matter.

    Thanks in advance for your hard work!
     
  13. Saniell

    Saniell

    Joined:
    Oct 24, 2015
    Posts:
    205
    I think it would help if you provided built in extensions methods to reduce boilerplate.

    Also I don't completely understand this part.
    If we can bind properties, then why is any of these binding objects necessary? If you want to convert some data you can just do that in a property itself. So binding objects seems to be only necessary when you want to "add/remove uss classes". If I had to guess one of the use cases would be, like, making health text red on low HP?
    In this case though I think it would be easier to have some value "post processors", that get a callback when property value is changed. At least this way you don't couple data conversion itself and how UI is styled based on it, not to mention you could stack these post processors
     
    Last edited: Aug 8, 2023
  14. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    484
    Hi @Pot!

    I did talk about how to bind to the
    ListView
    in this post here. As I mentioned in that post, I consider the
    ListView
    only partially supported at the moment as you will need to create a custom binding to refresh or rebuild the list. It's something we want to improve, but haven't had time to complete a full generic solution just yet.

    What I'm currently doing is a custom binding on the list view that tracks the count and call
    RefreshItems
    when it changes, so that the list view can add/remove them. Individual items should be fine if they are using the method in the link.

    Hope this helps!
     
    mariandev likes this.
  15. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    484
    Hi @Saniell

    There are several reasons why they might be necessary:
    1. The data you are binding against is not readily available through instance fields/properties and requires a method call.
    2. The binding you are creating doesn't need to go through data at all.
    3. You have a specialized data source and you can create a binding object that will perform better than
      DataBinding
      .
    4. You only care about when the resolved data source and not its individual properties.
    For example, when dealing with localization, you typically won't have a field or a property to bind against. Instead, you usually have a localization table id and localization key id and you can query the system to get the localized value. In this case, creating a custom binding is the way to go. The localization package will be updated to provide these custom bindings so that you won't have to. This should be part of the 1.5.0 version of the package.

    Another example is when dealing with events, such as adding a callback to a
    Button
    where the callback itself is defined by the binding object.

    One last example is something I'm toying with, which is to create an element that can generate a UI hierarchy based on some data and automatically bind to it. In that use-case, the element itself doesn't care about the current state of the data source, it only cares about when the data source itself has changed. This looks something like:

    Code (CSharp):
    1. private class GenerateHierarchyBinding : CustomBinding
    2. {
    3.     public GenerateHierarchyBinding()
    4.     {
    5.         // Doesn't need to be updated, so setting `WhenDirty` to avoid most updates.
    6.         updateTrigger = BindingUpdateTrigger.WhenDirty;
    7.     }
    8.    
    9.     // Overriding this method to receive a callback only when the resolved context changed.
    10.     protected override void OnDataSourceChanged(in DataSourceContextChanged context)
    11.     {
    12.         var generatorElement = context.targetElement as /* ... */;
    13.         // Generate hierarchy based on the `context.newContext.dataSource` and `context.newContext.dataSourcePath` properties.
    14.     }
    15. }
    It could be any use-case where you need to add/remove a uss class: change the text color of something, hide/display elements, etc. When dealing with style properties, it's usually better to go through adding/removing uss classes when it is possible than creating binding objects to individual properties. Not always possible, though.
     
    karl_jones likes this.
  16. Saniell

    Saniell

    Joined:
    Oct 24, 2015
    Posts:
    205
    Well couldn't you just do this?

    Code (CSharp):
    1. class Data
    2. {
    3.     public LocalizedTextID TextID;
    4.  
    5.     public string Text
    6.     {
    7.         get => Localization.Lookup(TextID);
    8.     }
    9. }
    In any case, can I bind multiple things at once to a property? Say I'm making My Friendly Neighbourhood and my health is displayed as localized text which also changes color based on value. The way I would want to do it is by binding text using code similar to above (if possible) and then have a callback "On value change - color the text"

    I guess my point is that just having a callback seems easier than a singular binding. But I am not very experienced in UIToolkit so this is just a first impression
     
  17. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    484
    Code (CSharp):
    1. class Data
    2. {
    3.     public LocalizedTextID TextID;
    4.     public string Text
    5.     {
    6.         get => Localization.Lookup(TextID);
    7.     }
    8. }
    Yes, that's what the localization bindings will do under the hood. The difference between using a custom binding and going through a data source and using
    DataBinding
    in this case is performance. If you have all the necessary information to resolve the value using the localization table id and localization key, there is no need to also extract the value from
    Data.Text
    , as it's pretty much just wasted computing time. A dedicated binding type here can also ensure that the binding is updated only when the localization database or the current locale is changed.

    Yes, you can. The preferred and most performant way is to use a custom element that has a value that can change multiple properties when it's set. You can also create multiple bindings that use the same input, but targets different UI properties, but it will less performant than using a custom element.

    Another way would be to extend an existing binding type and apply additional logic to it.
     
  18. Saniell

    Saniell

    Joined:
    Oct 24, 2015
    Posts:
    205
    Oh I see, makes sense right.

    Again, I see, makes sense. Really nice.

    Well I prefer to not have inheritance deeper than 1 level necessary :)
    Thanks for answering my questions, looking forward to using this API in the future to get a better feel for it. Btw if you still didn't, I would appreciate if you added some examples of having multiple bindings, maybe something similar to that colored text thing I mentioned
     
  19. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    484
    Sure, I'll provide three examples here:
    1. Using a custom element with a value that will set its own sub properties when changed. This requires the most code, but it is probably the most efficient solution and the most "self contained" solution as well.
    2. Using a normal Label and registering multiple bindings from the same input string with converters.
    3. Defining a binding that extend
      DataBinding
      .
    All examples will focus on using the UI Builder. All could be done through code by calling
    SetBinding
    as well.

    Using a custom element:
    ComplexLabel.gif

    This example requires more code, but it would behave the same way whether the value is set from a binding, from the UI Builder or from code. This is most likely the most efficient solution as well, as a single binding is necessary and no conversion is done.

    The custom element is defined as:
    Code (CSharp):
    1. using Unity.Properties;
    2. using UnityEditor;
    3. using UnityEngine;
    4. using UnityEngine.UIElements;
    5.  
    6. [UxmlElement]
    7. public partial class ComplexLabel : VisualElement
    8. {
    9.     public static BindingId valueProperty = nameof(value);
    10.    
    11.     private Label m_Label;
    12.  
    13.     [CreateProperty, UxmlAttribute]
    14.     public string value
    15.     {
    16.         get => m_Label.text;
    17.         set
    18.         {
    19.             if (string.CompareOrdinal(m_Label.text, value) == 0)
    20.                 return;
    21.  
    22.             m_Label.text = value;
    23.             UpdateVisuals();
    24.             NotifyPropertyChanged(valueProperty);
    25.         }
    26.     }
    27.    
    28.     public ComplexLabel()
    29.     {
    30.         m_Label = new Label();
    31.         Add(m_Label);
    32.         UpdateVisuals();
    33.     }
    34.  
    35.     // In this case, a more performant solution would be to use uss classes to drive these values since it is a known set.
    36.     private void UpdateVisuals()
    37.     {
    38.         if (string.IsNullOrEmpty(value))
    39.         {
    40.             m_Label.style.display = DisplayStyle.None;
    41.             m_Label.style.color = StyleKeyword.Null;
    42.         }
    43.         else
    44.         {
    45.             m_Label.style.display = DisplayStyle.Flex;
    46.             if (value.StartsWith("[Danger]"))
    47.                 m_Label.style.color = Color.red;
    48.             else if (value.StartsWith("[Warning]"))
    49.                 m_Label.style.color = Color.yellow;
    50.             else
    51.                 m_Label.style.color = StyleKeyword.Null;
    52.         }
    53.     }
    54. }
    Using multiple bindings:
    MultipleBindings.gif

    This example uses a single string as input and register multiple bindings that use a converter to convert the string to a StyleColor and to a DisplayStyle. This can then use a normal
    Label
    instead of a custom element, however, you would need to add multiple bindings on each of the targeted labels.

    The code to register the converters for uxml is defined as:
    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3. using UnityEngine.UIElements;
    4.  
    5. class UxmlConverters
    6. {
    7.     [InitializeOnLoadMethod]
    8.     static void RegisterUxmlConverters()
    9.     {
    10.         var group = new ConverterGroup("complex_label");
    11.         group.AddConverter((ref string str) =>
    12.         {
    13.             if (string.IsNullOrEmpty(str))
    14.                 return new StyleColor(StyleKeyword.Null);
    15.  
    16.             if (str.StartsWith("[Danger]"))
    17.                 return new StyleColor(Color.red);
    18.            
    19.             if (str.StartsWith("[Warning]"))
    20.                 return new StyleColor(Color.yellow);
    21.            
    22.             return new StyleColor(StyleKeyword.Null);
    23.         });
    24.        
    25.         group.AddConverter((ref string str) =>
    26.         {
    27.             if (string.IsNullOrEmpty(str))
    28.                 return new StyleEnum<DisplayStyle>(DisplayStyle.None);
    29.  
    30.             return new StyleEnum<DisplayStyle>(DisplayStyle.Flex);
    31.         });
    32.         ConverterGroups.RegisterConverterGroup(group);
    33.     }
    34. }
    Using a binding extending
    DataBinding
    :
    CustomBinding.gif

    This example uses a specialized data binding that will set the text color and the display style whenever the main targeted property is set. This solution would have the same performance characteristics as the first solution and also allow to be used across different elements.

    The binding object is defined as:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UIElements;
    3.  
    4. [UxmlObject]
    5. public partial class CustomLabelBinding : DataBinding
    6. {
    7.     protected override BindingResult UpdateUI<TValue>(in BindingContext context, ref TValue value)
    8.     {
    9.         var result =  base.UpdateUI(in context, ref value);
    10.         if (result.status == BindingStatus.Success && value is string str)
    11.         {
    12.             var target = context.targetElement;
    13.             if (string.IsNullOrEmpty(str))
    14.             {
    15.                 target.style.display = DisplayStyle.None;
    16.                 target.style.color = StyleKeyword.Null;
    17.             }
    18.             else
    19.             {
    20.                 target.style.display = DisplayStyle.Flex;
    21.                 if (str.StartsWith("[Danger]"))
    22.                     target.style.color = Color.red;
    23.                 else if (str.StartsWith("[Warning]"))
    24.                     target.style.color = Color.yellow;
    25.                 else
    26.                     target.style.color = StyleKeyword.Null;
    27.             }
    28.         }
    29.         return result;
    30.     }
    31. }
    Obviously, a data source could also be defined to expose the properties themselves as bindable properties, but hopefully, this can give some ideas of ways to achieve your use-case using the new system.
     
    npatch, PeppeJ10C, mariandev and 2 others like this.
  20. YourWaifu

    YourWaifu

    Joined:
    Oct 4, 2015
    Posts:
    45
    After reading the last comments, I was horrified, it's all so inconvenient for an ordinary developer that it's just creepy...
    And after some attempts to use runtime bindings, I realized that the old root.Q<Label>() and etc variants so still more convenient...
    I don't understand why unity always manages to recreate existing technologies and make them less convenient to use.
    WPF-like bindings?
    Razor pages? (also razor are just more ideally suited to unity, taking into code generation out of the box)
     
    FTF_Wilks and ontrigger like this.
  21. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    484
    Out of the three examples that I gave, I would almost always use the first one, as I like to have reusable pieces of UI that behave the same way in different contexts. Those kind of elements are mostly what I was using before the bindings feature and enabling the feature on it only needed two one-line changes (adding the
    CreateProperty
    attribute and notifying of the change).

    This would be the case for the majority of custom elements. Adding bindings can be done completely on the uxml side and most data binding can be added through code using a single call per property.

    However, I've seen enough users that want to create and maintain close to 0 custom elements that included the two additional examples. I would recommend to use the first approach, for the stated reasons.

    This is still completely supported. Querying visual elements, manually transferring your data and registering value changed callbacks whenever the source needs to be updated is a valid way to go.
     
    mariandev likes this.
  22. Sluggy

    Sluggy

    Joined:
    Nov 27, 2012
    Posts:
    1,005
    @Thygrrr It seems very similar to WPF, which is rather verbose when you are coding it but usually is hidden behind some kind of code-generation via xml tags. Not that it appears that is how it will work here. It's a step up from, well, absolutely nothing, which is what we have right now.

    All that being said, I don't disagree wtih you either. I've written my own databinding in the past for Unity projects via runtime code emitting and when it can be boiled down to just a couple function calls to binding and unbind it's hard to want to go back. Static analysis would be nice but at this point I'd be happy for something that works and comes out within the effective lifetime of UIToolkit.

    While I'm not crazy about it I'm used to writting code like that enough that I don't mind. But honestly, my biggest disappointment is that it's effectively still a year out at best before it can be used in any kind of real project.
     
  23. ChebanovDD

    ChebanovDD

    Joined:
    Mar 27, 2022
    Posts:
    7
    You don't have to wait for Unity 2023. You can take a look at the UnityMvvmToolkit package to get data-binding support.
     
    Sluggy likes this.
  24. Sluggy

    Sluggy

    Joined:
    Nov 27, 2012
    Posts:
    1,005
    Oh, don't worry. I wasn't waiting ;) Even if I was that patient I've been at this for too long to know how often these things don't pan out.

    Thanks for the link though! Looks much more fully-featured than my own solution.
     
  25. BlackSpider

    BlackSpider

    Joined:
    Oct 8, 2011
    Posts:
    52
    @martinpa_unity For me the basic example in the first message isn't working in latest Unity 2023.2.0b4. I have created a GameObject in the scene with UIDocument and the following TestScript on it.

    However when I start I see only the integer field be 0 and no name visible.

    Is it possible to get some example projects up on GitHub on how to set this up?
    Thank you for the help

    upload_2023-8-15_21-43-40.png

    upload_2023-8-15_21-44-50.png
     
  26. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    484
    Hi @BlackSpider!
    Apologies, I seemingly had copied the wrong example while writing the initial message. The DataSource should have been:

    Code (CSharp):
    1. public class MyDataSource
    2. {
    3.     [CreateProperty]
    4.     public string Name { get; set; }
    5.     [CreateProperty]
    6.     public int Level { get; set; }
    7. }
    There was also another typo where the example was using
    nameLabel
    instead of
    levelField
    to display the level.

    I've updated the post. Thanks for pointing it out!
     
    BlackSpider likes this.
  27. BlackSpider

    BlackSpider

    Joined:
    Oct 8, 2011
    Posts:
    52
    @martinpa_unity Thank you that made it work :D really cool. Still would love to see some example projects that maybe show like more complex examples. (Like the ListView)
     
  28. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    484
    BlackSpider likes this.
  29. Thygrrr

    Thygrrr

    Joined:
    Sep 23, 2013
    Posts:
    705
    We must just be patient, they're still busy typing up all that boilerplate for those samples, this can't be written overnight.

    I'll provide a sample implementation later hopefully when I have a few free hours. Admittedly I got ref fields from .net 8 mixed up, but just POCOs will do the job.

    I still think the data source should be passed into a method on the visual element, and that should then construct and return the binding. Not the other way around.
     
    Last edited: Aug 16, 2023
  30. BlackSpider

    BlackSpider

    Joined:
    Oct 8, 2011
    Posts:
    52
    I think the implementation works great when you use the UI Builder. Did try to write the least lines of C# I could. And for me the ListView which I got working quite easily with only a few lines of code.

    I assume with some custom VisualElements you can even hide all this and do something like, SetProfileViews(views);

    upload_2023-8-16_23-35-18.png

    upload_2023-8-16_23-54-44.png
     
    Last edited: Aug 16, 2023
    Deleted User likes this.
  31. Mj-Kkaya

    Mj-Kkaya

    Joined:
    Oct 10, 2017
    Posts:
    182
    Hi everyone,
    Is it possible binding data to USS selector's properties or custom properties?
    Such as: I created transition animation with USS selectors and I want to set this animations' parameters from "binding data class".
     
  32. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    484
    Hi @Mj-Kkaya!

    If you mean to change the values directly in the style sheet instance, then that is not possible. Style sheet selectors are cached and changing their values on the fly would most likely mean a lot of invalidation and it would force a styling pass on any visual hierarchy using that style sheet.

    However, there are ways to do this without changing the style sheet instance:
    • For the style properties, you could bind to the inline styles using the "style" prefix as the path (i.e. "style.backgroundColor", "style.transitionProperty", etc.) This will let you parametrize your animation for a given element.
    • For the custom style properties, usually, you need to query them in code and deal with them manually, as most custom properties won't do anything by default. Note that transitions on custom properties is not currently supported. You can instrument your own custom properties with
      [CreateProperty]
      and you should be able to use the binding system on them.
    • If your animations' parameters are part of a known ahead of time set (i.e. duration will either be 0ms, 250ms or 500ms), you can also define those in the style sheet and use a binding to add/remove uss classes. When trying to bind to multiple style properties at the same time, this method is probably the best.
    Hope this helps!
     
    Mj-Kkaya likes this.
  33. Mj-Kkaya

    Mj-Kkaya

    Joined:
    Oct 10, 2017
    Posts:
    182
    Hi @martinpa_unity , thanks for your response.
    I will consider this!

    Actually I'm using add/remove uss classes with my transition animation. And I avoid to use inline-style for performance considerations.
    But it seems, I have to use inline-style for transition parameters.

    There is my test transition parameters.
    For this example: If I want to control the "transition-duration" parameter with "Binding Data", I have to use "binding data" in "inline style".

    Code (JavaScript):
    1. .fade-effect__transition {
    2.     transition-duration: 1.5s, 1.5s;
    3.     transition-timing-function: ease-in-sine, ease-in-sine;
    4.     transition-property: scale, background-color;
    5. }
    6.  
    7. .fade-effect__transition--fade-in {
    8.     background-color: rgb(255, 0, 0);
    9.     scale: 0.5 0.5;
    10. }
    11.  
    12. .fade-effect__transition--fade-out {
    13.     background-color: rgba(255, 0, 0, 0);
    14.     scale: 1 1;
    15. }
     
  34. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    484
    If you need to pass in dynamic values, inline style will be the way to go.
     
    Mj-Kkaya likes this.
  35. GameDev273

    GameDev273

    Joined:
    Oct 4, 2021
    Posts:
    6
    Hi Martinpa_unity

    I can see you're working hard on all this.....you mentioned 'samples will be coming. Stay tuned!'

    Any ETA on this...
     
  36. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    484
    Hi @GameDev273, I'll check on the status of this. Unfortunately, the last weeks have been quite bumpy to say the least and it's vacation season at the moment.
     
  37. seika850113

    seika850113

    Joined:
    Jan 24, 2018
    Posts:
    4
    Could someone provide an example of event binding, like a button click?
     
  38. GameDev273

    GameDev273

    Joined:
    Oct 4, 2021
    Posts:
    6
    martinpa_unity - Ref ETA ...thanks

    seika850113
    There is an example in the 2023.2 documentation...

    https://docs.unity3d.com/2023.2/Documentation/Manual/UIE-get-started-runtime-binding.html

    Although when I followed it the uxml file generated wasn't quite the same....so at step 9 of
    Bind the Label to the data source
    where it says...

    'Save and close UI Builder. Your ExampleObject.uxml file looks like the following content:'

    It didn't.... so I duplicated the ExampleObject.uxml file and copied in the example shown in step 9.

    Also you need the 2023.2.a18 alpha release of Unity
     
  39. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    484
    Hi @seika850113 , event bindings are not supported out of the box, but here is a very quick and dirty example of how it can be achieved using a custom binding:

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using Unity.Properties;
    4. using UnityEngine.UIElements;
    5.  
    6. namespace Example
    7. {
    8.     [UxmlObject]
    9.     public partial class ActionBinding : CustomBinding
    10.     {
    11.         // Caching the delegate used for cleanup purposes.
    12.         private readonly Dictionary<VisualElement, Action> m_CachedDelegates = new ();
    13.  
    14.         // Only tracking the registration when the data source context changes. This wouldn't
    15.         // pick-up if the targeted `action` changed on the data source.
    16.         protected override void OnDataSourceChanged(in DataSourceContextChanged context)
    17.         {
    18.             if (context.targetElement is not Button button)
    19.                 return;
    20.  
    21.             // Clean previous callbacks
    22.             if (m_CachedDelegates.TryGetValue(button, out var action))
    23.             {
    24.                 button.clicked -= action;
    25.                 m_CachedDelegates.Remove(button);
    26.             }
    27.  
    28.             // Extract the `Action` from the hierarchy and register it.
    29.             var source = context.newContext.dataSource;
    30.             var path = context.newContext.dataSourcePath;
    31.  
    32.             if (null == source || !PropertyContainer.TryGetValue(ref source, in path, out action))
    33.                 return;
    34.            
    35.             button.clicked += action;
    36.             m_CachedDelegates.Add(button, action);
    37.         }
    38.     }
    39. }
    Hope this helps!
     
    seika850113 likes this.
  40. Knedlo

    Knedlo

    Joined:
    Oct 15, 2012
    Posts:
    54
    Hello @martinpa_unity,
    thanks for the example. I'm trying to make it work on a test project, but I can't pass dataSourcePath to it. What I have in uxml is:

    <ui:Button text="Press me">
    <Bindings>
    <ActionBinding property="whatever" data-source-path="_printStuff"/>
    </Bindings>
    </ui:Button>

    When I break at line 32 of your example (PropertyContainer.TryGetValue(ref source, in path, out action)) I see, that path is empty (isEmpty = true, length = 0). Not sure if CustomBinding has data-source-path attribute exposed, but I don't see how else I would pass the path to the binding. Do you have an idea of what I'm doing wrong?
    On the side note, it feels that the property attribute of a binding shouldn't be mandatory, as in this case and some other examples of custom bindings released so far, it is not used.

    Edit:
    I was able to add my own dataSourcePath UXMLAttribute and set that up. I tried it before but got confused by the transformation from dataSourcePath in code to data-source-path in UXML. However, it seems a bit strange that I'm using the context only for data source, but not the dataSourcePath:
    Code (CSharp):
    1.  
    2. // Extract the `Action` from the hierarchy and register it.
    3. var source = context.newContext.dataSource;
    4. //var path = context.newContext.dataSourcePath;
    5. var path = PropertyPath.FromName(dataSourcePath);
    6.  
     
    Last edited: Oct 11, 2023
  41. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    484
    Hi @Knedlo,
    CustomBinding
    does not provide the
    dataSourcePath
    by default. However, you can opt-in by implementing the IDataSourceProvider interface.

    You could set it in the
    VisualElement
    hierarchy directly instead of the binding.

    The property is used an Id and is passed as a parameter in the
    Update
    method of the binding. It's not used by the examples I've shown, but I've used it on other occasion, for example when binding against
    PlayerPrefs
    , I used it for the key of preference.

    Hope this helps!
     
    Knedlo likes this.
  42. Knedlo

    Knedlo

    Joined:
    Oct 15, 2012
    Posts:
    54
    Oh yes, that helps a lot. Thanks!
     
    martinpa_unity likes this.
  43. seika850113

    seika850113

    Joined:
    Jan 24, 2018
    Posts:
    4
    Hi @martinpa_unity, thank you for your patient response. I've actually implemented logic very similar to the example code you provided. May I ask if there's a specific reason for using the dictionary cache for events? Here's the ActionBinding code I'm currently using:

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Reflection;
    4. using Unity.Properties;
    5. using UnityEngine.UIElements;
    6.  
    7. [UxmlObject]
    8. public sealed partial class ActionBinding : CustomBinding, IDataSourceProvider {
    9.     static readonly PropertyInfo propertyPropertyInfo =
    10.         typeof(Binding).GetProperty("property", BindingFlags.Instance | BindingFlags.NonPublic)!;
    11.  
    12.     static readonly Dictionary<(Type, string), EventInfo> _eventInfos = new();
    13.  
    14.     Action? _onAction;
    15.  
    16.     public ActionBinding() {
    17.         updateTrigger = BindingUpdateTrigger.OnSourceChanged;
    18.     }
    19.  
    20.     public string Property {
    21.         get => (string)propertyPropertyInfo.GetValue(this)!;
    22.         set => propertyPropertyInfo.SetValue(this, value);
    23.     }
    24.  
    25.     public object? dataSource { get; set; }
    26.     public PropertyPath dataSourcePath { get; set; }
    27.  
    28.     [UxmlAttribute("data-source-path")]
    29.     public string DataSourcePathString {
    30.         get => dataSourcePath.ToString();
    31.         set => dataSourcePath = new PropertyPath(value);
    32.     }
    33.  
    34.     static EventInfo GetEventInfo(Type type, string name) {
    35.         if (_eventInfos.TryGetValue((type, name), out var eventInfo)) return eventInfo;
    36.         eventInfo = type.GetEvent(name);
    37.         _eventInfos.Add((type, name), eventInfo);
    38.         return eventInfo;
    39.     }
    40.  
    41.     protected override void OnDataSourceChanged(in DataSourceContextChanged context) {
    42.         var element = context.targetElement;
    43.         var type = element.GetType();
    44.         var eventInfo = GetEventInfo(type, Property);
    45.         if (_onAction is not null) eventInfo.RemoveEventHandler(element, _onAction);
    46.         var source = context.newContext.dataSource;
    47.         if (source is null) return;
    48.         var path = context.newContext.dataSourcePath;
    49.         if (!PropertyContainer.TryGetValue(ref source, in path, out _onAction)) return;
    50.         eventInfo.AddEventHandler(element, _onAction);
    51.     }
    52. }

    Code (uxml):
    1. <ui:UXML xmlns:ui="UnityEngine.UIElements">
    2.     <ui:VisualElement name="sample-window" class="sample-window">
    3.         <ui:Button name="sample-button" class="sample-window__sample-button">
    4.             <Bindings>
    5.                 <ActionBinding property="clicked" data-source-path="SampleButtonClicked" />
    6.             </Bindings>
    7.         </ui:Button>
    8.     </ui:VisualElement>
    9. </ui:UXML>
     
    Last edited: Oct 12, 2023
  44. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    484
    Hi @seika850113, I was using a cache of
    VisualElement
    to
    Action
    because a binding instance could be reused for different properties/elements. If you are not reusing the same instance on many properties/elements, then you can omit that part.
     
    seika850113 likes this.
  45. TheCelt

    TheCelt

    Joined:
    Feb 27, 2013
    Posts:
    743
    This still feels like a lot of tedious repetitive code to write especially if you have a lot of binding to do.
     
  46. Canijo

    Canijo

    Joined:
    Oct 9, 2018
    Posts:
    50
    Im just here to say that i love you SO MUCH for this and the whole Unity.Properties package.

    THANK YOU
     
    martinpa_unity and mariandev like this.
  47. BlackSpider

    BlackSpider

    Joined:
    Oct 8, 2011
    Posts:
    52
    @martinpa_unity Is it possible that there is a bug with using Converters in Attribute bindings through the UIBuilder?

    For PickingMode I made a custom converter group called: `PickingModeConverters` and based on the Color.alpha value I wanted to either set PickingMode to Position or Ignore. However when I add those Converter and assign it in the binding. It doesn't seem to call the converter. This is the code:

    FadeScreen.cs
    Code (CSharp):
    1. public class FadeScreen : MonoBehaviour
    2. {
    3.     [Serializable]
    4.     public class Data
    5.     {
    6.         public Color Color;
    7.     }
    8.  
    9.     public Data data;
    10.  
    11.     protected void Awake()
    12.     {
    13.         var group = new ConverterGroup(nameof(PickingModeConverters));
    14.         group.AddConverter((ref Color v) => { return PickingModeConverters.ResolveColor(v); });
    15.         group.AddConverter((ref float v) => { return PickingModeConverters.ResolveFloat(v); });
    16.         group.AddConverter((ref bool v) => { return PickingModeConverters.ResolveBool(v); });
    17.         ConverterGroups.RegisterConverterGroup(group);
    18.     }
    19.  
    20.     protected void Start()
    21.     {
    22.         GetComponent<UIDocument>().rootVisualElement.dataSource = data;
    23.     }
    24. }
    25.  
    PickingModeConverters.cs
    Code (CSharp):
    1.  
    2. public struct PickingModeConverters
    3. {
    4.     public static PickingMode ResolveFloat(float handle)
    5.     {
    6.         return ResolveBool(handle > 0f);
    7.     }
    8.  
    9.     public static PickingMode ResolveColor(Color handle)
    10.     {
    11.         return ResolveBool(handle.a > 0f);
    12.     }
    13.  
    14.     public static PickingMode ResolveBool(bool handle)
    15.     {
    16.         return handle ? PickingMode.Position : PickingMode.Ignore;
    17.     }
    18. }
    upload_2023-10-21_15-15-44.png

    Quick Update:
    Looks like it is working when I make RegisterConverters static class and InitalizeOnLoad like this:

    I notice warnings in my console because I overwrite a previous registered converter group but I don't see any way of avoiding that warning.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UIElements;
    3.  
    4. static class RegisterConverters
    5. {
    6. #if UNITY_EDITOR
    7.     [UnityEditor.InitializeOnLoadMethod]
    8. #endif // UNITY_EDITOR
    9.     [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
    10.     static void Register()
    11.     {
    12.         RegisterStyleConverters();
    13.         RegisterPickingModeConverters();
    14.     }
    15.  
    16.     private static void RegisterPickingModeConverters()
    17.     {
    18.         var pickingModeGroup = new ConverterGroup(nameof(PickingModeConverters));
    19.         pickingModeGroup.AddConverter((ref Color v) => { return PickingModeConverters.ResolveColor(v); });
    20.         pickingModeGroup.AddConverter((ref float v) => { return PickingModeConverters.ResolveFloat(v); });
    21.         pickingModeGroup.AddConverter((ref bool v) => { return PickingModeConverters.ResolveBool(v); });
    22.         ConverterGroups.RegisterConverterGroup(pickingModeGroup);
    23.     }
    24.  
    25.     private static void RegisterStyleConverters()
    26.     {
    27.         var styleGroup = new ConverterGroup(nameof(StyleConverters));
    28.         styleGroup.AddConverter((ref bool v) => { return StyleConverters.ResolveBool(v); });
    29.         styleGroup.AddConverter((ref int v) => { return StyleConverters.ResolveInt(v); });
    30.         ConverterGroups.RegisterConverterGroup(styleGroup);
    31.     }
    32. }
     
    Last edited: Oct 21, 2023
  48. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    484
    Hi @BlackSpider!

    If you are using the
    InitializeOnLoadMethod
    and
    RuntimeInitializeOnLoadMethod
    attributes, one way to get rid of this warning is to also have a static bool indicating if the registration has been done.

    Personally, I prefer to use a static constructor instead in one of the custom elements (or behaviour) I am using. That way, I don't need to deal with the double registration.

    Also @Canijo! thank you for the kind words.
     
  49. BlackSpider

    BlackSpider

    Joined:
    Oct 8, 2011
    Posts:
    52
    Added that isRegistered bool you mentioned and that works ok. My idea is more that they are generic converts I can use in all sorts of elements. So not sure if it would make sense to add it to a specific element. But this works.

    Any idea why they didn't work when adding them inside of the Awake method?
     
  50. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    484
    The
    Awake
    call is not always guaranteed to be called. For example, if the component is added to a
    GameObject
    that is disabled, I believe the
    Awake
    won't be called until it becomes enabled.

    However, you would get the same multiple registration issue if your component is used in more than one place.
     
    BlackSpider likes this.