Hi, I’m trying out the workflow for creating custom VisualElements and have got a certain way from the docs and afew other forum posts, but could do with some help. Alternatively if there are some good tutorials on this topic, please point me towards them.
I decided a reasonable usecase would be a localizable “LocLabel”. I have custom localization system working in uGUI, that for strings is very similar to I2 Loc from the asset store:
So I decorate a Unity Text Component with a LocText and (optionally) LocTextStringParams. For setup in UXML, my thinking is that it makes sense to:
-
Derive a LocLabel from Label that holds a localization key and most of the logic
-
Derive a LocParam from VisualElement, such that each LocParam holds a single parameter and value
-
Multiple parameters would require multiple LocParams
-
LocParams would be VisualElements, but always set to Display : none
So XML layout for the above looks like:
<RTM.Localization.LocLabel loc-key="CONTROLS/KEYBOARD/SELECT_ABILITY_NUM" name="SelectAbility">
<RTM.Localization.LocParam name="NUM" value="50" />
<RTM.Localization.LocParam name="OTHER_PARAM" value="TEST/OTHER_PARAM" localize="true" />
</RTM.Localization.LocLabel>
First Question
- is this even a sensible approach? Having hidden VisualElement children as an array of values feels slightly hacky, from a c# perspective, but feels very sensible from a UXML perspective, but maybe I’ve missed some features? Possibly something with the userData?
Assuming the answer to that is yes, here’s the implementation thus far (I’ve stripped out all the runtime translation parts, this is just the UIToolkit implementation:
public class LocLabel : Label
{
public LocKey locKey { get; set; }
public override string text
{
get => base.text;
set => base.text=value;
}
public class UXMLFactory : UxmlFactory<LocLabel, UXMLTraits> { }
public class UXMLTraits : Label.UxmlTraits
{
UxmlStringAttributeDescription locKey = new UxmlStringAttributeDescription{ name = "loc-key", defaultValue=""};
UxmlStringAttributeDescription text = new UxmlStringAttributeDescription{ name = "text", defaultValue=""};
public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription
{
get { yield return new UxmlChildElementDescription(typeof(LocParam)); }
}
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
{
base.Init(ve, bag, cc);
var locLabel = ve as LocLabel;
string oldKey = locLabel.locKey;
//locLabel.Clear();
locLabel.locKey = locKey.GetValueFromBag(bag, cc);
locLabel.text = text.GetValueFromBag(bag, cc);
// TODO: need to set localized text value and ensure that it gets pushed to XML
}
public class LocParam : VisualElement
{
public class UXMLFactory : UxmlFactory<LocParam, UXMLTraits> { }
public string value { get; private set; }
public bool localize { get; private set; }
public class UXMLTraits : VisualElement.UxmlTraits
{
UxmlStringAttributeDescription value = new UxmlStringAttributeDescription{ name = "value", defaultValue=""};
UxmlBoolAttributeDescription localize = new UxmlBoolAttributeDescription{ name = "localize", defaultValue=false};
public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription
{
get { yield break; }
}
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
{
base.Init(ve, bag, cc);
var locParam = ve as LocParam;
locParam.Clear();
locParam.value = value.GetValueFromBag(bag, cc);
locParam.localize = localize.GetValueFromBag(bag, cc);
locParam.style.display = DisplayStyle.None;
}
}
}
This kinda works somewhat how I’d expect, but with a few issues / queries / roadblocks:
- Calling Clear() (commented out on line 30) in LocText would make the children vanish in UI builder whenever I made a change in the UI. They were still in the XML and returned if I closed UI Builder and reopened the file. Why does clear do this and when / why should I be calling it?
- Once children have been added to a label (even children with Display : None) it stops using the text to determine it’s rect and uses it’s children, so becomes zero height. Is it possible to return this behaviour to the Label behaviour or this a stumbling block of my approach?
- I can’t see any way to push values down to the XML. Ideally when handling the localization key, I will update the Text field and then that will be serialized in the XML. There seems to be an assumption that in c# you only read from XML and writing to it is done manually via GUI or text editor
- Is there any support for Customization of the UIBuilder Inspector yet? - in my Component-based version I have a dropdown to select keys but of course by default it’s just a string field in UIBuilder
- Is there any way to get a per-update, or pre-repaint call to my VisualElement? In components when updating the key or parameters, I flag the LocText as dirty, then process the changes at the end of the LateUpdate (just prior to UI), so that I don’t process multiple changes in a single frame.
- When can you gather references between VisualElements? In the UxmlTraits.Init() the parent is null and the childcount is 0, regardless of the heirarchy. Is there some other init step that I can use to find references