I am using for tooltips, some of my items will have very big and complex tooltips with collapsible sections with nested tooltips, so it can’t simply use the ‘tooltip’ property.
I need to close it if the mouse is not over it for a whole second. I’ve ended up managing to finish a Manipulator that handles it just as I need it, ended up creating a Schedule.Until in the manipulator attachment, and using MouseOverEvent/MouseOutEvent to set a ‘IsMouseOver’ flag.
Code if you want to see how the system is being used, I assume plenty of it is not really like expected, especially the UserData part, which ended up being a container for a GetComponent-like feature:
TooltipManipulator
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
public class TooltipManipulator : Manipulator {
public VisualElement TooltipRoot { get => ToolipController.Root; }
private float TooltipCounter;
private float TooltipTime;
private bool Over;
private Func<VisualElement> CreateTooltip;
public TooltipManipulator(Func<VisualElement> CreateTooltip, float TooltipTime) {
this.CreateTooltip = CreateTooltip;
this.TooltipTime = TooltipTime;
}
protected override void RegisterCallbacksOnTarget() {
target.RegisterCallback<MouseEnterEvent>(AddDeltaTime);
target.RegisterCallback<MouseOutEvent>(ResetDeltaTime);
}
protected override void UnregisterCallbacksFromTarget() {
target.UnregisterCallback<MouseEnterEvent>(AddDeltaTime);
target.UnregisterCallback<MouseOutEvent>(ResetDeltaTime);
}
protected void ResetDeltaTime(MouseOutEvent e) {
Over = false;
TooltipCounter = 0;
}
private void AddDeltaTime(MouseEnterEvent e) {
Over = true;
target.schedule.Execute((E) => {
TooltipOwner tt = Utils.UIElementUserData<TooltipOwner>(target);
if (tt != null && tt.Tooltip != null) {
TooltipRoot root = Utils.UIElementUserData<TooltipRoot>(tt.Tooltip);
if (root != null) {
root.AutoDeleteTooltip.TooltipCounter = 0;
}
return;
}
TooltipCounter += Time.deltaTime;
if (TooltipCounter > TooltipTime) {
VisualElement child = CreateTooltip();
child.style.position = Position.Absolute;
child.style.left = Input.mousePosition.x - 3;
child.style.bottom = Input.mousePosition.y - 3;
if (tt == null) {
Utils.UIElementUserData(target, tt = new TooltipOwner());
}
tt.Tooltip = child;
TooltipRoot tr = new TooltipRoot();
Utils.UIElementUserData(child, tr);
tr.Self = child;
tr.ParentTooltip = Utils.UIElementUserDataInParent<TooltipRoot>(target);
AutoDeleteTooltip autodel = new AutoDeleteTooltip();
autodel.TooltipOwner = tt;
autodel.TooltipRoot = tr;
tr.AutoDeleteTooltip = autodel;
child.AddManipulator(autodel);
Over = true;
TooltipRoot.Add(child);
}
}).Until(() => !Over);
}
}
AutoDeleteTooltip
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
public class AutoDeleteTooltip : Manipulator {
private bool Over;
private bool MouseIn;
public float TooltipCounter;
private int TooltipTime;
public TooltipOwner TooltipOwner;
public TooltipRoot TooltipRoot;
public AutoDeleteTooltip() {
TooltipTime = 4;
}
protected override void RegisterCallbacksOnTarget() {
Over = false;
MouseIn = true;
target.schedule.Execute((E) => {
if (MouseIn) {
PropagateParent(Utils.UIElementUserData<TooltipRoot>(target));
} else {
TooltipCounter += Time.deltaTime;
if (TooltipCounter > TooltipTime) {
TooltipOwner.Tooltip = null;
target.RemoveFromHierarchy();
Over = false;
}
}
}).Until(() => Over);
target.RegisterCallback<MouseOverEvent>(MouseEnter);
target.RegisterCallback<MouseOutEvent>(MouseOut);
}
protected override void UnregisterCallbacksFromTarget() {
target.UnregisterCallback<MouseOverEvent>(MouseEnter);
target.UnregisterCallback<MouseOutEvent>(MouseOut);
Over = true;
}
protected void MouseEnter(MouseOverEvent e) {
MouseIn = true;
}
protected void MouseOut(MouseOutEvent e) {
MouseIn = false;
}
private void PropagateParent(TooltipRoot ParentTooltip) {
ParentTooltip.AutoDeleteTooltip.TooltipCounter = 0;
if (ParentTooltip.ParentTooltip != null) {
PropagateParent(ParentTooltip.ParentTooltip);
}
}
}
Utils and Others
public static T UIElementUserData<T>(VisualElement target) where T : class {
Dictionary<Type, object> data = target.userData as Dictionary<Type, object>;
if (data == null) {
return (T)null;
}
if (data.TryGetValue(typeof(T), out object val)) {
return (T)val;
}
return (T)null;
}
public static T UIElementUserDataInParent<T>(VisualElement target) where T : class {
target = target.parent;
if (target == null) {
return null;
}
Dictionary<Type, object> data = target.userData as Dictionary<Type, object>;
if (data == null) {
return UIElementUserDataInParent<T>(target);
}
if (data.TryGetValue(typeof(T), out object val)) {
return (T)val;
}
return UIElementUserDataInParent<T>(target);
}
public static void UIElementUserData<T>(VisualElement target, T val) where T : class {
Dictionary<Type, object> data = target.userData as Dictionary<Type, object>;
if (data == null) {
target.userData = data = new Dictionary<Type, object>();
}
data[typeof(T)] = val;
}
using System.Collections.Generic;
using UnityEngine.UIElements;
public class TooltipOwner {
public VisualElement Tooltip;
}
using System.Collections.Generic;
using UnityEngine.UIElements;
public class TooltipRoot {
public AutoDeleteTooltip AutoDeleteTooltip;
public TooltipRoot ParentTooltip;
}