MouseEnter vs MouseOver?

The documentation seem to indicate that MouseOver is just entering the element, where MouseEnter is the element OR/AND any of it’s children
However, they seem to work the opposite of that

I’d like however, that MouseOver were ‘every frame the mouse is above element’ as I assumed it was because that’s what it does in every other UI solution that have a MouseOver event that I know of

I assume you’re talking about this?

I’d recommend sticking with MouseEnterEvent and use MouseMoveEvent to “do something” while the mouse is on top of the element. MouseMoveEvent is not fired every frame, only the frames the mouse actually moves while over the element. In UI Toolkit, almost nothing on the user side should run “every frame” as it’s primarily a retained mode UI. So you’re just reacting to the user’s actions when they happen.

2 Likes

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;
}

It’s a perfectly valid way to implement it. Thanks for putting up the example.