Search Unity

Bug MouseEnter vs MouseOver?

Discussion in 'UI Toolkit' started by Guedez, Jul 8, 2020.

  1. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    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
     
  2. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    I assume you're talking about this?
    https://docs.unity3d.com/ScriptReference/UIElements.MouseOverEvent.html

    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.
     
    Rashonski and Guedez like this.
  3. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    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:
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEngine.UIElements;
    6.  
    7. public class TooltipManipulator : Manipulator {
    8.     public VisualElement TooltipRoot { get => ToolipController.Root; }
    9.     private float TooltipCounter;
    10.     private float TooltipTime;
    11.     private bool Over;
    12.     private Func<VisualElement> CreateTooltip;
    13.  
    14.     public TooltipManipulator(Func<VisualElement> CreateTooltip, float TooltipTime) {
    15.         this.CreateTooltip = CreateTooltip;
    16.         this.TooltipTime = TooltipTime;
    17.     }
    18.  
    19.     protected override void RegisterCallbacksOnTarget() {
    20.         target.RegisterCallback<MouseEnterEvent>(AddDeltaTime);
    21.         target.RegisterCallback<MouseOutEvent>(ResetDeltaTime);
    22.     }
    23.  
    24.     protected override void UnregisterCallbacksFromTarget() {
    25.         target.UnregisterCallback<MouseEnterEvent>(AddDeltaTime);
    26.         target.UnregisterCallback<MouseOutEvent>(ResetDeltaTime);
    27.     }
    28.  
    29.     protected void ResetDeltaTime(MouseOutEvent e) {
    30.         Over = false;
    31.         TooltipCounter = 0;
    32.     }
    33.  
    34.     private void AddDeltaTime(MouseEnterEvent e) {
    35.         Over = true;
    36.         target.schedule.Execute((E) => {
    37.             TooltipOwner tt = Utils.UIElementUserData<TooltipOwner>(target);
    38.             if (tt != null && tt.Tooltip != null) {
    39.                 TooltipRoot root = Utils.UIElementUserData<TooltipRoot>(tt.Tooltip);
    40.                 if (root != null) {
    41.                     root.AutoDeleteTooltip.TooltipCounter = 0;
    42.                 }
    43.                 return;
    44.             }
    45.             TooltipCounter += Time.deltaTime;
    46.             if (TooltipCounter > TooltipTime) {
    47.                 VisualElement child = CreateTooltip();
    48.                 child.style.position = Position.Absolute;
    49.                 child.style.left = Input.mousePosition.x - 3;
    50.                 child.style.bottom = Input.mousePosition.y - 3;
    51.                 if (tt == null) {
    52.                     Utils.UIElementUserData(target, tt = new TooltipOwner());
    53.                 }
    54.                 tt.Tooltip = child;
    55.                 TooltipRoot tr = new TooltipRoot();
    56.                 Utils.UIElementUserData(child, tr);
    57.                 tr.Self = child;
    58.                 tr.ParentTooltip = Utils.UIElementUserDataInParent<TooltipRoot>(target);
    59.                 AutoDeleteTooltip autodel = new AutoDeleteTooltip();
    60.                 autodel.TooltipOwner = tt;
    61.                 autodel.TooltipRoot = tr;
    62.                 tr.AutoDeleteTooltip = autodel;
    63.                 child.AddManipulator(autodel);
    64.  
    65.                 Over = true;
    66.                 TooltipRoot.Add(child);
    67.             }
    68.         }).Until(() => !Over);
    69.     }
    70. }
    71.  
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEngine.UIElements;
    6.  
    7. public class AutoDeleteTooltip : Manipulator {
    8.     private bool Over;
    9.     private bool MouseIn;
    10.     public float TooltipCounter;
    11.     private int TooltipTime;
    12.     public TooltipOwner TooltipOwner;
    13.     public TooltipRoot TooltipRoot;
    14.  
    15.  
    16.     public AutoDeleteTooltip() {
    17.         TooltipTime = 4;
    18.     }
    19.  
    20.     protected override void RegisterCallbacksOnTarget() {
    21.         Over = false;
    22.         MouseIn = true;
    23.         target.schedule.Execute((E) => {
    24.             if (MouseIn) {
    25.                 PropagateParent(Utils.UIElementUserData<TooltipRoot>(target));
    26.             } else {
    27.                 TooltipCounter += Time.deltaTime;
    28.                 if (TooltipCounter > TooltipTime) {
    29.                     TooltipOwner.Tooltip = null;
    30.                     target.RemoveFromHierarchy();
    31.                     Over = false;
    32.                 }
    33.             }
    34.         }).Until(() => Over);
    35.         target.RegisterCallback<MouseOverEvent>(MouseEnter);
    36.         target.RegisterCallback<MouseOutEvent>(MouseOut);
    37.     }
    38.  
    39.     protected override void UnregisterCallbacksFromTarget() {
    40.         target.UnregisterCallback<MouseOverEvent>(MouseEnter);
    41.         target.UnregisterCallback<MouseOutEvent>(MouseOut);
    42.         Over = true;
    43.     }
    44.     protected void MouseEnter(MouseOverEvent e) {
    45.         MouseIn = true;
    46.     }
    47.     protected void MouseOut(MouseOutEvent e) {
    48.         MouseIn = false;
    49.     }
    50.  
    51.     private void PropagateParent(TooltipRoot ParentTooltip) {
    52.         ParentTooltip.AutoDeleteTooltip.TooltipCounter = 0;
    53.         if (ParentTooltip.ParentTooltip != null) {
    54.             PropagateParent(ParentTooltip.ParentTooltip);
    55.         }
    56.     }
    57. }
    58.  
    Code (CSharp):
    1. public static T UIElementUserData<T>(VisualElement target) where T : class {
    2.         Dictionary<Type, object> data = target.userData as Dictionary<Type, object>;
    3.         if (data == null) {
    4.             return (T)null;
    5.         }
    6.         if (data.TryGetValue(typeof(T), out object val)) {
    7.             return (T)val;
    8.         }
    9.         return (T)null;
    10.     }
    11.     public static T UIElementUserDataInParent<T>(VisualElement target) where T : class {
    12.         target = target.parent;
    13.         if (target == null) {
    14.             return null;
    15.         }
    16.         Dictionary<Type, object> data = target.userData as Dictionary<Type, object>;
    17.         if (data == null) {
    18.             return UIElementUserDataInParent<T>(target);
    19.         }
    20.         if (data.TryGetValue(typeof(T), out object val)) {
    21.             return (T)val;
    22.         }
    23.         return UIElementUserDataInParent<T>(target);
    24.     }
    25.     public static void UIElementUserData<T>(VisualElement target, T val) where T : class {
    26.         Dictionary<Type, object> data = target.userData as Dictionary<Type, object>;
    27.         if (data == null) {
    28.             target.userData = data = new Dictionary<Type, object>();
    29.         }
    30.         data[typeof(T)] = val;
    31.     }
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine.UIElements;
    3.  
    4. public class TooltipOwner {
    5.     public VisualElement Tooltip;
    6. }
    7.  
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine.UIElements;
    3.  
    4. public class TooltipRoot {
    5.     public AutoDeleteTooltip AutoDeleteTooltip;
    6.     public TooltipRoot ParentTooltip;
    7. }
    8.  
     
  4. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    It's a perfectly valid way to implement it. Thanks for putting up the example.