This post announces the new cursor-brick
library. It is a small library to help you define brick
widgets for cursor
s.
While making picosmos
, nanosmos
, microsmos
, millismos
and incidentally also smos
, I noticed that I was duplicating a lot of functionality to draw cursors to the screen. Now, cursors in the cursor
library are defined independent of the machinery that will manipulate or draw them, but I'm still using them mostly only together with brick
.
Folds and widgets
As of the latest release, all cursors in the cursor
library have fold helper functions. For example, for nonempty list cursors, there is now a foldNonEmptyCursor
helper function:
foldNonEmptyCursor :: ([b] -> a -> [b] -> c) -> NonEmptyCursor a b -> c
NonEmptyCursor {..} =
foldNonEmptyCursor func reverse nonEmptyCursorPrev) nonEmptyCursorCurrent nonEmptyCursorNext func (
This allowed certain convenience functions like the following specialisation to Widget n
from brick
in cursor-brick
.
nonEmptyCursorWidget :: ([b] -> a -> [b] -> Widget n) -> NonEmptyCursor a b -> Widget n
= foldNonEmptyCursor nonEmptyCursorWidget
Easy enough, but cursor-brick
also offers more practical combinators like this:
horizontalNonEmptyCursorWidget :: (b -> Widget n) -- | How to render items before the focus
-> (a -> Widget n) -- | How to render the focus
-> (b -> Widget n) -- | How to render items after the focus
-> NonEmptyCursor a b -> Widget n
There is a similar verticalNonEmptyCursorWidget
and there are also effectful versions of these combinators:
horizontalNonEmptyCursorWidgetM ::
Applicative f
=> (b -> f (Widget n)) -- | How to render items before the focus
-> (a -> f (Widget n)) -- | How to render the focus
-> (b -> f (Widget n)) -- | How to render items after the focus
-> NonEmptyCursor a b
-> f (Widget n)
Text cursor and TextField cursor widgets
Drawing text to a screen is suprisingly complex. Brick takes care of most of the complexity, but there are some pieces that you still have to take care of.
Brick has two (modulo Text -> String
conversion) functions to draw text to the screen: txt
and txtWrap
. One wraps text that goes off-screen and the other one does not. We need to use the appropriate version for our use-case.
The next bit of complexity is that txt
and txtWrap
both require their input to be sanitised. Indeed, the documentation says:
The input string must not contain tab characters.
The cursor-brick
library has some helper functions to take care of this:
-- | Draw an arbitrary Text, it will be sanitised.
textWidget :: Text -> Widget n
-- | Draw an arbitrary Text (with wrapping), it will be sanitised.
textWidgetWrap :: Text -> Widget n
-- | Replace tabs by spaces so that brick doesn't render nonsense.
sanitiseText :: Text -> Text
Next, to draw the blink-y box to the screen that we call a cursor, brick
has a showCursor
function. This function can be used to draw a TextCursor
in such a way that the blink-y box ends up in the right place. The cursor-brick
library also takes care of that:
-- | Make a text cursor widget with a blink-y box.
selectedTextCursorWidget :: n -> TextCursor -> Widget n
-- | Make a text cursor widget without a blink-y box.
textCursorWidget :: TextCursor -> Widget n
Note that text will not be wrapped, because otherwise the blink-y box will not be in the right place on the screen.
There are similar functions for textfield cursors:
-- | Make a textfield cursor widget with a blink-y box.
selectedTextFieldCursorWidget :: n -> TextFieldCursor -> Widget n
-- | Make a textfield cursor widget without a blink-y box.
textFieldCursorWidget :: TextFieldCursor -> Widget n
Tree cursor fold and widgets
The tree cursor folds and widgets have already been alluded to in the previous blogpost about microsmos
. A tree cursor is a strange beast, and conceptualising how to render it took a few iterations. The currently selected node needs to be rendered possibly differently from the other nodes. (Otherwise there would be no difference between rendering a tree and rendering a tree cursor.)
The way this happens is that you supply a function to draw the currently selected tree, and another function that can wrap whatever you made with that. This wrapping function will then add onto what you just made with the surrounding trees.
treeCursorWidget ::
forall a b n.
CTree b] -> b -> [CTree b] -> Widget n -> Widget n)
([-> (a -> CForest b -> Widget n)
-> TreeCursor a b -> Widget n
= foldTreeCursor treeCursorWidget
There is also an effectful version of this combinator.
References
The cursor-brick
library is available on Hackage. Cursors originated in the work on Smos. This post is part of an effort to encourage contributions to Smos. The simplest contribution could be to just try out smos and provide feedback on the experience. Smos is a purely functional semantic forest editor of a subset of YAML that is intended to replace Emacs' Org-mode for Getting Things Done.