reactive-banana
reactive-banana copied to clipboard
Example request: double click detection
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/
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 I think the idiomatic approach would be to:
- Define a double-click in terms of an
Event Clickand anEvent Tick(orEvent DeltaSinceLastTick) - Tick at an appropriate rate so double-clicking feels natural
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 ()
@DevJac Does this example suffice? Perhaps I could try to get it added to doc/examples if it's what you were looking for.