varying 0.1.0.2 → 0.1.0.3
raw patch · 6 files changed
+251/−44 lines, 6 files
Files
- README.md +1/−1
- src/Control/Varying.hs +31/−4
- src/Control/Varying/Core.hs +92/−10
- src/Control/Varying/Event.hs +33/−17
- src/Control/Varying/Tween.hs +93/−11
- varying.cabal +1/−1
README.md view
@@ -5,7 +5,7 @@ [auto](http://hackage.haskell.org/package/auto) packages. Unlike netwire the concepts of inhibition and time are explicit (through `Control.Varying.Event` and `Control.Varying.Time`) and the library aims at being minimal and well -documented with a small API surface area.+documented with a small API. Depending on your types and values varying can provide discrete or continuous time semantics.
src/Control/Varying.hs view
@@ -1,7 +1,34 @@--- | Module: Control.Varying--- Copyright: (c) 2015 Schell Scivally--- License: MIT--- Maintainer: Schell Scivally <schell.scivally@synapsegroup.com>+-- |+-- Module: Control.Varying+-- Copyright: (c) 2015 Schell Scivally+-- License: MIT+-- Maintainer: Schell Scivally <schell.scivally@synapsegroup.com>+--+-- The simplest, squishiest FRP library around.+--+-- [@Core@]+-- Get started writing varying values (also called streams) using the pure+-- constructor 'var', the monadic constructor 'varM' or the raw constructor+-- 'Var'+--+-- [@Event@]+-- Write event streams using the many event emitters and combinators.+--+-- [@Tween@]+-- Tween numerical values over time using interpolation functions and the+-- "quick 'n dirty" time generators in 'Control.Varying.Time'.+--+-- [@Time@]+-- The 'Control.Varying.Time' module is not reexported because some of the+-- functions collide with those in 'Event' - namely 'before' and 'after'.+-- I think this is okay because in my experience most modules will either+-- deal with events based on user input or events based on time, an in+-- rare cases both - but in that case the majority of streams will be of one+-- type making the choice of which module to import qualified an easy one.+-- The time generator 'Control.Varying.Time.deltaUTC' in 'Control.Varying.Time'+-- is practical and based on 'Data.Time.Clock.getCurrentTime'. It's meant+-- to be simple, not optimal.+-- module Control.Varying ( -- * Reexports module Control.Varying.Core,
src/Control/Varying/Core.hs view
@@ -1,8 +1,44 @@--- | Module: Control.Varying.Core+-- |+-- Module: Control.Varying.Core -- Copyright: (c) 2015 Schell Scivally -- License: MIT -- Maintainer: Schell Scivally <schell.scivally@synapsegroup.com>-module Control.Varying.Core where+--+-- Values that change over a given domain.+--+-- Varying values take some input (the domain ~ time, place, etc) and produce+-- a sample and a new varying value. This pattern is known as an automaton.+-- `varying` uses this pattern as its base type with the additon of a monadic+-- computation to create locally stateful signals that change over some+-- domain.+module Control.Varying.Core (+ Var(..),+ -- * Creating varying values+ -- $creation+ var,+ varM,+ -- * Composing varying values+ -- $composition+ (<~),+ (~>),+ -- * Adjusting and accumulating+ delay,+ accumulate,+ -- * Sampling varying values (running, entry points)+ -- $running+ evalVar,+ execVar,+ loopVar,+ loopVar_,+ whileVar,+ whileVar_,+ -- * Testing varying values+ testVar,+ testVar_,+ testWhile_,+ vtrace,+ vstrace+) where import Prelude hiding (id, (.)) import Control.Arrow@@ -10,7 +46,36 @@ import Control.Applicative import Debug.Trace ----------------------------------------------------------------------------------- Constructing+-- $creation+-- You can create a pure varying value by lifting a function @(a -> b)@+-- with 'var':+--+-- @+-- addsOne :: Monad m => Var m Int Int+-- addsOne = var (+1)+-- @+--+-- 'var' is also equivalent to 'arr'.+--+-- You can create a monadic varying value by lifting a monadic computation+-- @(a -> m b)@ using 'varM':+--+-- @+-- getsFile :: Var IO FilePath String+-- getsFile = varM readFile+-- @+--+-- You can create either with the raw constructor. You can also create your+-- own combinators using the raw constructor, as it allows you full control+-- over how varying values are stepped and sampled:+--+-- @+-- delay :: Monad m => b -> Var m a b -> Var m a b+-- delay b v = Var $ \a -> return (b, go a v)+-- where go a v' = Var $ \a' -> do (b', v'') <- runVar v' a+-- return (b', go a' v'')+-- @+-- -------------------------------------------------------------------------------- -- | Lift a pure computation into a 'Var'. var :: Applicative a => (b -> c) -> Var a b c@@ -22,8 +87,18 @@ b <- f a return (b, varM f) ----------------------------------------------------------------------------------- Running+-- $running+-- The easiest way to sample a 'Var' is to run it in the desired monad with+-- 'runVar'. This will give you a sample value and a new 'Var' bundled up in a+-- tuple:+--+-- > do (sample, v') <- runVar v inputValue+--+-- Much like Control.Monad.State there are other entry points for running+-- varying values like 'evalVar', 'execVar'. There are also extra control+-- structures like 'loopVar' and 'whileVar' and more. --------------------------------------------------------------------------------+ -- | Iterate a 'Var' once and return the sample value. evalVar :: Functor m => Var m a b -> a -> m b evalVar v a = fst <$> (runVar v a)@@ -91,7 +166,7 @@ testVar_ :: Show b => Var IO () b -> IO () testVar_ v = loopVar_ $ pure () ~> v ~> varM print ~> varM (const $ getLine) ----------------------------------------------------------------------------------- Composition and accumulation+-- Adjusting and accumulating -------------------------------------------------------------------------------- -- | Accumulates input values using a folding function and yields -- that accumulated value each sample.@@ -103,12 +178,20 @@ -- | Delays the given 'Var' by one sample using a parameter as the first -- sample. This enables the programmer to create 'Var's that depend on -- themselves for values. For example:--- > let v = 1 + delay 0 v in testVar_ v+--+-- > let v = 1 + delay 0 v in testVar_ v delay :: Monad m => b -> Var m a b -> Var m a b delay b v = Var $ \a -> return (b, go a v) where go a v' = Var $ \a' -> do (b', v'') <- runVar v' a return (b', go a' v'')-+--------------------------------------------------------------------------------+-- $composition+-- You can compose varying values together using '~>' and '<~'. The "right plug"+-- ('~>') takes the output from a varying value on the left and "plugs" it+-- into the input of the varying value on the right. The "left plug" does+-- the same thing only in the opposite direction. This allows you to write+-- varying values that read naturally.+-------------------------------------------------------------------------------- -- | Same as '~>' with flipped parameters. (<~) :: Monad m => Var m b c -> Var m a b -> Var m a c (<~) = flip (~>)@@ -210,9 +293,8 @@ -------------------------------------------------------------------------------- -- | The vessel of a varying value. A 'Var' is a structure that contains a value -- that changes over some input. That input could be time (Float, Double, etc)--- or events or a stream of 'Char' - whatever. Similar to the--- 'Control.Monad.State' monad.--- A kind of Mealy machine (an automaton) with effects.+-- or 'Control.Varying.Event.Event's or 'Char' - whatever.+-- It's a kind of Mealy machine (an automaton) with effects. data Var m b c = Var { runVar :: b -> m (c, Var m b c) -- ^ Given an input value, return a computation that
src/Control/Varying/Event.hs view
@@ -1,7 +1,16 @@--- | Module: Control.Varying.Event+-- |+-- Module: Control.Varying.Event -- Copyright: (c) 2015 Schell Scivally -- License: MIT -- Maintainer: Schell Scivally <schell.scivally@synapsegroup.com>+--+-- 'Event' streams describe things that happen at a specific time or place+-- or value in general. For example, you can think of the event stream+-- @Var IO Double (Event ())@ as an occurrence of `()` at a specific time+-- (`Double`).+--+-- You can use 'Event' just like you would 'Maybe'.+-- {-# LANGUAGE Arrows #-} {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE PartialTypeSignatures #-}@@ -10,12 +19,12 @@ -- * Transforming event values. toMaybe, isEvent,- -- * Combining events and values+ -- * Combining event streams and value streams latchWith, orE, tagOn, tagM,- ringM,+ --ringM, -- * Generating events from values use, onTrue,@@ -23,13 +32,13 @@ onUnique, onWhen, toEvent,- -- * Using events+ -- * Using event streams collect, hold, holdWith, startingWith, startWith,- -- * Temporal operations+ -- * Temporal operations (time - related) between, until, after,@@ -73,8 +82,9 @@ -- Combining varying values and events -------------------------------------------------------------------------------- -- | Holds the last value of one event stream while waiting for another event--- stream to produce a value. Once both streams have produced a value combine--- the two using the given combine function.+-- stream to produce a value. Once both streams have produced a value, combine+-- the two using the given combine function and emit an event with the+-- value. latchWith :: Monad m => (b -> c -> d) -> Var m a (Event b) -> Var m a (Event c) -> Var m a (Event d)@@ -87,7 +97,6 @@ , latchWith' (eb'', vb'') vc'' ) - -- | Produces values from the first unless the second produces event -- values and if so, produces the values of those events. orE :: Monad m => Var m a b -> Var m a (Event b) -> Var m a b@@ -111,14 +120,14 @@ -- previous event is used in a clean up function. -- -- This is like `tagM` but performs a cleanup function first.-ringM :: Monad m- => (c -> m ()) -> (b -> m c) -> Var m a (Event b) -> Var m a (Event c)-ringM cln = (go (const $ return ()) .) . tagM- where go f ve = Var $ \a -> do (ec, ve') <- runVar ve a- case ec of- NoEvent -> return (ec, go f ve')- Event c -> do f c- return (ec, go cln ve')+--ringM :: Monad m+-- => (c -> m ()) -> (b -> m c) -> Var m a (Event b) -> Var m a (Event c)+--ringM cln = (go (const $ return ()) .) . tagM+-- where go f ve = Var $ \a -> do (ec, ve') <- runVar ve a+-- case ec of+-- NoEvent -> return (ec, go f ve')+-- Event c -> do f c+-- return (ec, go cln ve') -- | Injects a monadic computation into the events of `vb`, providing a way -- to perform side-effects inside an `Event` inside a `Var`.@@ -401,5 +410,12 @@ fmap f (Event a) = Event $ f a fmap _ NoEvent = NoEvent --- | An Event is just like a Maybe.+-- | For all intents and purposes you can think of an Event as a Maybe.+-- A value of @Event ()@ means that an event has occurred and that the+-- result is a @()@. A value of @NoEvent@ means that an event did not+-- occur.+--+-- Event streams (like @Var m a (Event b)@) describe events that may occur over+-- varying @a@ (also known as the series of @a@). Usually @a@ would be some+-- form of time or some user input type. data Event a = Event a | NoEvent deriving (Eq)
src/Control/Varying/Tween.hs view
@@ -1,17 +1,69 @@--- | Module: Control.Varying.Tween+-- |+-- Module: Control.Varying.Tween -- Copyright: (c) 2015 Schell Scivally -- License: MIT -- Maintainer: Schell Scivally <schell.scivally@synapsegroup.com>+--+-- Tweening is a technique of generating intermediate samples of a type+-- __between__ a start and end value. By sampling a running tween+-- each frame we get a smooth animation of a value over time.+--+-- At first release `varying` is only capable of tweening numerical+-- values of type @(Fractional t, Ord t) => t@ that match the type of+-- time you use. At some point it would be great to be able to tween+-- arbitrary types, and possibly tween one type into another (pipe+-- dreams).++-- {-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE Arrows #-} {-# LANGUAGE Rank2Types #-}-module Control.Varying.Tween where+module Control.Varying.Tween (+ -- * Creating tweens+ -- $creation+ tween,+ -- * Interpolation functions+ -- $lerping+ constant,+ linear,+ easeInCirc,+ easeOutCirc,+ easeInOutCirc,+ easeInExpo,+ easeOutExpo,+ easeInOutExpo,+ easeInSine,+ easeOutSine,+ easeInOutSine,+ easeInPow,+ easeOutPow,+ easeInOutPow,+ easeInCubic,+ easeOutCubic,+ easeInOutCubic,+ easeInQuad,+ easeOutQuad,+ easeInOutQuad,+ -- * Interpolation helpers+ easeInOut,+ -- * Writing your own tweens+ Tween,+ Easing+) where import Control.Varying.Core import Control.Varying.Event hiding (after, before) import Control.Varying.Time import Control.Arrow +--------------------------------------------------------------------------------+-- $lerping+-- These pure functions take a `c` (total change in value, ie end - start),+-- `t` (percent of duration completion) and `b` (start value) and result in+-- and interpolation of a value. To see what these look like please check+-- out http://www.gizma.com/easing/.+--------------------------------------------------------------------------------+ -- | Ease in quadratic. easeInQuad :: Num t => Easing t easeInQuad c t b = c * t*t + b@@ -36,6 +88,10 @@ easeInOutCubic :: (Ord t, Fractional t) => Easing t easeInOutCubic = easeInOut easeInCubic easeOutCubic +-- | Ease in and out by some power.+easeInOutPow :: (Fractional t, Ord t) => Int -> Easing t+easeInOutPow p = easeInOut (easeInPow p) (easeOutPow p)+ -- | Ease in by some power. easeInPow :: Num t => Int -> Easing t easeInPow power c t b = c * (t^power) + b@@ -101,16 +157,27 @@ -- constant value until the duration is up. constant :: (Monad m, Num t, Ord t) => a -> t -> Var m t (Event a) constant value duration = use value $ before duration---- | An easing function.-type Easing t = t -> t -> t -> t---- | A linear interpolation between two values over some duration.-type Tween m t = t -> t -> t -> Var m t (Event t)+--------------------------------------------------------------------------------+-- $creation+--+-- The standard way to start tweening values is to use 'tween' along with+-- an interpolation function such as 'easeInOutExpo'. For example,+-- @tween easeInOutExpo 0 100 10@, this will create an event stream that+-- produces @Event t@s where `t` is tweened from 0 to 100 over 10 seconds.+-- Once the 10 seconds are up, the stream will inhibit (produce `NoEvent`)+-- forever. To create a stream of `t` that is tweened from 0 to 100 and+-- then stays at 100 forever after requires you to use a combinator from the+-- 'Event' module, like so:+--+-- >tween easeInOutExpo 0 100 10 `andThen` 100+--+-- The 'andThen' combinator "disolves" our 'Event's by switching to+-- another stream once the first inhibits.+-------------------------------------------------------------------------------- --- | Produces an event sample interpolated between a start and end value--- using an easing equation ('Easing') over a duration. The resulting 'Var' will--- take a time delta as input. For example:+-- | Creates an event stream that produces an event value interpolated between+-- a start and end value using an easing equation ('Easing') over a duration.+-- The resulting 'Var' will take a time delta as input. For example: -- -- @ -- testWhile_ isEvent v@@ -140,3 +207,18 @@ timeAsPercentageOf t = proc dt -> do t' <- accumulate (+) 0 -< dt returnA -< min 1 (t' / t)++-- | An easing function. The parameters or often named `c`, `t` and `b`,+-- where `c` is the total change in value over the complete duration+-- (endValue - startValue), `t` is the current percentage of the duration+-- that has elapsed and `b` is the start value.+--+-- To make things simple only numerical values can be tweened and the type+-- of time deltas much match the tween's value type. This may change in the+-- future :)+type Easing t = t -> t -> t -> t++-- | A linear interpolation between two values over some duration.+-- A `Tween` takes three values - a start value, an end value and+-- a duration.+type Tween m t = t -> t -> t -> Var m t (Event t)
varying.cabal view
@@ -10,7 +10,7 @@ -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change-version: 0.1.0.2+version: 0.1.0.3 -- A short (one-line) description of the package. synopsis: Automaton based varying values, event streams and tweening.