neuecc / UniRx

Reactive Extensions for Unity
MIT License
7.09k stars 891 forks source link

Double-click detection without delay #348

Open bartlomiejwolk opened 6 years ago

bartlomiejwolk commented 6 years ago

If I undersand correctly, in the double-click example from the README.md, there'll always be a minimum 250 ms delay to recognize that double click was performed.

The example:

var clickStream = Observable.EveryUpdate()
    .Where(_ => Input.GetMouseButtonDown(0));

clickStream.Buffer(clickStream.Throttle(TimeSpan.FromMilliseconds(250)))
    .Where(xs => xs.Count >= 2)
    .Subscribe(xs => Debug.Log("DoubleClick Detected! Count:" + xs.Count));

Is there a way to detect double-click the moment that the second click was entered?

FodderMK commented 6 years ago

Buffer has a call that lets you choose the buffer size as well. Does this work for you?

Observable.EveryUpdate().Where(_ => Input.GetMouseButtonDown(0))
    .Buffer(TimeSpan.FromMilliseconds(250), 2)
    .Where(buffer => buffer.Count >= 2)
    .Subscribe(_ => Debug.Log("DoubleClicked!"));
bartlomiejwolk commented 6 years ago

I think it can lead to missing double-clicks. Buffer gatheres clicks into fixed time windows, each window being e.g. 250 ms apart. In my understanding, it's possible to make first click in window A and then the second in window B. Even if both clicks were within 250 ms timeframe this double-click won't be registered. After the first click, Buffer will close window A and emit an IList with just one click in in. The same will happen with window B.

EDIT: Maybe I could use Window(2, 1) to create a stream of 2 elements for each source element. If elemens in those streams would come at the same time as the source elements then I could use Buffer(TimeSpan.FromMilliseconds(250)) on each of them and then check if number of elements in each IList is more than 2.

What is the UniRx operator that works like a Window()?

chrPetry commented 4 years ago

@bartlomiejwolk Did you find a solution for this?

chrPetry commented 4 years ago

I tried to copy the Window behaviour myself. Take your primary click stream and on subscribe add another stream. This second stream does a timeout (ignore errors) but fires a doubleclick if the primary click stream hits again.

Don't know if this is ideal but it does what I need.

var primaryClickStream = Observable.EveryUpdate()
      .Where(_ => Input.GetMouseButtonDown(0));

primaryClickStream
     .Subscribe(_ =>
       {
         Observable.Timeout(primaryClickStream, TimeSpan.FromSeconds(_doubleClickTimeoutInSec))
              .Subscribe(x => { _doubleClicked = true; },
                              xe => { } //nothing
        );
});
elhimp commented 2 years ago

Don't know if this is ideal

Each click you're creating new object ...and this object fails to do what it supposed to do most of the times ...and you're ignoring error reports. That's quite opposite of ideal.

Just timestamp your stuff

var doubleClickObservable =
    clickSource
    // !!!
    .Timestamp()
    // !!!
    .Pairwise((prev, current) => (current.Timestamp - prev.Timestamp).TotalMilliseconds <= 300)
    .Where(fastEnough => fastEnough);
cpetry commented 2 years ago

Don't know if this is ideal Just timestamp your stuff Oh yeah this is much cleaner. Thanks for this!

Just for a working example I'll add my finished observable here (with subscribe):

var doubleClickObservable =
    clickSource
    .Timestamp()
    .Pairwise((prev, current) => (current.Timestamp - prev.Timestamp).TotalMilliseconds <= 300)
    .Where(fastEnough => fastEnough)
    .Subscribe(x => _doubleClicked = true);