reactive-banana icon indicating copy to clipboard operation
reactive-banana copied to clipboard

Example request: double click detection

Open DevJac opened this issue 9 years ago • 4 comments
trafficstars

I don't see any reactive-banana examples that involve measuring time between events. About half the examples, like Counter.hs don't have an element of time at all (it seems), and the other half seem to involve a fixed time step "framerate". It would also help me, personally, if this example did not involve wx, as I cannot get reactive-banana-wx to install.

I would propose a console demonstration, like SlotMachine.hs, where an output is only emitted if the user enters two lines within a limited amount of time (the lines that the user enters may be blank), this would be similar to a double click.

Or better yet, I would love to see something like this implemented in reactive-banana: http://jsfiddle.net/staltz/4gGgs/27/

DevJac avatar Jun 18 '16 21:06 DevJac

Just my two cents, but this may be helpful. Not sure what is actually possible or feasible, but this is how I would do it if all the right features were available

type TimeStamp -- whatever this is

sometime :: TimeStamp
-- timestamp long enough in the past that nothing matters

getTime :: a -> IO (a,TimeStamp)

doubled :: Int -> Event a -> MomentIO (Event a)
doubled t e = mdo
    e' <- mapEventIO getTime a
    b <- stepper sometime (snd <$> e')
    let
        bWithin = ((<t).).(.snd).difference <$> b
    return $ fst <$> filterApply bWithin e'

difference :: TimeStamp -> TimeStamp -> Int
-- difference between timestamps in whatever units

This probably doesn't work for some practical reason, but this is what I would try to implement if possible.

archaephyrryx avatar Aug 15 '16 15:08 archaephyrryx

@archaephyrryx I think the idiomatic approach would be to:

  • Define a double-click in terms of an Event Click and an Event Tick (or Event DeltaSinceLastTick)
  • Tick at an appropriate rate so double-clicking feels natural

mitchellwrosen avatar Aug 06 '17 23:08 mitchellwrosen

Here is an example:

{-# language RecursiveDo         #-}
{-# language ScopedTypeVariables #-}

import Reactive.Banana
import Reactive.Banana.Frameworks

import Control.Concurrent
import Control.Monad
import System.IO

type Time      = Int -- microseconds
type TimeDelta = Int -- microseconds

-- Step time roughly 60 times per second
timeRate :: TimeDelta
timeRate = 16667

-- For a click to be registered as a double, it has to occur within this delta
-- of the previous click.
doubleClickRate :: TimeDelta
doubleClickRate = 500000

main :: IO ()
main = do
  (tickAddHandler, fireTick) <- newAddHandler
  (clickAddHandler, fireClick) <- newAddHandler

  network <- compile $ mdo
    -- Time delta since last tick (in microseconds)
    eTick :: Event TimeDelta <-
      fromAddHandler tickAddHandler

    -- "Click" events (really, key presses)
    eClick :: Event () <-
      fromAddHandler clickAddHandler

    -- The total amount of time elapsed.
    bElapsed :: Behavior Time <-
      accumB 0 ((+) <$> eTick)

    -- Clicks tagged with the time they occurred at
    let eClickAt :: Event Time
        eClickAt = bElapsed <@ eClick

    -- If a click happens *now*, would it be a double click?
    bIsDoubleClick :: Behavior (Time -> Bool) <-
      stepper
        -- The first click is a single click no matter what.
        (const False)
        (unionWith
          -- The union function doesnt matter, because single/double clicks will
          -- never happen simultaneously.
          const
          -- When a double click occurs, go back to 'const False'. This causes
          -- a "triple click" to register as single-double-single, rather than
          -- single-double-double.
          (const False <$ eDoubleClickAt)
          -- Each time a single click occurs, become a function that, when given
          -- a timestamp, returns true if it's within 'doubleClickRate'
          -- microseconds of the single click.
          ((\x y -> y - x < doubleClickRate) <$> eSingleClickAt))

    -- The subset of click events that are single clicks
    let eSingleClickAt :: Event Time
        eSingleClickAt = filterApply ((not .) <$> bIsDoubleClick) eClickAt

        eDoubleClickAt :: Event Time
        eDoubleClickAt = filterApply bIsDoubleClick eClickAt

    reactimate (putStrLn "Single click" <$ eSingleClickAt)
    reactimate (putStrLn "Double click" <$ eDoubleClickAt)

  actuate network

  -- Tick every 'timeRate' microseconds, forever.
  void . forkIO . forever $ do
    fireTick timeRate
    threadDelay timeRate

  -- Emit key presses as click events
  hSetBuffering stdin NoBuffering
  hSetEcho stdout False
  forever $ do
    _ <- getChar
    fireClick ()

mitchellwrosen avatar Aug 28 '17 14:08 mitchellwrosen

@DevJac Does this example suffice? Perhaps I could try to get it added to doc/examples if it's what you were looking for.

mitchellwrosen avatar Sep 27 '17 13:09 mitchellwrosen