Okay, I have found a some what working solution. It is very cumbersome and not easy to do.
I followed the texture dragging example from the unite video/posted on github to get dragging working.
I made an extension method to get the root VisualElement.
I then get all the VisualElements from that root that has picking mode set to position.
At that point I use the MouseMoveEvent to iterate over all the VisualElements and check the world bounds for overlapping. I then simulate the drag events according to what phase it is. I made a custom element that accepts drops which is mostly for visual effect, but also allows for drop to be disabled. Got that setup to work some what in UIBuilder.
I need to figure out how to get the inspector to list a few values without it adding it to the element itself. The example [mention|0lDOuugxWKeU3CelW13Kjw==] posted here works fine for the editor, but also added the elements to the scene at runtime.
One other problem I have is, I am moving the dragged element to the root and putting it in front of everything. When I do this it puts the element in the top left corner if the mouse doesnât move(the left and top are 0 from the last parent). Once I move it brings it to the right location with the mouse. How do I get it to stay in the same position when I change the parent?
What kind of values do you want to display in the (I assume) UI Builder attributes inspector, but not add them as real attributes on the element?
UI Toolkit does not have an automatic âre-parent without changing visible positionâ function. Absolute positioning is actually actively discouraged and only useful in special cases (like drag and drop ). That said, my suggestion is to create a âdrag layerâ element at the root, like this:
rootVisualElement
contentContainer
dragDropLayer
Then, when starting a drag, reparent the element to the dragDropLayer so itâs always on top and use Absolute + top/left to move it around. Once done dragging, reparent it as needed, removing Absolute + top/left styles.
Some basic values like boolâs, Vector2âs and enums. I have a custom element that sets the position of itâs children based on an offset. I would like to be able to be set them in the builder so I donât need to have monolithic code for each element.
I am already somewhat doing that, minus the dragDropLayer. I put the item(s) being dragged on the root and make sure to bring them to the front. I have it working most of the time if I use a drag state system that doesnât re-parent the element until you physically start dragging it. This has almost eliminated the elements popping up to the top left corner, though it still does it every now and then. I need to test further to see what is causing it to happen when it shouldnât, theoretically.
About the absolute positioning being discouraged I completely understand. After setting up some custom inspectors and editor windows it makes short work of a lot of what I was doing in the past with IMGUI. The UIBuilder is really a god send, even with the bugs.
Right, thatâs what UXML attributes are for. Iâm just curious why you specifically said this:
Like, how can you add them to the Builder but not also on the element? Remember that the Builder can only âeditâ values can be saved in the UXML. If you move some child of a custom C# element to the right, and that child is not in the UXML because itâs being created by the custom C# element, this wonât be saved in UXML and your change will be lost when you reload your UXML. Use the UXML Preview at the bottom of the Viewport to see what is actually being saved.
Please do send us any bugs you come across in the UI Builder. Either post on the forum if unsure or submit a bug report via the Unity Bug Reporter. We want to iron those out.
Sorry I worded that wrong, yes I only want it in the inspector of the UIBuilder and not on the element itself.
This makes sense. I need the attributes to be saved in the UXML so when I query the element I donât need to worry about setting those values.
Iâve added the dragDropLayer to avoid some bugs that kept happening with it getting stuck in the root element. That and it was going over top of other HUD items that it shouldnât.
Is there a way to keep the world/root position of the element when changing parents? Any drag/drop inventory system is going to need this.
I wanted to update to let you know I figured out how to get the attributes to save in the Uxml. I found the Unity document page here, noticed it was a simple change to your example and it works great.
Found a bug with attributes, it is doing the same thing that it was doing for read only. You can change the value in the inspector, save it, and the value resets. It seems like it just not displaying the values from the uxml.
Out of curiosity, what was the change you need to make?
The above two statements are currently mutually exclusive. If you donât add a public C# property, named the same as the uxml attribute (but without â-â), to your element, the UI Builder Inspector wonât be able to read it back after it sets it via UXML and thatâs why the UI Builder inspector looks broken.
Thatâs why my example has C# properties for all UXML attributes it exposes.
Instead of adding visual elements in the Init method, I got the values from the bag and applied them to the objects fields instead, like it showed in the docs.
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
{
base.Init(ve, bag, cc);
CardPosition cp = ve as CardPosition;
cp.DropAllowed = m_Bool.GetValueFromBag(bag, cc);
cp.nextCardOffset = new Vector2(m_Left.GetValueFromBag(bag, cc), m_Top.GetValueFromBag(bag, cc));
cp.CardAddType = m_CardAddType.GetValueFromBag(bag, cc);
}
I couldnât find a Uxml attribute for Vector2 so I used two floats instead. Is there a better way?
The two floats wonât work in this scenario then, as I have it taking the two floats and converting them to a Vector2, so UIBuilder wonât be able to read the properties for them because they donât exist.
Once I had the rest of properties the same name it saved them and displayed them properly. All I need now is a way to handle Vector2âs.
Ah yes, makes sense. No need to create new elements of course. Thatâs just my example.
Thereâs no built-in way but you can inherit from TypedUxmlAttributeDescription<T> and define your own custom attribute with a string conversion function that works with Vector2.
Iâm trying to figure out a bit of a visual bug when it displays the Vector2 back to me after reloading the uxml. Itâs rounding the values so that 0.25 becomes 0.3. Its not a big problem because in the uxml it shows 0.25, but I donât want the values rounded at all in the inspector.
This is my custom uxml attribute description
using UnityEngine;
using UnityEngine.UIElements;
using System.Collections;
public class UxmlVector2AttributeDescription : TypedUxmlAttributeDescription<Vector2>
{
public override string defaultValueAsString => Vector2.zero.ToString();
public override Vector2 GetValueFromBag(IUxmlAttributes bag, CreationContext cc)
{
Vector2 v = defaultValue;
if (bag.TryGetAttributeValue(name, out string value))
{
v = Utils.Vector2FromString(value);
}
return v;
}
}
And the Utils method.
public static Vector2 Vector2FromString(string value)
{
Vector2 v = Vector2.zero;
string[] values = value.Substring(1, value.Length - 2).Split(',');//Need to remove the parentheses.
if (float.TryParse(values[0], out float x))
{
v.x = x;
}
if (float.TryParse(values[1], out float y))
{
v.y = y;
}
return v;
}
Also is there a way to define how UIBuilder writes the values to the uxml? I am saying this because I am assuming that its is using the ToString() method, or is it how I define the defaultValueAsString?
Thereâs no way to customize the UI Builderâs Attributes Inspector in any official way. Itâs not currently a priority but itâs been asked a few times before, like:
so Iâll see what I can do about bumping the priority on this.
The problem with that example is, itâs for the editor. The drag and drop events do not seem to work at runtime at all. I had to hack a very inefficient way of getting all the elements that can be dropped on and check for bounds overlaps on the dragged element. The events would make it much simpler. If I get some time I can post the code to github or something like that for a basic example, itâs not trivial and most of the code is specific to the project.
Its pretty basic.
//Remove from current parent
element.RemoveFromHierarchy();
//Add to new parent
newParent.Add(element);
I think it can be done by ditching the event system and using a schedule update and use Input.Mouse functions, but well⌠thatâs a strange way to go about it.