Search Unity

Hovering: how do you detect mouse is no longer over the UIToolkit UI? NB: MouseOut won't work

Discussion in 'UI Toolkit' started by a436t4ataf, Jul 25, 2021.

  1. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Simplest example: I have a container with three children, and I want to highlight each child when mouse hovers over them. Seems we could do:

    1. Each child: create two VisualElements, one for 'normal', one for 'hovered'
    2. Each child: RegisterCallback<MouseOverEvent>() ... hide the 'normal' element, show the 'hovered' element
    3. Each child: RegisterCallback<MouseOutEvent>() ... [reverse the above]

    This is terrible: it causes flickering because of the way UIToolkit chooses to process Out/Over. It also often fails to trigger the MouseOutEvent because of flaws (I'd say 'bugs' but I've reported one and it was rejected as 'broken but too difficult to fix', so ... we'll call them 'flaws') in how MouseOut is implemented ... there's a timing bug and a pixel-sensitivity bug, and there's a popup menu bug, and ... and ... and: basically MouseOut is mostly useless in real-world apps.

    OK. So ... if we remove the MouseOutEvent and try to achieve the same effect only using MouseOver:
    1. Store 'last hovered VisualElement'
    2. Each child: create two VisualElements, one for 'normal', one for 'hovered'
    3. Each child: RegisterCallback<MouseOverEvent>() ... hide the 'normal' element, show the 'hovered' element.
      1. Also: if there was a 'last hovered' item, reverse the above to reset it
      2. Also: mark self as the new 'last hovered' item
    But this doesn't work because there's no 'mouse has left the VisualElement tree' callback, so if the user moves mouse onto a different window we're left with incorrect visual state.

    ...

    So. Is there a better way to achieve this common pattern? The above approaches are both clean, require little code, and work great except that MouseOutEvent has problems. I'm hopeful there's an equally simple solution that works much better?
     
  2. sebastiend-unity

    sebastiend-unity

    Unity Technologies

    Joined:
    Nov 9, 2015
    Posts:
    184
    Hi,
    So I guess the simplest way to do this would be to use the :hover state in the styles. Let's say we take one of the elements (answer would be the same for all 3 elements) and just want to show a different background color for that element:

    1. You create the element and add a class to your element (either using the UI Builder or by code):

    var myElement = new VisualElement { ... };
    myElement.AddToClassList("myElement");
    myElement.AddStyleSheetPath("mySheet");

    2. In mySheet.uss, you define your class selectors and set your properties:

    .myElement {
    width: 100px;
    height: 100px;
    background-color: blue;
    }

    .myElement:hover {
    background-color: red;
    }

    If, as stated in your example, you prefer to show some content (let's say, a blue label) in your element, and another when hovering that element (let's say, a red label), you would need to add both Labels as children of the main element (myElement), then style those directly:

    1. You create the element + children and add a class to your element (either using the UI Builder or by code):

    var myElement = new VisualElement { ... };
    myElement.AddToClassList("myElement");
    myElement.AddStyleSheetPath("mySheet");
    var myBlueLabel = new Label { ... };
    myBlueLabel.AddToClassList("blue");
    myBlueLabel.StretchToParentSize();
    myElement.Add(myBlueLabel);
    var myRedLabel = new Label { ... };
    myRedLabel.AddToClassList("red");
    myRedLabel.StretchToParentSize();
    myElement.Add(myRedLabel);

    2. In mySheet.uss, you define your class selectors and set your properties for labels as well:

    .myElement {
    width: 100px;
    height: 100px;
    background-color: blue;
    }

    .blue, .red {
    font-size: 20px;
    }

    .blue {
    color: blue;
    display: flex;
    }

    .red {
    color: red;
    display: none;
    }

    .myElement:hover .red {
    display; flex;
    }

    .myElement:hover .blue {
    display; none;
    }

    Hope I understood your question and that this helps a bit.
     
  3. sebastiend-unity

    sebastiend-unity

    Unity Technologies

    Joined:
    Nov 9, 2015
    Posts:
    184
    Otherwise you could also look at the MouseEnterEvent and MouseLeaveEvent which are not quite the same as MouseOverEvent and MouseOutEvent.
     
  4. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Thanks - I'm not using USS (for most projects I'm on it has no obvious benefit, and slows down development considerably) so I'm looking for code-only solutions.

    I should have added that we've tried MouseLeave in the past (on the parent / grandparent object to try and catch all) and it also has serious bug(s) that meant it simply couldn't solve the problems here -- but I haven't tried it recently, so I'll try it again now and see what happens. Maybe things have been fixed enough now that it will Just Work :).