Search Unity

Question Distinguish click from double click

Discussion in 'UI Toolkit' started by Maverick, Sep 7, 2020.

  1. Maverick

    Maverick

    Joined:
    Dec 18, 2009
    Posts:
    240
    Hello.

    I'm adding click and doubleclick activation via ManipulatorActivationFilter. Lets have for example following code:

    Code (CSharp):
    1.  
    2. var v = new Button();
    3. b.clickable.clickedWithEventInfo += Clickable_clickedWithEventInfo;
    4. b.clickable.activators.Clear();
    5. b.clickable.activators.Add(new ManipulatorActivationFilter { button = MouseButton.LeftMouse, clickCount = 1, modifiers = EventModifiers.None });
    6. b.clickable.activators.Add(new ManipulatorActivationFilter { button = MouseButton.LeftMouse, clickCount = 2, modifiers = EventModifiers.None });
    7.  
    Each filter works fine if it was added separately. When they are both added, on double click I get 2 callbacks. That's kind of normal, cause double click is composed from 2 clicks.

    Few questions:
    1. When having only one filter with clickCount = 2, mouseUpEvent.clickCount in the callback is always 1
    Code (CSharp):
    1.  
    2. private void Clickable_clickedWithEventInfo(EventBase obj)
    3. {
    4. MouseUpEvent mouseUpEvent = obj as MouseUpEvent;
    5. if (mouseUpEvent != null)
    6. UnityEngine.Debug.Log($"Clickable_clickedWithEventInfo {this} - {mouseUpEvent.clickCount}");        
    7. }
    8.  
    Is it a bug or as designed?

    2. Is there a way to now what filter "triggered" the callback?
    3. Is there a way to combine this 2 filters and how to distinguish them?

    Or my approach is totally wrong :)
     
  2. MostHated

    MostHated

    Joined:
    Nov 29, 2015
    Posts:
    1,235
    Last edited: Sep 7, 2020
    t2g4 likes this.
  3. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,260
    The MouseUpEvent has no way to know what triggered it, just that it was triggered. The activator only defines what triggers the event. So in the MouseUpEvent if you want to know if it was a single or double click you need to check the click count there. There are more long winded ways to do this like with your own manipulator for double clicking, but its easier to check the click count.
     
  4. Maverick

    Maverick

    Joined:
    Dec 18, 2009
    Posts:
    240
    Thanks for suggestions. I'll look into list view. Thought there is more a subtle method there :)

    That's the problem, in my tests I always get click count = 1.


    PS: Is there a way to see base elements source code, like TextElement for example. Couldn't find it on unity's github.
     
  5. uBenoitA

    uBenoitA

    Unity Technologies

    Joined:
    Apr 15, 2020
    Posts:
    216
    When you're receiving mouse or pointer events in the Editor (for example in an EditorWindow), the data for the events are populated from the IMGUI events that were sent to the window, which are then translated into the corresponding UI Toolkit events. Because IMGUI uses
    clickCount
    only during events of type MouseDown (see https://docs.unity3d.com/ScriptReference/Event-clickCount.html), UI Toolkit events will also not receive it except on MouseDown, which is why you're always getting 1 on mouse up.

    However, there is indeed a way to do what you're trying to do with Clickables, though it's a bit tricky at the moment. Please note that we're actively thinking of ways to improve the Clickable workflow in the future. In the meantime, this should work:

    Code (CSharp):
    1. void OnSingleClick(EventBase evt) { Debug.Log("Single Click"); }
    2. void OnDoubleClick(EventBase evt) { Debug.Log("Double Click"); }
    3.  
    4. var clickable1 = new Clickable(OnSingleClick);
    5. clickable1.activators.Clear();
    6. clickable1.activators.Add(new ManipulatorActivationFilter { button = MouseButton.LeftMouse, clickCount = 1 });
    7. var clickable2 = new Clickable(OnDoubleClick);
    8. clickable2.activators.Clear();
    9. clickable2.activators.Add(new ManipulatorActivationFilter { button = MouseButton.LeftMouse, clickCount = 2 });
    10.  
    11. var b = new Button {text = "Click or Double Click Me!"};
    12. b.AddManipulator(clickable2);
    13. b.clickable = clickable1;
    14.  
    (We need to add clickable1 last because of the way ManipulatorActivationFilters work. Filters will match any clickCount that is at least what they want, so clickable1 would activate even if it received clickCount = 2, here. By putting it last, we let clickable2 see the events first, and when they match, stop them from propagating to clickable1.)
     
  6. Maverick

    Maverick

    Joined:
    Dec 18, 2009
    Posts:
    240
    Thanks for reply, @uBenoitA .

    Unfortunately I'm still getting both events, single and then double click (on double click). Adding "evt.StopPropagation();" to OnDoubleClick did not help as well.
     
  7. uBenoitA

    uBenoitA

    Unity Technologies

    Joined:
    Apr 15, 2020
    Posts:
    216
    You're right, I don't know that there's a way to prevent the first click to send a SingleClick, since it sends it immediately when it perceives a MouseUp, and doesn't wait for the second click to happen. I'm not aware that that's considered a bug at the moment.

    It's the way the OS does it too, and the double-click delay is set by the OS. In some cases you can receive a DoubleClick after waiting for 0.5s between the clicks. If that's acceptable, you'd need to wait that much time before you could safely send the SingleClick. I don't suppose that would be a logical thing to do in general.

    That being said, if you really want to wait for the double-click to fail before you send in the single click, then I would recommend monitoring the clicks on your side and using a custom delay that better suits your needs, that is: receive events (click or double-click, regardless), accumulate them into a local clickCount, then invoke the corresponding callback after a time threshold where no event was received or when you've accumulated your maximum clickCount (presumably 2).

    I'm assuming that you're creating UI that will be used in the Editor, not in runtime. Is that the case? If you're developing for runtime, then you might have some customization options on double-clicks when using the Input System package with your UITK event system component (with UITK 1.0.0-preview.8).
     
  8. Maverick

    Maverick

    Joined:
    Dec 18, 2009
    Posts:
    240
    I'm doing it for runtime. Guess I'll have to do it in the old fashion way.

    Thanks for info and help.
     
  9. FuguFirecracker

    FuguFirecracker

    Joined:
    Sep 20, 2011
    Posts:
    419
    My solution to this problem was to register a ClickEvent callback on the VisualElement in order to evaluate the conditions surrounding said "click"


    Code (CSharp):
    1.  
    2.  
    3. // if you wanna check against to see if your coroutine is already firing
    4. private IEnumerator   _evaluateClick;
    5.  
    6. // in constructor or wherever  you register stuff
    7. public VisualElement {
    8. RegisterCallback<ClickEvent>(EvaluateClick);
    9. }
    10.  
    11.     private void EvaluateClick(ClickEvent evt)
    12.         {
    13.             // if you need it ... Context is a static class that
    14.             // keeps track of overarching modifiers et al ...
    15.             if (Context.IsControlPressed)
    16.             {
    17.                 DoCtrlClick();
    18.                 return;
    19.             }
    20.  
    21.             _clickCount = evt.clickCount;
    22.  
    23.             //  SimpleEditorCoroutine is a custom utility class
    24.             // that I *could* share if you don't have your own
    25.             // prefered method for waiting for things.
    26.             // There are lots of ways to wait for things in Unity || .NET
    27.             // Heck, here's Unity's implementation:
    28.             // https://docs.unity3d.com/Packages/com.unity.editorcoroutines@1.0/manual/index.html
    29.        
    30.            if (_evaluateClick == null)
    31.             {
    32.                 _evaluateClick = new SimpleEditorCoroutine(Wait());
    33.             }
    34.         }
    35.      
    36.         // How long do I wanna allow for doubleClick ?
    37.         // Do note that any single click event will be delayed by this amount.
    38.         // I find roughly 1/3rd of a second to be acceptable.
    39.           private const double HANG_TIME = 0.321;
    40.        
    41.          private IEnumerator Wait()
    42.         {
    43.             var initTime = EditorApplication.timeSinceStartup;
    44.  
    45.             while (EditorApplication.timeSinceStartup - initTime < HANG_TIME)
    46.                       { yield return null; }
    47.  
    48.             if (_clickCount > 1) { DoDoubleClick(); }
    49.             else if (_clickCount == 1) { DoClick(); }
    50.  
    51.             _evaluateClick = null;
    52.         }
    53.  
     
    JG-Denver likes this.