brick-2.9: A declarative terminal user interface library

Safe Haskell | None
Language | Haskell2010

Brick.Animation

Contents

- Animation managers
- Animations
- Starting and stopping animations
- Rendering animations
- Creating clips
- Transforming clips

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

- data AnimationManager s e n
- startAnimationManager :: MonadIO m => Int -> BChan e -> (EventM n s () -> e) -> m (AnimationManager s e n)
- stopAnimationManager :: MonadIO m => AnimationManager s e n -> m ()
- minTickTime :: Int
- data Animation s n
- animationFrameIndex :: Animation s n -> Int
- data RunMode
  - = Once
  - | Loop
- startAnimation :: MonadIO m => AnimationManager s e n -> Clip s n -> Integer -> RunMode -> Traversal' s (Maybe (Animation s n)) -> m ()
- stopAnimation :: MonadIO m => AnimationManager s e n -> Animation s n -> m ()
- renderAnimation :: (s -> Widget n) -> s -> Maybe (Animation s n) -> Widget n
- data Clip s n
- newClip :: [s -> Widget n] -> Clip s n
- newClip_ :: [Widget n] -> Clip s n
- clipLength :: Clip s n -> Int
- pingPongClip :: Clip s n -> Clip s n
- reverseClip :: Clip s n -> Clip s n

# 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 Methods showsPrec :: Int -> RunMode -> ShowS Source # show :: RunMode -> String Source # showList :: [RunMode] -> ShowS Source #
Eq RunMode Source # |
Instance details Defined in Brick.Animation Methods (==) :: RunMode -> RunMode -> Bool Source # (/=) :: RunMode -> RunMode -> Bool Source #
Ord RunMode Source # |
Instance details Defined in Brick.Animation Methods compare :: RunMode -> RunMode -> Ordering Source # (<) :: RunMode -> RunMode -> Bool Source # (<=) :: RunMode -> RunMode -> Bool Source # (>) :: RunMode -> RunMode -> Bool Source # (>=) :: RunMode -> RunMode -> Bool Source # max :: RunMode -> RunMode -> RunMode Source # min :: RunMode -> RunMode -> RunMode Source #

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 Source # sconcat :: NonEmpty (Clip s n) -> Clip s n Source # stimes :: Integral b => b -> Clip s n -> Clip s n Source #

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.