packages feed

brick 0.11 → 0.12

raw patch · 27 files changed

+1034/−326 lines, 27 filesdep ~microlensdep ~vtynew-component:exe:brick-mouse-demonew-component:exe:brick-progressbar-demoPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: microlens, vty

API changes (from Hackage documentation)

- Brick.Main: [appLiftVtyEvent] :: App s e n -> Event -> e
+ Brick.AttrMap: mapAttrName :: AttrName -> AttrName -> AttrMap -> AttrMap
+ Brick.AttrMap: mapAttrNames :: [(AttrName, AttrName)] -> AttrMap -> AttrMap
+ Brick.Main: clickedExtent :: (Int, Int) -> Extent n -> Bool
+ Brick.Main: findClickedExtents :: (Int, Int) -> EventM n [Extent n]
+ Brick.Main: lookupExtent :: (Eq n) => n -> EventM n (Maybe (Extent n))
+ Brick.Types: AppEvent :: e -> BrickEvent n e
+ Brick.Types: Extent :: n -> Location -> (Int, Int) -> Extent n
+ Brick.Types: MouseDown :: n -> Button -> [Modifier] -> Location -> BrickEvent n e
+ Brick.Types: MouseUp :: n -> (Maybe Button) -> Location -> BrickEvent n e
+ Brick.Types: VtyEvent :: Event -> BrickEvent n e
+ Brick.Types: [extents] :: Result n -> [Extent n]
+ Brick.Types: data BrickEvent n e
+ Brick.Types: data Extent n
+ Brick.Types: extentsL :: forall n_apze. Lens' (Result n_apze) [Extent n_apze]
+ Brick.Widgets.Core: clickable :: n -> Widget n -> Widget n
+ Brick.Widgets.Core: overrideAttr :: AttrName -> AttrName -> Widget n -> Widget n
+ Brick.Widgets.Core: reportExtent :: n -> Widget n -> Widget n
+ Brick.Widgets.Edit: instance (GHC.Show.Show t, GHC.Show.Show n) => GHC.Show.Show (Brick.Widgets.Edit.Editor t n)
+ Brick.Widgets.List: instance (GHC.Show.Show n, GHC.Show.Show e) => GHC.Show.Show (Brick.Widgets.List.List n e)
- Brick.Main: App :: (s -> [Widget n]) -> (s -> [CursorLocation n] -> Maybe (CursorLocation n)) -> (s -> e -> EventM n (Next s)) -> (s -> EventM n s) -> (s -> AttrMap) -> (Event -> e) -> App s e n
+ Brick.Main: App :: (s -> [Widget n]) -> (s -> [CursorLocation n] -> Maybe (CursorLocation n)) -> (s -> BrickEvent n e -> EventM n (Next s)) -> (s -> EventM n s) -> (s -> AttrMap) -> App s e n
- Brick.Main: [appHandleEvent] :: App s e n -> s -> e -> EventM n (Next s)
+ Brick.Main: [appHandleEvent] :: App s e n -> s -> BrickEvent n e -> EventM n (Next s)
- Brick.Main: defaultMain :: (Ord n) => App s Event n -> s -> IO s
+ Brick.Main: defaultMain :: (Ord n) => App s e n -> s -> IO s
- Brick.Main: resizeOrQuit :: s -> Event -> EventM n (Next s)
+ Brick.Main: resizeOrQuit :: s -> BrickEvent n e -> EventM n (Next s)
- Brick.Types: Result :: Image -> [CursorLocation n] -> [VisibilityRequest] -> Result n
+ Brick.Types: Result :: Image -> [CursorLocation n] -> [VisibilityRequest] -> [Extent n] -> Result n
- Brick.Types: cursorLocationL :: forall n_atdr. Lens' (CursorLocation n_atdr) Location
+ Brick.Types: cursorLocationL :: forall n_apAj. Lens' (CursorLocation n_apAj) Location
- Brick.Types: cursorLocationNameL :: forall n_atdr n_attD. Lens (CursorLocation n_atdr) (CursorLocation n_attD) (Maybe n_atdr) (Maybe n_attD)
+ Brick.Types: cursorLocationNameL :: forall n_apAj n_aq43. Lens (CursorLocation n_apAj) (CursorLocation n_aq43) (Maybe n_apAj) (Maybe n_aq43)
- Brick.Types: cursorsL :: forall n_atcs n_atm6. Lens (Result n_atcs) (Result n_atm6) [CursorLocation n_atcs] [CursorLocation n_atm6]
+ Brick.Types: cursorsL :: forall n_apze. Lens' (Result n_apze) [CursorLocation n_apze]
- Brick.Types: handleEventLensed :: a -> Lens' a b -> (Event -> b -> EventM n b) -> Event -> EventM n a
+ Brick.Types: handleEventLensed :: a -> Lens' a b -> (e -> b -> EventM n b) -> e -> EventM n a
- Brick.Types: imageL :: forall n_atcs. Lens' (Result n_atcs) Image
+ Brick.Types: imageL :: forall n_apze. Lens' (Result n_apze) Image
- Brick.Types: visibilityRequestsL :: forall n_atcs. Lens' (Result n_atcs) [VisibilityRequest]
+ Brick.Types: visibilityRequestsL :: forall n_apze. Lens' (Result n_apze) [VisibilityRequest]
- Brick.Widgets.Dialog: dialogButtonsL :: forall a_aYro a_aYrY. Lens (Dialog a_aYro) (Dialog a_aYrY) [(String, a_aYro)] [(String, a_aYrY)]
+ Brick.Widgets.Dialog: dialogButtonsL :: forall a_a11pP a_a11qp. Lens (Dialog a_a11pP) (Dialog a_a11qp) [(String, a_a11pP)] [(String, a_a11qp)]
- Brick.Widgets.Dialog: dialogSelectedIndexL :: forall a_aYro. Lens' (Dialog a_aYro) (Maybe Int)
+ Brick.Widgets.Dialog: dialogSelectedIndexL :: forall a_a11pP. Lens' (Dialog a_a11pP) (Maybe Int)
- Brick.Widgets.Dialog: dialogTitleL :: forall a_aYro. Lens' (Dialog a_aYro) (Maybe String)
+ Brick.Widgets.Dialog: dialogTitleL :: forall a_a11pP. Lens' (Dialog a_a11pP) (Maybe String)
- Brick.Widgets.Dialog: dialogWidthL :: forall a_aYro. Lens' (Dialog a_aYro) Int
+ Brick.Widgets.Dialog: dialogWidthL :: forall a_a11pP. Lens' (Dialog a_a11pP) Int
- Brick.Widgets.Edit: editContentsL :: forall t_a10f9 n_a10fa. Lens' (Editor t_a10f9 n_a10fa) (TextZipper t_a10f9)
+ Brick.Widgets.Edit: editContentsL :: forall t_a139w n_a139x. Lens' (Editor t_a139w n_a139x) (TextZipper t_a139w)
- Brick.Widgets.Edit: editDrawContentsL :: forall t_a10f9 n_a10fa. Lens' (Editor t_a10f9 n_a10fa) ([t_a10f9] -> Widget n_a10fa)
+ Brick.Widgets.Edit: editDrawContentsL :: forall t_a139w n_a139x. Lens' (Editor t_a139w n_a139x) ([t_a139w] -> Widget n_a139x)
- Brick.Widgets.List: handleListEvent :: (Show n, Ord n) => Event -> List n e -> EventM n (List n e)
+ Brick.Widgets.List: handleListEvent :: (Ord n) => Event -> List n e -> EventM n (List n e)
- Brick.Widgets.List: listElementsL :: forall n_a17ex e_a17ey e_a17kd. Lens (List n_a17ex e_a17ey) (List n_a17ex e_a17kd) (Vector e_a17ey) (Vector e_a17kd)
+ Brick.Widgets.List: listElementsL :: forall n_a14Uh e_a14Ui e_a154M. Lens (List n_a14Uh e_a14Ui) (List n_a14Uh e_a154M) (Vector e_a14Ui) (Vector e_a154M)
- Brick.Widgets.List: listItemHeightL :: forall n_a17ex e_a17ey. Lens' (List n_a17ex e_a17ey) Int
+ Brick.Widgets.List: listItemHeightL :: forall n_a14Uh e_a14Ui. Lens' (List n_a14Uh e_a14Ui) Int
- Brick.Widgets.List: listNameL :: forall n_a17ex e_a17ey n_a17ke. Lens (List n_a17ex e_a17ey) (List n_a17ke e_a17ey) n_a17ex n_a17ke
+ Brick.Widgets.List: listNameL :: forall n_a14Uh e_a14Ui n_a154N. Lens (List n_a14Uh e_a14Ui) (List n_a154N e_a14Ui) n_a14Uh n_a154N
- Brick.Widgets.List: listSelectedL :: forall n_a17ex e_a17ey. Lens' (List n_a17ex e_a17ey) (Maybe Int)
+ Brick.Widgets.List: listSelectedL :: forall n_a14Uh e_a14Ui. Lens' (List n_a14Uh e_a14Ui) (Maybe Int)

Files

CHANGELOG.md view
@@ -2,6 +2,58 @@ Brick changelog --------------- +0.12+----++This release primarily adds support for mouse interaction. For details,+see the Mouse Support section of the User Guide. This release also+includes breaking API changes for the App type. Here's a migration+guide:++ * Event handlers now take "BrickEvent n e" instead of "e", where "e"+   was the custom event type used before this change. To recover your+   own custom events, pattern-match on "AppEvent"; to recover Vty input+   events, pattern-match on "VtyEvent".+ * appLiftVtyEvent went away and can just be removed from your App+   record constructor.+ * If you aren't using the custom event type or were just using Vty's+   "Event" type as your App's event type, you can set your event type to+   just "e" because you'll now be able to get Vty events regardless of+   whether you use a custom event type.++API changes:+ * Added the Widget combinator "clickable" to indicate that a widget+   should generate mouse click events+ * Added the Extent data type and the "reportExtent" widget combinator+   to report the positions and sizes of widgets+ * Rendering "Result" values now include reported extents and update+   their offsets (adds "extents" field and "extentsL" lens)+ * Added "lookupExtent", "findClickedExtents", and "clickedExtent" in+   EventM to find extents and check them for mouse clicks+ * Removed appLiftVtyEvent. Instead of wrapping Vty's events in your own+   type, you now get a "BrickEvent" that always contains Vty events but+   has the ability to embed *your* custom events. See the User Guide for+   details.+ * Added demo program MouseDemo.hs+ * Added demo program ProgressBarDemo.hs (thanks Kevin Quick)+ * Added mapAttrname, mapAttrNames, and overrideAttr functions (thanks+   Kevin Quick)+ * Make handleEventLensed polymorphic over event type to allow use with+   custom events (thanks Kevin Quick)+ * Added Ord constraint to some library startup functions++Bug fixes:+ * Added Show instance for Editor, List (fixes #63)++Documentation changes:+ * Updated documentation to use new "resource name" terminology to+   reduce confusion and better explain the purpose of names.+ * Updated user guide with sections on mouse support, the rendering+   cache, resource names, paste mode, and extents++Package changes:+ * Depend on Vty 5.11.3 to get mouse mode support+ 0.11 ---- 
README.md view
@@ -34,6 +34,8 @@  * Border-drawing widgets (put borders around or in between things)  * Generic scrollable viewports  * Extensible widget-building API+ * Mouse interaction+ * Rendering cache  * (And many more general-purpose layout control combinators)  In addition, some of `brick`'s more powerful features may not be obvious
brick.cabal view
@@ -1,5 +1,5 @@ name:                brick-version:             0.11+version:             0.12 synopsis:            A declarative terminal user interface library description:   Write terminal applications painlessly with 'brick'! You write an@@ -78,7 +78,7 @@     Brick.Widgets.Internal    build-depends:       base <= 5,-                       vty >= 5.5.0,+                       vty >= 5.11.3,                        transformers,                        data-default,                        containers,@@ -102,7 +102,7 @@   main-is:             CacheDemo.hs   build-depends:       base,                        brick,-                       vty >= 5.5.0,+                       vty >= 5.11.3,                        text,                        microlens >= 0.3.0.0,                        microlens-th@@ -116,7 +116,7 @@   main-is:             VisibilityDemo.hs   build-depends:       base,                        brick,-                       vty >= 5.5.0,+                       vty >= 5.11.3,                        data-default,                        text,                        microlens >= 0.3.0.0,@@ -132,7 +132,7 @@   main-is:             ViewportScrollDemo.hs   build-depends:       base,                        brick,-                       vty >= 5.5.0,+                       vty >= 5.11.3,                        data-default,                        text,                        microlens@@ -146,11 +146,26 @@   main-is:             DialogDemo.hs   build-depends:       base <= 5,                        brick,-                       vty >= 5.5.0,+                       vty >= 5.11.3,                        data-default,                        text,                        microlens +executable brick-mouse-demo+  if !flag(demos)+    Buildable: False+  hs-source-dirs:      programs+  ghc-options:         -threaded -Wall -fno-warn-unused-do-bind -O3+  default-language:    Haskell2010+  main-is:             MouseDemo.hs+  build-depends:       base <= 5,+                       brick,+                       vty >= 5.11.3,+                       data-default,+                       text,+                       microlens >= 0.3.0.0,+                       microlens-th+ executable brick-layer-demo   if !flag(demos)     Buildable: False@@ -160,7 +175,7 @@   main-is:             LayerDemo.hs   build-depends:       base <= 5,                        brick,-                       vty >= 5.5.0,+                       vty >= 5.11.3,                        data-default,                        text,                        microlens >= 0.3.0.0,@@ -175,7 +190,7 @@   main-is:             SuspendAndResumeDemo.hs   build-depends:       base <= 5,                        brick,-                       vty >= 5.5.0,+                       vty >= 5.11.3,                        data-default,                        text,                        microlens >= 0.3.0.0,@@ -190,7 +205,7 @@   main-is:             PaddingDemo.hs   build-depends:       base <= 5,                        brick,-                       vty >= 5.5.0,+                       vty >= 5.11.3,                        data-default,                        text,                        microlens@@ -204,7 +219,7 @@   main-is:             AttrDemo.hs   build-depends:       base <= 5,                        brick,-                       vty >= 5.5.0,+                       vty >= 5.11.3,                        data-default,                        text,                        microlens@@ -218,7 +233,7 @@   main-is:             MarkupDemo.hs   build-depends:       base <= 5,                        brick,-                       vty >= 5.5.0,+                       vty >= 5.11.3,                        data-default,                        text,                        microlens@@ -232,7 +247,7 @@   main-is:             ListDemo.hs   build-depends:       base <= 5,                        brick,-                       vty >= 5.5.0,+                       vty >= 5.11.3,                        data-default,                        text,                        microlens >= 0.3.0.0,@@ -247,7 +262,7 @@   main-is:             CustomEventDemo.hs   build-depends:       base <= 5,                        brick,-                       vty >= 5.5.0,+                       vty >= 5.11.3,                        data-default,                        text,                        microlens >= 0.3.0.0,@@ -262,7 +277,7 @@   main-is:             HelloWorldDemo.hs   build-depends:       base <= 5,                        brick,-                       vty >= 5.5.0,+                       vty >= 5.11.3,                        data-default,                        text,                        microlens@@ -276,7 +291,7 @@   main-is:             EditDemo.hs   build-depends:       base <= 5,                        brick,-                       vty >= 5.5.0,+                       vty >= 5.11.3,                        data-default,                        text,                        vector,@@ -291,6 +306,21 @@   default-extensions:  CPP   default-language:    Haskell2010   main-is:             BorderDemo.hs+  build-depends:       base <= 5,+                       brick,+                       vty >= 5.11.3,+                       data-default,+                       text,+                       microlens++executable brick-progressbar-demo+  if !flag(demos)+    Buildable: False+  hs-source-dirs:      programs+  ghc-options:         -threaded -Wall -fno-warn-unused-do-bind -O3+  default-extensions:  CPP+  default-language:    Haskell2010+  main-is:             ProgressBarDemo.hs   build-depends:       base <= 5,                        brick,                        vty >= 5.5.0,
docs/guide.rst view
@@ -106,7 +106,6 @@            , appHandleEvent  :: s -> e -> EventM n (Next s)            , appStartEvent   :: s -> EventM n s            , appAttrMap      :: s -> AttrMap-           , appLiftVtyEvent :: Event -> e            }  The ``App`` type is parameterized over three types. These type variables@@ -118,28 +117,42 @@   application will provide the library with its starting value and event   handling will transform it as the program executes. When a ``brick``   application exits, the final application state will be returned.-- The **event type** ``e``: the type of events that your event-  handler (``appHandleEvent``) will handle. The underlying ``vty``-  library provides ``Graphics.Vty.Event`` and this forms the basis-  of all events we will handle with ``brick`` applications. The-  ``Brick.Main.defaultMain`` function expects an ``App s Event n``-  since this is a common case. Applications with higher levels of-  sophistication will need to use their own events; in that case Vty's-  ``Event`` will need to be embedded in the custom event type. ``brick``-  does this by calling ``appLiftVtyEvent``. For more details, see `Using-  Your Own Event Type`_.-- The **widget name type** ``n``: during application execution we need a-  way to refer to widgets by name. Whether it's to distinguish two-  cursor position requests, make changes to a scrollable viewport, or-  any other situation where we need a unique handle to a given widget-  state, some data type is needed. ``brick`` applications require you to-  provide that type for ``n``.  For more details, see `Widget Names`_.+- The **event type** ``e``: the type of custom application events+  that your application will need to produce and handle in+  ``appHandleEvent``. All applications will be provided with events from+  the underlying ``vty`` library, such as keyboard events or resize+  events; this type variable indicates the type of *additional* events+  the application will need. For more details, see `Using Your Own Event+  Type`_.+- The **resource name type** ``n``: during application execution we+  sometimes need a way to refer to rendering state, such as the space+  taken up by a given widget, the state for a scrollable viewport, a+  mouse click, or a cursor position. For these situations we need a+  unique handle called a *resource name*. The type ``n`` specifies the+  name type the application will use to identify these bits of state+  produced and managed by the renderer. The resource name type must be+  provided by your application; for more details, see `Resource Names`_.  The various fields of ``App`` will be described in the sections below. +Running an Application+----------------------+ To run an ``App``, we pass it to ``Brick.Main.defaultMain`` or-``Brick.Main.customMain`` along with an initial application state value.+``Brick.Main.customMain`` along with an initial application state value: +.. code:: haskell++   main :: IO ()+   main = do+     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 ----------------------------- @@ -187,26 +200,24 @@ -------------------------------  The value of ``appHandleEvent`` is a function that decides how to modify-the application state as a result of an event. It also decides whether-to continue program execution. The function takes the current-application state and the event and returns the *next application-state*:+the application state as a result of an event:  .. code:: haskell -   appHandleEvent :: s -> e -> EventM n (Next s)+   appHandleEvent :: s -> BrickEvent n e -> EventM n (Next s) -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 just used to make scrolling-requests to the renderer (see `Viewports`_). 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 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. -The ``Next s`` value describes what should happen after the event-handler is finished. We have three choices:+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 return value type ``Next s`` value describes what should happen+after the event handler is finished. We have three choices:+ * ``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.@@ -225,6 +236,15 @@   suspend your interface and execute some other program that needs to   gain control of the terminal (such as an external editor). +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.+ Widget Event Handlers ********************* @@ -249,15 +269,16 @@    data Name = Edit1    type MyState = Editor String Name -   myEvent :: MyState -> e -> EventM Name (Next MyState)-   myEvent s e = continue =<< handleEditorEvent e s+   myEvent :: MyState -> BrickEvent n e -> EventM Name (Next MyState)+   myEvent s (VtyEvent e) = continue =<< handleEditorEvent e s -This pattern works fine 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:+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 @@ -266,53 +287,57 @@                           }    makeLenses ''MyState -   myEvent :: MyState -> e -> EventM Name (Next MyState)-   myEvent s e = continue =<< handleEventLensed s theEdit handleEditorEvent e+   myEvent :: MyState -> BrickEvent n e -> EventM Name (Next MyState)+   myEvent s (VtyEvent e) = continue =<< handleEventLensed s theEdit handleEditorEvent e  You might consider that preferable to the desugared version:  .. code:: haskell -   myEvent :: MyState -> e -> EventM Name (Next MyState)-   myEvent s e = do+   myEvent :: MyState -> BrickEvent n e -> EventM Name (Next MyState)+   myEvent s (VtyEvent e) = do      newVal <- handleEditorEvent e (s^.theEdit)      continue $ s & theEdit .~ newVal  Using Your Own Event Type ************************* -Since we often need to communicate application-specific events-beyond input events to the event handler, the ``App`` type is-polymorphic over the event type ``e`` that we want to handle. If we-use ``Brick.Main.defaultMain`` to run our ``App``, we have to use-``Graphics.Vty.Event`` as our event type since ``defaultMain`` is-provided as a convenience so that no extra event type is needed. But if-our application has other event-handling needs, we need to use our own-event type.+Since we often need to communicate application-specific events beyond+Vty input events to the event handler, brick supports embedding your+application's custom events in the stream of ``BrickEvent``s that+your handler will receive. The type of these events is the type ``e``+mentioned in ``BrickEvent n e`` and ``App s e n``. -To do this, we first define an event type:+Note: ordinarily your application will not have its own custom event+type, so you can leave this type unused (e.g. ``App MyState e MyName``)+or just set it to unit (``App MyState () MyName``). +Here's an example of using a custom event type. Suppose that you'd like+to be able to handle counter events in your event handler. First we+define the counter event type:+ .. code:: haskell -   data CustomEvent =-       VtyEvent Graphics.Vty.Event-       | CustomEvent1-       | CustomEvent2+   data CounterEvent = Counter Int -Our custom event type *must* provide a constructor capable of taking-a ``Graphics.Vty.Event`` value. This allows the ``brick`` event loop-to send us ``vty`` events in the midst of our custom ones. To allow-``brick`` to do this, we provide this constructor as the value of-``appLiftVtyEvent``. This way, ``brick`` can wrap a ``vty`` event using-our custom event type and then pass it to our event handler (which takes-``CustomEvent`` values). In this case we'd set ``appLiftVtyEvent =-VtyEvent``.+With this type declaration we can now use counter events in our app by+using the application type ``App s e CounterEvent``. To handle these+events we'll just need to look for ``AppEvent`` values in the event+handler: -Once we have set ``appLiftVtyEvent`` in this way, we also need to set up-a mechanism for getting our custom events into the ``brick`` event loop-from other threads. To do this we use a ``Control.Concurrent.Chan`` and-call ``Brick.Main.customMain`` instead of ``Brick.Main.defaultMain``:+.. code:: haskell +   myEvent :: s -> BrickEvent n CounterEvent -> EventM n (Next s)+   myEvent s (AppEvent (CounterEvent 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+event handler. To do that we need to create a ``Chan`` for our+custom events, provide that ``Chan`` to ``brick``, and then send+our events over that channel. Once we've created the channel with+``Control.Concurrent.newChan``, we provide it to ``brick`` with+``customMain`` instead of ``defaultMain``:+ .. code:: haskell     main :: IO ()@@ -322,10 +347,19 @@        -- Use finalState and exit  The ``customMain`` function lets us have control over how the ``vty``-library is initialized and how ``brick`` gets custom events to give to+library is initialized *and* how ``brick`` gets custom events to give to our event handler. ``customMain`` is the entry point into ``brick`` when-you need to use your own event type.+you need to use your own event type as shown here. +With all of this in place, sending our custom events to the event+handler is straightforward:++.. code:: haskell++   counterThread :: Chan CounterEvent -> IO ()+   counterThread chan = do+       Control.Concurrent.writeChan chan $ Counter 1+ Starting up: appStartEvent ************************** @@ -370,13 +404,13 @@ is "focused," say) you can decide which of the locations to return or return ``Nothing`` if you do not want to show a cursor. -We decide which location to show by looking at the name value contained-in the ``cursorLocationName`` field. The name value associated-with a cursor location will be the name used to request the cursor-position, which is usually going to be the name you passed to the-widget's constructor. This is why constructors for widgets like-``Brick.Widgets.Edit.editor`` require a name parameter. The name lets us-distinguish between many cursor-placing widgets of the same type.+Many widgets in the rendering process can request cursor placements, but+it is up to our application to determine which one (if any) should be+used. Since we can only show at most a single cursor in the terminal,+we need to decide which location to show. One way is by looking at the+resource name contained in the ``cursorLocationName`` field. The name+value associated with a cursor location will be the name used to request+the cursor position with ``Brick.Widgets.Core.showCursor``.  ``Brick.Main`` provides various convenience functions to make cursor selection easy in common cases:@@ -384,16 +418,12 @@ * ``neverShowCursor``: never show any cursor. * ``showFirstCursor``: always show the first cursor request given; good   for applications with only one cursor-placing widget.-* ``showCursorNamed``: show the cursor with the specified name or show-  no cursor if the name was not associated with any requested cursor-  position.+* ``showCursorNamed``: show the cursor with the specified resource name+  or show no cursor if the name was not associated with any requested+  cursor position. -Widgets request cursor placement by using the-``Brick.Widgets.Core.showCursor`` combinator. For example, this widget-places a cursor on the first "``o``" in "``foo``" assocated with the-cursor name "``myCursor``". The event handler for this application would-use ``MyName`` as its name type ``n`` and would be able to pattern-match-on ``CustomName`` to match cursor requests when this widget is rendered:+For example, this widget requests a cursor placement on the first+"``o``" in "``foo``" assocated with the cursor name "``myCursor``":  .. code:: haskell @@ -402,6 +432,80 @@    let w = showCursor CustomName (Brick.Types.Location (1, 0))              (Brick.Widgets.Core.str "foobar") +The event handler for this application would use ``MyName`` as its+resource name type ``n`` and would be able to pattern-match on+``CustomName`` to match cursor requests when this widget is rendered:++.. code:: haskell++   myApp = App { ...+               , appChooseCursor = showCursorNamed CustomName+               }++See the next section for more information on using names.++Resource Names+--------------++We saw above in `appChooseCursor: Placing the Cursor`_ that resource+names are used to describe cursor locations. Resource names are also+used to name other kinds of resources:++* viewports (see `Viewports`_)+* rendering extents (see `Extents`_)+* mouse events (see `Mouse Support`_)++Assigning names to these resource types allows us to distinguish between+events based on the part of the interface to which an event is related.++Your application must provide some type of name. For simple applications+that don't make use of resource names, you may use ``()``. But if your+application has more than one named resource, you *must* provide a type+capable of assigning a unique name to every resource that needs one.++A Note of Caution+*****************++Resource names can be assigned to any of the resource types mentioned+above, but some resource types--viewports, extents, the render cache,+and cursor locations--form separate resource namespaces. So, for+example, the same name can be assigned to both a viewport and an extent,+since the ``brick`` API provides access to viewports and extents using+separate APIs and data structures. However, if the same name is used for+two resources of the same kind, it is undefined *which* of those you'll+be getting access to when you go to use one of those resources in your+event handler.++For example, if the same name is assigned to two viewports:++.. code:: haskell++   data Name = Viewport1++   ui :: Widget Name+   ui = (viewport Viewport1 Vertical $ str "Foo") <+>+        (viewport Viewport1 Vertical $ str "Bar") <+>++then in ``EventM`` when we attempt to scroll the viewport ``Viewport1``+we don't know which of the two uses of ``Viewport1`` will be affected:++.. code:: haskell++   do+     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.++.. code:: haskell++   data Name = Viewport1 | Viewport2++   ui :: Widget Name+   ui = (viewport Viewport1 Vertical $ str "Foo") <+>+        (viewport Viewport2 Vertical $ str "Bar") <+>+ appAttrMap: Managing Attributes ------------------------------- @@ -454,56 +558,6 @@ the Haddock documentation for the ``Brick.AttrMap`` module. See also `How Attributes Work`_. -Widget Names---------------We saw above in `appChooseCursor: Placing the Cursor`_ that names are-used to describe cursor locations. Names are also used to name viewports-(see `Viewports`_). Assigning names to viewports, cursors, and widgets-allows us to distinguish between events during execution. We need some-way to associate events with the widgets that generated them, and names-give us a mechanism.--You might be wondering why we don't just use ``String`` as the name type-instead of making the application developer supply a type. In fact,-``brick`` used to use ``String`` but there were several problems with-this approach:--- Since any widget could choose its own name by using any ``String``,-  name clashes could arise if two widgets used the same name. But those-  clashes would not be easy to observe.-- String names are not amenable to safe refactoring since an "invalid"-  name could be used and silently fail to cause the desired behavior at-  runtime.-- String names are not amenable to compile-time checking when being-  matched; a custom type allows the user to do compile-time checking of-  e.g. ``case`` expressions checking names.-- Extension widgets were not forced to be polymorphic in their names,-  which breaks good abstraction boundaries if extension authors elect to-  choose their own widget names.--Although requiring the user to provide a custom name type means that-more work must be done to manage the set of possible names, this is work-that should have been done up front anyway: ``String`` names could be-allocated ad-hoc but never centrally managed, resulting in troublesome-runtime problems.--A Note of Caution-*****************--**NOTE: Unique names for all named widgets are required to ensure-that the renderer correctly tracks widget states during application-execution.** If you assign the same name to, say, two viewports, they-will both use the same viewport scrolling state! So unless you want that-and know what you are doing, use a unique name for every widget that-needs one.--Your application must provide some type of name to be used to name-widgets that need names. For simple applications with only one such-widget, you may use ``()``, but if your application has more than one-named widget, you *must* provide a type capable of assigning a unique-name value to every named widget.- How Widgets and Rendering Work ============================== @@ -670,13 +724,200 @@ * ``Brick.Widgets.Core.updateAttrMap`` * ``Brick.Widgets.Core.forceAttr`` * ``Brick.Widgets.Core.withDefAttr``+* ``Brick.Widgets.Core.overrideAttr`` +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:++.. code:: haskell++   ui = center $ border $ str "Foo"++We don't want to have to care about the particulars of the layout to+find out where the bordered box got placed during rendering. To get this+information we request that the extent of the box be reported to us by+the renderer using a resource name:++.. code:: haskell++   data Name = FooBox++   ui = center $+        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``:++.. code:: haskell++   do+     mExtent <- Brick.Main.lookupExtent FooBox+     case mExtent of+       Nothing -> ...+       Just (Extent _ upperLeft (width, height)) -> ...++Paste Support+=============++Some terminal emulators support "bracketed paste" support. This feature+enables OS-level paste operations to send the pasted content as a+single chunk of data and bypass the usual input processing that the+application does. This enales more secure handling of pasted data since+the application can detect that a pasted occurred and avoid processing+the pasted data as ordinary keyboard input. For more information, see+`bracketed paste mode`_.++The Vty library used by brick provides support for bracketed pastes, but+this mode must be enabled. To enable paste mode, we need to get access+to the Vty library handle in ``EventM``:++.. code:: haskell++   do+     vty <- Brick.Main.getVtyHandle+     let output = outputIface vty+     when (supportsMode output BracketedPaste) $+       liftIO $ setMode output BracketedPaste True++Once enabled, paste mode will generate Vty ``EvPaste`` events. These+events will give you the entire pasted content as a ``ByteString`` which+you must decode yourself if, for example, you expect it to contain UTF-8+text data.++Mouse Support+=============++Some terminal emulators support mouse interaction. The Vty library used+by brick provides these low-level events if mouse mode has been enabled.+To enable mouse mode, we need to get access to the Vty library handle in+``EventM``:++.. code:: haskell++   do+     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+provide mouse events.++Also bear in mind that terminal users will usually expect to be able+to interact with your application entirely without a mouse, so if you+do choose to enable mouse interaction, consider using it to improve+existing interactions rather than provide new functionality that cannot+already be managed with a keyboard.++Low-level Mouse Events+----------------------++Once mouse events have been enabled, Vty will generate ``EvMouseDown``+and ``EvMouseUp`` events containing the mouse button clicked, the+location in the terminal, and any modifier keys pressed.++.. code:: haskell++   handleEvent s (VtyEvent (EvMouseDown col row button mods) = ...++Brick Mouse Events+------------------++Although these events may be adequate for your needs, ``brick`` provides+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*.++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.++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 ...++This approach works well enough if you know which extent you're+interested in checking, but what if there are many extents and you+want to know which one was clicked? And what if those extents are in+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.++This approach finds all clicked extents and returns them in a list with+the following properties:++* For extents ``A`` and ``B``, if ``A``'s layer is higher than ``B``'s+  layer, ``A`` comes before ``B`` in the list.+* For extents ``A`` and ``B``, if ``A`` and ``B`` are in the same layer+  and ``A`` is contained within ``B``, ``A`` comes before ``B`` in the+  list.++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 ========= -A *viewport* is a scrollable window onto another widget. Viewports have-a *scrolling direction* of type ``Brick.Types.ViewportType`` which can-be one of:+A *viewport* is a scrollable window onto a widget. Viewports have a+*scrolling direction* of type ``Brick.Types.ViewportType`` which can be+one of:  * ``Horizontal``: the viewport can only scroll horizontally. * ``Vertical``: the viewport can only scroll vertically.@@ -686,7 +927,7 @@ and embeds it in a named viewport. We name the viewport so that we can keep track of its scrolling state in the renderer, and so that you can make scrolling requests. The viewport's name is its handle for these-operations (see `Scrolling Viewports in Event Handlers`_ and `Widget+operations (see `Scrolling Viewports in Event Handlers`_ and `Resource Names`_). **The viewport name must be unique across your application.**  For example, the following puts a string in a horizontally-scrollable@@ -694,7 +935,7 @@  .. code:: haskell -   -- Assuming that App uses 'Name' for its names:+   -- Assuming that App uses 'Name' for its resource names:    data Name = Viewport1    let w = viewport Viewport1 Horizontal $ str "Hello, world!" @@ -734,7 +975,7 @@  .. code:: haskell -   -- Assuming that App uses 'Name' for its names:+   -- Assuming that App uses 'Name' for its resource names:    data Name = Viewport1    let vp = viewportScroll Viewport1 @@ -776,8 +1017,8 @@ Scrolling Viewports With Visibility Requests -------------------------------------------- -When we need to scroll widgets only when a cursor in the viewport leaves-the viewport's bounds, we need to use *visibility requests*. A+When we need to scroll widgets only when a cursor in the viewport+leaves the viewport's bounds, we need to use *visibility requests*. A visibility request is a hint to the renderer that some element of a widget inside a viewport should be made visible, i.e., that the viewport should be scrolled to bring the requested element into view.@@ -787,13 +1028,13 @@  .. code:: haskell -   -- Assuming that App uses 'Name' for its names:+   -- Assuming that App uses 'Name' for its resource names:    data Name = Viewport1    let w = viewport Viewport1 Horizontal $            (visible $ str "Hello," <+> (str " world!") -This example requests that the "``myViewport``" viewport be scrolled so-that "Hello," is visible. We could extend this example with a value+This example requests that the "``myViewport``" viewport be scrolled+so that "Hello," is visible. We could extend this example with a value in the application state indicating which word in our string should be visible and then use that to change which string gets wrapped with ``visible``; this is the basis of cursor-based scrolling.@@ -813,17 +1054,59 @@ Viewport Restrictions --------------------- -Viewports impose one restriction: a viewport that is scrollable in some-direction can only embed a widget that has a ``Fixed`` size in that-direction. This extends to ``Both`` type viewports: they can only embed-widgets that are ``Fixed`` in both directions. This restriction is-because when viewports embed a widget, they relax the rendering area+Viewports impose one restriction: a viewport that is scrollable in+some direction can only embed a widget that has a ``Fixed`` size in+that direction. This extends to ``Both`` type viewports: they can only+embed widgets that are ``Fixed`` in both directions. This restriction+is because when viewports embed a widget, they relax the rendering area constraint in the rendering context, but doing so to a large enough number for ``Greedy`` widgets would result in a widget that is too big and not scrollable in a useful way.  Violating this restriction will result in a runtime exception. +The Rendering Cache+===================++When widgets become expensive to render, ``brick`` provides a *rendering+cache* that automatically caches and re-uses stored Vty images from+previous renderings to avoid expensive renderings. To cache the+rendering of a widget, just wrap it in the ``Brick.Widgets.Core.cached``+function:++.. code:: haskell++   data Name = ExpensiveThing++   ui :: Widget Name+   ui = center $+        cached ExpensiveThing $+        border $+        str "This will be cached"++In the example above, the first time the ``border $ str "This will be+cached"`` widget is rendered, the resulting Vty image will be stored+in the rendering cache under the key ``ExpensiveThing``. On subsequent+renderings the cached Vty image will be used instead of re-rendering the+widget. This example doesn't need caching to improve performance, but+more sophisticated widgets might.++Once ``cached`` has been used to store something in the rendering cache,+periodic cache invalidation may be required. For example, if the cached+widget is built from application state, the cache will need to be+invalidated when the relevant state changes. The cache may also need to+be invalidated when the terminal is resized. To invalidate the cache, we+use the cache invalidation functions in ``EventM``:++.. code:: haskell++   handleEvent s ... = do+     -- Invalidate just a single cache entry:+     Brick.Main.invalidateCacheEntry ExpensiveThing++     -- Invalidate the entire cache (useful on a resize):+     Brick.Main.invalidateCache+ Implementing Your Own Widgets ============================= @@ -938,6 +1221,13 @@ ``vty``'s cropping functions to operate on the ``Result`` image as desired. +Sub-widgets may specify specific attribute name values influencing+that sub-widget.  If the custom widget utilizes its own attribute+names but needs to render the sub-widget, it can use ``overrideAttr``+or ``mapAttrNames`` to convert its custom names to the names that the+sub-widget uses for rendering its output.+ .. _vty: https://github.com/coreyoconnor/vty .. _Hackage: http://hackage.haskell.org/ .. _microlens: http://hackage.haskell.org/package/microlens+.. _bracketed paste mode: https://cirw.in/blog/bracketed-paste
programs/AttrDemo.hs view
@@ -3,7 +3,7 @@  import Data.Monoid import Graphics.Vty-  ( Event, Attr, white, blue, cyan, green, red, yellow+  ( Attr, white, blue, cyan, green, red, yellow   , black   ) @@ -20,7 +20,7 @@ import Brick.Util (on, fg) import Brick.AttrMap (attrMap, AttrMap) -ui :: Widget ()+ui :: Widget n ui =     vBox [ str "This text uses the global default attribute."          , withAttr "foundFull" $@@ -51,14 +51,13 @@     , ("general" <> "specific",   fg cyan)     ] -app :: App () Event ()+app :: App () e () app =     App { appDraw = const [ui]         , appHandleEvent = resizeOrQuit         , appStartEvent = return         , appAttrMap = const theMap         , appChooseCursor = neverShowCursor-        , appLiftVtyEvent = id         }  main :: IO ()
programs/CacheDemo.hs view
@@ -14,6 +14,7 @@ import qualified Brick.Widgets.Center as C import Brick.Types   ( Widget+  , BrickEvent(..)   ) import Brick.Widgets.Core   ( vBox@@ -57,22 +58,21 @@              , str "'Esc' to quit."              ] -appEvent :: Int -> V.Event -> T.EventM Name (T.Next Int)-appEvent i (V.EvKey (V.KChar '+') []) = M.continue $ i + 1-appEvent i (V.EvKey (V.KChar 'i') []) = M.invalidateCacheEntry ExpensiveWidget >> M.continue i-appEvent i (V.EvKey V.KEsc []) = M.halt i+appEvent :: Int -> BrickEvent Name e -> T.EventM Name (T.Next Int)+appEvent i (VtyEvent (V.EvKey (V.KChar '+') [])) = M.continue $ i + 1+appEvent i (VtyEvent (V.EvKey (V.KChar 'i') [])) = M.invalidateCacheEntry ExpensiveWidget >> M.continue i+appEvent i (VtyEvent (V.EvKey V.KEsc [])) = M.halt i appEvent i _ = M.continue i  emphAttr :: AttrName emphAttr = "emphasis" -app :: M.App Int V.Event Name+app :: M.App Int e Name app =     M.App { M.appDraw = drawUi           , M.appStartEvent = return           , M.appHandleEvent = appEvent           , M.appAttrMap = const $ attrMap V.defAttr [(emphAttr, V.white `on` V.blue)]-          , M.appLiftVtyEvent = id           , M.appChooseCursor = M.neverShowCursor           } 
programs/CustomEventDemo.hs view
@@ -21,39 +21,41 @@   ( Widget   , Next   , EventM+  , BrickEvent(..)   ) import Brick.Widgets.Core   ( (<=>)   , str   ) +data CustomEvent = Counter deriving Show+ data St =-    St { _stLastVtyEvent :: Maybe V.Event+    St { _stLastBrickEvent :: Maybe (BrickEvent () CustomEvent)        , _stCounter :: Int        }  makeLenses ''St -data CustomEvent = VtyEvent V.Event-                 | Counter- drawUI :: St -> [Widget ()] drawUI st = [a]     where-        a = (str $ "Last Vty event: " <> (show $ st^.stLastVtyEvent))+        a = (str $ "Last event: " <> (show $ st^.stLastBrickEvent))             <=>             (str $ "Counter value is: " <> (show $ st^.stCounter)) -appEvent :: St -> CustomEvent -> EventM () (Next St)+appEvent :: St -> BrickEvent () CustomEvent -> EventM () (Next St) appEvent st e =     case e of         VtyEvent (V.EvKey V.KEsc []) -> halt st-        VtyEvent ev -> continue $ st & stLastVtyEvent .~ (Just ev)-        Counter -> continue $ st & stCounter %~ (+1)+        VtyEvent _ -> continue $ st & stLastBrickEvent .~ (Just e)+        AppEvent Counter -> continue $ st & stCounter %~ (+1)+                                          & stLastBrickEvent .~ (Just e)+        _ -> continue st  initialState :: St initialState =-    St { _stLastVtyEvent = Nothing+    St { _stLastBrickEvent = Nothing        , _stCounter = 0        } @@ -64,7 +66,6 @@         , appHandleEvent = appEvent         , appStartEvent = return         , appAttrMap = def-        , appLiftVtyEvent = VtyEvent         }  main :: IO ()
programs/DialogDemo.hs view
@@ -7,6 +7,7 @@ import qualified Brick.Main as M import Brick.Types   ( Widget+  , BrickEvent(..)   ) import Brick.Widgets.Core   ( padAll@@ -26,12 +27,13 @@     where         ui = D.renderDialog d $ C.hCenter $ padAll 1 $ str "This is the dialog body." -appEvent :: D.Dialog Choice -> V.Event -> T.EventM () (T.Next (D.Dialog Choice))-appEvent d ev =+appEvent :: D.Dialog Choice -> BrickEvent () e -> T.EventM () (T.Next (D.Dialog Choice))+appEvent d (VtyEvent ev) =     case ev of         V.EvKey V.KEsc [] -> M.halt d         V.EvKey V.KEnter [] -> M.halt d         _ -> M.continue =<< D.handleDialogEvent ev d+appEvent d _ = M.continue d  initialState :: D.Dialog Choice initialState = D.dialog (Just "Title") (Just (0, choices)) 50@@ -48,14 +50,13 @@     , (D.buttonSelectedAttr, bg V.yellow)     ] -theApp :: M.App (D.Dialog Choice) V.Event ()+theApp :: M.App (D.Dialog Choice) e () theApp =     M.App { M.appDraw = drawUI           , M.appChooseCursor = M.showFirstCursor           , M.appHandleEvent = appEvent           , M.appStartEvent = return           , M.appAttrMap = const theMap-          , M.appLiftVtyEvent = id           }  main :: IO ()
programs/EditDemo.hs view
@@ -47,8 +47,8 @@             str " " <=>             str "Press Tab to switch between editors, Esc to quit." -appEvent :: St -> V.Event -> T.EventM Name (T.Next St)-appEvent st ev =+appEvent :: St -> T.BrickEvent Name e -> T.EventM Name (T.Next St)+appEvent st (T.VtyEvent ev) =     case ev of         V.EvKey V.KEsc [] -> M.halt st         V.EvKey (V.KChar '\t') [] -> M.continue $ st & focusRing %~ F.focusNext@@ -58,6 +58,7 @@                Just Edit1 -> T.handleEventLensed st edit1 E.handleEditorEvent ev                Just Edit2 -> T.handleEventLensed st edit2 E.handleEditorEvent ev                Nothing -> return st+appEvent st _ = M.continue st  initialState :: St initialState =@@ -74,14 +75,13 @@ appCursor :: St -> [T.CursorLocation Name] -> Maybe (T.CursorLocation Name) appCursor = F.focusRingCursor (^.focusRing) -theApp :: M.App St V.Event Name+theApp :: M.App St e Name theApp =     M.App { M.appDraw = drawUI           , M.appChooseCursor = appCursor           , M.appHandleEvent = appEvent           , M.appStartEvent = return           , M.appAttrMap = const theMap-          , M.appLiftVtyEvent = id           }  main :: IO ()
programs/LayerDemo.hs view
@@ -43,27 +43,26 @@     translateBy (st^.bottomLayerLocation) $     B.border $ str "Bottom layer\n(Ctrl-arrow keys move)" -appEvent :: St -> V.Event -> T.EventM () (T.Next St)-appEvent st (V.EvKey V.KDown [])  = M.continue $ st & topLayerLocation.rowL %~ (+ 1)-appEvent st (V.EvKey V.KUp [])    = M.continue $ st & topLayerLocation.rowL %~ (subtract 1)-appEvent st (V.EvKey V.KRight []) = M.continue $ st & topLayerLocation.columnL %~ (+ 1)-appEvent st (V.EvKey V.KLeft [])  = M.continue $ st & topLayerLocation.columnL %~ (subtract 1)+appEvent :: St -> T.BrickEvent () e -> T.EventM () (T.Next St)+appEvent st (T.VtyEvent (V.EvKey V.KDown []))  = M.continue $ st & topLayerLocation.rowL %~ (+ 1)+appEvent st (T.VtyEvent (V.EvKey V.KUp []))    = M.continue $ st & topLayerLocation.rowL %~ (subtract 1)+appEvent st (T.VtyEvent (V.EvKey V.KRight [])) = M.continue $ st & topLayerLocation.columnL %~ (+ 1)+appEvent st (T.VtyEvent (V.EvKey V.KLeft []))  = M.continue $ st & topLayerLocation.columnL %~ (subtract 1) -appEvent st (V.EvKey V.KDown  [V.MCtrl]) = M.continue $ st & bottomLayerLocation.rowL %~ (+ 1)-appEvent st (V.EvKey V.KUp    [V.MCtrl]) = M.continue $ st & bottomLayerLocation.rowL %~ (subtract 1)-appEvent st (V.EvKey V.KRight [V.MCtrl]) = M.continue $ st & bottomLayerLocation.columnL %~ (+ 1)-appEvent st (V.EvKey V.KLeft  [V.MCtrl]) = M.continue $ st & bottomLayerLocation.columnL %~ (subtract 1)+appEvent st (T.VtyEvent (V.EvKey V.KDown  [V.MCtrl])) = M.continue $ st & bottomLayerLocation.rowL %~ (+ 1)+appEvent st (T.VtyEvent (V.EvKey V.KUp    [V.MCtrl])) = M.continue $ st & bottomLayerLocation.rowL %~ (subtract 1)+appEvent st (T.VtyEvent (V.EvKey V.KRight [V.MCtrl])) = M.continue $ st & bottomLayerLocation.columnL %~ (+ 1)+appEvent st (T.VtyEvent (V.EvKey V.KLeft  [V.MCtrl])) = M.continue $ st & bottomLayerLocation.columnL %~ (subtract 1) -appEvent st (V.EvKey V.KEsc []) = M.halt st+appEvent st (T.VtyEvent (V.EvKey V.KEsc [])) = M.halt st appEvent st _ = M.continue st -app :: M.App St V.Event ()+app :: M.App St e () app =     M.App { M.appDraw = drawUi           , M.appStartEvent = return           , M.appHandleEvent = appEvent           , M.appAttrMap = const def-          , M.appLiftVtyEvent = id           , M.appChooseCursor = M.neverShowCursor           } 
programs/ListDemo.hs view
@@ -45,8 +45,8 @@                               , C.hCenter $ str "Press Esc to exit."                               ] -appEvent :: L.List () Char -> V.Event -> T.EventM () (T.Next (L.List () Char))-appEvent l e =+appEvent :: L.List () Char -> T.BrickEvent () e -> T.EventM () (T.Next (L.List () Char))+appEvent l (T.VtyEvent e) =     case e of         V.EvKey (V.KChar '+') [] ->             let el = nextElement (L.listElements l)@@ -64,6 +64,7 @@     where       nextElement :: Vec.Vector Char -> Char       nextElement v = fromMaybe '?' $ Vec.find (flip Vec.notElem v) (Vec.fromList ['a' .. 'z'])+appEvent l _ = M.continue l  listDrawElement :: (Show a) => Bool -> a -> Widget () listDrawElement sel a =@@ -85,14 +86,13 @@     , (customAttr,            fg V.cyan)     ] -theApp :: M.App (L.List () Char) V.Event ()+theApp :: M.App (L.List () Char) e () theApp =     M.App { M.appDraw = drawUI           , M.appChooseCursor = M.showFirstCursor           , M.appHandleEvent = appEvent           , M.appStartEvent = return           , M.appAttrMap = const theMap-          , M.appLiftVtyEvent = id           }  main :: IO ()
programs/MarkupDemo.hs view
@@ -32,14 +32,13 @@     , ("keyword2",      V.white `on` V.blue)     ] -app :: App () V.Event ()+app :: App () e () app =     App { appDraw = const [ui]         , appHandleEvent = resizeOrQuit         , appAttrMap = const theMap         , appStartEvent = return         , appChooseCursor = neverShowCursor-        , appLiftVtyEvent = id         }  main :: IO ()
+ programs/MouseDemo.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+module Main where++import Control.Applicative ((<$>))+import Data.Monoid ((<>))+import Lens.Micro ((^.), (&), (.~))+import Lens.Micro.TH (makeLenses)+import Control.Monad (void)+import Data.Monoid ((<>))+import qualified Graphics.Vty as V++import qualified Brick.Types as T+import Brick.AttrMap+import Brick.Util+import Brick.Types (Widget)+import qualified Brick.Main as M+import qualified Brick.Widgets.Center as C+import qualified Brick.Widgets.Border as B+import Brick.Widgets.Core++data Name = Info | Button1 | Button2 | Button3 deriving (Show, Ord, Eq)++data St =+    St { _clicked :: [T.Extent Name]+       , _lastReportedClick :: Maybe (Name, T.Location)+       }++makeLenses ''St++drawUi :: St -> [Widget Name]+drawUi st =+    [ buttonLayer st+    , infoLayer st+    ]++buttonLayer :: St -> Widget Name+buttonLayer st =+    C.centerLayer $ hBox $ padAll 1 <$> buttons+    where+        buttons = mkButton <$> buttonData+        buttonData = [ (Button1, "Button 1", "button1")+                     , (Button2, "Button 2", "button2")+                     , (Button3, "Button 3", "button3")+                     ]+        mkButton (name, label, attr) =+            let wasClicked = (fst <$> st^.lastReportedClick) == Just name+            in clickable name $+               withDefAttr attr $+               B.border $+               padTopBottom 1 $+               padLeftRight (if wasClicked then 2 else 3) $+               str (if wasClicked then "<" <> label <> ">" else label)++infoLayer :: St -> Widget Name+infoLayer st = T.Widget T.Fixed T.Fixed $ do+    c <- T.getContext+    let h = c^.T.availHeightL+        msg = case st^.lastReportedClick of+                Nothing -> "nothing"+                Just (name, T.Location l) -> show name <> " at " <> show l+    T.render $ translateBy (T.Location (0, h-1)) $ clickable Info $+               withDefAttr "info" $+               C.hCenter (str ("Last reported click: " <> msg))++appEvent :: St -> T.BrickEvent Name e -> T.EventM Name (T.Next St)+appEvent st (T.MouseDown n _ _ loc) = M.continue $ st & lastReportedClick .~ Just (n, loc)+appEvent st (T.MouseUp _ _ _) = M.continue $ st & lastReportedClick .~ Nothing+appEvent st (T.VtyEvent (V.EvMouseUp _ _ _)) = M.continue $ st & lastReportedClick .~ Nothing+appEvent st (T.VtyEvent (V.EvKey V.KEsc [])) = M.halt st+appEvent st _ = M.continue st++aMap :: AttrMap+aMap = attrMap V.defAttr+    [ ("info",      V.white `on` V.magenta)+    , ("button1",   V.white `on` V.cyan)+    , ("button2",   V.white `on` V.green)+    , ("button3",   V.white `on` V.blue)+    ]++app :: M.App St e Name+app =+    M.App { M.appDraw = drawUi+          , M.appStartEvent = return+          , M.appHandleEvent = appEvent+          , M.appAttrMap = const aMap+          , M.appChooseCursor = M.neverShowCursor+          }++main :: IO ()+main = void $ M.defaultMain app $ St [] Nothing
programs/PaddingDemo.hs view
@@ -2,7 +2,6 @@ module Main where  import Data.Default-import qualified Graphics.Vty as V  import Brick.Main (App(..), neverShowCursor, resizeOrQuit, defaultMain) import Brick.Types@@ -46,14 +45,13 @@          , padAll 2 $ str "Padded by 2 on all sides"          ] -app :: App () V.Event ()+app :: App () e () app =     App { appDraw = const [ui]         , appHandleEvent = resizeOrQuit         , appStartEvent = return         , appAttrMap = const def         , appChooseCursor = neverShowCursor-        , appLiftVtyEvent = id         }  main :: IO ()
+ programs/ProgressBarDemo.hs view
@@ -0,0 +1,101 @@+{-# LANGUAGE OverloadedStrings #-}+module Main where++import Control.Monad (void)+import Data.Monoid+import qualified Graphics.Vty as V++import qualified Brick.AttrMap as A+import qualified Brick.Main as M+import qualified Brick.Types as T+import qualified Brick.Widgets.ProgressBar as P+import Brick.Types+  ( Widget+  )+import Brick.Widgets.Core+  ( (<+>), (<=>)+  , str+  , updateAttrMap+  , overrideAttr+  )+import Brick.Util (fg, bg, on, clamp)++data MyAppState n = MyAppState { x, y, z :: Float }++drawUI :: MyAppState () -> [Widget ()]+drawUI p = [ui]+    where+      -- use mapAttrNames+      xBar = updateAttrMap+             (A.mapAttrNames [ (xDoneAttr, P.progressCompleteAttr)+                             , (xToDoAttr, P.progressIncompleteAttr)+                             ]+             ) $ bar $ x p+      -- or use individual mapAttrName calls+      yBar = updateAttrMap+             (A.mapAttrName yDoneAttr P.progressCompleteAttr .+              A.mapAttrName yToDoAttr P.progressIncompleteAttr) $+             bar $ y p+      -- or use overrideAttr calls+      zBar = overrideAttr P.progressCompleteAttr zDoneAttr $+             overrideAttr P.progressIncompleteAttr zToDoAttr $+             bar $ z p+      lbl c = Just $ show $ fromEnum $ c * 100+      bar v = P.progressBar (lbl v) v+      ui = (str "X: " <+> xBar) <=>+           (str "Y: " <+> yBar) <=>+           (str "Z: " <+> zBar) <=>+           str "" <=>+           str "Hit 'x', 'y', or 'z' to advance progress, or 'q' to quit"++appEvent :: MyAppState () -> T.BrickEvent () e -> T.EventM () (T.Next (MyAppState ()))+appEvent p (T.VtyEvent e) =+    let valid = clamp (0.0 :: Float) 1.0+    in case e of+         V.EvKey (V.KChar 'x') [] -> M.continue $ p { x = valid $ x p + 0.05 }+         V.EvKey (V.KChar 'y') [] -> M.continue $ p { y = valid $ y p + 0.03 }+         V.EvKey (V.KChar 'z') [] -> M.continue $ p { z = valid $ z p + 0.02 }+         V.EvKey (V.KChar 'q') [] -> M.halt p+         _ -> M.continue p+appEvent p _ = M.continue p++initialState :: MyAppState ()+initialState = MyAppState 0.25 0.18 0.63++theBaseAttr :: A.AttrName+theBaseAttr = A.attrName "theBase"++xDoneAttr, xToDoAttr :: A.AttrName+xDoneAttr = theBaseAttr <> A.attrName "X:done"+xToDoAttr = theBaseAttr <> A.attrName "X:remaining"++yDoneAttr, yToDoAttr :: A.AttrName+yDoneAttr = theBaseAttr <> A.attrName "Y:done"+yToDoAttr = theBaseAttr <> A.attrName "Y:remaining"++zDoneAttr, zToDoAttr :: A.AttrName+zDoneAttr = theBaseAttr <> A.attrName "Z:done"+zToDoAttr = theBaseAttr <> A.attrName "Z:remaining"++theMap :: A.AttrMap+theMap = A.attrMap V.defAttr+         [ (theBaseAttr,               bg V.brightBlack)+         , (xDoneAttr,                 V.black `on` V.white)+         , (xToDoAttr,                 V.white `on` V.black)+         , (yDoneAttr,                 V.magenta `on` V.yellow)+         , (zDoneAttr,                 V.blue `on` V.green)+         , (zToDoAttr,                 V.blue `on` V.red)+         , (P.progressIncompleteAttr,  fg V.yellow)+         ]++theApp :: M.App (MyAppState ()) e ()+theApp =+    M.App { M.appDraw = drawUI+          , M.appChooseCursor = M.showFirstCursor+          , M.appHandleEvent = appEvent+          , M.appStartEvent = return+          , M.appAttrMap = const theMap+          }++main :: IO ()+main = void $ M.defaultMain theApp initialState
programs/SuspendAndResumeDemo.hs view
@@ -17,6 +17,7 @@   ( Widget   , EventM   , Next+  , BrickEvent(..)   ) import Brick.Widgets.Core   ( vBox@@ -36,8 +37,8 @@                   , str "(Press Esc to quit or Space to ask for input)"                   ] -appEvent :: St -> V.Event -> EventM () (Next St)-appEvent st e =+appEvent :: St -> BrickEvent () e -> EventM () (Next St)+appEvent st (VtyEvent e) =     case e of         V.EvKey V.KEsc [] -> halt st         V.EvKey (V.KChar ' ') [] -> suspendAndResume $ do@@ -45,20 +46,20 @@             s <- getLine             return $ st & stExternalInput .~ s         _ -> continue st+appEvent st _ = continue st  initialState :: St initialState =     St { _stExternalInput = ""        } -theApp :: App St V.Event ()+theApp :: App St e () theApp =     App { appDraw = drawUI         , appChooseCursor = neverShowCursor         , appHandleEvent = appEvent         , appStartEvent = return         , appAttrMap = const def-        , appLiftVtyEvent = id         }  main :: IO ()
programs/ViewportScrollDemo.hs view
@@ -58,25 +58,24 @@ vp3Scroll :: M.ViewportScroll Name vp3Scroll = M.viewportScroll VP3 -appEvent :: () -> V.Event -> T.EventM Name (T.Next ())-appEvent _ (V.EvKey V.KDown  [V.MCtrl]) = M.vScrollBy vp3Scroll 1 >> M.continue ()-appEvent _ (V.EvKey V.KUp    [V.MCtrl]) = M.vScrollBy vp3Scroll (-1) >> M.continue ()-appEvent _ (V.EvKey V.KRight [V.MCtrl]) = M.hScrollBy vp3Scroll 1 >> M.continue ()-appEvent _ (V.EvKey V.KLeft  [V.MCtrl]) = M.hScrollBy vp3Scroll (-1) >> M.continue ()-appEvent _ (V.EvKey V.KDown [])  = M.vScrollBy vp1Scroll 1 >> M.continue ()-appEvent _ (V.EvKey V.KUp [])    = M.vScrollBy vp1Scroll (-1) >> M.continue ()-appEvent _ (V.EvKey V.KRight []) = M.hScrollBy vp2Scroll 1 >> M.continue ()-appEvent _ (V.EvKey V.KLeft [])  = M.hScrollBy vp2Scroll (-1) >> M.continue ()-appEvent _ (V.EvKey V.KEsc []) = M.halt ()+appEvent :: () -> T.BrickEvent Name e -> T.EventM Name (T.Next ())+appEvent _ (T.VtyEvent (V.EvKey V.KDown  [V.MCtrl])) = M.vScrollBy vp3Scroll 1 >> M.continue ()+appEvent _ (T.VtyEvent (V.EvKey V.KUp    [V.MCtrl])) = M.vScrollBy vp3Scroll (-1) >> M.continue ()+appEvent _ (T.VtyEvent (V.EvKey V.KRight [V.MCtrl])) = M.hScrollBy vp3Scroll 1 >> M.continue ()+appEvent _ (T.VtyEvent (V.EvKey V.KLeft  [V.MCtrl])) = M.hScrollBy vp3Scroll (-1) >> M.continue ()+appEvent _ (T.VtyEvent (V.EvKey V.KDown []))  = M.vScrollBy vp1Scroll 1 >> M.continue ()+appEvent _ (T.VtyEvent (V.EvKey V.KUp []))    = M.vScrollBy vp1Scroll (-1) >> M.continue ()+appEvent _ (T.VtyEvent (V.EvKey V.KRight [])) = M.hScrollBy vp2Scroll 1 >> M.continue ()+appEvent _ (T.VtyEvent (V.EvKey V.KLeft []))  = M.hScrollBy vp2Scroll (-1) >> M.continue ()+appEvent _ (T.VtyEvent (V.EvKey V.KEsc [])) = M.halt () appEvent _ _ = M.continue () -app :: M.App () V.Event Name+app :: M.App () e Name app =     M.App { M.appDraw = drawUi           , M.appStartEvent = return           , M.appHandleEvent = appEvent           , M.appAttrMap = const def-          , M.appLiftVtyEvent = id           , M.appChooseCursor = M.neverShowCursor           } 
programs/VisibilityDemo.hs view
@@ -99,16 +99,16 @@ vp3Scroll :: M.ViewportScroll Name vp3Scroll = M.viewportScroll VP3 -appEvent :: St -> V.Event -> T.EventM Name (T.Next St)-appEvent st (V.EvKey V.KDown  [V.MCtrl]) = M.continue $ st & vp3Index._1 %~ min (vp3Size^._1) . (+ 1)-appEvent st (V.EvKey V.KUp    [V.MCtrl]) = M.continue $ st & vp3Index._1 %~ max 1 . subtract 1-appEvent st (V.EvKey V.KRight [V.MCtrl]) = M.continue $ st & vp3Index._2 %~ min (vp3Size^._1) . (+ 1)-appEvent st (V.EvKey V.KLeft  [V.MCtrl]) = M.continue $ st & vp3Index._2 %~ max 1 .  subtract 1-appEvent st (V.EvKey V.KDown [])         = M.continue $ st & vp1Index %~ min vp1Size . (+ 1)-appEvent st (V.EvKey V.KUp [])           = M.continue $ st & vp1Index %~ max 1 . subtract 1-appEvent st (V.EvKey V.KRight [])        = M.continue $ st & vp2Index %~ min vp2Size . (+ 1)-appEvent st (V.EvKey V.KLeft [])         = M.continue $ st & vp2Index %~ max 1 . subtract 1-appEvent st (V.EvKey V.KEsc []) = M.halt st+appEvent :: St -> T.BrickEvent Name e -> T.EventM Name (T.Next St)+appEvent st (T.VtyEvent (V.EvKey V.KDown  [V.MCtrl])) = M.continue $ st & vp3Index._1 %~ min (vp3Size^._1) . (+ 1)+appEvent st (T.VtyEvent (V.EvKey V.KUp    [V.MCtrl])) = M.continue $ st & vp3Index._1 %~ max 1 . subtract 1+appEvent st (T.VtyEvent (V.EvKey V.KRight [V.MCtrl])) = M.continue $ st & vp3Index._2 %~ min (vp3Size^._1) . (+ 1)+appEvent st (T.VtyEvent (V.EvKey V.KLeft  [V.MCtrl])) = M.continue $ st & vp3Index._2 %~ max 1 .  subtract 1+appEvent st (T.VtyEvent (V.EvKey V.KDown []))         = M.continue $ st & vp1Index %~ min vp1Size . (+ 1)+appEvent st (T.VtyEvent (V.EvKey V.KUp []))           = M.continue $ st & vp1Index %~ max 1 . subtract 1+appEvent st (T.VtyEvent (V.EvKey V.KRight []))        = M.continue $ st & vp2Index %~ min vp2Size . (+ 1)+appEvent st (T.VtyEvent (V.EvKey V.KLeft []))         = M.continue $ st & vp2Index %~ max 1 . subtract 1+appEvent st (T.VtyEvent (V.EvKey V.KEsc [])) = M.halt st appEvent st _ = M.continue st  theMap :: AttrMap@@ -116,13 +116,12 @@     [ (selectedAttr, V.black `on` V.yellow)     ] -app :: M.App St V.Event Name+app :: M.App St e Name app =     M.App { M.appDraw = drawUi           , M.appStartEvent = return           , M.appHandleEvent = appEvent           , M.appAttrMap = const theMap-          , M.appLiftVtyEvent = id           , M.appChooseCursor = M.neverShowCursor           } 
src/Brick/AttrMap.hs view
@@ -35,6 +35,8 @@   , setDefault   , applyAttrMappings   , mergeWithDefault+  , mapAttrName+  , mapAttrNames   ) where @@ -169,3 +171,18 @@ applyAttrMappings :: [(AttrName, Attr)] -> AttrMap -> AttrMap applyAttrMappings _ (ForceAttr a) = ForceAttr a applyAttrMappings ms (AttrMap d m) = AttrMap d ((M.fromList ms) `M.union` m)++-- | Update an attribute map such that a lookup of 'ontoName' returns+-- the attribute value specified by 'fromName'.  This is useful for+-- composite widgets with specific attribute names mapping those names+-- to the sub-widget's expected name when calling that sub-widget's+-- rendering function.  See the ProgressBarDemo for an example usage,+-- and 'overrideAttr' for an alternate syntax.+mapAttrName :: AttrName -> AttrName -> AttrMap -> AttrMap+mapAttrName fromName ontoName inMap =+    applyAttrMappings [(ontoName, attrMapLookup fromName inMap)] inMap++-- | Map several attributes to return the value associated with an+-- alternate name.  Applies 'mapAttrName' across a list of mappings.+mapAttrNames :: [(AttrName, AttrName)] -> AttrMap -> AttrMap+mapAttrNames names inMap = foldr (uncurry mapAttrName) inMap names
src/Brick/Focus.hs view
@@ -1,6 +1,6 @@ -- | This module provides a type and functions for handling focus rings -- of widgets. Note that this interface is merely provided for managing--- the focus state for a sequence of widget names; it does not do+-- the focus state for a sequence of resource names; it does not do -- anything beyond keep track of that. -- -- This interface is experimental.@@ -21,12 +21,12 @@ import Brick.Types import Brick.Widgets.Core (Named(..)) --- | A focus ring containing a sequence of widget names to focus and a--- currently-focused widget name.+-- | A focus ring containing a sequence of resource names to focus and a+-- currently-focused name. data FocusRing n = FocusRingEmpty                  | FocusRingNonempty ![n] !Int --- | Construct a focus ring from the list of names.+-- | Construct a focus ring from the list of resource names. focusRing :: [n] -> FocusRing n focusRing [] = FocusRingEmpty focusRing names = FocusRingNonempty names 0@@ -48,11 +48,11 @@         i' = (i + (length ns) - 1) `mod` (length ns)  -- | This function is a convenience function to look up a widget state--- value's name in a focus ring and set its focus setting according to--- the focus ring's state. This function determines whether a given--- widget state value is the focus of the ring and passes the resulting--- boolean to a rendering function, along with the state value (a), to--- produce whatever comes next (b).+-- value's resource name in a focus ring and set its focus setting+-- according to the focus ring's state. This function determines whether+-- a given widget state value is the focus of the ring and passes the+-- resulting boolean to a rendering function, along with the state value+-- (a), to produce whatever comes next (b). -- -- Focus-aware widgets have rendering functions that should be -- usable with this combinator; see 'Brick.Widgets.List.List' and@@ -68,8 +68,8 @@               -- ^ The rest of the computation. withFocusRing ring f a = f (focusGetCurrent ring == Just (getName a)) a --- | Get the currently-focused widget name from the ring. If the ring is--- emtpy, return 'Nothing'.+-- | Get the currently-focused resource name from the ring. If the ring+-- is emtpy, return 'Nothing'. focusGetCurrent :: FocusRing n -> Maybe n focusGetCurrent FocusRingEmpty = Nothing focusGetCurrent (FocusRingNonempty ns i) = Just $ ns !! i@@ -85,8 +85,8 @@                 -> [CursorLocation n]                 -- ^ The list of available cursor positions.                 -> Maybe (CursorLocation n)-                -- ^ The cursor position, if any, that matches the name-                -- currently focused by the 'FocusRing'.+                -- ^ The cursor position, if any, that matches the+                -- resource name currently focused by the 'FocusRing'. focusRingCursor getRing st ls =     listToMaybe $ filter isCurrent ls     where
src/Brick/Main.hs view
@@ -10,6 +10,9 @@   , halt   , suspendAndResume   , lookupViewport+  , lookupExtent+  , findClickedExtents+  , clickedExtent   , getVtyHandle    -- ** Viewport scrolling@@ -57,6 +60,8 @@   , Picture(..)   , Cursor(..)   , Event(..)+  , Mode(..)+  , setMode   , update   , outputIface   , displayBounds@@ -65,21 +70,21 @@   , mkVty   ) -import Brick.Types (Viewport, Direction, Widget, rowL, columnL, CursorLocation(..), cursorLocationNameL, EventM(..))-import Brick.Types.Internal (EventRO(..), ScrollRequest(..), RenderState(..), observedNamesL, Next(..), EventState(..), CacheInvalidateRequest(..))-import Brick.Widgets.Internal (renderFinal)+import Brick.Types (Widget, EventM(..))+import Brick.Types.Internal+import Brick.Widgets.Internal import Brick.AttrMap  -- | The library application abstraction. Your application's operations -- are represented here and passed to one of the various main functions -- in this module. An application is in terms of an application state--- type 's', an application event type 'e', and a name type 'n'. In the--- simplest case 'e' is vty's 'Event' type, but you may define your own--- event type, permitted that it has a constructor for wrapping Vty--- events, so that Vty events can be handled by your event loop. The--- state type is the type of application state to be provided by you and--- iteratively modified by event handlers. The name type is the type of--- names you can assign to viewports and widgets.+-- type 's', an application event type 'e', and a resource name type+-- 'n'. In the simplest case 'e' is unused (left polymorphic or set to+-- '()'), but you may define your own event type and use 'customMain'+-- to provide custom events. The state type is the type of application+-- state to be provided by you and iteratively modified by event+-- handlers. The resource name type is the type of names you can assign+-- to rendering resources such as viewports and cursor locations. data App s e n =     App { appDraw :: s -> [Widget n]         -- ^ This function turns your application state into a list of@@ -92,7 +97,7 @@         -- is that many widgets may request a cursor placement but your         -- application state is what you probably want to use to decide         -- which one wins.-        , appHandleEvent :: s -> e -> EventM n (Next s)+        , appHandleEvent :: s -> BrickEvent n e -> EventM n (Next s)         -- ^ This function takes the current application state and an         -- event and returns an action to be taken and a corresponding         -- transformed application state. Possible options are@@ -103,17 +108,13 @@         -- initial scrolling requests, for example.         , appAttrMap :: s -> AttrMap         -- ^ The attribute map that should be used during rendering.-        , appLiftVtyEvent :: Event -> e-        -- ^ The event constructor to use to wrap Vty events in your own-        -- event type. For example, if the application's event type is-        -- 'Event', this is just 'id'.         }  -- | The default main entry point which takes an application and an -- initial state and returns the final state returned by a 'halt' -- operation. defaultMain :: (Ord n)-            => App s Event n+            => App s e n             -- ^ The application.             -> s             -- ^ The initial application state.@@ -134,7 +135,6 @@                   , appHandleEvent = resizeOrQuit                   , appStartEvent = return                   , appAttrMap = def-                  , appLiftVtyEvent = id                   , appChooseCursor = neverShowCursor                   }     in defaultMain app ()@@ -144,8 +144,8 @@ -- a halt. This is a convenience function useful as an 'appHandleEvent' -- value for simple applications using the 'Event' type that do not need -- to get more sophisticated user input.-resizeOrQuit :: s -> Event -> EventM n (Next s)-resizeOrQuit s (EvResize _ _) = continue s+resizeOrQuit :: s -> BrickEvent n e -> EventM n (Next s)+resizeOrQuit s (VtyEvent (EvResize _ _)) = continue s resizeOrQuit s _ = halt s  data InternalNext n a = InternalSuspendAndResume (RenderState n) (IO a)@@ -153,16 +153,18 @@  runWithNewVty :: (Ord n)               => IO Vty-              -> Chan (Either Event e)+              -> Chan (BrickEvent n e)               -> App s e n               -> RenderState n               -> s               -> IO (InternalNext n s) runWithNewVty buildVty chan app initialRS initialSt =     withVty buildVty $ \vty -> do+        setMode (outputIface vty) Mouse True         pid <- forkIO $ supplyVtyEvents vty chan         let runInner rs st = do-              (result, newRS) <- runVty vty chan app st (rs & observedNamesL .~ S.empty)+              (result, newRS) <- runVty vty chan app st (rs & observedNamesL .~ S.empty+                                                            & clickableNamesL .~ mempty)               case result of                   SuspendAndResume act -> do                       killThread pid@@ -199,45 +201,74 @@                     run newRS newAppState chan          emptyES = ES [] []-        eventRO = EventRO M.empty Nothing+        eventRO = EventRO M.empty Nothing mempty     (st, eState) <- runStateT (runReaderT (runEventM (appStartEvent app initialAppState)) eventRO) emptyES-    let initialRS = RS M.empty (esScrollRequests eState) S.empty mempty+    let initialRS = RS M.empty (esScrollRequests eState) S.empty mempty []     chan <- newChan-    forkIO $ forever $ readChan userChan >>= (\userEvent -> writeChan chan (Right userEvent))+    forkIO $ forever $ readChan userChan >>= (\userEvent -> writeChan chan $ AppEvent userEvent)     run initialRS st chan -supplyVtyEvents :: Vty -> Chan (Either Event e) -> IO ()+supplyVtyEvents :: Vty -> Chan (BrickEvent n e) -> IO () supplyVtyEvents vty chan =     forever $ do         e <- nextEvent vty-        writeChan chan $ Left e+        writeChan chan $ VtyEvent e  runVty :: (Ord n)        => Vty-       -> Chan (Either Event e)+       -> Chan (BrickEvent n e)        -> App s e n        -> s        -> RenderState n        -> IO (Next s, RenderState n) runVty vty chan app appState rs = do-    firstRS <- renderApp vty app appState rs+    (firstRS, exts) <- renderApp vty app appState rs     e <- readChan chan -    -- If the event was a resize, redraw the UI to update the viewport-    -- states before we invoke the event handler since we want the event-    -- handler to have access to accurate viewport information.-    nextRS <- case e of-        Left (EvResize _ _) ->-            renderApp vty app appState $ firstRS & observedNamesL .~ S.empty-        _ -> return firstRS+    (e', nextRS, nextExts) <- case e of+        -- If the event was a resize, redraw the UI to update the+        -- viewport states before we invoke the event handler since we+        -- want the event handler to have access to accurate viewport+        -- information.+        VtyEvent (EvResize _ _) -> do+            (rs', exts') <- renderApp vty app appState $ firstRS & observedNamesL .~ S.empty+            return (e, rs', exts')+        VtyEvent (EvMouseDown c r button mods) -> do+            let matching = findClickedExtents_ (c, r) exts+            case matching of+                (Extent n (Location (ec, er)) _:_) ->+                    -- If the clicked extent was registered as+                    -- clickable, send a click event. Otherwise, just+                    -- send the raw mouse event+                    case n `elem` firstRS^.clickableNamesL of+                        True -> do+                            let localCoords = Location (lc, lr)+                                lc = c - ec+                                lr = r - er+                            return (MouseDown n button mods localCoords, firstRS, exts)+                        False -> return (e, firstRS, exts)+                _ -> return (e, firstRS, exts)+        VtyEvent (EvMouseUp c r button) -> do+            let matching = findClickedExtents_ (c, r) exts+            case matching of+                (Extent n (Location (ec, er)) _:_) ->+                    -- If the clicked extent was registered as+                    -- clickable, send a click event. Otherwise, just+                    -- send the raw mouse event+                    case n `elem` firstRS^.clickableNamesL of+                        True -> do+                            let localCoords = Location (lc, lr)+                                lc = c - ec+                                lr = r - er+                            return (MouseUp n button localCoords, firstRS, exts)+                        False -> return (e, firstRS, exts)+                _ -> return (e, firstRS, exts)+        _ -> return (e, firstRS, exts)      let emptyES = ES [] []-        userEvent = case e of-            Left e' -> appLiftVtyEvent app e'-            Right e' -> e'+        eventRO = EventRO (viewportMap nextRS) (Just vty) nextExts -    let eventRO = EventRO (viewportMap nextRS) (Just vty)-    (next, eState) <- runStateT (runReaderT (runEventM (appHandleEvent app appState userEvent))+    (next, eState) <- runStateT (runReaderT (runEventM (appHandleEvent app appState e'))                                 eventRO) emptyES     return (next, nextRS { rsScrollRequests = esScrollRequests eState                          , renderCache = applyInvalidations (cacheInvalidateRequests eState) $@@ -258,11 +289,37 @@ lookupViewport :: (Ord n) => n -> EventM n (Maybe Viewport) lookupViewport n = EventM $ asks (M.lookup n . eventViewportMap) +-- | Did the specified mouse coordinates (column, row) intersect the+-- specified extent?+clickedExtent :: (Int, Int) -> Extent n -> Bool+clickedExtent (c, r) (Extent _ (Location (lc, lr)) (w, h)) =+   c >= lc && c < (lc + w) &&+   r >= lr && r < (lr + h)++-- | Given a resource name, get the most recent rendering extent for the+-- name (if any).+lookupExtent :: (Eq n) => n -> EventM n (Maybe (Extent n))+lookupExtent n = EventM $ asks (listToMaybe . filter f . latestExtents)+    where+        f (Extent n' _ _) = n == n'++-- | Given a mouse click location, return the extents intersected by the+-- click. The returned extents are sorted such that the first extent in+-- the list is the most specific extent and the last extent is the most+-- generic (top-level). So if two extents A and B both intersected the+-- mouse click but A contains B, then they would be returned [B, A].+findClickedExtents :: (Int, Int) -> EventM n [Extent n]+findClickedExtents pos = EventM $ asks (findClickedExtents_ pos . latestExtents)++findClickedExtents_ :: (Int, Int) -> [Extent n] -> [Extent n]+findClickedExtents_ pos = reverse . filter (clickedExtent pos)+ -- | Get the Vty handle currently in use. getVtyHandle :: EventM n (Maybe Vty) getVtyHandle = EventM $ asks eventVtyHandle --- | Invalidate the rendering cache entry with the specified name.+-- | Invalidate the rendering cache entry with the specified resource+-- name. invalidateCacheEntry :: n -> EventM n () invalidateCacheEntry n = EventM $ do     lift $ modify (\s -> s { cacheInvalidateRequests = InvalidateSingle n : cacheInvalidateRequests s })@@ -277,21 +334,21 @@     vty <- buildVty     useVty vty `finally` shutdown vty -renderApp :: Vty -> App s e n -> s -> RenderState n -> IO (RenderState n)+renderApp :: Vty -> App s e n -> s -> RenderState n -> IO (RenderState n, [Extent n]) renderApp vty app appState rs = do     sz <- displayBounds $ outputIface vty-    let (newRS, pic, theCursor) = renderFinal (appAttrMap app appState)-                                    (appDraw app appState)-                                    sz-                                    (appChooseCursor app appState)-                                    rs+    let (newRS, pic, theCursor, exts) = renderFinal (appAttrMap app appState)+                                        (appDraw app appState)+                                        sz+                                        (appChooseCursor app appState)+                                        rs         picWithCursor = case theCursor of             Nothing -> pic { picCursor = NoCursor }-            Just loc -> pic { picCursor = Cursor (loc^.columnL) (loc^.rowL) }+            Just cloc -> pic { picCursor = Cursor (cloc^.columnL) (cloc^.rowL) }      update vty picWithCursor -    return newRS+    return (newRS, exts)  -- | Ignore all requested cursor positions returned by the rendering -- process. This is a convenience function useful as an@@ -307,11 +364,11 @@ showFirstCursor :: s -> [CursorLocation n] -> Maybe (CursorLocation n) showFirstCursor = const listToMaybe --- | Show the cursor with the specified name, if such a cursor location--- has been reported.+-- | Show the cursor with the specified resource name, if such a cursor+-- location has been reported. showCursorNamed :: (Eq n) => n -> [CursorLocation n] -> Maybe (CursorLocation n) showCursorNamed name locs =-    let matches loc = loc^.cursorLocationNameL == Just name+    let matches l = l^.cursorLocationNameL == Just name     in listToMaybe $ filter matches locs  -- | A viewport scrolling handle for managing the scroll state of
src/Brick/Types.hs view
@@ -25,6 +25,7 @@   -- * Event-handling types   , EventM(..)   , Next+  , BrickEvent(..)   , handleEventLensed    -- * Rendering infrastructure@@ -43,11 +44,13 @@   -- ** Rendering results   , Result(..)   , lookupAttrName+  , Extent(..)    -- ** Rendering result lenses   , imageL   , cursorsL   , visibilityRequestsL+  , extentsL    -- ** Visibility requests   , VisibilityRequest(..)@@ -74,7 +77,7 @@ import Lens.Micro.Type (Getting) import Control.Monad.Trans.State.Lazy import Control.Monad.Trans.Reader-import Graphics.Vty (Event, Attr)+import Graphics.Vty (Attr) import Control.Monad.IO.Class  import Brick.Types.TH@@ -97,9 +100,9 @@                   -> Lens' a b                   -- ^ The lens to use to extract and store the target                   -- of the event.-                  -> (Event -> b -> EventM n b)+                  -> (e -> b -> EventM n b)                   -- ^ The event handler.-                  -> Event+                  -> e                   -- ^ The event to handle.                   -> EventM n a handleEventLensed v target handleEvent ev = do
src/Brick/Types/Internal.hs view
@@ -22,10 +22,13 @@   , EventRO(..)   , Next(..)   , Result(..)+  , Extent(..)   , CacheInvalidateRequest(..)+  , BrickEvent(..)    , rsScrollRequestsL   , viewportMapL+  , clickableNamesL   , renderCacheL   , observedNamesL   , vpSize@@ -33,6 +36,7 @@   , vpTop   , imageL   , cursorsL+  , extentsL   , visibilityRequestsL   ) where@@ -46,7 +50,7 @@ import Lens.Micro.Internal (Field1, Field2) import qualified Data.Set as S import qualified Data.Map as M-import Graphics.Vty (Vty, DisplayRegion, Image, emptyImage)+import Graphics.Vty (Vty, Event, Button, Modifier, DisplayRegion, Image, emptyImage) import Data.Default (Default(..))  import Brick.Types.TH@@ -99,8 +103,14 @@                        , cacheInvalidateRequests :: [CacheInvalidateRequest n]                        } +-- | An extent of a named area indicating the location of its upper-left+-- corner and its size (width, height).+data Extent n = Extent n Location (Int, Int)+              deriving (Show)+ data EventRO n = EventRO { eventViewportMap :: M.Map n Viewport                          , eventVtyHandle :: Maybe Vty+                         , latestExtents :: [Extent n]                          }  -- | The type of actions to take upon completion of an event handler.@@ -174,19 +184,34 @@            , visibilityRequests :: [VisibilityRequest]            -- ^ The list of visibility requests made by widgets rendered            -- while rendering this one (used by viewports)+           , extents :: [Extent n]            }            deriving Show  suffixLenses ''Result  instance Default (Result n) where-    def = Result emptyImage [] []+    def = Result emptyImage [] [] [] +-- | The type of events.+data BrickEvent n e = VtyEvent Event+                    -- ^ The event was a Vty event.+                    | AppEvent e+                    -- ^ The event was an application event.+                    | MouseDown n Button [Modifier] Location+                    -- ^ A mouse-down event on the specified region was+                    -- received.+                    | MouseUp n (Maybe Button) Location+                    -- ^ A mouse-down event on the specified region was+                    -- received.+                    deriving (Show, Eq)+ data RenderState n =     RS { viewportMap :: M.Map n Viewport        , rsScrollRequests :: [(n, ScrollRequest)]        , observedNames :: !(S.Set n)        , renderCache :: M.Map n (Result n)+       , clickableNames :: [n]        }  -- | The rendering context. This tells widgets how to render: how much
src/Brick/Widgets/Core.hs view
@@ -35,6 +35,7 @@   , withDefAttr   , withAttr   , forceAttr+  , overrideAttr   , updateAttrMap    -- * Border style management@@ -55,6 +56,10 @@   , cropTopBy   , cropBottomBy +  -- * Extent reporting+  , reportExtent+  , clickable+   -- * Scrollable viewports   , viewport   , visible@@ -113,21 +118,43 @@ emptyWidget :: Widget n emptyWidget = raw V.emptyImage --- | Add an offset to all cursor locations and visbility requests--- in the specified rendering result. This function is critical for--- maintaining correctness in the rendering results as they are+-- | Add an offset to all cursor locations, visbility requests, and+-- extents in the specified rendering result. This function is critical+-- for maintaining correctness in the rendering results as they are -- processed successively by box layouts and other wrapping combinators, -- since calls to this function result in converting from widget-local--- coordinates to (ultimately) terminal-global ones so they can be used--- by other combinators. You should call this any time you render+-- coordinates to (ultimately) terminal-global ones so they can be+-- used by other combinators. You should call this any time you render -- something and then translate it or otherwise offset it from its -- original origin. addResultOffset :: Location -> Result n -> Result n-addResultOffset off = addCursorOffset off . addVisibilityOffset off+addResultOffset off = addCursorOffset off . addVisibilityOffset off . addExtentOffset off  addVisibilityOffset :: Location -> Result n -> Result n addVisibilityOffset off r = r & visibilityRequestsL.each.vrPositionL %~ (off <>) +addExtentOffset :: Location -> Result n -> Result n+addExtentOffset off r = r & extentsL.each %~ (\(Extent n l sz) -> Extent n (off <> l) sz)++-- | Render the specified widget and record its rendering extent using+-- the specified name (see also 'lookupExtent').+reportExtent :: n -> Widget n -> Widget n+reportExtent n p =+    Widget (hSize p) (vSize p) $ do+        result <- render p+        let ext = Extent n (Location (0, 0)) sz+            sz = ( result^.imageL.to V.imageWidth+                 , result^.imageL.to V.imageHeight+                 )+        return $ result & extentsL %~ (ext:)++-- | Request mouse click events on the specified widget.+clickable :: n -> Widget n -> Widget n+clickable n p =+    Widget (hSize p) (vSize p) $ do+        clickableNamesL %= (n:)+        render $ reportExtent n p+ addCursorOffset :: Location -> Result n -> Result n addCursorOffset off r =     let onlyVisible = filter isVisible@@ -425,6 +452,7 @@       cropResultToContext $ Result (concatenatePrimary br paddedImages)                             (concat $ cursors <$> allTranslatedResults)                             (concat $ visibilityRequests <$> allTranslatedResults)+                            (concat $ extents <$> allTranslatedResults)  -- | Limit the space available to the specified widget to the specified -- number of columns. This is important for constraining the horizontal@@ -481,6 +509,13 @@         c <- getContext         withReaderT (& ctxAttrMapL .~ (forceAttrMap (attrMapLookup an (c^.ctxAttrMapL)))) (render p) +-- | Override the lookup of 'targetName' to return the attribute value+-- associated with 'fromName' when rendering the specified widget.+-- See also 'mapAttrName'.+overrideAttr :: AttrName -> AttrName -> Widget n -> Widget n+overrideAttr targetName fromName =+    updateAttrMap (mapAttrName fromName targetName)+ -- | Build a widget directly from a raw Vty image. raw :: V.Image -> Widget n raw img = Widget Fixed Fixed $ return $ def & imageL .~ img@@ -624,7 +659,7 @@                                   "in each interface have unique name values. This means either " <>                                   "using a different name type or adding constructors to your " <>                                   "existing one and using those to name your widgets.  For more " <>-                                  "information, see the \"Widget Names\" section of the Brick User Guide."+                                  "information, see the \"Resource Names\" section of the Brick User Guide."        observeName vpname 
src/Brick/Widgets/Edit.hs view
@@ -67,6 +67,14 @@  suffixLenses ''Editor +instance (Show t, Show n) => Show (Editor t n) where+    show e =+        concat [ "Editor { "+               , "editContents = " <> show (editContents e)+               , ", editorName = " <> show (editorName e)+               , "}"+               ]+ instance Named (Editor t n) n where     getName = editorName 
src/Brick/Widgets/Internal.hs view
@@ -25,8 +25,8 @@             -> V.DisplayRegion             -> ([CursorLocation n] -> Maybe (CursorLocation n))             -> RenderState n-            -> (RenderState n, V.Picture, Maybe (CursorLocation n))-renderFinal aMap layerRenders sz chooseCursor rs = (newRS, picWithBg, theCursor)+            -> (RenderState n, V.Picture, Maybe (CursorLocation n), [Extent n])+renderFinal aMap layerRenders sz chooseCursor rs = (newRS, picWithBg, theCursor, concat layerExtents)     where         (layerResults, !newRS) = flip runState rs $ sequence $             (\p -> runReaderT p ctx) <$>@@ -37,6 +37,7 @@         -- See https://github.com/coreyoconnor/vty/issues/95         picWithBg = pic { V.picBackground = V.Background ' ' V.defAttr }         layerCursors = (^.cursorsL) <$> layerResults+        layerExtents = reverse $ (^.extentsL) <$> layerResults         theCursor = chooseCursor $ concat layerCursors  -- | After rendering the specified widget, crop its result image to the
src/Brick/Widgets/List.hs view
@@ -76,14 +76,14 @@          , listSelected :: !(Maybe Int)          , listName :: n          , listItemHeight :: Int-         } deriving (Functor, Foldable, Traversable)+         } deriving (Functor, Foldable, Traversable, Show)  suffixLenses ''List  instance Named (List n e) n where     getName = listName -handleListEvent :: (Show n, Ord n) => Event -> List n e -> EventM n (List n e)+handleListEvent :: (Ord n) => Event -> List n e -> EventM n (List n e) handleListEvent e theList =     case e of         EvKey KUp [] -> return $ listMoveUp theList