Search Unity

Why are there multiple Event metaphors in UIElements?

Discussion in 'UIElements' started by SonicBloomEric, Jun 2, 2019.

  1. SonicBloomEric

    SonicBloomEric

    Joined:
    Sep 11, 2014
    Posts:
    581
    I'm just beginning to play around with UIElements and I've been struggling with figuring out some basics. The biggest confusion has been how to hook into UI events of various types.

    Why does the Button element have a "clickable" action?

    Why do most other elements use "RegisterForEvent<EventType>" to receive callbacks?

    Is there a method to the madness? Why aren't these consistent?

    And why isn't the "clickable" event documented in the Events Reference? Why do I have to go to the Button class documentation to find out what "clickable" is?
     
  2. AlexandreT-unity

    AlexandreT-unity

    Unity Technologies

    Joined:
    Feb 1, 2018
    Posts:
    20
    Hi SonicBloomEric,

    Clickable is not an event, but a manipulator that wraps lower-level events and allows to get higher-level click-related callbacks and other features, such as being able to enable/disable clicking.
     
  3. SonicBloomEric

    SonicBloomEric

    Joined:
    Sep 11, 2014
    Posts:
    581
    @AlexandreT-unity Neat. We're getting into semantics.

    At the time I wrote this post I had not yet come across the term "Manipulator". The "Responding to Events" section of the documentation doesn't mention Manipulators even once. In fact, the only place the term is even mentioned in relation to UIElements in the entire manual is in the "Built-in Controls" section. And there it's simply dropped with no definition or explanation: it is expected that you understand the term already. The only hint is that the explanation suggests that adding a manipulator somehow "adds callbacks that respond to certain events".

    This is... extremely confusing.

    Is there somewhere to describe what a manipulator is and why they exist? From what I've seen they simply add an extra layer of callback confusion to the system.

    Let me expand upon the confusion. You guys have designed UIElements to work use a "RegisterCallbacks" based metaphor for subscribing to event callbacks. This is clean and easy to understand. However, when it comes to looking for a "Click" event, a developer (e.g. me) isn't able to find one in the Events Reference. Imagine my surprise when looking over the Button class I notice a "clickable" that has a C# event called "clicked" and that unlike all [so far as I can tell] other events in UIElements I subscribe to these with a standard C# approach:

    Code (CSharp):
    1. someButton.clickable.clicked += OnClicked;
    But... why not simply allow me to do the following:

    Code (CSharp):
    1. someButton.RegisterCallback<ClickEvent>(OnClicked);
    What is going on here? What's the method to this madness? Why isn't this documented?
     
  4. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    155
    In the case of the Button and Clickable, the Button needs to:
    1. run its callback on MouseUpEvent
    2. but not run if the corresponding MouseDownEvent did not occur on top of it first

    Therefore, it is not enough to register for a single event and it's why we need Manipulators. We've debated adding a dedicated ClickEvent before but it never materialized so for now we need to use the Clickable manipulator.

    For a demo-based explanation of Manipulators, try this (timestamped URL) video:
     
  5. SonicBloomEric

    SonicBloomEric

    Joined:
    Sep 11, 2014
    Posts:
    581
    Certainly. But those are implementation details. There is nothing stopping you from managing the state required to check for "MouseDown→MouseUp" and then, when detected, trigger a ClickEvent, right?

    So... I just poked around the Manipulator source code a bit and it looks a bit like an anti-pattern. From the source code:
    Code (CSharp):
    1. public static void AddManipulator(this VisualElement ele, IManipulator manipulator)
    2. {
    3.     if (manipulator != null)
    4.     {
    5.         manipulator.target = ele;
    6.     }
    7. }
    8.  
    9. public static void RemoveManipulator(this VisualElement ele, IManipulator manipulator)
    10. {
    11.     if (manipulator != null)
    12.     {
    13.         manipulator.target = null;
    14.     }
    15. }
    You are inverting the Add/Remove semantics here. "Adding" a manipulator to a VisualElement is actually adding the VisualElement as a target to the Manipulator. Why not simply use one of the following:

    Code (CSharp):
    1. // Given:
    2. VisualElement someElt = root.Q("dragger");
    3. MyDragManipulator dragControl;
    4.  
    5. // Option one:
    6. dragControl = new MyDragManipulator(someElt);
    7.  
    8. // Option two:
    9. dragControl = new MyDragManipulator();
    10. dragControl.target = someElt;
    11.  
    12. // Option etc:
    13. dragControl = new MyDragManipulator();
    14. dragControl.Manipulate(someElt);
    That's not only far more clear, it's also easier to understand the reference ownership. When you say "VisualElement.AddManipulator(manipulator)" you make it seem as though the VisualElement now has an explicit reference to the "manipulator" instance that it tracks in some Manipulator array. But that's not true.

    Honestly, the whole "Manipulator" paradigm feels a bit unnecessary here. Is there some deeper-seated reason that they are being sprinkled into the API? The only two places I've seen them look as though they would be better off without them:
    1. Button.Clickable - This should absolutely be a ClickEvent.
    2. VisualElement.Add/RemoveManipulator - These should go away and be replaced with some way to clearly set the Manipulator instance's "target".
    As a concept, "Manipulators" seem fine. They are a way to conceptually deal with state across multiple atomic UI "events". Neat. But these are effectively implementation details that appear to "poke out of" the core API in odd ways. I think the UIElements API might be improved by relegating Manipulators to their own self-contained subsection of the API that users can optionally use if they wish to define complex event interactions.

    I mean... even the ManipulatorActivationFilter system is little more than a compartmentalized "if-check" to see if certain buttons or modifiers were hit... Were I implementing my own "Manipulator", I'd probably skip that whole thing and write my own simple check against the event's modifiers and buttons...

    Given that you've gone out of your way to make UIElements seem as web-like as possible, you're likely to find more and more people checking for a ClickEvent as the click event is perhaps the most basic event used on the web...

    The timestamp didn't carry over. For those interested, click here.
     
    Last edited: Jun 5, 2019
    cecarlsen, TonyLi and Sylmerria like this.
  6. AlexandreT-unity

    AlexandreT-unity

    Unity Technologies

    Joined:
    Feb 1, 2018
    Posts:
    20
    Hi SonicBloomEric,

    I understand your concern regarding the "anti-pattern" and the confusion that it may create. I brought your post to the attention of the UIElements team. Manipulators need some love, and we may be revisiting this API in the future, but the shape it will take is yet to be defined. They may end up in the garbage truck or be revamped (e.g. adding multi-target support), so stay tuned.

    Regarding the sparse documentation, we are aware of the issue and this will improve as our documentation team iterates on the API doc and manuals.

    Thanks
     
    TonyLi, SonicBloomEric and Sylmerria like this.