brick-2.8: A declarative terminal user interface library
Safe HaskellSafe-Inferred
LanguageHaskell2010

Brick.Animation

Description

This module provides some infrastructure for adding animations to Brick applications. See programs/AnimationDemo.hs for a complete working example of this API.

At a high level, this works as follows:

This module provides a threaded animation manager that manages a set of running animations. The application creates the manager and starts animations, which automatically loop or run once, depending on their configuration. Each animation has some state in the application's state that is automatically managed by the animation manager using a lens-based API. Whenever animations need to be redrawn, the animation manager sends a custom event with a state update to the application, which must be evaluated by the main event loop to update animation states. Each animation is associated with a Clip -- sequence of frames -- which may be static or may be built from the application state at rendering time.

To use this module:

  • Use a custom event type e in your App and give the event type a constructor EventM n s () -> e (where s and n are those in App s e n). This will require the use of customMain and will also require the creation of a BChan for custom events.
  • Add an AnimationManager field to the application state s.
  • Create an AnimationManager at startup with startAnimationManager, providing the custom event constructor and BChan created above. Store the manager in the application state.
  • For each animation you want to run at any given time, add a field to the application state of type Maybe (Animation s n), initialized to Nothing. A value of Nothing indicates that the animation is not running.
  • Ensure that each animation state field in s has a lens, usually by using makeLenses.
  • Start new animations in EventM with startAnimation; stop them with stopAnimation. Supply clips for new animations with newClip, newClip_, and the clip transformation functions.
  • Call renderAnimation in appDraw for each animation in the application state.
  • If needed, stop the animation manager with stopAnimationManager.

See AnimationManager and the docs for the rest of this module for details.

Synopsis

Animation managers

data AnimationManager s e n Source #

A manager for animations. The type variables for this type are the same as those for App.

This asynchronously manages a set of running animations, advancing each one over time. When a running animation's current frame needs to be changed, the manager sends an EventM update for that animation to the application's event loop to perform the update to the animation in the application state. The manager will batch such updates if more than one animation needs to be changed at a time.

The manager has a tick duration in milliseconds which is the resolution at which animations are checked to see if they should be updated. Animations also have their own frame duration in milliseconds. For example, if a manager has a tick duration of 50 milliseconds and is running an animation with a frame duration of 100 milliseconds, then the manager will advance that animation by one frame every two ticks. On the other hand, if a manager has a tick duration of 100 milliseconds and is running an animation with a frame duration of 50 milliseconds, the manager will advance that animation by two frames on each tick.

Animation managers are started with startAnimationManager and stopped with stopAnimationManager.

Animations are started with startAnimation and stopped with stopAnimation. Each animation must be associated with an application state field accessible with a traversal given to startAnimation.

When an animation is started, every time it advances a frame, and when it is ended, the manager communicates these changes to the application by using the custom event constructor provided to startAnimationManager. The manager uses that to schedule a state update which the application is responsible for evaluating. The state updates are built from the traversals provided to startAnimation.

The manager-updated Animation values in the application state are then drawn with renderAnimation.

Animations in Loop mode are run forever until stopped with stopAnimation; animations in Once mode run once and are removed from the application state (set to Nothing) when they finish. All state updates to the application state are performed by the manager's custom event mechanism; the application never needs to directly modify the Animation application state fields except to initialize them to Nothing.

There is nothing here to prevent an application from running multiple managers, each at a different tick rate. That may have performance consequences, though, due to the loss of batch efficiency in state updates, so we recommend using only one manager per application at a sufficiently short tick duration.

startAnimationManager Source #

Arguments

:: MonadIO m 
=> Int

The tick duration for this manager in milliseconds

-> BChan e

The event channel to use to send updates to the application (i.e. the same one given to e.g. customVty)

-> (EventM n s () -> e)

A constructor for building custom events that perform application state updates. The application must evaluate these custom events' EventM actions in order to record animation updates in the application state.

-> m (AnimationManager s e n) 

Start a new animation manager. For full details about how managers work, see AnimationManager.

If the specified tick duration is less than minTickTime, this will call error. This bound is in place to prevent API misuse leading to ticking so fast that the terminal can't keep up with redraws.

stopAnimationManager :: MonadIO m => AnimationManager s e n -> m () Source #

Stop the animation manager, ending all running animations.

minTickTime :: Int Source #

The minimum tick duration in milliseconds allowed by startAnimationManager.

Animations

data Animation s n Source #

The state of a running animation.

Put one of these (wrapped in Maybe) in your application state for each animation that you'd like to run concurrently.

animationFrameIndex :: Animation s n -> Int Source #

The animation's current frame index, provided for convenience. Applications won't need to access this in most situations; use renderAnimation instead.

Starting and stopping animations

data RunMode Source #

The running mode for an animation.

Constructors

Once

Run the animation once and then end

Loop

Run the animation in a loop forever

Instances

Instances details
Show RunMode Source # 
Instance details

Defined in Brick.Animation

Eq RunMode Source # 
Instance details

Defined in Brick.Animation

Methods

(==) :: RunMode -> RunMode -> Bool #

(/=) :: RunMode -> RunMode -> Bool #

Ord RunMode Source # 
Instance details

Defined in Brick.Animation

startAnimation Source #

Arguments

:: MonadIO m 
=> AnimationManager s e n

The manager to run the animation

-> Clip s n

The frames for the animation

-> Integer

The animation's frame duration in milliseconds

-> RunMode

The animation's run mode

-> Traversal' s (Maybe (Animation s n))

Where in the application state to manage this animation's state

-> m () 

Start a new animation at its first frame.

This will result in an application state update to initialize the animation state at the provided traversal's location.

stopAnimation :: MonadIO m => AnimationManager s e n -> Animation s n -> m () Source #

Stop an animation.

This will result in an application state update to remove the animation state.

Rendering animations

renderAnimation Source #

Arguments

:: (s -> Widget n)

The fallback function to use for drawing if the animation is not running

-> s

The state to provide when rendering the animation's current frame

-> Maybe (Animation s n)

The animation state itself

-> Widget n 

Render an animation.

Creating clips

data Clip s n Source #

A sequence of a animation frames.

Instances

Instances details
Semigroup (Clip s n) Source # 
Instance details

Defined in Brick.Animation

Methods

(<>) :: Clip s n -> Clip s n -> Clip s n #

sconcat :: NonEmpty (Clip s n) -> Clip s n #

stimes :: Integral b => b -> Clip s n -> Clip s n #

newClip :: [s -> Widget n] -> Clip s n Source #

Build a clip.

Each frame in a clip is represented by a function from a state to a Widget. This allows applications to determine on a per-frame basis what should be drawn in an animation based on application state, if desired, in the same style as appDraw.

If the provided list is empty, this calls error.

newClip_ :: [Widget n] -> Clip s n Source #

Like newClip but for static frames.

clipLength :: Clip s n -> Int Source #

Get the number of frames in a clip.

Transforming clips

pingPongClip :: Clip s n -> Clip s n Source #

Extend a clip so that when the end of the original clip is reached, it continues in reverse order to create a loop.

For example, if this is given a clip with frames A, B, C, and D, then this returns a clip with frames A, B, C, D, C, and B.

If the given clip contains less than two frames, this is equivalent to id.

reverseClip :: Clip s n -> Clip s n Source #

Reverse a clip.