I’ve been attempting to use the new runtime bindings to bind a mono behaviour type datasource to elements of my UI (all in code not the UI builder or uxml) and everything is working when changing value type stuff. For example I have custom element inheriting from Label and am able to have bindings updating things like the text of the label and a Vector3 as well.
However, I cannot seem to get the style opacity to bind in the same manner. My suspicion is that it is due to the style.opacity needing to be expressed as a StyleFloat whereas I am of course simply using a normal float in my datasource. I see that the documentation states that there are all sorts of global converters that are supposed to automatically handle this conversion… but at least for me this is not the case. Is this possibly because the converters are only working when this is all setup through UI Builder / UXML???
When setting up bindings completely in C# do I need to call these converters somehow explicitly?
I notice its possible to enable logging of bindings on the panel settings and when I do I do get this warning for the opacity binding (although as I say all other non-style related bindings are working fine):
[UI Toolkit] Could not set value for target of type 'WorldSpaceLabel' at path 'opacity': the path is either invalid or contains a null value. (PanelSettings)
What am I doing wrong here? The docs are pointing me in a certain direction but info on the C# world for this is scant. I can’t seem to find ANY example where a datasource path / binding for a style property is being set in C# rather than UI builder / UXML and it is a frustrating absence in the documentation.
Hi @NumT , to bind against style.opacity, you must use style.opacity as the binding id and not opacity.
You are correct that the property itself expects a StyleFloat, however, we have added converters for float and StyleKeyword already, so it should work out of the box.
One thing to be aware of is that the .style and .resolved properties will not currently report changes.
So… I have a MonoBehaviour with a local class definition which I am using as my datasource(s):
public class WorldSpaceAnnotations : MonoBehaviour
{
[System.Serializable]
public class Annotation
{
[SerializeField]
[DontCreateProperty]
private Vector3 m_WorldPosition;
[SerializeField]
[DontCreateProperty]
[TextArea(3, 10)]
private string m_Text;
[SerializeField]
[DontCreateProperty]
private float m_Opacity;
[CreateProperty]
public Vector3 worldPosition
{
get => m_WorldPosition;
set => m_WorldPosition = value;
}
[CreateProperty]
public string text
{
get => m_Text;
set => m_Text = value;
}
[CreateProperty]
public float opacity
{
get => m_Opacity;
set => m_Opacity = value;
}
}
public Annotation[] annotations;
public UIDocument root;
public VisualTreeAsset worldSpaceLabelAsset;
private void OnEnable()
{
root = GetComponent<UIDocument>();
foreach (Annotation annotation in annotations)
{
for (int i = 0; i < 2; i++)
{
// instantiate from template and set some values
TemplateContainer container = worldSpaceLabelAsset.Instantiate();
// do container related value setting
//
// retrieve world space label
WorldSpaceLabel label = container.Q<WorldSpaceLabel>();
// sets initial values
label.text = annotation.text;
label.worldPosition = annotation.worldPosition;
label.style.opacity = annotation.opacity;
// setup bindings
label.dataSource = annotation;
// style.opacity binding - this is the only one not working
DataBinding opacityDataBinding = new DataBinding
{
dataSourcePath = new PropertyPath(nameof(Annotation.opacity)),
bindingMode = BindingMode.ToTarget,
};
label.SetBinding(nameof(WorldSpaceLabel.style.opacity), opacityDataBinding);
// bindings for text - this works fine
DataBinding textDataBinding = new DataBinding
{
dataSourcePath = new PropertyPath(nameof(Annotation.text)),
bindingMode = BindingMode.ToTarget
};
label.SetBinding(nameof(WorldSpaceLabel.text), textDataBinding);
// just testing converter
textDataBinding.sourceToUiConverters.AddConverter((ref string text) => text + " Added text");
// binding for world position - this also works fine
label.SetBinding(nameof(WorldSpaceLabel.worldPosition), new DataBinding
{
dataSourcePath = new PropertyPath(nameof(Annotation.worldPosition)),
bindingMode = BindingMode.TwoWay
});
// need to add container to both sides as we can't do any heierachy changes
if (i == 0)
{
label.AddToClassList("left-side-label");
root.rootVisualElement.Q("LeftSide").Add(container);
}
else
{
label.AddToClassList("right-side-label");
root.rootVisualElement.Q("RightSide").Add(container);
}
}
}
}
}
My WorldSpaceLabel class is inheriting from Label, then has a Vector3 and does some drawing in “OnGenerateVisualContent”:
[UxmlElement]
public partial class WorldSpaceLabel : Label
{
[UxmlAttribute]
public Vector3 worldPosition;
public WorldSpaceLabel()
{
generateVisualContent += OnGenerateVisualContent;
}
private void OnGenerateVisualContent(MeshGenerationContext mgc)
{
if (Application.isPlaying)
{
Rect r = paddingRect;
float right = r.width;
float left = r.x;
// get start and end points
// Vector2 startPoint = new Vector2 (mgc.visualElement.layout.xMax, mgc.visualElement.layout.center.y);
Vector3 viewPos = Camera.main.WorldToViewportPoint(worldPosition);
Vector2 startPoint = new Vector2(right, r.center.y);
if (viewPos.x > 0.5f)
{
startPoint = new Vector2(left, r.center.y);
}
Vector2 endPointPanelPos = RuntimePanelUtils.CameraTransformWorldToPanel(mgc.visualElement.panel, worldPosition, Camera.main);
Vector2 endPointLocalPos = mgc.visualElement.WorldToLocal(endPointPanelPos);
//
if (mgc.visualElement.worldBound.center.x > endPointPanelPos.x)
{
// we are to the right of the point
startPoint = new Vector2(left, r.center.y);
}
// do drawing
Painter2D paint2D = mgc.painter2D;
// draw outlines first
paint2D.strokeColor = Color.black;
paint2D.fillColor = Color.black;
paint2D.BeginPath();
paint2D.MoveTo(startPoint);
paint2D.LineTo(endPointLocalPos);
paint2D.ClosePath();
paint2D.Stroke();
//
//paint2D.BeginPath();
//paint2D.MoveTo(startPoint);
//paint2D.LineTo(endPointLocalPos);
//paint2D.ClosePath();
//paint2D.Stroke();
paint2D.BeginPath();
paint2D.Arc(endPointLocalPos, 4f, 0, 360);
paint2D.ClosePath();
paint2D.Fill();
}
}
}
That’s about it really (I’ll upload the full code in case that’s handy). I am now wondering if the way I am specifying the binding path i.e. nameof(WorldSpaceLabel.style.opacity) is the problem - is this not providing a full path as it were to the binding? Does it somehow resolve to just “opacity” instead of “style.opacity”? If so what is the correct way to provide this full path to a binding???
Ah actually am I trying to set these style bindings to soon maybe? Is it possible set style type bindings in OnEnable or not? In the debugger I’m getting a message on label.style.opacity of Could not find a member 'opacity' for 'label.style'. Is it not possible to setup bindings for these type of properties in OnEnable as you can for the more simple value types?
nameof(WorldSpaceLabel.style.opacity) will return opacity, which is not the correct binding id to bind against the style property. The correct id is style.opacity, so you can use "style."+ nameof(WorldSpaceLabel.style.opacity) instead.