Safe Haskell | Safe-Inferred |
---|---|
Language | Haskell2010 |
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 yourApp
and give the event type a constructorEventM n s () -> e
(wheres
andn
are those inApp s e n
). This will require the use ofcustomMain
and will also require the creation of aBChan
for custom events. - Add an
AnimationManager
field to the application states
. - Create an
AnimationManager
at startup withstartAnimationManager
, providing the custom event constructor andBChan
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 toNothing
. A value ofNothing
indicates that the animation is not running. - Ensure that each animation state field in
s
has a lens, usually by usingmakeLenses
. - Start new animations in
EventM
withstartAnimation
; stop them withstopAnimation
. Supply clips for new animations withnewClip
,newClip_
, and the clip transformation functions. - Call
renderAnimation
inappDraw
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
- 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. |
-> (EventM n s () -> e) | A constructor for building custom events
that perform application state updates. The
application must evaluate these custom events'
|
-> 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
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
The running mode for an animation.
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
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
A sequence of a animation frames.
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
.
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.