Merge branch 'documentation/brick-1.0-staging'

This commit is contained in:
Jonathan Daugherty 2022-08-08 15:29:00 -07:00
commit 2b467f55e3
5 changed files with 422 additions and 765 deletions

View File

@ -2,6 +2,85 @@
Brick changelog
---------------
1.0
---
Version 1.0 of `brick` comes with some improvements that will require
you to update your programs. This section details the list of API
changes in 1.0 that are likely to introduce breakage and how to deal
with each one. You can also consult the demonstration
programs to see orking examples of the new API. For those
interested in a bit of discussion on the changes, see [this
ticket](https://github.com/jtdaugherty/brick/issues/379).
* The event-handling monad `EventM` was improved and changed in some
substantial ways, all aimed at making `EventM` code cleaner, more
composable, and more amenable to lens updates to the application
state.
* The type has changed from `EventM n a` to `EventM n s a` and is now
an `mtl`-compatible state monad over `s`. Some consequences and
related changes are:
* Event handlers no longer take and return an explicit state value;
an event handler that formerly had the type `handler :: s ->
BrickEvent n e -> EventM n (Next s)` now has type `handler ::
BrickEvent n e -> EventM n s ()`. This also affected all of
Brick's built-in event handler functions for `List`, `Editor`,
etc.
* The `appHandleEvent` and `appStartEvent` fields of `App` changed
types to reflect the new structure of `EventM`. `appStartEvent`
will just be `return ()` rather than `return` for most
applications.
* `EventM` can be used with the `MonadState` API from `mtl` as well
as with the very nice lens combinators in `microlens-mtl`.
* The `Next` type was removed.
* State-specific event handlers like `handleListEvent` and
`handleEditorEvent` are now statically typed to be scoped to
just the states they manage, so `zoom` from `microlens-mtl` must
be used to invoke them. `Brick.Types` re-exports `zoom` for
convenience. `handleEventLensed` was removed from the API in lieu
of the new `zoom` behavior. Code that previously handled events
with `handleEventLensed s someLens someHandler e` is now just
written `zoom someLens $ someHandler e`.
* If an `EventM` block needs to operate on some state `s` that is
not accessible via a lens into the application state, the `EventM`
block can be set up with `Brick.Types.nestEventM`.
* Since `Next` was removed, control flow is now as follows:
* Without any explicit specification, an `EventM` block always
continues execution of the `brick` event loop when it finishes.
`continue` was removed from the API. What was previously `continue
$ s & someLens .~ value` will become `someLens .= value`.
* `halt` is still used to indicate that the event loop should halt
after the calling handler is finished, but `halt` no longer takes
an explicit state value argument.
* `suspendAndResume` is now immediate; previously,
`suspendAndResume` indicated that the specified action should run
once the event handler finished. Now, the event handler is paused
while the specified action is run. This allows `EventM` code to
continue to run after `suspendAndResume` is called and before
control is returned to `brick`.
* Brick now depends on `mtl` rather than `transformers`.
* The `IsString` instance for `AttrName` was removed.
* This change is motivated by the API wart that resulted from the
overloading of both `<>` and string literals (via
`OverloadedStrings`) that resulted in code such as `someAttrName
= "blah" <> "things"`. While that worked to create an `AttrName`
with two segments, it was far too easy to read as two strings
concatenated. The overloading hid what is really going on with the
segments of the attribute name. The way to write the above example
after this change is `someAttrName = attrName "blah" <> attrName
"things"`.
Other changes in this release:
* Brick now provides an optional API for user-defined keybindings
for applications! See the User Guide section "Customizable
Keybindings", the Haddock for `Brick.Keybindings.KeyDispatcher`,
and the new demo program `programs/CustomKeybindingDemo.hs` to get
started.
* `Brick.Widgets.List` got `listSelectedElementL`, a traversal for
accessing the currently selected element of a list. (Thanks Fraser
Tweedale)
0.73
----

View File

@ -119,7 +119,6 @@ Documentation
Documentation for `brick` comes in a variety of forms:
* [The official brick user guide](https://github.com/jtdaugherty/brick/blob/master/docs/guide.rst)
* [Samuel Tay's brick tutorial](https://github.com/jtdaugherty/brick/blob/master/docs/samtay-tutorial.md)
* Haddock (all modules)
* [Demo programs](https://github.com/jtdaugherty/brick/blob/master/programs) ([Screenshots](https://github.com/jtdaugherty/brick/blob/master/docs/programs-screenshots.md))
* [FAQ](https://github.com/jtdaugherty/brick/blob/master/FAQ.md)

View File

@ -42,7 +42,6 @@ tested-with: GHC == 8.2.2, GHC == 8.4.4, GHC == 8.6.5, GHC == 8.8.4, GHC
extra-doc-files: README.md,
docs/guide.rst,
docs/samtay-tutorial.md,
docs/snake-demo.gif,
CHANGELOG.md,
programs/custom_keys.ini,

View File

@ -68,16 +68,17 @@ Conventions
documentation and as you explore the library source and write your own
programs.
- Use of `microlens`_ packages: ``brick`` uses ``microlens`` family of
packages internally and also exposes lenses for many types in the
- Use of `microlens`_ packages: ``brick`` uses the ``microlens`` family
of packages internally and also exposes lenses for many types in the
library. However, if you prefer not to use the lens interface in your
program, all lens interfaces have non-lens equivalents exported by
the same module. In general, the "``L``" suffix on something tells
you it is a lens; the name without the "``L``" suffix is the non-lens
version. You can get by without using ``brick``'s lens interface but
your life will probably be much more pleasant once your application
state becomes sufficiently complex if you use lenses to modify it (see
`appHandleEvent: Handling Events`_).
version. You can get by without using ``brick``'s lens interface
but your life will probably be much more pleasant if you use lenses
to modify your application state once it state becomes sufficiently
complex (see `appHandleEvent: Handling Events`_ and `Event Handlers
for Component State`_).
- Attribute names: some modules export attribute names (see `How
Attributes Work`_) associated with user interface elements. These tend
to end in an "``Attr``" suffix (e.g. ``borderAttr``). In addition,
@ -96,8 +97,8 @@ Compiling Brick Applications
Brick applications must be compiled with the threaded RTS using the GHC
``-threaded`` option.
The App Type
============
The ``App`` Type
================
To use the library we must provide it with a value of type
``Brick.Main.App``. This type is a record type whose fields perform
@ -108,8 +109,8 @@ various functions:
data App s e n =
App { appDraw :: s -> [Widget n]
, appChooseCursor :: s -> [CursorLocation n] -> Maybe (CursorLocation n)
, appHandleEvent :: s -> BrickEvent n e -> EventM n (Next s)
, appStartEvent :: s -> EventM n s
, appHandleEvent :: BrickEvent n e -> EventM n s ()
, appStartEvent :: EventM n s ()
, appAttrMap :: s -> AttrMap
}
@ -150,16 +151,16 @@ To run an ``App``, we pass it to ``Brick.Main.defaultMain`` or
main :: IO ()
main = do
let app = App { ... }
initialState = ...
finalState <- defaultMain app initialState
-- Use finalState and exit
let app = App { ... }
initialState = ...
finalState <- defaultMain app initialState
-- Use finalState and exit
The ``customMain`` function is for more advanced uses; for details see
`Using Your Own Event Type`_.
appDraw: Drawing an Interface
-----------------------------
``appDraw``: Drawing an Interface
---------------------------------
The value of ``appDraw`` is a function that turns the current
application state into a list of *layers* of type ``Widget``, listed
@ -201,115 +202,161 @@ The most important module providing drawing functions is
``Brick.Widgets.Core``. Beyond that, any module in the ``Brick.Widgets``
namespace provides specific kinds of functionality.
appHandleEvent: Handling Events
-------------------------------
``appHandleEvent``: Handling Events
-----------------------------------
The value of ``appHandleEvent`` is a function that decides how to modify
the application state as a result of an event:
.. code:: haskell
appHandleEvent :: s -> BrickEvent n e -> EventM n (Next s)
appHandleEvent :: BrickEvent n e -> EventM n s ()
The first parameter of type ``s`` is your application's state at the
time the event arrives. ``appHandleEvent`` is responsible for deciding
how to change the state based on the event and then return it.
``appHandleEvent`` is responsible for deciding how to change the state
based on the event. The single parameter to the event handler is the
event to be handled. Its type variables ``n`` and ``e`` correspond
to the *resource name type* and *event type* of your application,
respectively, and must match the corresponding types in ``App`` and
``EventM``.
The second parameter of type ``BrickEvent n e`` is the event itself.
The type variables ``n`` and ``e`` correspond to the *resource name
type* and *event type* of your application, respectively, and must match
the corresponding types in ``App`` and ``EventM``.
The ``EventM`` monad is parameterized on the *resource name type*
``n`` and your application's state type ``s``. The ``EventM`` monad
is a state monad over ``s``, so one way to access and modify your
application's state in an event handler is to use the ``MonadState``
type class and associated operations from the ``mtl`` package. The
recommended approach, however, is to use the lens operations from the
``microlens-mtl`` package with lenses to perform concise state updates.
We'll cover this topic in more detail in `Event Handlers for Component
State`_.
The return value type ``Next s`` value describes what should happen
after the event handler is finished. We have four choices:
Once the event handler has performed any relevant state updates, it can
also indicate what should happen once the event handler has finished
executing. By default, after an event handler has completed, Brick will
redraw the screen with the application state (by calling ``appDraw``)
and wait for the next input event. However, there are two other options:
* ``Brick.Main.continue s``: continue executing the event loop with the
specified application state ``s`` as the next value. Commonly this is
where you'd modify the state based on the event and return it.
* ``Brick.Main.continueWithoutRedraw s``: continue executing the event
loop with the specified application state ``s`` as the next value, but
unlike ``continue``, do not redraw the screen using the new state.
This is a faster version of ``continue`` since it doesn't redraw the
screen; it just leaves up the previous screen contents. This function
is only useful when you know that your state change won't cause
anything on the screen to change. When in doubt, use ``continue``.
* ``Brick.Main.halt s``: halt the event loop and return the final
application state value ``s``. This state value is returned to the
caller of ``defaultMain`` or ``customMain`` where it can be used prior
to finally exiting ``main``.
* ``Brick.Main.suspendAndResume act``: suspend the ``brick`` event loop
and execute the specified ``IO`` action ``act``. The action ``act``
must be of type ``IO s``, so when it executes it must return the next
application state. When ``suspendAndResume`` is used, the ``brick``
event loop is shut down and the terminal state is restored to its
state when the ``brick`` event loop began execution. When it finishes
executing, the event loop will be resumed using the returned state
value. This is useful for situations where your program needs to
suspend your interface and execute some other program that needs to
gain control of the terminal (such as an external editor).
* ``Brick.Main.halt``: halt the event loop. The application state as it
exists after the event handler completes is returned to the caller
of ``defaultMain`` or ``customMain``.
* ``Brick.Main.continueWithoutRedraw``: continue executing the event
loop, but do not redraw the screen using the new state before waiting
for another input event. This is faster than the default continue
behavior since it doesn't redraw the screen; it just leaves up the
previous screen contents. This function is only useful when you know
that your event handler's state change(s) won't cause anything on
the screen to change. Use this only when you are certain that no
redraw of the screen is needed *and* when you are trying to address a
performance problem. (See also `The Rendering Cache`_ for details on
how to detail with rendering performance issues.)
The ``EventM`` monad is the event-handling monad. This monad is a
transformer around ``IO`` so you are free to do I/O in this monad by
using ``liftIO``. Beyond I/O, this monad is used to make scrolling
requests to the renderer (see `Viewports`_) and obtain named extents
(see `Extents`_). Keep in mind that time spent blocking in your event
handler is time during which your UI is unresponsive, so consider this
when deciding whether to have background threads do work instead of
inlining the work in the event handler.
The ``EventM`` monad is a transformer around ``IO`` so I/O is possible
in this monad by using ``liftIO``. Keep in mind, however, that event
handlers should execute as quickly as possible to avoid introducing
screen redraw latency. Consider using background threads to work
asynchronously when it would otherwise cause redraw latency.
Widget Event Handlers
*********************
Beyond I/O, ``EventM`` is used to make scrolling requests to the
renderer (see `Viewports`_), obtain named extents (see `Extents`_), and
other duties.
Event handlers are responsible for transforming the application state.
While you can use ordinary methods to do this such as pattern matching
and pure function calls, some widget state types such as the ones
provided by the ``Brick.Widgets.List`` and ``Brick.Widgets.Edit``
modules provide their own widget-specific event-handling functions.
For example, ``Brick.Widgets.Edit`` provides ``handleEditorEvent`` and
``Brick.Widgets.List`` provides ``handleListEvent``.
Event Handlers for Component State
**********************************
Since these event handlers run in ``EventM``, they have access to
rendering viewport states via ``Brick.Main.lookupViewport`` and the
``IO`` monad via ``liftIO``.
The top-level ``appHandleEvent`` handler is responsible for managing
the application state, but it also needs to be able to update the state
associated with UI components such as those that come with Brick.
To use these handlers in your program, invoke them on the relevant piece
of state in your application state. In the following example we use an
``Edit`` state from ``Brick.Widgets.Edit``:
For example, consider an application that uses Brick's built-in text
editor from ``Brick.Widgets.Edit``. The built-in editor is similar to
the main application in that it has three important elements:
* The editor state of type ``Editor t n``: this stores the editor's
contents, cursor position, etc.
* The editor's drawing function, ``renderEditor``: this is responsible
for drawing the editor in the UI.
* The editor's event handler, ``handleEditorEvent``: this is responsible
for updating the editor's contents and cursor position in response to
key events.
To use the built-in editor, the application must:
* Embed an ``Editor t n`` somewhere in the application state ``s``,
* Render the editor's state at the appropriate place in ``appDraw`` with
``renderEditor``, and
* Dispatch events to the editor in the ``appHandleEvent`` with
``handleEditorEvent``.
An example application state using an editor might look like this:
.. code:: haskell
data Name = Edit1
type MyState = Editor String Name
myEvent :: MyState -> BrickEvent n e -> EventM Name (Next MyState)
myEvent s (VtyEvent e) = continue =<< handleEditorEvent e s
This pattern works well enough when your application state has an
event handler as shown in the ``Edit`` example above, but it can
become unpleasant if the value on which you want to invoke a handler
is embedded deeply within your application state. If you have chosen
to generate lenses for your application state fields, you can use the
convenience function ``handleEventLensed`` by specifying your state, a
lens, and the event:
.. code:: haskell
data Name = Edit1
data MyState = MyState { _theEdit :: Editor String Name
}
data MyState = MyState { _editor :: Editor Text n }
makeLenses ''MyState
myEvent :: MyState -> BrickEvent n e -> EventM Name (Next MyState)
myEvent s (VtyEvent e) = continue =<< handleEventLensed s theEdit handleEditorEvent e
This declares the ``MyState`` type with an ``Editor`` contained within
it and uses Template Haskell to generate a lens, ``editor``, to allow us
to easily update the editor state in our event handler.
You might consider that preferable to the desugared version:
To dispatch events to the ``editor`` we'd start by writing the
application event handler:
.. code:: haskell
myEvent :: MyState -> BrickEvent n e -> EventM Name (Next MyState)
myEvent s (VtyEvent e) = do
newVal <- handleEditorEvent e (s^.theEdit)
continue $ s & theEdit .~ newVal
handleEvent :: BrickEvent n e -> EventM n MyState ()
handleEvent e = do
...
But there's a problem: ``handleEditorEvent``'s type indicates that it
can only run over a state of type ``Editor t n``, but our handler runs
on ``MyState``. Specifically, ``handleEditorEvent`` has this type:
.. code:: haskell
handleEditorEvent :: BrickEvent n e -> EventM n (Editor t n) ()
This means that to use ``handleEditorEvent``, it must be composed
into the application's event handler, but since the state types ``s``
and ``Editor t n`` do not match, we need a way to compose these event
handlers. There are two ways to do this:
* Use ``Lens.Micro.Mtl.zoom`` from the ``microlens-mtl`` package
(re-exported by ``Brick.Types`` for convenience). This function is
required when you want to change the state type to a field embedded in
your application state using a lens. For example:
.. code:: haskell
handleEvent :: BrickEvent n e -> EventM n MyState ()
handleEvent e = do
zoom editor $ handleEditorEvent e
* Use ``Brick.Types.nestEventM``: this function lets you provide a state
value and run ``EventM`` using that state. The following
``nestEventM`` example is equivalent to the ``zoom`` example above:
.. code:: haskell
import Lens.Micro (_1)
import Lens.Micro.Mtl (use, (.=))
handleEvent :: BrickEvent n e -> EventM n MyState ()
handleEvent e = do
editorState <- use editor
(newEditorState, ()) <- nestEventM editorState $ do
handleEditorEvent e
editor .= newEditorState
The ``zoom`` function, together with lenses for your application state's
fields, is by far the best way to manage your state in ``EventM``. As
you can see from the examples above, the ``zoom`` approach avoids a lot
of boilerplate. The ``nestEventM`` approach is provided in cases where
the state that you need to mutate is not easily accessed by ``zoom``.
Finally, if you prefer to avoid the use of lenses, you can always use
the ``MonadState`` API to get, put, and modify your state. Keep in
mind that the ``MonadState`` approach will still require the use of
``nestEventM`` when events scoped to widget states such as ``Editor``
need to be handled.
Using Your Own Event Type
*************************
@ -339,8 +386,8 @@ handler:
.. code:: haskell
myEvent :: s -> BrickEvent n CounterEvent -> EventM n (Next s)
myEvent s (AppEvent (Counter i)) = ...
myEvent :: BrickEvent n CounterEvent -> EventM n s ()
myEvent (AppEvent (Counter i)) = ...
The next step is to actually *generate* our custom events and
inject them into the ``brick`` event stream so they make it to the
@ -391,8 +438,8 @@ bound for the event channel. In general, consider the performance of
your event handler when choosing the channel capacity and design event
producers so that they can block if the channel is full.
appStartEvent: Starting up
--------------------------
``appStartEvent``: Starting up
------------------------------
When an application starts, it may be desirable to perform some of
the duties typically only possible when an event has arrived, such as
@ -403,16 +450,17 @@ type provides ``appStartEvent`` function for this purpose:
.. code:: haskell
appStartEvent :: s -> EventM n s
appStartEvent :: EventM n s ()
This function takes the initial application state and returns it in
``EventM``, possibly changing it and possibly making viewport requests.
This function is invoked once and only once, at application startup.
For more details, see `Viewports`_. You will probably just want to use
``return`` as the implementation of this function for most applications.
This function is a handler action to run on the initial application
state. This function is invoked once and only once, at application
startup. This might be a place to make initial viewport scroll requests
or make changes to the Vty environment. You will probably just want
to use ``return ()`` as the implementation of this function for most
applications.
appChooseCursor: Placing the Cursor
-----------------------------------
``appChooseCursor``: Placing the Cursor
---------------------------------------
The rendering process for a ``Widget`` may return information about
where that widget would like to place the cursor. For example, a text
@ -469,9 +517,10 @@ resource name type ``n`` and would be able to pattern-match on
.. code:: haskell
myApp = App { ...
, appChooseCursor = \_ -> showCursorNamed CustomName
}
myApp =
App { ...
, appChooseCursor = \_ -> showCursorNamed CustomName
}
See the next section for more information on using names.
@ -522,9 +571,8 @@ we don't know which of the two uses of ``Viewport1`` will be affected:
.. code:: haskell
do
let vp = viewportScroll Viewport1
vScrollBy vp 1
let vp = viewportScroll Viewport1
vScrollBy vp 1
The solution is to ensure that for a given resource type (in this case
viewport), a unique name is assigned in each use.
@ -537,8 +585,8 @@ viewport), a unique name is assigned in each use.
ui = (viewport Viewport1 Vertical $ str "Foo") <+>
(viewport Viewport2 Vertical $ str "Bar") <+>
appAttrMap: Managing Attributes
-------------------------------
``appAttrMap``: Managing Attributes
-----------------------------------
In ``brick`` we use an *attribute map* to assign attributes to elements
of the interface. Rather than specifying specific attributes when
@ -918,8 +966,8 @@ Attributes`_.
If the theme is further customized at runtime, any changes can be saved
with ``Brick.Themes.saveCustomizations``.
Wide Character Support and the TextWidth class
==============================================
Wide Character Support and the ``TextWidth`` class
==================================================
Brick attempts to support rendering wide characters in all widgets,
and the brick editor supports entering and editing wide characters.
@ -941,26 +989,28 @@ to support wide characters in your application, this will not work:
let width = Data.Text.length t
because if the string contains any wide characters, their widths
will not be counted properly. In order to get this right, use the
``TextWidth`` type class to compute the width:
If the string contains any wide characters, their widths will not be
counted properly. In order to get this right, use the ``TextWidth`` type
class to compute the width:
.. code:: haskell
let width = Brick.Widgets.Core.textWidth t
The ``TextWidth`` type class uses Vty's character width routine
to compute the correct width. If you need to compute
the width of a single character, use ``Graphics.Text.wcwidth``.
The ``TextWidth`` type class uses Vty's character width routine to
compute the width by looking up the string's characdters in a Unicode
width table. If you need to compute the width of a single character, use
``Graphics.Text.wcwidth``.
Extents
=======
When an application needs to know where a particular widget was drawn by
the renderer, the application can request that the renderer record the
*extent* of the widget--its upper-left corner and size--and provide it
in an event handler. In the following example, the application needs to
know where the bordered box containing "Foo" is rendered:
When an application needs to know where a particular widget was drawn
by the renderer, the application can request that the renderer record
the *extent* of the widget--its upper-left corner and size--and provide
access to it in an event handler. Extents are represented using Brick's
``Brick.Types.Extent`` type. In the following example, the application
needs to know where the bordered box containing "Foo" is rendered:
.. code:: haskell
@ -979,15 +1029,14 @@ the renderer using a resource name:
reportExtent FooBox $
border $ str "Foo"
Now, whenever the ``ui`` is rendered, the location and size of the
bordered box containing "Foo" will be recorded. We can then look it up
in event handlers in ``EventM``:
Now, whenever the ``ui`` is rendered, the extent of the bordered box
containing "Foo" will be recorded. We can then look it up in event
handlers in ``EventM``:
.. code:: haskell
do
mExtent <- Brick.Main.lookupExtent FooBox
case mExtent of
mExtent <- Brick.Main.lookupExtent FooBox
case mExtent of
Nothing -> ...
Just (Extent _ upperLeft (width, height)) -> ...
@ -1012,10 +1061,10 @@ to the Vty library handle in ``EventM`` (in e.g. ``appHandleEvent``):
import qualified Graphics.Vty as V
do
vty <- Brick.Main.getVtyHandle
let output = V.outputIface vty
when (V.supportsMode output V.BracketedPaste) $
liftIO $ V.setMode output V.BracketedPaste True
vty <- Brick.Main.getVtyHandle
let output = V.outputIface vty
when (V.supportsMode output V.BracketedPaste) $
liftIO $ V.setMode output V.BracketedPaste True
Once enabled, paste mode will generate Vty ``EvPaste`` events. These
events will give you the entire pasted content as a ``ByteString`` which
@ -1033,10 +1082,10 @@ To enable mouse mode, we need to get access to the Vty library handle in
.. code:: haskell
do
vty <- Brick.Main.getVtyHandle
let output = outputIface vty
when (supportsMode output Mouse) $
liftIO $ setMode output Mouse True
vty <- Brick.Main.getVtyHandle
let output = outputIface vty
when (supportsMode output Mouse) $
liftIO $ setMode output Mouse True
Bear in mind that some terminals do not support mouse interaction, so
use Vty's ``getModeStatus`` to find out whether your terminal will
@ -1057,7 +1106,7 @@ location in the terminal, and any modifier keys pressed.
.. code:: haskell
handleEvent s (VtyEvent (EvMouseDown col row button mods) = ...
handleEvent (VtyEvent (EvMouseDown col row button mods) = ...
Brick Mouse Events
------------------
@ -1067,27 +1116,60 @@ a higher-level mouse event interface that ties into the drawing
language. The disadvantage to the low-level interface described above is
that you still need to determine *what* was clicked, i.e., the part of
the interface that was under the mouse cursor. There are two ways to do
this with ``brick``: with *extent checking* and *click reporting*.
this with ``brick``: with *click reporting* and *extent checking*.
Click reporting
***************
The *click reporting* approach is the most high-level approach offered
by ``brick`` and the one that we recommend you use. In this approach,
we use ``Brick.Widgets.Core.clickable`` when drawing the interface to
request that a given widget generate ``MouseDown`` and ``MouseUp``
events when it is clicked.
.. code:: haskell
data Name = MyButton
ui :: Widget Name
ui = center $
clickable MyButton $
border $
str "Click me"
handleEvent (MouseDown MyButton button modifiers coords) = ...
handleEvent (MouseUp MyButton button coords) = ...
This approach enables event handlers to use pattern matching to check
for mouse clicks on specific regions; this uses `Extent checking`_
under the hood but makes it possible to denote which widgets are
clickable in the interface description. The event's click coordinates
are local to the widget being clicked. In the above example, a click
on the upper-left corner of the border would result in coordinates of
``(0,0)``.
Extent checking
***************
The *extent checking* approach entails requesting extents (see
`Extents`_) for parts of your interface, then checking the Vty mouse
click event's coordinates against one or more extents.
click event's coordinates against one or more extents. This approach
is slightly lower-level than the direct mouse click reporting approach
above but is provided in case you need more control over how mouse
clicks are dealt with.
The most direct way to do this is to check a specific extent:
.. code:: haskell
handleEvent s (VtyEvent (EvMouseDown col row _ _)) = do
mExtent <- lookupExtent SomeExtent
case mExtent of
Nothing -> continue s
Just e -> do
if Brick.Main.clickedExtent (col, row) e
then ...
else ...
handleEvent (VtyEvent (EvMouseDown col row _ _)) = do
mExtent <- lookupExtent SomeExtent
case mExtent of
Nothing -> return ()
Just e -> do
if Brick.Main.clickedExtent (col, row) e
then ...
else ...
This approach works well enough if you know which extent you're
interested in checking, but what if there are many extents and you
@ -1096,10 +1178,10 @@ different layers? The next approach is to find all clicked extents:
.. code:: haskell
handleEvent s (VtyEvent (EvMouseDown col row _ _)) = do
extents <- Brick.Main.findClickedExtents (col, row)
-- Then check to see if a specific extent is in the list, or just
-- take the first one in the list.
handleEvent (VtyEvent (EvMouseDown col row _ _)) = do
extents <- Brick.Main.findClickedExtents (col, row)
-- Then check to see if a specific extent is in the list, or just
-- take the first one in the list.
This approach finds all clicked extents and returns them in a list with
the following properties:
@ -1113,35 +1195,6 @@ the following properties:
As a result, the extents are ordered in a natural way, starting with the
most specific extents and proceeding to the most general.
Click reporting
***************
The *click reporting* approach is the most high-level approach
offered by ``brick``. When rendering the interface we use
``Brick.Widgets.Core.clickable`` to request that a given widget generate
``MouseDown`` and ``MouseUp`` events when it is clicked.
.. code:: haskell
data Name = MyButton
ui :: Widget Name
ui = center $
clickable MyButton $
border $
str "Click me"
handleEvent s (MouseDown MyButton button modifiers coords) = ...
handleEvent s (MouseUp MyButton button coords) = ...
This approach enables event handlers to use pattern matching to check
for mouse clicks on specific regions; this uses extent reporting
under the hood but makes it possible to denote which widgets are
clickable in the interface description. The event's click coordinates
are local to the widget being clicked. In the above example, a click
on the upper-left corner of the border would result in coordinates of
``(0,0)``.
Viewports
=========
@ -1214,14 +1267,14 @@ functions for making scrolling requests:
.. code:: haskell
hScrollPage :: Direction -> EventM n ()
hScrollBy :: Int -> EventM n ()
hScrollToBeginning :: EventM n ()
hScrollToEnd :: EventM n ()
vScrollPage :: Direction -> EventM n ()
vScrollBy :: Int -> EventM n ()
vScrollToBeginning :: EventM n ()
vScrollToEnd :: EventM n ()
hScrollPage :: Direction -> EventM n s ()
hScrollBy :: Int -> EventM n s ()
hScrollToBeginning :: EventM n s ()
hScrollToEnd :: EventM n s ()
vScrollPage :: Direction -> EventM n s ()
vScrollBy :: Int -> EventM n s ()
vScrollToBeginning :: EventM n s ()
vScrollToEnd :: EventM n s ()
In each case the scrolling function scrolls the viewport by the
specified amount in the specified direction; functions prefixed with
@ -1237,11 +1290,10 @@ Using ``viewportScroll`` we can write an event handler that scrolls the
.. code:: haskell
myHandler :: s -> e -> EventM n (Next s)
myHandler s e = do
myHandler :: e -> EventM n s ()
myHandler e = do
let vp = viewportScroll Viewport1
hScrollBy vp 1
continue s
Scrolling Viewports With Visibility Requests
--------------------------------------------
@ -1473,7 +1525,7 @@ control layout, or change attributes:
.. code:: haskell
(str "Name: " <+>) @@=
editTextField name NameField (Just 1)
editTextField name NameField (Just 1)
Now when we invoke ``renderForm`` on a form using the above example,
we'll see a ``"Name:"`` label to the left of the editor field for
@ -1508,14 +1560,14 @@ attribute map are:
Handling Form Events
--------------------
Handling form events is easy: we just call
``Brick.Forms.handleFormEvent`` with the ``BrickEvent`` and the
``Form``. This automatically dispatches input events to the
currently-focused input field, and it also manages focus changes with
``Tab`` and ``Shift-Tab`` keybindings. (For details on all of its
behaviors, see the Haddock documentation for ``handleFormEvent``.) It's
still up to the application to decide when events should go to the form
in the first place.
Handling form events is easy: we just use ``zoom`` to call
``Brick.Forms.handleFormEvent`` with the ``BrickEvent`` and a lens
to access the ``Form`` in the application state. This automatically
dispatches input events to the currently-focused input field, and it
also manages focus changes with ``Tab`` and ``Shift-Tab`` keybindings.
(For details on all of its behaviors, see the Haddock documentation for
``handleFormEvent``.) It's still up to the application to decide when
events should go to the form in the first place.
Since the form field handlers take ``BrickEvent`` values, that means
that custom fields could even handle application-specific events (of the
@ -1561,6 +1613,80 @@ For more details on how to do this, see the Haddock documentation for
the ``FormFieldState`` and ``FormField`` data types along with the
implementations of the built-in form field types.
Customizable Keybindings
========================
Brick applications typically start out by explicitly checking incoming
events for specific keys in ``appHandleEvent``. While this works well
enough, it results in *tight coupling* between the input key events and
the event handlers that get run. As applications evolve, it becomes
important to decouple the input key events and their handlers to allow
the input keys to be customized by the user. That's where Brick's
customizable keybindings API comes in.
The customizable keybindings API provides:
* ``Brick.Keybindings.Parse``: parsing and loading user-provided
keybinding configuration files,
* ``Brick.Keybindings.Pretty``: pretty-printing keybindings and
generating keybinding help text in ``Widget``, plain text, and
Markdown formats so you can provide help to users both within the
program and outside of it,
* ``Brick.Keybindings.KeyEvents``: specifying the application's abstract
key events and their configuration names,
* ``Brick.Keybindings.KeyConfig``: bundling default and customized
keybindings for each abstract event into a structure for use by the
dispatcher, and
* ``Brick.Keybindings.KeyDispatcher``: specifying handlers and
dispatching incoming key events to them.
This section of the User Guide describes the API at a high level,
but Brick also provides a complete working example of the custom
keybinding API in ``programs/CustomKeybindingDemo.hs`` and
provides detailed documentation on how to use the API, including a
step-by-step process for using it, in the module documentation for
``Brick.Keybindings.KeyDispatcher``.
The following table compares Brick application design decisions and
runtime behaviors in a typical application compared to one that uses the
customizable keybindings API:
+---------------------+------------------------+-------------------------+
| **Approach** | **Before runtime** | **At runtime** |
+---------------------+------------------------+-------------------------+
| Typical application | The application author | #. An input event |
| (no custom | decides which keys will| arrives when the user|
| keybindings) | trigger application | presses a key. |
| | behaviors. The event | #. The event handler |
| | handler is written to | pattern-mathces on |
| | pattern-match on | the input event to |
| | specific keys. | check for a match and|
| | | then runs the |
| | | corresponding |
| | | handler. |
+---------------------+------------------------+-------------------------+
| Application with | The application author | #. A Vty input event |
| custom keybindings | specifies the possible | arrives when the user|
| API integrated | *abstract events* that | presses a key. |
| | correspond to the | #. The input event is |
| | application's | provided to |
| | behaviors. The events | ``appHandleEvent``. |
| | are given default | #. ``appHandleEvent`` |
| | keybindings. The | passes the event on |
| | application provides | to a |
| | event handlers for the | ``KeyDispatcher``. |
| | abstract events, not | #. The key dispatcher |
| | specific keys. If | checks to see whether|
| | desired, the | the input key event |
| | application can load | maps to an abstract |
| | user-defined custom | event. |
| | keybindings from an INI| #. If the dispatcher |
| | file at startup to | finds a match, the |
| | override the | corresponding |
| | application's defaults.| abstract event's key |
| | | handler is run. |
+---------------------+------------------------+-------------------------+
Joining Borders
===============
@ -1782,12 +1908,12 @@ use the cache invalidation functions in ``EventM``:
.. code:: haskell
handleEvent s ... = do
-- Invalidate just a single cache entry:
Brick.Main.invalidateCacheEntry ExpensiveThing
handleEvent ... = do
-- Invalidate just a single cache entry:
Brick.Main.invalidateCacheEntry ExpensiveThing
-- Invalidate the entire cache (useful on a resize):
Brick.Main.invalidateCache
-- Invalidate the entire cache (useful on a resize):
Brick.Main.invalidateCache
Implementing Custom Widgets
===========================

View File

@ -1,546 +0,0 @@
# Brick Tutorial by Samuel Tay
This tutorial was written by Samuel Tay, Copyright 2017
(https://github.com/samtay, https://samtay.github.io/). It is provided
as part of the brick distribution with permission.
## Introduction
I'm going to give a short introduction to
[brick](https://hackage.haskell.org/package/brick), a Haskell library
for building terminal user interfaces. So far I've used `brick` to
implement [Conway's Game of Life](https://github.com/samtay/conway) and
a [Tetris clone](https://github.com/samtay/tetris). I'll explain the
basics, walk through an example [snake](https://github.com/samtay/snake)
application, and then explain some more complicated scenarios.
The first thing I'll say is that this package has some of the most
impressive documentation and resources, which makes it easy to figure
out pretty much anything you need to do. I'll try to make this useful,
but I imagine if you're reading this then it is mostly being used as a
reference in addition to the existing resources:
1. [Demo programs](https://github.com/jtdaugherty/brick/tree/master/programs)
(clone down to explore the code and run them locally)
2. [User guide](https://github.com/jtdaugherty/brick/blob/master/docs/guide.rst)
3. [Haddock docs](https://hackage.haskell.org/package/brick)
4. [Google group](https://groups.google.com/forum/#!forum/brick-users)
### The basic idea
`brick` is very declarative. Once your base application logic is in
place, the interface is generally built by two functions: drawing and
handling events. The drawing function
```haskell
appDraw :: s -> [Widget n]
```
takes your app state `s` and produces the visuals `[Widget n]`. The
handler
```haskell
appHandleEvent :: s -> BrickEvent n e -> EventM n (Next s)
```
takes your app state, an event (e.g. user presses the `'m'` key), and
produces the resulting app state. *That's pretty much it.*
## `snake`
We're going to build the [classic
snake](https://en.wikipedia.org/wiki/Snake_(video_game)) game that you
might recall from arcades or the first cell phones. The full source code
is [here](https://github.com/samtay/snake). This is the end product:
![](snake-demo.gif)
### Structure of the app
The library makes it easy to separate the concerns of your application
and the interface; I like to have a module with all of the core business
logic that exports the core state of the app and functions for modifying
it, and then have an interface module that just handles the setup,
drawing, and handling events. So let's just use the `simple` stack
template and add two modules
```
├── LICENSE
├── README.md
├── Setup.hs
├── snake.cabal
├── src
│   ├── Main.hs
│   ├── Snake.hs
│   └── UI.hs
└── stack.yaml
```
and our dependencies to `test.cabal`
```yaml
executable snake
hs-source-dirs: src
main-is: Main.hs
ghc-options: -threaded
exposed-modules: Snake
, UI
default-language: Haskell2010
build-depends: base >= 4.7 && < 5
, brick
, containers
, linear
, microlens
, microlens-th
, random
```
### `Snake`
Since this tutorial is about `brick`, I'll elide most of the
implementation details of the actual game, but here are some of the key
types and scaffolding:
```haskell
{-# LANGUAGE TemplateHaskell, FlexibleContexts #-}
module Snake where
import Control.Applicative ((<|>))
import Control.Monad (guard)
import Data.Maybe (fromMaybe)
import Data.Sequence (Seq, ViewL(..), ViewR(..), (<|))
import qualified Data.Sequence as S
import Lens.Micro.TH (makeLenses)
import Lens.Micro ((&), (.~), (%~), (^.))
import Linear.V2 (V2(..), _x, _y)
import System.Random (Random(..), newStdGen)
-- Types
data Game = Game
{ _snake :: Snake -- ^ snake as a sequence of points in R2
, _dir :: Direction -- ^ direction
, _food :: Coord -- ^ location of the food
, _foods :: Stream Coord -- ^ infinite list of random food locations
, _dead :: Bool -- ^ game over flag
, _paused :: Bool -- ^ paused flag
, _score :: Int -- ^ score
, _frozen :: Bool -- ^ freeze to disallow duplicate turns
} deriving (Show)
type Coord = V2 Int
type Snake = Seq Coord
data Stream a = a :| Stream a
deriving (Show)
data Direction
= North
| South
| East
| West
deriving (Eq, Show)
```
All of this is pretty self-explanatory, with the possible exception
of lenses if you haven't seen them. At first glance they may seem
complicated (and the underlying theory arguably is), but using them as
getters and setters is very straightforward. So, if you are following
along because you are writing a terminal app like this, I'd recommend
using them, but they are not required to use `brick`.
Here are the core functions for playing the game:
```haskell
-- | Step forward in time
step :: Game -> Game
step g = fromMaybe g $ do
guard (not $ g ^. paused || g ^. dead)
let g' = g & frozen .~ False
return . fromMaybe (move g') $ die g' <|> eatFood g'
-- | Possibly die if next head position is disallowed
die :: Game -> Maybe Game
-- | Possibly eat food if next head position is food
eatFood :: Game -> Maybe Game
-- | Move snake along in a marquee fashion
move :: Game -> Game
-- | Turn game direction (only turns orthogonally)
--
-- Implicitly unpauses yet freezes game
turn :: Direction -> Game -> Game
-- | Initialize a paused game with random food location
initGame :: IO Game
```
### `UI`
To start, we need to determine what our `App s e n` type parameters are.
This will completely describe the interface application and be passed
to one of the library's `main` style functions for execution. Note that
`s` is the app state, `e` is an event type, and `n` is a resource name.
The `e` is abstracted so that we can provide custom events. The `n`
is usually a custom sum type called `Name` which allows us to *name*
particular viewports. This is important so that we can keep track of
where the user currently has *focus*, such as typing in one of two
textboxes; however, for this simple snake game we don't need to worry
about that.
In simpler cases, the state `s` can directly coincide with a core
datatype such as our `Snake.Game`. In many cases however, it will be
necessary to wrap the core state within the ui state `s` to keep track
of things that are interface specific (more on this later).
Let's write out our app definition and leave some undefined functions:
```haskell
{-# LANGUAGE OverloadedStrings #-}
module UI where
import Control.Monad (forever, void)
import Control.Monad.IO.Class (liftIO)
import Control.Concurrent (threadDelay, forkIO)
import Data.Maybe (fromMaybe)
import Snake
import Brick
( App(..), AttrMap, BrickEvent(..), EventM, Next, Widget
, customMain, neverShowCursor
, continue, halt
, hLimit, vLimit, vBox, hBox
, padRight, padLeft, padTop, padAll, Padding(..)
, withBorderStyle
, str
, attrMap, withAttr, emptyWidget, AttrName, on, fg
, (<+>)
)
import Brick.BChan (newBChan, writeBChan)
import qualified Brick.Widgets.Border as B
import qualified Brick.Widgets.Border.Style as BS
import qualified Brick.Widgets.Center as C
import qualified Graphics.Vty as V
import Data.Sequence (Seq)
import qualified Data.Sequence as S
import Linear.V2 (V2(..))
import Lens.Micro ((^.))
-- Types
-- | Ticks mark passing of time
--
-- This is our custom event that will be constantly fed into the app.
data Tick = Tick
-- | Named resources
--
-- Not currently used, but will be easier to refactor
-- if we call this "Name" now.
type Name = ()
data Cell = Snake | Food | Empty
-- App definition
app :: App Game Tick Name
app = App { appDraw = drawUI
, appChooseCursor = neverShowCursor
, appHandleEvent = handleEvent
, appStartEvent = return
, appAttrMap = const theMap
}
main :: IO ()
main = undefined
-- Handling events
handleEvent :: Game -> BrickEvent Name Tick -> EventM Name (Next Game)
handleEvent = undefined
-- Drawing
drawUI :: Game -> [Widget Name]
drawUI = undefined
theMap :: AttrMap
theMap = undefined
```
#### Custom Events
So far I've only used `brick` to make games which need to be redrawn
as time passes, with or without user input. This requires using
`Brick.customMain` with that `Tick` event type, and opening a forked
process to `forever` feed that event type into the channel. Since this
is a common scenario, there is a `Brick.BChan` module that makes this
pretty quick:
```haskell
main :: IO ()
main = do
chan <- newBChan 10
forkIO $ forever $ do
writeBChan chan Tick
threadDelay 100000 -- decides how fast your game moves
g <- initGame
let buildVty = V.mkVty V.defaultConfig
initialVty <- buildVty
void $ customMain initialVty buildVty (Just chan) app g
```
We do need to import `Vty.Graphics` since `customMain` allows us
to specify a custom `IO Vty.Graphics.Vty` handle, but we're only
customizing the existence of the event channel `BChan Tick`. The app
is now bootstrapped, and all we need to do is implement `handleEvent`,
`drawUI`, and `theMap` (handles styling).
#### Handling events
Handling events is largely straightforward, and can be very clean when
your underlying application logic is taken care of in a core module. All
we do is essentially map events to the proper state modifiers.
```haskell
handleEvent :: Game -> BrickEvent Name Tick -> EventM Name (Next Game)
handleEvent g (AppEvent Tick) = continue $ step g
handleEvent g (VtyEvent (V.EvKey V.KUp [])) = continue $ turn North g
handleEvent g (VtyEvent (V.EvKey V.KDown [])) = continue $ turn South g
handleEvent g (VtyEvent (V.EvKey V.KRight [])) = continue $ turn East g
handleEvent g (VtyEvent (V.EvKey V.KLeft [])) = continue $ turn West g
handleEvent g (VtyEvent (V.EvKey (V.KChar 'k') [])) = continue $ turn North g
handleEvent g (VtyEvent (V.EvKey (V.KChar 'j') [])) = continue $ turn South g
handleEvent g (VtyEvent (V.EvKey (V.KChar 'l') [])) = continue $ turn East g
handleEvent g (VtyEvent (V.EvKey (V.KChar 'h') [])) = continue $ turn West g
handleEvent g (VtyEvent (V.EvKey (V.KChar 'r') [])) = liftIO (initGame) >>= continue
handleEvent g (VtyEvent (V.EvKey (V.KChar 'q') [])) = halt g
handleEvent g (VtyEvent (V.EvKey V.KEsc [])) = halt g
handleEvent g _ = continue g
```
It's probably obvious, but `continue` will continue execution with
the supplied state value, which is then drawn. We can also `halt` to
stop execution, which will essentially finish the evaluation of our
`customMain` and result in `IO Game`, where the resulting game is the
last value that we supplied to `halt`.
#### Drawing
Drawing is fairly simple as well but can require a good amount of code
to position things how you want them. I like to break up the visual
space into regions with drawing functions for each one.
```haskell
drawUI :: Game -> [Widget Name]
drawUI g =
[ C.center $ padRight (Pad 2) (drawStats g) <+> drawGrid g ]
drawStats :: Game -> Widget Name
drawStats = undefined
drawGrid :: Game -> Widget Name
drawGrid = undefined
```
This will center the overall interface (`C.center`), put the stats and
grid widgets horizontally side by side (`<+>`), and separate them by a
2-character width (`padRight (Pad 2)`).
Let's move forward with the stats column:
```haskell
drawStats :: Game -> Widget Name
drawStats g = hLimit 11
$ vBox [ drawScore (g ^. score)
, padTop (Pad 2) $ drawGameOver (g ^. dead)
]
drawScore :: Int -> Widget Name
drawScore n = withBorderStyle BS.unicodeBold
$ B.borderWithLabel (str "Score")
$ C.hCenter
$ padAll 1
$ str $ show n
drawGameOver :: Bool -> Widget Name
drawGameOver dead =
if dead
then withAttr gameOverAttr $ C.hCenter $ str "GAME OVER"
else emptyWidget
gameOverAttr :: AttrName
gameOverAttr = "gameOver"
```
I'm throwing in that `hLimit 11` to prevent the widget greediness caused
by the outer `C.center`. I'm also using `vBox` to show some other
options of aligning widgets; `vBox` and `hBox` align a list of widgets
vertically and horizontally, respectfully. They can be thought of as
folds over the binary `<=>` and `<+>` operations.
The score is straightforward, but it is the first border in
this tutorial. Borders are well documented in the [border
demo](https://github.com/jtdaugherty/brick/blob/master/programs/BorderDemo.hs)
and the Haddocks for that matter.
We also only show the "game over" widget if the game is actually over.
In that case, we are rendering the string widget with the `gameOverAttr`
attribute name. Attribute names are basically type safe *names* that
we can assign to widgets to apply predetermined styles, similar to
assigning a class name to a div in HTML and defining the CSS styles for
that class elsewhere.
Attribute names implement `IsString`, so they are easy to construct with
the `OverloadedStrings` pragma.
Now for the main event:
```haskell
drawGrid :: Game -> Widget Name
drawGrid g = withBorderStyle BS.unicodeBold
$ B.borderWithLabel (str "Snake")
$ vBox rows
where
rows = [hBox $ cellsInRow r | r <- [height-1,height-2..0]]
cellsInRow y = [drawCoord (V2 x y) | x <- [0..width-1]]
drawCoord = drawCell . cellAt
cellAt c
| c `elem` g ^. snake = Snake
| c == g ^. food = Food
| otherwise = Empty
drawCell :: Cell -> Widget Name
drawCell Snake = withAttr snakeAttr cw
drawCell Food = withAttr foodAttr cw
drawCell Empty = withAttr emptyAttr cw
cw :: Widget Name
cw = str " "
snakeAttr, foodAttr, emptyAttr :: AttrName
snakeAttr = "snakeAttr"
foodAttr = "foodAttr"
emptyAttr = "emptyAttr"
```
There's actually nothing new here! We've already covered all the
`brick` functions necessary to draw the grid. My approach to grids is
to render a square cell widget `cw` with different colors depending
on the cell state. The easiest way to draw a colored square is to
stick two characters side by side. If we assign an attribute with a
matching foreground and background, then it doesn't matter what the two
characters are (provided that they aren't some crazy Unicode characters
that might render to an unexpected size). However, if we want empty
cells to render with the same color as the user's default background
color, then spaces are a good choice.
Finally, we'll define the attribute map:
```haskell
theMap :: AttrMap
theMap = attrMap V.defAttr
[ (snakeAttr, V.blue `on` V.blue)
, (foodAttr, V.red `on` V.red)
, (gameOverAttr, fg V.red `V.withStyle` V.bold)
]
```
Again, styles aren't terribly complicated, but it
will be one area where you might have to look in the
[vty](http://hackage.haskell.org/package/vty) package (specifically
[Graphics.Vty.Attributes](http://hackage.haskell.org/package/vty-5.15.1/docs/Graphics-Vty-Attributes.html)) to find what you need.
Another thing to mention is that the attributes form a hierarchy and
can be combined in a parent-child relationship via `mappend`. I haven't
actually used this feature, but it does sound quite handy. For a more
detailed discussion see the
[Brick.AttrMap](https://hackage.haskell.org/package/brick-0.18/docs/Brick-AttrMap.html) haddocks.
## Variable speed
One difficult problem I encountered was implementing a variable speed in
the GoL. I could have just used the same approach above with the minimum
thread delay (corresponding to the maximum speed) and counted `Tick`
events, only issuing an actual `step` in the game when the modular count
of `Tick`s reached an amount corresponding to the current game speed,
but that's kind of an ugly approach.
Instead, I reached out to the author and he advised me to use a `TVar`
within the app state. I had never used `TVar`, but it's pretty easy!
```haskell
main :: IO ()
main = do
chan <- newBChan 10
tv <- atomically $ newTVar (spToInt initialSpeed)
forkIO $ forever $ do
writeBChan chan Tick
int <- atomically $ readTVar tv
threadDelay int
let buildVty = V.mkVty V.defaultConfig
initialVty <- buildVty
customMain initialVty buildVty (Just chan) app (initialGame tv)
>>= printResult
```
The `tv <- atomically $ newTVar (value :: a)` creates a new mutable
reference to a value of type `a`, i.e. `TVar a`, and returns it in `IO`.
In this case `value` is an `Int` which represents the delay between game
steps. Then in the forked process, we read the delay from the `TVar`
reference and use that to space out the calls to `writeBChan chan Tick`.
I store that same `tv :: TVar Int` in the brick app state, so that the
user can change the speed:
```haskell
handleEvent :: Game -> BrickEvent Name Tick -> EventM Name (Next Game)
handleEvent g (VtyEvent (V.EvKey V.KRight [V.MCtrl])) = handleSpeed g (+)
handleEvent g (VtyEvent (V.EvKey V.KLeft [V.MCtrl])) = handleSpeed g (-)
handleSpeed :: Game -> (Float -> Float -> Float) -> EventM n (Next Game)
handleSpeed g (+/-) = do
let newSp = validS $ (g ^. speed) +/- speedInc
liftIO $ atomically $ writeTVar (g ^. interval) (spToInt newSp)
continue $ g & speed .~ newSp
-- where
-- | Speed increments = 0.01 gives 100 discrete speed settings
speedInc :: Float
speedInc = 0.01
-- | Game state
data Game = Game
{ _board :: Board -- ^ Board state
, _time :: Int -- ^ Time elapsed
, _paused :: Bool -- ^ Playing vs. paused
, _speed :: Float -- ^ Speed in [0..1]
, _interval :: TVar Int -- ^ Interval kept in TVar
, _focus :: F.FocusRing Name -- ^ Keeps track of grid focus
, _selected :: Cell -- ^ Keeps track of cell focus
}
```
## Conclusion
`brick` let's you build TUIs very quickly. I was able to write `snake`
along with this tutorial within a few hours. More complicated interfaces
can be tougher, but if you can successfully separate the interface and
core functionality, you'll have an easier time tacking on the frontend.
Lastly, let me remind you to look in the
[demo programs](https://github.com/jtdaugherty/brick/tree/master/programs)
to figure stuff out, as *many* scenarios are covered throughout them.
## Links
* [brick](https://hackage.haskell.org/package/brick)
* [snake](https://github.com/samtay/snake)
* [tetris](https://github.com/samtay/tetris)
* [conway](https://github.com/samtay/conway)