packages feed

brick 0.1 → 0.2

raw patch · 30 files changed

+1373/−1122 lines, 30 filesdep +deepseqPVP ok

version bump matches the API change (PVP)

Dependencies added: deepseq

API changes (from Hackage documentation)

- Brick.Main: data Next a
- Brick.Main: type EventM a = StateT EventState IO a
- Brick.Types: instance Eq Name
- Brick.Types: instance Field1 Location Location Int Int
- Brick.Types: instance Field2 Location Location Int Int
- Brick.Types: instance IsString Name
- Brick.Types: instance Monoid Location
- Brick.Types: instance Ord Name
- Brick.Types: instance Show CursorLocation
- Brick.Types: instance Show Location
- Brick.Types: instance Show Name
- Brick.Types: instance TerminalLocation Location
- Brick.Widgets.Core: Both :: ViewportType
- Brick.Widgets.Core: Down :: Direction
- Brick.Widgets.Core: Fixed :: Size
- Brick.Widgets.Core: Greedy :: Size
- Brick.Widgets.Core: Horizontal :: ViewportType
- Brick.Widgets.Core: Max :: Padding
- Brick.Widgets.Core: Pad :: Int -> Padding
- Brick.Widgets.Core: Result :: Image -> [CursorLocation] -> [VisibilityRequest] -> Result
- Brick.Widgets.Core: Up :: Direction
- Brick.Widgets.Core: VR :: Location -> DisplayRegion -> VisibilityRequest
- Brick.Widgets.Core: Vertical :: ViewportType
- Brick.Widgets.Core: Widget :: Size -> Size -> RenderM Result -> Widget
- Brick.Widgets.Core: attrL :: (Contravariant f, Functor f) => (Attr -> f Attr) -> Context -> f Context
- Brick.Widgets.Core: availHeightL :: Lens' Context Int
- Brick.Widgets.Core: availWidthL :: Lens' Context Int
- Brick.Widgets.Core: ctxAttrMapL :: Lens' Context AttrMap
- Brick.Widgets.Core: ctxAttrNameL :: Lens' Context AttrName
- Brick.Widgets.Core: ctxBorderStyleL :: Lens' Context BorderStyle
- Brick.Widgets.Core: cursors :: Result -> [CursorLocation]
- Brick.Widgets.Core: cursorsL :: Lens' Result [CursorLocation]
- Brick.Widgets.Core: data Context
- Brick.Widgets.Core: data Direction
- Brick.Widgets.Core: data Padding
- Brick.Widgets.Core: data Result
- Brick.Widgets.Core: data Size
- Brick.Widgets.Core: data ViewportType
- Brick.Widgets.Core: data VisibilityRequest
- Brick.Widgets.Core: data Widget
- Brick.Widgets.Core: getContext :: RenderM Context
- Brick.Widgets.Core: hSize :: Widget -> Size
- Brick.Widgets.Core: image :: Result -> Image
- Brick.Widgets.Core: imageL :: Lens' Result Image
- Brick.Widgets.Core: lookupAttrName :: AttrName -> RenderM Attr
- Brick.Widgets.Core: render :: Widget -> RenderM Result
- Brick.Widgets.Core: type RenderM a = ReaderT Context (State RenderState) a
- Brick.Widgets.Core: vSize :: Widget -> Size
- Brick.Widgets.Core: visibilityRequests :: Result -> [VisibilityRequest]
- Brick.Widgets.Core: visibilityRequestsL :: Lens' Result [VisibilityRequest]
- Brick.Widgets.Core: vrPosition :: VisibilityRequest -> Location
- Brick.Widgets.Core: vrPositionL :: Lens' VisibilityRequest Location
- Brick.Widgets.Core: vrSize :: VisibilityRequest -> DisplayRegion
- Brick.Widgets.Core: vrSizeL :: Lens' VisibilityRequest DisplayRegion
+ Brick.Main: lookupViewport :: Name -> EventM (Maybe Viewport)
+ Brick.Types: Both :: ViewportType
+ Brick.Types: Down :: Direction
+ Brick.Types: Fixed :: Size
+ Brick.Types: Greedy :: Size
+ Brick.Types: Horizontal :: ViewportType
+ Brick.Types: Max :: Padding
+ Brick.Types: Pad :: Int -> Padding
+ Brick.Types: Result :: Image -> [CursorLocation] -> [VisibilityRequest] -> Result
+ Brick.Types: Up :: Direction
+ Brick.Types: VP :: Int -> Int -> DisplayRegion -> Viewport
+ Brick.Types: VR :: Location -> DisplayRegion -> VisibilityRequest
+ Brick.Types: Vertical :: ViewportType
+ Brick.Types: Widget :: Size -> Size -> RenderM Result -> Widget
+ Brick.Types: _vpLeft :: Viewport -> Int
+ Brick.Types: _vpSize :: Viewport -> DisplayRegion
+ Brick.Types: _vpTop :: Viewport -> Int
+ Brick.Types: attrL :: (Contravariant f, Functor f) => (Attr -> f Attr) -> Context -> f Context
+ Brick.Types: availHeightL :: Lens' Context Int
+ Brick.Types: availWidthL :: Lens' Context Int
+ Brick.Types: ctxAttrMapL :: Lens' Context AttrMap
+ Brick.Types: ctxAttrNameL :: Lens' Context AttrName
+ Brick.Types: ctxBorderStyleL :: Lens' Context BorderStyle
+ Brick.Types: cursors :: Result -> [CursorLocation]
+ Brick.Types: cursorsL :: Lens' Result [CursorLocation]
+ Brick.Types: data Context
+ Brick.Types: data Direction
+ Brick.Types: data Next a
+ Brick.Types: data Padding
+ Brick.Types: data Result
+ Brick.Types: data Size
+ Brick.Types: data Viewport
+ Brick.Types: data ViewportType
+ Brick.Types: data VisibilityRequest
+ Brick.Types: data Widget
+ Brick.Types: getContext :: RenderM Context
+ Brick.Types: hSize :: Widget -> Size
+ Brick.Types: handleEventLensed :: HandleEvent b => a -> Lens' a b -> Event -> EventM a
+ Brick.Types: image :: Result -> Image
+ Brick.Types: imageL :: Lens' Result Image
+ Brick.Types: instance Default Result
+ Brick.Types: instance Eq Size
+ Brick.Types: instance Ord Size
+ Brick.Types: instance Show Result
+ Brick.Types: instance Show Size
+ Brick.Types: lookupAttrName :: AttrName -> RenderM Attr
+ Brick.Types: render :: Widget -> RenderM Result
+ Brick.Types: type EventM a = ReaderT (Map Name Viewport) (StateT EventState IO) a
+ Brick.Types: type RenderM a = ReaderT Context (State RenderState) a
+ Brick.Types: vSize :: Widget -> Size
+ Brick.Types: visibilityRequests :: Result -> [VisibilityRequest]
+ Brick.Types: visibilityRequestsL :: Lens' Result [VisibilityRequest]
+ Brick.Types: vpLeft :: Lens' Viewport Int
+ Brick.Types: vpSize :: Lens' Viewport DisplayRegion
+ Brick.Types: vpTop :: Lens' Viewport Int
+ Brick.Types: vrPosition :: VisibilityRequest -> Location
+ Brick.Types: vrPositionL :: Lens' VisibilityRequest Location
+ Brick.Types: vrSize :: VisibilityRequest -> DisplayRegion
+ Brick.Types: vrSizeL :: Lens' VisibilityRequest DisplayRegion
+ Brick.Widgets.List: listItemHeightL :: Lens' (List e_az8y) Int
- Brick.Types: handleEvent :: HandleEvent a => Event -> a -> a
+ Brick.Types: handleEvent :: HandleEvent a => Event -> a -> EventM a
- Brick.Widgets.Dialog: dialogButtonsL :: Lens (Dialog a_axsZ) (Dialog a_axtv) [(String, a_axsZ)] [(String, a_axtv)]
+ Brick.Widgets.Dialog: dialogButtonsL :: Lens (Dialog a_auI8) (Dialog a_auIE) [(String, a_auI8)] [(String, a_auIE)]
- Brick.Widgets.Dialog: dialogNameL :: Lens' (Dialog a_axsZ) Name
+ Brick.Widgets.Dialog: dialogNameL :: Lens' (Dialog a_auI8) Name
- Brick.Widgets.Dialog: dialogSelectedIndexL :: Lens' (Dialog a_axsZ) (Maybe Int)
+ Brick.Widgets.Dialog: dialogSelectedIndexL :: Lens' (Dialog a_auI8) (Maybe Int)
- Brick.Widgets.Dialog: dialogTitleL :: Lens' (Dialog a_axsZ) (Maybe String)
+ Brick.Widgets.Dialog: dialogTitleL :: Lens' (Dialog a_auI8) (Maybe String)
- Brick.Widgets.Dialog: dialogWidthL :: Lens' (Dialog a_axsZ) Int
+ Brick.Widgets.Dialog: dialogWidthL :: Lens' (Dialog a_auI8) Int
- Brick.Widgets.List: list :: Name -> (Bool -> e -> Widget) -> [e] -> List e
+ Brick.Widgets.List: list :: Name -> Vector e -> Int -> List e
- Brick.Widgets.List: listElementsL :: Lens' (List e_azAv) [e_azAv]
+ Brick.Widgets.List: listElementsL :: Lens (List e_az8y) (List e_az9M) (Vector e_az8y) (Vector e_az9M)
- Brick.Widgets.List: listNameL :: Lens' (List e_azAv) Name
+ Brick.Widgets.List: listNameL :: Lens' (List e_az8y) Name
- Brick.Widgets.List: listReplace :: Eq e => [e] -> List e -> List e
+ Brick.Widgets.List: listReplace :: Eq e => Vector e -> List e -> List e
- Brick.Widgets.List: listSelectedL :: Lens' (List e_azAv) (Maybe Int)
+ Brick.Widgets.List: listSelectedL :: Lens' (List e_az8y) (Maybe Int)
- Brick.Widgets.List: renderList :: List e -> Widget
+ Brick.Widgets.List: renderList :: List e -> (Bool -> e -> Widget) -> Widget

Files

CHANGELOG.md view
@@ -2,6 +2,68 @@ Brick changelog --------------- +0.2+---++API changes:+* Added top-level `Brick` module that re-exports the most important+  modules in the library.+* List:+  * Now instead of passing the item-drawing function to the `list` state+    constructor, it is passed to `renderList`+  * `renderList` now takes the row height of the list's item widgets.+    The list item-drawing function must respect this in order for+    scrolling to work properly. This change made it possible to optimize+    the list so that it only draws widgets visible in the viewport+    rather than rendering all of the list's items (even the ones+    off-screen). But to do this we must be able to tell in advance+    how high each one is, so we require this parameter. In addition+    this change means that lists no longer support items of different+    heights.+  * The list now uses Data.Vector instead of [a] to store items; this+    permits efficient slicing so we can do the optimized rendering+    described above.+* The `HandleEvent` type class `handleEvent` method now runs in+  `EventM`. This permits event-handling code implemented in terms of+  `HandleEvent` to do get access to viewport state and to run IO code,+  making it just as powerful as code in the top-level `EventM` handler.+* Many types were moved from `Brick.Widgets.Core` and `Brick.Main` to+  `Brick.Types`, making the former module merely a home for `Widget`+  constructors and combinators.+* The `IsString` instance for `Widget` was removed; this might be+  reinstated later, but this package provides enough `IsString`+  instances that things can get confusing.+* `EventM` is now reader monad over the most recent rendering pass's+  viewport state, in addition to being a state monad over viewport+  requests for the renderer. Added the `lookupViewport` function to+  provide access to the most recent viewport state. Exported the+  `Viewport` type and lenses.+* Now that `handleEvent` is now an `EventM` action, composition with+  `continue` et al got a little messier when using lenses to+  update the application state. To help with this, there is now+  `handleEventLensed`.++Bugfixes:+* Lists now perform well with 10 items or a million (see above; fixes+  #7, thanks Simon Michael)+* Added more haddock notes to `Brick.Widgets.Core` about growth+  policies.+* Forced evaluation of render states to address a space leak in the+  renderer (fixes #14, thanks Sebastian Reuße <seb@wirrsal.net>)+* str: only reference string content that can be shown (eliminates a+  space leak, fixes #14, thanks Sebastian Reuße <seb@wirrsal.net>)++Misc:+* Added a makefile for the user guide.+* List: added support for Home and End keys (thanks Simon Michael)+* Viewports: when rendering viewports, scroll requests from `EventM` are+  processed before visibility requests from the rendering process; this+  reverses this previous order of operations but permits user-supplied+  event handlers to reset viewports when desired.++Package changes:+* Added `deepseq` dependency+ 0.1 --- Initial release
README.md view
@@ -70,10 +70,10 @@  Your documentation options, in recommended order, are: -* [FAQ](FAQ.md)-* [The brick user guide](docs/guide.rst)+* [FAQ](https://github.com/jtdaugherty/brick/blob/master/FAQ.md)+* [The brick user guide](https://github.com/jtdaugherty/brick/blob/master/docs/guide.rst) * Haddock (all modules)-* [Demo programs](programs)+* [Demo programs](https://github.com/jtdaugherty/brick/blob/master/programs)  Status ------
brick.cabal view
@@ -1,5 +1,5 @@ name:                brick-version:             0.1+version:             0.2 synopsis:            A declarative terminal user interface library description:   Write terminal applications painlessly with 'brick'! You write an@@ -8,8 +8,7 @@   .   > module Main where   >-  > import Brick.Main (simpleMain)-  > import Brick.Widgets.Core (Widget, str)+  > import Brick   >   > ui :: Widget   > ui = str "Hello, world!"@@ -56,6 +55,7 @@   ghc-options:         -Wall -fno-warn-unused-do-bind -O3   hs-source-dirs:      src   exposed-modules:+    Brick     Brick.AttrMap     Brick.Focus     Brick.Main@@ -73,6 +73,7 @@     Data.Text.Markup   other-modules:     Brick.Types.TH+    Brick.Types.Internal     Brick.Widgets.Internal    build-depends:       base <= 5,@@ -86,7 +87,8 @@                        contravariant,                        text,                        text-zipper >= 0.2.1,-                       template-haskell+                       template-haskell,+                       deepseq >= 1.3 && < 1.5  executable brick-visibility-demo   if !flag(demos)@@ -212,7 +214,8 @@                        vty >= 5.3.1,                        data-default,                        text,-                       lens+                       lens,+                       vector  executable brick-custom-event-demo   if !flag(demos)
docs/guide.rst view
@@ -222,6 +222,59 @@   suspend your interface and execute some other program that needs to   gain control of the terminal (such as an external editor). +The HandleEvent Type Class+**************************++Event handlers are responsible for transforming the application state.+While you can use ordinary methods to do this such as pattern matching+and pure function calls, some widget state types such as the ones+provided by the ``Brick.Widgets.List`` and ``Brick.Widgets.Edit``+modules support another interface: the ``Brick.Types.HandleEvent`` type+class.++The ``HandleEvent`` type class has only one method:++.. code:: haskell++   handleEvent :: Event -> a -> EventM a++Instances of ``HandleEvent`` provide reasonable default behavior for+handling input events; for example, arrow keys change the ``List``+selection and input keys modify the text in ``Editor`` states.++Since ``handleEvent`` runs in ``EventM``, event handlers+written this way have access to rendering viewport states via+``Brick.Main.lookupViewport`` and the ``IO`` monad via ``liftIO``.++To use ``handleEvent`` in your program, invoke it on the relevant piece+of state in your event handler, e.g.,++.. code:: haskell++   myEvent :: s -> e -> EventM (Next s)+   myEvent s e = continue =<< handleEvent e s++This pattern works fine when your application state instances+``HandleEvent``, but it can become unpleasant if the value on which+you want to invoke ``handleEvent`` 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++   myEvent :: s -> e -> EventM (Next s)+   myEvent s e = continue =<< handleEventLensed s someLens e++Compare that with the more verbose explicit version:++.. code:: haskell++   myEvent :: s -> e -> EventM (Next s)+   myEvent s e = do+     newVal <- handleEvent e (s^.someLens)+     continue $ s & someLens .~ newVal+ Using Your Own Event Type ************************* @@ -419,8 +472,8 @@  How widgets use space when rendered is described in two pieces of information in each ``Widget``: the widget's horizontal and vertical-growth policies. These fields have type ``Brick.Widgets.Core.Size`` and-can have the values ``Fixed`` and ``Greedy``.+growth policies. These fields have type ``Brick.Types.Size`` and can+have the values ``Fixed`` and ``Greedy``.  A widget advertising a ``Fixed`` size in a given dimension is a widget that will always consume the same number of rows or columns no@@ -562,8 +615,8 @@ =========  A *viewport* is a scrollable window onto another widget. Viewports have-a *scrolling direction* of type ``Brick.Widgets.Core.ViewportType``-which can be one of:+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.@@ -729,18 +782,18 @@     render :: RenderM Result -which is a function returning a ``Brick.Widgets.Core.Result``:+which is a function returning a ``Brick.Types.Result``:  .. code:: haskell      data Result =         Result { image :: Graphics.Vty.Image                , cursors :: [Brick.Types.CursorLocation]-               , visibilityRequests :: [Brick.Widgets.Core.VisibilityRequest]+               , visibilityRequests :: [Brick.Types.VisibilityRequest]                }  The ``RenderM`` monad gives us access to the rendering context (see `How-Widgets and Rendering Work`_) via the ``Brick.Widgets.Core.getContext``+Widgets and Rendering Work`_) via the ``Brick.Types.getContext`` function. The context type is:  .. code:: haskell@@ -774,7 +827,7 @@ determine how much space your widget has to render.  To perform an attribute lookup in the attribute map for the context's-current attribute, use ``Brick.Widgets.Core.attrL``.+current attribute, use ``Brick.Types.attrL``.  For example, to build a widget that always fills the available width and height with a fill character using the current attribute, we could
programs/AttrDemo.hs view
@@ -8,33 +8,36 @@   )  import Brick.Main-import Brick.Widgets.Core+import Brick.Types   ( Widget-  , (<=>)+  )+import Brick.Widgets.Core+  ( (<=>)   , withAttr   , vBox+  , str   ) import Brick.Util (on, fg) import Brick.AttrMap (attrMap, AttrMap)  ui :: Widget ui =-    vBox [ "This text uses the global default attribute."-         , withAttr "foundFull"-           "Specifying an attribute name means we look it up in the attribute tree."-         , withAttr "foundFgOnly"-           ("When we find a value, we merge it with its parent in the attribute"-           <=> "name tree all the way to the root (the global default).")-         , withAttr "missing"-           "A missing attribute name just resumes the search at its parent."-         , withAttr ("general" <> "specific")-           "In this way we build complete attribute values by using an inheritance scheme."-         , withAttr "foundFull"-           "You can override everything ..."-         , withAttr "foundFgOnly"-           "... or only you want to change and inherit the rest."-         , "Attribute names are assembled with the Monoid append operation to indicate"-         , "hierarchy levels, e.g. \"window\" <> \"title\"."+    vBox [ str "This text uses the global default attribute."+         , withAttr "foundFull" $+           str "Specifying an attribute name means we look it up in the attribute tree."+         , (withAttr "foundFgOnly" $+           str ("When we find a value, we merge it with its parent in the attribute")+           <=> str "name tree all the way to the root (the global default).")+         , withAttr "missing" $+           str "A missing attribute name just resumes the search at its parent."+         , withAttr ("general" <> "specific") $+           str "In this way we build complete attribute values by using an inheritance scheme."+         , withAttr "foundFull" $+           str "You can override everything ..."+         , withAttr "foundFgOnly" $+           str "... or only you want to change and inherit the rest."+         , str "Attribute names are assembled with the Monoid append operation to indicate"+         , str "hierarchy levels, e.g. \"window\" <> \"title\"."          ]  globalDefault :: Attr
programs/BorderDemo.hs view
@@ -9,9 +9,11 @@ import qualified Brick.Main as M import Brick.Util (fg, bg, on) import qualified Brick.AttrMap as A-import Brick.Widgets.Core+import Brick.Types   ( Widget-  , (<=>)+  )+import Brick.Widgets.Core+  ( (<=>)   , (<+>)   , vLimit   , hLimit@@ -19,6 +21,7 @@   , updateAttrMap   , withBorderStyle   , txt+  , str   ) import qualified Brick.Widgets.Center as C import qualified Brick.Widgets.Border as B@@ -55,7 +58,7 @@ mkBorderDemo :: (T.Text, BS.BorderStyle) -> Widget mkBorderDemo (styleName, sty) =     withBorderStyle sty $-    B.borderWithLabel "label" $+    B.borderWithLabel (str "label") $     vLimit 5 $     C.vCenter $     txt $ "  " <> styleName <> " style  "@@ -75,21 +78,21 @@ colorDemo :: Widget colorDemo =     updateAttrMap (A.applyAttrMappings borderMappings) $-    B.borderWithLabel "title" $+    B.borderWithLabel (str "title") $     hLimit 20 $     vLimit 5 $     C.center $-    "colors!"+    str "colors!"  ui :: Widget ui =     hBox borderDemos     <=> B.hBorder     <=> colorDemo-    <=> B.hBorderWithLabel "horizontal border label"-    <=> (C.center "Left of vertical border"+    <=> B.hBorderWithLabel (str "horizontal border label")+    <=> (C.center (str "Left of vertical border")          <+> B.vBorder-         <+> C.center "Right of vertical border")+         <+> C.center (str "Right of vertical border"))  main :: IO () main = M.simpleMain ui
programs/CustomEventDemo.hs view
@@ -11,16 +11,18 @@  import Brick.Main   ( App(..)-  , Next-  , EventM   , showFirstCursor   , customMain   , continue   , halt   )-import Brick.Widgets.Core+import Brick.Types   ( Widget-  , (<=>)+  , Next+  , EventM+  )+import Brick.Widgets.Core+  ( (<=>)   , str   ) 
programs/DialogDemo.hs view
@@ -5,9 +5,11 @@ import qualified Graphics.Vty as V  import qualified Brick.Main as M-import Brick.Widgets.Core+import Brick.Types   ( Widget-  , padAll+  )+import Brick.Widgets.Core+  ( padAll   , str   ) import qualified Brick.Widgets.Dialog as D@@ -24,12 +26,12 @@     where         ui = D.renderDialog d $ C.hCenter $ padAll 1 $ str "This is the dialog body." -appEvent :: D.Dialog Choice -> V.Event -> M.EventM (M.Next (D.Dialog Choice))+appEvent :: D.Dialog Choice -> V.Event -> T.EventM (T.Next (D.Dialog Choice)) appEvent d ev =     case ev of         V.EvKey V.KEsc [] -> M.halt d         V.EvKey V.KEnter [] -> M.halt d-        _ -> M.continue $ T.handleEvent ev d+        _ -> M.continue =<< T.handleEvent ev d  initialState :: D.Dialog Choice initialState = D.dialog "dialog" (Just "Title") (Just (0, choices)) 50
programs/EditDemo.hs view
@@ -9,8 +9,7 @@ import qualified Brick.Main as M import qualified Brick.Types as T import Brick.Widgets.Core-  ( Widget-  , (<+>)+  ( (<+>)   , (<=>)   , hLimit   , vLimit@@ -47,21 +46,21 @@     then edit1     else edit2 -drawUI :: St -> [Widget]+drawUI :: St -> [T.Widget] drawUI st = [ui]     where-        ui = C.center $ ("Input 1 (unlimited): " <+> (hLimit 30 $ vLimit 5 $ E.renderEditor $ st^.edit1)) <=>-                        " " <=>-                        ("Input 2 (limited to 2 lines): " <+> (hLimit 30 $ E.renderEditor $ st^.edit2)) <=>-                        " " <=>-                        "Press Tab to switch between editors, Esc to quit."+        ui = C.center $ (str "Input 1 (unlimited): " <+> (hLimit 30 $ vLimit 5 $ E.renderEditor $ st^.edit1)) <=>+                        str " " <=>+                        (str "Input 2 (limited to 2 lines): " <+> (hLimit 30 $ E.renderEditor $ st^.edit2)) <=>+                        str " " <=>+                        str "Press Tab to switch between editors, Esc to quit." -appEvent :: St -> V.Event -> M.EventM (M.Next St)+appEvent :: St -> V.Event -> T.EventM (T.Next St) appEvent st ev =     case ev of         V.EvKey V.KEsc [] -> M.halt st         V.EvKey (V.KChar '\t') [] -> M.continue $ switchEditors st-        _ -> M.continue $ st & currentEditorL st %~ T.handleEvent ev+        _ -> M.continue =<< T.handleEventLensed st (currentEditorL st) ev  initialState :: St initialState =
programs/HelloWorldDemo.hs view
@@ -1,11 +1,9 @@-{-# LANGUAGE OverloadedStrings #-} module Main where -import Brick.Main (simpleMain)-import Brick.Widgets.Core (Widget)+import Brick  ui :: Widget-ui = "Hello, world!"+ui = str "Hello, world!"  main :: IO () main = simpleMain ui
programs/LayerDemo.hs view
@@ -11,10 +11,13 @@ import Brick.Types (rowL, columnL) import qualified Brick.Main as M import qualified Brick.Widgets.Border as B-import Brick.Widgets.Core+import Brick.Types   ( Widget-  , translateBy   )+import Brick.Widgets.Core+  ( translateBy+  , str+  )  data St =     St { _topLayerLocation :: T.Location@@ -32,14 +35,14 @@ topLayer :: St -> Widget topLayer st =     translateBy (st^.topLayerLocation) $-    B.border "Top layer\n(Arrow keys move)"+    B.border $ str "Top layer\n(Arrow keys move)"  bottomLayer :: St -> Widget bottomLayer st =     translateBy (st^.bottomLayerLocation) $-    B.border "Bottom layer\n(Ctrl-arrow keys move)"+    B.border $ str "Bottom layer\n(Ctrl-arrow keys move)" -appEvent :: St -> V.Event -> M.EventM (M.Next St)+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)
programs/ListDemo.hs view
@@ -12,9 +12,12 @@ import qualified Brick.Widgets.List as L import qualified Brick.Widgets.Center as C import qualified Brick.AttrMap as A-import Brick.Widgets.Core+import qualified Data.Vector as V+import Brick.Types   ( Widget-  , (<+>)+  )+import Brick.Widgets.Core+  ( (<+>)   , str   , vLimit   , hLimit@@ -26,26 +29,26 @@ drawUI :: L.List Int -> [Widget] drawUI l = [ui]     where-        label = "Item " <+> cur <+> " of " <+> total+        label = str "Item " <+> cur <+> str " of " <+> total         cur = case l^.(L.listSelectedL) of-                Nothing -> "-"+                Nothing -> str "-"                 Just i -> str (show (i + 1))-        total = str $ show $ length $ l^.(L.listElementsL)+        total = str $ show $ V.length $ l^.(L.listElementsL)         box = B.borderWithLabel label $               hLimit 25 $               vLimit 15 $-              L.renderList l+              L.renderList l listDrawElement         ui = C.vCenter $ vBox [ C.hCenter box-                              , " "-                              , C.hCenter "Press +/- to add/remove list elements."-                              , C.hCenter "Press Esc to exit."+                              , str " "+                              , C.hCenter $ str "Press +/- to add/remove list elements."+                              , C.hCenter $ str "Press Esc to exit."                               ] -appEvent :: L.List Int -> V.Event -> M.EventM (M.Next (L.List Int))+appEvent :: L.List Int -> V.Event -> T.EventM (T.Next (L.List Int)) appEvent l e =     case e of         V.EvKey (V.KChar '+') [] ->-            let el = length $ l^.(L.listElementsL)+            let el = V.length $ l^.(L.listElementsL)             in M.continue $ L.listInsert el el l          V.EvKey (V.KChar '-') [] ->@@ -55,17 +58,17 @@          V.EvKey V.KEsc [] -> M.halt l -        ev -> M.continue $ T.handleEvent ev l+        ev -> M.continue =<< T.handleEvent ev l  listDrawElement :: Bool -> Int -> Widget listDrawElement sel i =     let selStr s = if sel                    then withAttr customAttr (str $ "<" <> s <> ">")                    else str s-    in C.hCenter $ "Item " <+> (selStr $ show i)+    in C.hCenter $ str "Item " <+> (selStr $ show i)  initialState :: L.List Int-initialState = L.list (T.Name "list") listDrawElement [0, 1, 2]+initialState = L.list (T.Name "list") (V.fromList [0, 1, 2]) 1  customAttr :: A.AttrName customAttr = L.listSelectedAttr <> "custom"
programs/MarkupDemo.hs view
@@ -5,9 +5,11 @@ import qualified Graphics.Vty as V  import Brick.Main (App(..), defaultMain, resizeOrQuit, neverShowCursor)-import Brick.Widgets.Core+import Brick.Types   ( Widget-  , (<=>)+  )+import Brick.Widgets.Core+  ( (<=>)   ) import Brick.Util (on, fg) import Brick.Markup (markup, (@?))
programs/PaddingDemo.hs view
@@ -5,11 +5,14 @@ import qualified Graphics.Vty as V  import Brick.Main (App(..), neverShowCursor, resizeOrQuit, defaultMain)-import Brick.Widgets.Core+import Brick.Types   ( Widget-  , vBox-  , hBox   , Padding(..)+  )+import Brick.Widgets.Core+  ( vBox+  , hBox+  , str   , padAll   , padLeft   , padRight@@ -23,24 +26,24 @@  ui :: Widget ui =-    vBox [ hBox [ padLeft Max $ vCenter "Left-padded"+    vBox [ hBox [ padLeft Max $ vCenter $ str "Left-padded"                 , B.vBorder-                , padRight Max $ vCenter "Right-padded"+                , padRight Max $ vCenter $ str "Right-padded"                 ]          , B.hBorder-         , hBox [ padTop Max $ hCenter "Top-padded"+         , hBox [ padTop Max $ hCenter $ str "Top-padded"                 , B.vBorder-                , padBottom Max $ hCenter "Bottom-padded"+                , padBottom Max $ hCenter $ str "Bottom-padded"                 ]          , B.hBorder-         , hBox [ padLeftRight 2 "Padded by 2 on left/right"+         , hBox [ padLeftRight 2 $ str "Padded by 2 on left/right"                 , B.vBorder-                , vBox [ padTopBottom 1 "Padded by 1 on top/bottom"+                , vBox [ padTopBottom 1 $ str "Padded by 1 on top/bottom"                        , B.hBorder                        ]                 ]          , B.hBorder-         , padAll 2 "Padded by 2 on all sides"+         , padAll 2 $ str "Padded by 2 on all sides"          ]  app :: App () V.Event
programs/SuspendAndResumeDemo.hs view
@@ -11,11 +11,14 @@ import Brick.Main   ( App(..), neverShowCursor, defaultMain   , suspendAndResume, halt, continue-  , EventM, Next   )-import Brick.Widgets.Core+import Brick.Types   ( Widget-  , vBox+  , EventM+  , Next+  )+import Brick.Widgets.Core+  ( vBox   , str   ) @@ -29,7 +32,7 @@ drawUI st = [ui]     where         ui = vBox [ str $ "External input: \"" <> st^.stExternalInput <> "\""-                  , "(Press Esc to quit or Space to ask for input)"+                  , str "(Press Esc to quit or Space to ask for input)"                   ]  appEvent :: St -> V.Event -> EventM (Next St)
programs/ViewportScrollDemo.hs view
@@ -1,5 +1,5 @@-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE OverloadedStrings #-} module Main where  import Control.Applicative@@ -12,10 +12,12 @@ import qualified Brick.Main as M import qualified Brick.Widgets.Center as C import qualified Brick.Widgets.Border as B-import Brick.Widgets.Core+import Brick.Types   ( Widget   , ViewportType(Horizontal, Vertical, Both)-  , hLimit+  )+import Brick.Widgets.Core+  ( hLimit   , vLimit   , hBox   , vBox@@ -38,15 +40,15 @@         ui = C.center $ B.border $ hLimit 60 $ vLimit 21 $              vBox [ pair, B.hBorder, singleton ]         singleton = viewport vp3Name Both $-                    vBox $ "Press ctrl-arrow keys to scroll this viewport horizontally and vertically."+                    vBox $ str "Press ctrl-arrow keys to scroll this viewport horizontally and vertically."                          : (str <$> [ "Line " <> show i | i <- [2..25::Int] ])         pair = hBox [ viewport vp1Name Vertical $-                      vBox $ "Press up and down arrow keys" :-                             "to scroll this viewport." :+                      vBox $ str "Press up and down arrow keys" :+                             str "to scroll this viewport." :                              (str <$> [ "Line " <> (show i) | i <- [3..50::Int] ])                     , B.vBorder-                    , viewport vp2Name Horizontal-                      "Press left and right arrow keys to scroll this viewport."+                    , viewport vp2Name Horizontal $+                      str "Press left and right arrow keys to scroll this viewport."                     ]  vp1Scroll :: M.ViewportScroll@@ -58,7 +60,7 @@ vp3Scroll :: M.ViewportScroll vp3Scroll = M.viewportScroll vp3Name -appEvent :: () -> V.Event -> M.EventM (M.Next ())+appEvent :: () -> V.Event -> T.EventM (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 ()
programs/VisibilityDemo.hs view
@@ -13,10 +13,12 @@ import qualified Brick.Widgets.Border as B import Brick.AttrMap (AttrMap, AttrName, attrMap) import Brick.Util (on)-import Brick.Widgets.Core+import Brick.Types   ( Widget   , ViewportType(Horizontal, Vertical, Both)-  , withAttr+  )+import Brick.Widgets.Core+  ( withAttr   , hLimit   , vLimit   , hBox@@ -100,7 +102,7 @@ vp3Scroll :: M.ViewportScroll vp3Scroll = M.viewportScroll vp3Name -appEvent :: St -> V.Event -> M.EventM (M.Next St)+appEvent :: St -> V.Event -> T.EventM (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)
+ src/Brick.hs view
@@ -0,0 +1,16 @@+-- | This module is provided as a convenience to import the most+-- important parts of the API all at once.+module Brick+  ( module Brick.Main+  , module Brick.Types+  , module Brick.Widgets.Core+  , module Brick.AttrMap+  , module Brick.Util+  )+where++import Brick.Main+import Brick.Types+import Brick.Widgets.Core+import Brick.AttrMap+import Brick.Util
src/Brick/Main.hs view
@@ -6,11 +6,10 @@   , resizeOrQuit    -- * Event handler functions-  , EventM-  , Next   , continue   , halt   , suspendAndResume+  , lookupViewport    -- ** Viewport scrolling   , viewportScroll@@ -34,7 +33,9 @@ import Control.Exception (finally) import Control.Lens ((^.)) import Control.Monad (forever)+import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.State+import Control.Monad.Trans.Reader import Control.Concurrent (forkIO, Chan, newChan, readChan, writeChan, killThread) import Data.Default import Data.Maybe (listToMaybe)@@ -52,16 +53,11 @@   , mkVty   ) -import Brick.Widgets.Core (Widget)-import Brick.Widgets.Internal (renderFinal, RenderState(..), ScrollRequest(..), Direction(..))-import Brick.Types (rowL, columnL, CursorLocation(..), cursorLocationNameL, Name(..))+import Brick.Types (Viewport, Direction, Widget, rowL, columnL, CursorLocation(..), cursorLocationNameL, Name(..), EventM)+import Brick.Types.Internal (ScrollRequest(..), RenderState(..), Next(..))+import Brick.Widgets.Internal (renderFinal) import Brick.AttrMap --- | The type of actions to take in an event handler.-data Next a = Continue a-            | SuspendAndResume (IO a)-            | Halt a- -- | 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@@ -98,11 +94,6 @@         -- 'Event', this is just 'id'.         } --- | The monad in which event handlers run.-type EventM a = StateT EventState IO a--type EventState = [(Name, ScrollRequest)]- -- | The default main entry point which takes an application and an -- initial state and returns the final state returned by a 'halt' -- operation.@@ -183,7 +174,7 @@                     newAppState <- action                     run newRS newAppState -    (st, initialScrollReqs) <- runStateT (appStartEvent app initialAppState) []+    (st, initialScrollReqs) <- runStateT (runReaderT (appStartEvent app initialAppState) M.empty) []     let initialRS = RS M.empty initialScrollReqs     run initialRS st @@ -197,9 +188,17 @@ runVty vty chan app appState rs = do     firstRS <- renderApp vty app appState rs     e <- readChan chan-    (next, scrollReqs) <- runStateT (appHandleEvent app appState e) []+    (next, scrollReqs) <- runStateT (runReaderT (appHandleEvent app appState e) (viewportMap rs)) []     return (next, firstRS { scrollRequests = scrollReqs }) +-- | Given a viewport name, get the viewport's size and offset+-- information from the most recent rendering. Returns 'Nothing' if+-- no such state could be found, either because the name was invalid+-- or because no rendering has occurred (e.g. in an 'appStartEvent'+-- handler).+lookupViewport :: Name -> EventM (Maybe Viewport)+lookupViewport = asks . M.lookup+ withVty :: IO Vty -> (Vty -> IO a) -> IO a withVty buildVty useVty = do     vty <- buildVty@@ -277,14 +276,14 @@ viewportScroll :: Name -> ViewportScroll viewportScroll n =     ViewportScroll { viewportName = n-                   , hScrollPage = \dir -> modify ((n, HScrollPage dir) :)-                   , hScrollBy = \i -> modify ((n, HScrollBy i) :)-                   , hScrollToBeginning = modify ((n, HScrollToBeginning) :)-                   , hScrollToEnd = modify ((n, HScrollToEnd) :)-                   , vScrollPage = \dir -> modify ((n, HScrollPage dir) :)-                   , vScrollBy = \i -> modify ((n, VScrollBy i) :)-                   , vScrollToBeginning = modify ((n, VScrollToBeginning) :)-                   , vScrollToEnd = modify ((n, VScrollToEnd) :)+                   , hScrollPage = \dir -> lift $ modify ((n, HScrollPage dir) :)+                   , hScrollBy = \i -> lift $ modify ((n, HScrollBy i) :)+                   , hScrollToBeginning = lift $ modify ((n, HScrollToBeginning) :)+                   , hScrollToEnd = lift $ modify ((n, HScrollToEnd) :)+                   , vScrollPage = \dir -> lift $ modify ((n, HScrollPage dir) :)+                   , vScrollBy = \i -> lift $ modify ((n, VScrollBy i) :)+                   , vScrollToBeginning = lift $ modify ((n, VScrollToBeginning) :)+                   , vScrollToEnd = lift $ modify ((n, VScrollToEnd) :)                    }  -- | Continue running the event loop with the specified application
src/Brick/Markup.hs view
@@ -19,8 +19,8 @@  import Graphics.Vty (Attr, horizCat, string) -import Brick.Widgets.Core import Brick.AttrMap+import Brick.Types  -- | A type class for types that provide access to an attribute in the -- rendering monad.  You probably won't need to instance this.
src/Brick/Types.hs view
@@ -1,81 +1,172 @@ -- | Basic types used by this library. {-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RankNTypes #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} module Brick.Types-  ( Location(..)+  ( -- * The Widget type+    Widget(..)++    -- * Location types and lenses+  , Location(..)   , locL   , TerminalLocation(..)   , CursorLocation(..)   , cursorLocationL   , cursorLocationNameL++  -- * Viewports+  , Viewport(..)+  , ViewportType(..)+  , vpSize+  , vpTop+  , vpLeft++  -- * Event-handling types+  , EventM+  , Next   , HandleEvent(..)-  , Name(..)+  , handleEventLensed++  -- * Rendering infrastructure+  , RenderM+  , getContext++  -- ** The rendering context+  , Context(ctxAttrName, availWidth, availHeight, ctxBorderStyle, ctxAttrMap)+  , attrL+  , availWidthL+  , availHeightL+  , ctxAttrMapL+  , ctxAttrNameL+  , ctxBorderStyleL++  -- ** Rendering results+  , Result(..)+  , lookupAttrName++  -- ** Rendering result lenses+  , imageL+  , cursorsL+  , visibilityRequestsL++  -- ** Visibility requests+  , VisibilityRequest(..)+  , vrPositionL+  , vrSizeL++  -- * Making lenses   , suffixLenses++  -- * Miscellaneous+  , Size(..)+  , Padding(..)+  , Direction(..)+  , Name(..)+   ) where -import Control.Lens-import Data.String+import Control.Lens (_1, _2, to, (^.), (&), (.~), Lens') import Data.Monoid (Monoid(..))-import Graphics.Vty (Event)+import Control.Monad.Trans.State.Lazy+import Control.Monad.Trans.Reader+import Graphics.Vty (Event, Image, emptyImage, Attr)+import Data.Default (Default(..))+import Data.Functor.Contravariant+import qualified Data.Map as M  import Brick.Types.TH+import Brick.Types.Internal+import Brick.AttrMap (AttrName, attrMapLookup) --- | A terminal screen location.-data Location = Location { loc :: (Int, Int)-                         -- ^ (Column, Row)-                         }-                deriving Show+-- | The type of padding.+data Padding = Pad Int+             -- ^ Pad by the specified number of rows or columns.+             | Max+             -- ^ Pad up to the number of available rows or columns. -suffixLenses ''Location+-- | The class of types that provide some basic event-handling.+class HandleEvent a where+    -- | Handle a Vty event+    handleEvent :: Event -> a -> EventM a -instance Field1 Location Location Int Int where-    _1 = locL._1+-- | A convenience function for handling events intended for values+-- that are targets of lenses in your application state. This function+-- obtains the target value of the specified lens, invokes 'handleEvent'+-- on it, and stores the resulting transformed value back in the state+-- using the lens.+handleEventLensed :: (HandleEvent b)+                  => a+                  -- ^ The state value.+                  -> Lens' a b+                  -- ^ The lens to use to extract and store the target+                  -- of the event.+                  -> Event+                  -- ^ The event to handle.+                  -> EventM a+handleEventLensed v target ev = do+    newB <- handleEvent ev (v^.target)+    return $ v & target .~ newB -instance Field2 Location Location Int Int where-    _2 = locL._2+-- | The monad in which event handlers run. Although it may be tempting+-- to dig into the reader value yourself, just use+-- 'Brick.Main.lookupViewport'.+type EventM a = ReaderT (M.Map Name Viewport) (StateT EventState IO) a --- | The class of types that behave like terminal locations.-class TerminalLocation a where-    -- | Get the column out of the value-    columnL :: Lens' a Int-    column :: a -> Int-    -- | Get the row out of the value-    rowL :: Lens' a Int-    row :: a -> Int+-- | Widget growth policies. These policies communicate to layout+-- algorithms how a widget uses space when being rendered. These+-- policies influence rendering order and space allocation in the box+-- layout algorithm.+data Size = Fixed+          -- ^ Fixed widgets take up the same amount of space no matter+          -- how much they are given (non-greedy).+          | Greedy+          -- ^ Greedy widgets take up all the space they are given.+          deriving (Show, Eq, Ord) -instance TerminalLocation Location where-    columnL = _1-    column (Location t) = fst t-    rowL = _2-    row (Location t) = snd t+-- | The type of widgets.+data Widget =+    Widget { hSize :: Size+           -- ^ This widget's horizontal growth policy+           , vSize :: Size+           -- ^ This widget's vertical growth policy+           , render :: RenderM Result+           -- ^ This widget's rendering function+           } --- | Names of things. Used to name cursor locations, widgets, and--- viewports.-newtype Name = Name String-             deriving (Eq, Show, Ord)+-- | The type of the rendering monad. This monad is used by the+-- library's rendering routines to manage rendering state and+-- communicate rendering parameters to widgets' rendering functions.+type RenderM a = ReaderT Context (State RenderState) a -instance IsString Name where-    fromString = Name+-- | The type of result returned by a widget's rendering function. The+-- result provides the image, cursor positions, and visibility requests+-- that resulted from the rendering process.+data Result =+    Result { image :: Image+           -- ^ The final rendered image for a widget+           , cursors :: [CursorLocation]+           -- ^ The list of reported cursor positions for the+           -- application to choose from+           , visibilityRequests :: [VisibilityRequest]+           -- ^ The list of visibility requests made by widgets rendered+           -- while rendering this one (used by viewports)+           }+           deriving Show --- | The origin (upper-left corner).-origin :: Location-origin = Location (0, 0)+instance Default Result where+    def = Result emptyImage [] [] -instance Monoid Location where-    mempty = origin-    mappend (Location (w1, h1)) (Location (w2, h2)) = Location (w1+w2, h1+h2)+-- | Get the current rendering context.+getContext :: RenderM Context+getContext = ask --- | A cursor location.  These are returned by the rendering process.-data CursorLocation =-    CursorLocation { cursorLocation :: !Location-                   -- ^ The location-                   , cursorLocationName :: !(Maybe Name)-                   -- ^ The name of the widget associated with the location-                   }-                   deriving Show+suffixLenses ''Context+suffixLenses ''Result -suffixLenses ''CursorLocation+-- | The rendering context's current drawing attribute.+attrL :: (Contravariant f, Functor f) => (Attr -> f Attr) -> Context -> f Context+attrL = to (\c -> attrMapLookup (c^.ctxAttrNameL) (c^.ctxAttrMapL))  instance TerminalLocation CursorLocation where     columnL = cursorLocationL._1@@ -83,7 +174,9 @@     rowL = cursorLocationL._2     row = row . cursorLocation --- | The class of types that provide some basic event-handling.-class HandleEvent a where-    -- | Handle a Vty event-    handleEvent :: Event -> a -> a+-- | Given an attribute name, obtain the attribute for the attribute+-- name by consulting the context's attribute map.+lookupAttrName :: AttrName -> RenderM Attr+lookupAttrName n = do+    c <- getContext+    return $ attrMapLookup n (c^.ctxAttrMapL)
+ src/Brick/Types/Internal.hs view
@@ -0,0 +1,166 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE MultiParamTypeClasses #-}+module Brick.Types.Internal+  ( ScrollRequest(..)+  , VisibilityRequest(..)+  , vrPositionL+  , vrSizeL+  , Name(..)+  , Location(..)+  , locL+  , origin+  , TerminalLocation(..)+  , Viewport(..)+  , ViewportType(..)+  , RenderState(..)+  , Direction(..)+  , CursorLocation(..)+  , cursorLocationL+  , cursorLocationNameL+  , Context(..)+  , EventState+  , Next(..)++  , scrollRequestsL+  , viewportMapL+  , vpSize+  , vpLeft+  , vpTop+  )+where++import Control.Lens (Field1, Field2, _1, _2, Lens', makeLenses)+import Data.String+import Data.Monoid+import qualified Data.Map as M+import Graphics.Vty (DisplayRegion)++import Brick.Types.TH+import Brick.AttrMap (AttrName, AttrMap)+import Brick.Widgets.Border.Style (BorderStyle)++-- | Names of things. Used to name cursor locations, widgets, and+-- viewports.+newtype Name = Name String+             deriving (Eq, Show, Ord)++instance IsString Name where+    fromString = Name++data RenderState =+    RS { viewportMap :: M.Map Name Viewport+       , scrollRequests :: [(Name, ScrollRequest)]+       }++data ScrollRequest = HScrollBy Int+                   | HScrollPage Direction+                   | HScrollToBeginning+                   | HScrollToEnd+                   | VScrollBy Int+                   | VScrollPage Direction+                   | VScrollToBeginning+                   | VScrollToEnd++data VisibilityRequest =+    VR { vrPosition :: Location+       , vrSize :: DisplayRegion+       }+       deriving Show++-- | Describes the state of a viewport as it appears as its most recent+-- rendering.+data Viewport =+    VP { _vpLeft :: Int+       -- ^ The column offset of left side of the viewport.+       , _vpTop :: Int+       -- ^ The row offset of the top of the viewport.+       , _vpSize :: DisplayRegion+       -- ^ The size of the viewport.+       }+       deriving Show++-- | The type of viewports that indicates the direction(s) in which a+-- viewport is scrollable.+data ViewportType = Vertical+                  -- ^ Viewports of this type are scrollable only vertically.+                  | Horizontal+                  -- ^ Viewports of this type are scrollable only horizontally.+                  | Both+                  -- ^ Viewports of this type are scrollable vertically and horizontally.+                  deriving Show++type EventState = [(Name, ScrollRequest)]++-- | The type of actions to take upon completion of an event handler.+data Next a = Continue a+            | SuspendAndResume (IO a)+            | Halt a++-- | Scrolling direction.+data Direction = Up+               -- ^ Up/left+               | Down+               -- ^ Down/right++-- | A terminal screen location.+data Location = Location { loc :: (Int, Int)+                         -- ^ (Column, Row)+                         }+                deriving Show++suffixLenses ''Location++instance Field1 Location Location Int Int where+    _1 = locL._1++instance Field2 Location Location Int Int where+    _2 = locL._2++-- | The class of types that behave like terminal locations.+class TerminalLocation a where+    -- | Get the column out of the value+    columnL :: Lens' a Int+    column :: a -> Int+    -- | Get the row out of the value+    rowL :: Lens' a Int+    row :: a -> Int++instance TerminalLocation Location where+    columnL = _1+    column (Location t) = fst t+    rowL = _2+    row (Location t) = snd t++-- | The origin (upper-left corner).+origin :: Location+origin = Location (0, 0)++instance Monoid Location where+    mempty = origin+    mappend (Location (w1, h1)) (Location (w2, h2)) = Location (w1+w2, h1+h2)++-- | A cursor location.  These are returned by the rendering process.+data CursorLocation =+    CursorLocation { cursorLocation :: !Location+                   -- ^ The location+                   , cursorLocationName :: !(Maybe Name)+                   -- ^ The name of the widget associated with the location+                   }+                   deriving Show++-- | The rendering context. This tells widgets how to render: how much+-- space they have in which to render, which attribute they should use+-- to render, which bordring style should be used, and the attribute map+-- available for rendering.+data Context =+    Context { ctxAttrName :: AttrName+            , availWidth :: Int+            , availHeight :: Int+            , ctxBorderStyle :: BorderStyle+            , ctxAttrMap :: AttrMap+            }++suffixLenses ''RenderState+suffixLenses ''VisibilityRequest+suffixLenses ''CursorLocation+makeLenses ''Viewport
src/Brick/Util.hs view
@@ -12,7 +12,7 @@ import Data.Monoid ((<>)) import Graphics.Vty -import Brick.Types (Location(..), CursorLocation(..), cursorLocationL)+import Brick.Types.Internal (Location(..), CursorLocation(..), cursorLocationL)  -- | Given a minimum value and a maximum value, clamp a value to that -- range (values less than the minimum map to the minimum and values
src/Brick/Widgets/Border.hs view
@@ -36,6 +36,7 @@ import Graphics.Vty (imageHeight, imageWidth)  import Brick.AttrMap+import Brick.Types import Brick.Widgets.Core import Brick.Widgets.Center (hCenterWith) import Brick.Widgets.Border.Style (BorderStyle(..))
src/Brick/Widgets/Core.hs view
@@ -1,19 +1,17 @@+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TupleSections #-} -- | This module provides the core widget combinators and rendering -- routines. Everything this library does is in terms of these basic -- primitives. module Brick.Widgets.Core-  ( Widget(..)-  , Size(..)--  -- * Basic rendering primitives-  , emptyWidget+  ( -- * Basic rendering primitives+    emptyWidget   , raw   , txt   , str   , fill    -- * Padding-  , Padding(..)   , padLeft   , padRight   , padTop@@ -54,43 +52,674 @@   , cropBottomBy    -- * Scrollable viewports-  , ViewportType(..)   , viewport   , visible   , visibleRegion -  -- * Rendering infrastructure-  , RenderM-  , getContext-  , lookupAttrName--  -- ** The rendering context-  , Context(ctxAttrName, availWidth, availHeight, ctxBorderStyle, ctxAttrMap)-  , attrL-  , availWidthL-  , availHeightL-  , ctxAttrMapL-  , ctxAttrNameL-  , ctxBorderStyleL--  -- ** Rendering results-  , Result(..)-  -- ** Result lenses-  , imageL-  , cursorsL-  , visibilityRequestsL-  -- ** Visibility requests-  , VisibilityRequest(..)-  , vrPositionL-  , vrSizeL   -- ** Adding offsets to cursor positions and visibility requests   , addResultOffset+   -- ** Cropping results   , cropToContext--  -- * Misc-  , Direction(..)   ) where +import Control.Applicative+import Control.Lens ((^.), (.~), (&), (%~), to, _1, _2, each, to, ix)+import Control.Monad (when)+import Control.Monad.Trans.State.Lazy+import Control.Monad.Trans.Reader+import Control.Monad.Trans.Class (lift)+import qualified Data.Text as T+import Data.Default+import Data.Monoid ((<>), mempty)+import qualified Data.Map as M+import qualified Data.Function as DF+import Data.List (sortBy, partition)+import Control.Lens (Lens')+import qualified Graphics.Vty as V+import Control.DeepSeq++import Brick.Types+import Brick.Types.Internal+import Brick.Widgets.Border.Style+import Brick.Util (clOffset, clamp)+import Brick.AttrMap import Brick.Widgets.Internal++-- | When rendering the specified widget, use the specified border style+-- for any border rendering.+withBorderStyle :: BorderStyle -> Widget -> Widget+withBorderStyle bs p = Widget (hSize p) (vSize p) $ withReaderT (& ctxBorderStyleL .~ bs) (render p)++-- | The empty widget.+emptyWidget :: Widget+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+-- 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+-- something and then translate it or otherwise offset it from its+-- original origin.+addResultOffset :: Location -> Result -> Result+addResultOffset off = addCursorOffset off . addVisibilityOffset off++addVisibilityOffset :: Location -> Result -> Result+addVisibilityOffset off r = r & visibilityRequestsL.each.vrPositionL %~ (off <>)++addCursorOffset :: Location -> Result -> Result+addCursorOffset off r =+    let onlyVisible = filter isVisible+        isVisible l = l^.columnL >= 0 && l^.rowL >= 0+    in r & cursorsL %~ (\cs -> onlyVisible $ (`clOffset` off) <$> cs)++unrestricted :: Int+unrestricted = 100000++-- | Build a widget from a 'String'. Breaks newlines up and space-pads+-- short lines out to the length of the longest line.+str :: String -> Widget+str s =+    Widget Fixed Fixed $ do+      c <- getContext+      let theLines = fixEmpty <$> (dropUnused . lines) s+          fixEmpty [] = " "+          fixEmpty l = l+          dropUnused l = take (availWidth c) <$> take (availHeight c) l+      case force theLines of+          [] -> return def+          [one] -> return $ def & imageL .~ (V.string (c^.attrL) one)+          multiple ->+              let maxLength = maximum $ length <$> multiple+                  lineImgs = lineImg <$> multiple+                  lineImg lStr = V.string (c^.attrL) (lStr ++ replicate (maxLength - length lStr) ' ')+              in return $ def & imageL .~ (V.vertCat lineImgs)++-- | Build a widget from a one-line 'T.Text' value. Behaves the same as+-- 'str'.+txt :: T.Text -> Widget+txt = str . T.unpack++-- | Pad the specified widget on the left. If max padding is used, this+-- grows greedily horizontally; otherwise it defers to the padded+-- widget.+padLeft :: Padding -> Widget -> Widget+padLeft padding p =+    let (f, sz) = case padding of+          Max -> (id, Greedy)+          Pad i -> (hLimit i, hSize p)+    in Widget sz (vSize p) $ do+        result <- render p+        render $ (f $ vLimit (result^.imageL.to V.imageHeight) $ fill ' ') <+>+                 (Widget Fixed Fixed $ return result)++-- | Pad the specified widget on the right. If max padding is used,+-- this grows greedily horizontally; otherwise it defers to the padded+-- widget.+padRight :: Padding -> Widget -> Widget+padRight padding p =+    let (f, sz) = case padding of+          Max -> (id, Greedy)+          Pad i -> (hLimit i, hSize p)+    in Widget sz (vSize p) $ do+        result <- render p+        render $ (Widget Fixed Fixed $ return result) <+>+                 (f $ vLimit (result^.imageL.to V.imageHeight) $ fill ' ')++-- | Pad the specified widget on the top. If max padding is used, this+-- grows greedily vertically; otherwise it defers to the padded widget.+padTop :: Padding -> Widget -> Widget+padTop padding p =+    let (f, sz) = case padding of+          Max -> (id, Greedy)+          Pad i -> (vLimit i, vSize p)+    in Widget (hSize p) sz $ do+        result <- render p+        render $ (f $ hLimit (result^.imageL.to V.imageWidth) $ fill ' ') <=>+                 (Widget Fixed Fixed $ return result)++-- | Pad the specified widget on the bottom. If max padding is used,+-- this grows greedily vertically; otherwise it defers to the padded+-- widget.+padBottom :: Padding -> Widget -> Widget+padBottom padding p =+    let (f, sz) = case padding of+          Max -> (id, Greedy)+          Pad i -> (vLimit i, vSize p)+    in Widget (hSize p) sz $ do+        result <- render p+        render $ (Widget Fixed Fixed $ return result) <=>+                 (f $ hLimit (result^.imageL.to V.imageWidth) $ fill ' ')++-- | Pad a widget on the left and right. Defers to the padded widget for+-- growth policy.+padLeftRight :: Int -> Widget -> Widget+padLeftRight c w = padLeft (Pad c) $ padRight (Pad c) w++-- | Pad a widget on the top and bottom. Defers to the padded widget for+-- growth policy.+padTopBottom :: Int -> Widget -> Widget+padTopBottom r w = padTop (Pad r) $ padBottom (Pad r) w++-- | Pad a widget on all sides. Defers to the padded widget for growth+-- policy.+padAll :: Int -> Widget -> Widget+padAll v w = padLeftRight v $ padTopBottom v w++-- | Fill all available space with the specified character. Grows both+-- horizontally and vertically.+fill :: Char -> Widget+fill ch =+    Widget Greedy Greedy $ do+      c <- getContext+      return $ def & imageL .~ (V.charFill (c^.attrL) ch (c^.availWidthL) (c^.availHeightL))++-- | Vertical box layout: put the specified widgets one above the other+-- in the specified order (uppermost first). Defers growth policies to+-- the growth policies of the contained widgets (if any are greedy, so+-- is the box).+vBox :: [Widget] -> Widget+vBox [] = emptyWidget+vBox pairs = renderBox vBoxRenderer pairs++-- | Horizontal box layout: put the specified widgets next to each other+-- in the specified order (leftmost first). Defers growth policies to+-- the growth policies of the contained widgets (if any are greedy, so+-- is the box).+hBox :: [Widget] -> Widget+hBox [] = emptyWidget+hBox pairs = renderBox hBoxRenderer pairs++-- | The process of rendering widgets in a box layout is exactly the+-- same except for the dimension under consideration (width vs. height),+-- in which case all of the same operations that consider one dimension+-- in the layout algorithm need to be switched to consider the other.+-- Because of this we fill a BoxRenderer with all of the functions+-- needed to consider the "primary" dimension (e.g. vertical if the+-- box layout is vertical) as well as the "secondary" dimension (e.g.+-- horizontal if the box layout is vertical). Doing this permits us to+-- have one implementation for box layout and parameterizing on the+-- orientation of all of the operations.+data BoxRenderer =+    BoxRenderer { contextPrimary :: Lens' Context Int+                , contextSecondary :: Lens' Context Int+                , imagePrimary :: V.Image -> Int+                , imageSecondary :: V.Image -> Int+                , limitPrimary :: Int -> Widget -> Widget+                , limitSecondary :: Int -> Widget -> Widget+                , primaryWidgetSize :: Widget -> Size+                , concatenatePrimary :: [V.Image] -> V.Image+                , locationFromOffset :: Int -> Location+                , padImageSecondary :: Int -> V.Image -> V.Attr -> V.Image+                }++vBoxRenderer :: BoxRenderer+vBoxRenderer =+    BoxRenderer { contextPrimary = availHeightL+                , contextSecondary = availWidthL+                , imagePrimary = V.imageHeight+                , imageSecondary = V.imageWidth+                , limitPrimary = vLimit+                , limitSecondary = hLimit+                , primaryWidgetSize = vSize+                , concatenatePrimary = V.vertCat+                , locationFromOffset = Location . (0 ,)+                , padImageSecondary = \amt img a ->+                    let p = V.charFill a ' ' amt (V.imageHeight img)+                    in V.horizCat [img, p]+                }++hBoxRenderer :: BoxRenderer+hBoxRenderer =+    BoxRenderer { contextPrimary = availWidthL+                , contextSecondary = availHeightL+                , imagePrimary = V.imageWidth+                , imageSecondary = V.imageHeight+                , limitPrimary = hLimit+                , limitSecondary = vLimit+                , primaryWidgetSize = hSize+                , concatenatePrimary = V.horizCat+                , locationFromOffset = Location . (, 0)+                , padImageSecondary = \amt img a ->+                    let p = V.charFill a ' ' (V.imageWidth img) amt+                    in V.vertCat [img, p]+                }++-- | Render a series of widgets in a box layout in the order given.+--+-- The growth policy of a box layout is the most unrestricted of the+-- growth policies of the widgets it contains, so to determine the hSize+-- and vSize of the box we just take the maximum (using the Ord instance+-- for Size) of all of the widgets to be rendered in the box.+--+-- Then the box layout algorithm proceeds as follows. We'll use+-- the vertical case to concretely describe the algorithm, but the+-- horizontal case can be envisioned just by exchanging all+-- "vertical"/"horizontal" and "rows"/"columns", etc., in the+-- description.+--+-- The growth policies of the child widgets determine the order in which+-- they are rendered, i.e., the order in which space in the box is+-- allocated to widgets as the algorithm proceeds. This is because order+-- matters: if we render greedy widgets first, there will be no space+-- left for non-greedy ones.+--+-- So we render all widgets with size 'Fixed' in the vertical dimension+-- first. Each is rendered with as much room as the overall box has, but+-- we assume that they will not be greedy and use it all. If they do,+-- maybe it's because the terminal is small and there just isn't enough+-- room to render everything.+--+-- Then the remaining height is distributed evenly amongst all remaining+-- (greedy) widgets and they are rendered in sub-boxes that are as high+-- as this even slice of rows and as wide as the box is permitted to be.+-- We only do this step at all if rendering the non-greedy widgets left+-- us any space, i.e., if there were any rows left.+--+-- After rendering the non-greedy and then greedy widgets, their images+-- are sorted so that they are stored in the order the original widgets+-- were given. All cursor locations and visibility requests in each+-- sub-widget are translated according to the position of the sub-widget+-- in the box.+--+-- All images are padded to be as wide as the widest sub-widget to+-- prevent attribute over-runs. Without this step the attribute used by+-- a sub-widget may continue on in an undesirable fashion until it hits+-- something with a different attribute. To prevent this and to behave+-- in the least surprising way, we pad the image on the right with+-- whitespace using the context's current attribute.+--+-- Finally, the padded images are concatenated together vertically and+-- returned along with the translated cursor positions and visibility+-- requests.+renderBox :: BoxRenderer -> [Widget] -> Widget+renderBox br ws = do+    Widget (maximum $ hSize <$> ws) (maximum $ vSize <$> ws) $ do+      c <- getContext++      let pairsIndexed = zip [(0::Int)..] ws+          (his, lows) = partition (\p -> (primaryWidgetSize br $ snd p) == Fixed) pairsIndexed++      renderedHis <- mapM (\(i, prim) -> (i,) <$> render prim) his++      renderedLows <- case lows of+          [] -> return []+          ls -> do+              let remainingPrimary = c^.(contextPrimary br) - (sum $ (^._2.imageL.(to $ imagePrimary br)) <$> renderedHis)+                  primaryPerLow = remainingPrimary `div` length ls+                  padFirst = remainingPrimary - (primaryPerLow * length ls)+                  secondaryPerLow = c^.(contextSecondary br)+                  primaries = replicate (length ls) primaryPerLow & ix 0 %~ (+ padFirst)++              let renderLow ((i, prim), pri) =+                      (i,) <$> (render $ limitPrimary br pri+                                       $ limitSecondary br secondaryPerLow+                                       $ cropToContext prim)++              if remainingPrimary > 0 then mapM renderLow (zip ls primaries) else return []++      let rendered = sortBy (compare `DF.on` fst) $ renderedHis ++ renderedLows+          allResults = snd <$> rendered+          allImages = (^.imageL) <$> allResults+          allPrimaries = imagePrimary br <$> allImages+          allTranslatedResults = (flip map) (zip [0..] allResults) $ \(i, result) ->+              let off = locationFromOffset br offPrimary+                  offPrimary = sum $ take i allPrimaries+              in addResultOffset off result+          -- Determine the secondary dimension value to pad to. In a+          -- vertical box we want all images to be the same width to+          -- avoid attribute over-runs or blank spaces with the wrong+          -- attribute. In a horizontal box we want all images to have+          -- the same height for the same reason.+          maxSecondary = maximum $ imageSecondary br <$> allImages+          padImage img = padImageSecondary br (maxSecondary - imageSecondary br img) img (c^.attrL)+          paddedImages = padImage <$> allImages++      cropResultToContext $ Result (concatenatePrimary br paddedImages)+                            (concat $ cursors <$> allTranslatedResults)+                            (concat $ visibilityRequests <$> allTranslatedResults)++-- | Limit the space available to the specified widget to the specified+-- number of columns. This is important for constraining the horizontal+-- growth of otherwise-greedy widgets. This is non-greedy horizontally+-- and defers to the limited widget vertically.+hLimit :: Int -> Widget -> Widget+hLimit w p =+    Widget Fixed (vSize p) $ do+      withReaderT (& availWidthL .~ w) $ render $ cropToContext p++-- | Limit the space available to the specified widget to the specified+-- number of rows. This is important for constraining the vertical+-- growth of otherwise-greedy widgets. This is non-greedy vertically and+-- defers to the limited widget horizontally.+vLimit :: Int -> Widget -> Widget+vLimit h p =+    Widget (hSize p) Fixed $ do+      withReaderT (& availHeightL .~ h) $ render $ cropToContext p++-- | When drawing the specified widget, set the current attribute used+-- for drawing to the one with the specified name. Note that the widget+-- may use further calls to 'withAttr' to override this; if you really+-- want to prevent that, use 'forceAttr'. Attributes used this way still+-- get merged hierarchically and still fall back to the attribute map's+-- default attribute. If you want to change the default attribute, use+-- 'withDefAttr'.+withAttr :: AttrName -> Widget -> Widget+withAttr an p =+    Widget (hSize p) (vSize p) $ do+      withReaderT (& ctxAttrNameL .~ an) (render p)++-- | Update the attribute map while rendering the specified widget: set+-- its new default attribute to the one that we get by looking up the+-- specified attribute name in the map.+withDefAttr :: AttrName -> Widget -> Widget+withDefAttr an p =+    Widget (hSize p) (vSize p) $ do+        c <- getContext+        withReaderT (& ctxAttrMapL %~ (setDefault (attrMapLookup an (c^.ctxAttrMapL)))) (render p)++-- | When rendering the specified widget, update the attribute map with+-- the specified transformation.+updateAttrMap :: (AttrMap -> AttrMap) -> Widget -> Widget+updateAttrMap f p =+    Widget (hSize p) (vSize p) $ do+        withReaderT (& ctxAttrMapL %~ f) (render p)++-- | When rendering the specified widget, force all attribute lookups+-- in the attribute map to use the value currently assigned to the+-- specified attribute name.+forceAttr :: AttrName -> Widget -> Widget+forceAttr an p =+    Widget (hSize p) (vSize p) $ do+        c <- getContext+        withReaderT (& ctxAttrMapL .~ (forceAttrMap (attrMapLookup an (c^.ctxAttrMapL)))) (render p)++-- | Build a widget directly from a raw Vty image.+raw :: V.Image -> Widget+raw img = Widget Fixed Fixed $ return $ def & imageL .~ img++-- | Translate the specified widget by the specified offset amount.+-- Defers to the translated width for growth policy.+translateBy :: Location -> Widget -> Widget+translateBy off p =+    Widget (hSize p) (vSize p) $ do+      result <- render p+      return $ addResultOffset off+             $ result & imageL %~ (V.translate (off^.columnL) (off^.rowL))++-- | Crop the specified widget on the left by the specified number of+-- columns. Defers to the translated width for growth policy.+cropLeftBy :: Int -> Widget -> Widget+cropLeftBy cols p =+    Widget (hSize p) (vSize p) $ do+      result <- render p+      let amt = V.imageWidth (result^.imageL) - cols+          cropped img = if amt < 0 then V.emptyImage else V.cropLeft amt img+      return $ addResultOffset (Location (-1 * cols, 0))+             $ result & imageL %~ cropped++-- | Crop the specified widget on the right by the specified number of+-- columns. Defers to the translated width for growth policy.+cropRightBy :: Int -> Widget -> Widget+cropRightBy cols p =+    Widget (hSize p) (vSize p) $ do+      result <- render p+      let amt = V.imageWidth (result^.imageL) - cols+          cropped img = if amt < 0 then V.emptyImage else V.cropRight amt img+      return $ result & imageL %~ cropped++-- | Crop the specified widget on the top by the specified number of+-- rows. Defers to the translated width for growth policy.+cropTopBy :: Int -> Widget -> Widget+cropTopBy rows p =+    Widget (hSize p) (vSize p) $ do+      result <- render p+      let amt = V.imageHeight (result^.imageL) - rows+          cropped img = if amt < 0 then V.emptyImage else V.cropTop amt img+      return $ addResultOffset (Location (0, -1 * rows))+             $ result & imageL %~ cropped++-- | Crop the specified widget on the bottom by the specified number of+-- rows. Defers to the translated width for growth policy.+cropBottomBy :: Int -> Widget -> Widget+cropBottomBy rows p =+    Widget (hSize p) (vSize p) $ do+      result <- render p+      let amt = V.imageHeight (result^.imageL) - rows+          cropped img = if amt < 0 then V.emptyImage else V.cropBottom amt img+      return $ result & imageL %~ cropped++-- | When rendering the specified widget, also register a cursor+-- positioning request using the specified name and location.+showCursor :: Name -> Location -> Widget -> Widget+showCursor n cloc p =+    Widget (hSize p) (vSize p) $ do+      result <- render p+      return $ result & cursorsL %~ (CursorLocation cloc (Just n):)++hRelease :: Widget -> Maybe Widget+hRelease p =+    case hSize p of+        Fixed -> Just $ Widget Greedy (vSize p) $ withReaderT (& availWidthL .~ unrestricted) (render p)+        Greedy -> Nothing++vRelease :: Widget -> Maybe Widget+vRelease p =+    case vSize p of+        Fixed -> Just $ Widget (hSize p) Greedy $ withReaderT (& availHeightL .~ unrestricted) (render p)+        Greedy -> Nothing++-- | Render the specified widget in a named viewport with the+-- specified type. This permits widgets to be scrolled without being+-- scrolling-aware. To make the most use of viewports, the specified+-- widget should use the 'visible' combinator to make a "visibility+-- request". This viewport combinator will then translate the resulting+-- rendering to make the requested region visible. In addition, the+-- 'Brick.Main.EventM' monad provides primitives to scroll viewports+-- created by this function if 'visible' is not what you want.+--+-- If a viewport receives more than one visibility request, only the+-- first is honored. If a viewport receives more than one scrolling+-- request from 'Brick.Main.EventM', all are honored in the order in+-- which they are received.+viewport :: Name+         -- ^ The name of the viewport (must be unique and stable for+         -- reliable behavior)+         -> ViewportType+         -- ^ The type of viewport (indicates the permitted scrolling+         -- direction)+         -> Widget+         -- ^ The widget to be rendered in the scrollable viewport+         -> Widget+viewport vpname typ p =+    Widget Greedy Greedy $ do+      -- First, update the viewport size.+      c <- getContext+      let newVp = VP 0 0 newSize+          newSize = (c^.availWidthL, c^.availHeightL)+          doInsert (Just vp) = Just $ vp & vpSize .~ newSize+          doInsert Nothing = Just newVp++      lift $ modify (& viewportMapL %~ (M.alter doInsert vpname))++      -- Then render the sub-rendering with the rendering layout+      -- constraint released (but raise an exception if we are asked to+      -- render an infinitely-sized widget in the viewport's scrolling+      -- dimension)+      let Name vpn = vpname+          release = case typ of+            Vertical -> vRelease+            Horizontal -> hRelease+            Both -> \w -> vRelease w >>= hRelease+          released = case release p of+            Just w -> w+            Nothing -> case typ of+                Vertical -> error $ "tried to embed an infinite-height widget in vertical viewport " <> (show vpn)+                Horizontal -> error $ "tried to embed an infinite-width widget in horizontal viewport " <> (show vpn)+                Both -> error $ "tried to embed an infinite-width or infinite-height widget in 'Both' type viewport " <> (show vpn)++      initialResult <- render released++      -- If the rendering state includes any scrolling requests for this+      -- viewport, apply those+      reqs <- lift $ gets $ (^.scrollRequestsL)+      let relevantRequests = snd <$> filter (\(n, _) -> n == vpname) reqs+      when (not $ null relevantRequests) $ do+          Just vp <- lift $ gets $ (^.viewportMapL.to (M.lookup vpname))+          let updatedVp = applyRequests relevantRequests vp+              applyRequests [] v = v+              applyRequests (rq:rqs) v =+                  case typ of+                      Horizontal -> scrollTo typ rq (initialResult^.imageL) $ applyRequests rqs v+                      Vertical -> scrollTo typ rq (initialResult^.imageL) $ applyRequests rqs v+                      Both -> scrollTo Horizontal rq (initialResult^.imageL) $+                              scrollTo Vertical rq (initialResult^.imageL) $+                              applyRequests rqs v+          lift $ modify (& viewportMapL %~ (M.insert vpname updatedVp))+          return ()++      -- If the sub-rendering requested visibility, update the scroll+      -- state accordingly+      when (not $ null $ initialResult^.visibilityRequestsL) $ do+          Just vp <- lift $ gets $ (^.viewportMapL.to (M.lookup vpname))+          let rq = head $ initialResult^.visibilityRequestsL+              updatedVp = case typ of+                  Both -> scrollToView Horizontal rq $ scrollToView Vertical rq vp+                  Horizontal -> scrollToView typ rq vp+                  Vertical -> scrollToView typ rq vp+          lift $ modify (& viewportMapL %~ (M.insert vpname updatedVp))++      -- Get the viewport state now that it has been updated.+      Just vp <- lift $ gets (M.lookup vpname . (^.viewportMapL))++      -- Then perform a translation of the sub-rendering to fit into the+      -- viewport+      translated <- render $ translateBy (Location (-1 * vp^.vpLeft, -1 * vp^.vpTop))+                           $ Widget Fixed Fixed $ return initialResult++      -- Return the translated result with the visibility requests+      -- discarded+      let translatedSize = ( translated^.imageL.to V.imageWidth+                           , translated^.imageL.to V.imageHeight+                           )+      case translatedSize of+          (0, 0) -> return $ translated & imageL .~ (V.charFill (c^.attrL) ' ' (c^.availWidthL) (c^.availHeightL))+                                        & visibilityRequestsL .~ mempty+          _ -> render $ cropToContext+                      $ padBottom Max+                      $ padRight Max+                      $ Widget Fixed Fixed $ return $ translated & visibilityRequestsL .~ mempty++scrollTo :: ViewportType -> ScrollRequest -> V.Image -> Viewport -> Viewport+scrollTo Both _ _ _ = error "BUG: called scrollTo on viewport type 'Both'"+scrollTo Vertical req img vp = vp & vpTop .~ newVStart+    where+        newVStart = clamp 0 (V.imageHeight img - vp^.vpSize._2) adjustedAmt+        adjustedAmt = case req of+            VScrollBy amt -> vp^.vpTop + amt+            VScrollPage Up -> vp^.vpTop - vp^.vpSize._2+            VScrollPage Down -> vp^.vpTop + vp^.vpSize._2+            VScrollToBeginning -> 0+            VScrollToEnd -> V.imageHeight img - vp^.vpSize._2+            _ -> vp^.vpTop+scrollTo Horizontal req img vp = vp & vpLeft .~ newHStart+    where+        newHStart = clamp 0 (V.imageWidth img - vp^.vpSize._1) adjustedAmt+        adjustedAmt = case req of+            HScrollBy amt -> vp^.vpLeft + amt+            HScrollPage Up -> vp^.vpLeft - vp^.vpSize._1+            HScrollPage Down -> vp^.vpLeft + vp^.vpSize._1+            HScrollToBeginning -> 0+            HScrollToEnd -> V.imageWidth img - vp^.vpSize._1+            _ -> vp^.vpLeft++scrollToView :: ViewportType -> VisibilityRequest -> Viewport -> Viewport+scrollToView Both _ _ = error "BUG: called scrollToView on 'Both' type viewport"+scrollToView Vertical rq vp = vp & vpTop .~ newVStart+    where+        curStart = vp^.vpTop+        curEnd = curStart + vp^.vpSize._2+        reqStart = rq^.vrPositionL.rowL++        reqEnd = rq^.vrPositionL.rowL + rq^.vrSizeL._2+        newVStart :: Int+        newVStart = if reqStart < curStart+                   then reqStart+                   else if reqStart > curEnd || reqEnd > curEnd+                        then reqEnd - vp^.vpSize._2+                        else curStart+scrollToView Horizontal rq vp = vp & vpLeft .~ newHStart+    where+        curStart = vp^.vpLeft+        curEnd = curStart + vp^.vpSize._1+        reqStart = rq^.vrPositionL.columnL++        reqEnd = rq^.vrPositionL.columnL + rq^.vrSizeL._1+        newHStart :: Int+        newHStart = if reqStart < curStart+                   then reqStart+                   else if reqStart > curEnd || reqEnd > curEnd+                        then reqEnd - vp^.vpSize._1+                        else curStart++-- | Request that the specified widget be made visible when it is+-- rendered inside a viewport. This permits widgets (whose sizes and+-- positions cannot be known due to being embedded in arbitrary layouts)+-- to make a request for a parent viewport to locate them and scroll+-- enough to put them in view. This, together with 'viewport', is what+-- makes the text editor and list widgets possible without making them+-- deal with the details of scrolling state management.+--+-- This does nothing if not rendered in a viewport.+visible :: Widget -> Widget+visible p =+    Widget (hSize p) (vSize p) $ do+      result <- render p+      let imageSize = ( result^.imageL.to V.imageWidth+                      , result^.imageL.to V.imageHeight+                      )+      -- The size of the image to be made visible in a viewport must have+      -- non-zero size in both dimensions.+      return $ if imageSize^._1 > 0 && imageSize^._2 > 0+               then result & visibilityRequestsL %~ (VR (Location (0, 0)) imageSize :)+               else result++-- | Similar to 'visible', request that a region (with the specified+-- 'Location' as its origin and 'V.DisplayRegion' as its size) be made+-- visible when it is rendered inside a viewport. The 'Location' is+-- relative to the specified widget's upper-left corner of (0, 0).+--+-- This does nothing if not rendered in a viewport.+visibleRegion :: Location -> V.DisplayRegion -> Widget -> Widget+visibleRegion vrloc sz p =+    Widget (hSize p) (vSize p) $ do+      result <- render p+      -- The size of the image to be made visible in a viewport must have+      -- non-zero size in both dimensions.+      return $ if sz^._1 > 0 && sz^._2 > 0+               then result & visibilityRequestsL %~ (VR vrloc sz :)+               else result++-- | Horizontal box layout: put the specified widgets next to each other+-- in the specified order. Defers growth policies to the growth policies+-- of both widgets.  This operator is a binary version of 'hBox'.+(<+>) :: Widget+      -- ^ Left+      -> Widget+      -- ^ Right+      -> Widget+(<+>) a b = hBox [a, b]++-- | Vertical box layout: put the specified widgets one above the other+-- in the specified order. Defers growth policies to the growth policies+-- of both widgets.  This operator is a binary version of 'vBox'.+(<=>) :: Widget+      -- ^ Top+      -> Widget+      -- ^ Bottom+      -> Widget+(<=>) a b = vBox [a, b]
src/Brick/Widgets/Dialog.hs view
@@ -68,9 +68,9 @@ instance HandleEvent (Dialog a) where     handleEvent ev d =         case ev of-            EvKey (KChar '\t') [] -> nextButtonBy 1 d-            EvKey KBackTab [] -> nextButtonBy (-1) d-            _ -> d+            EvKey (KChar '\t') [] -> return $ nextButtonBy 1 d+            EvKey KBackTab [] -> return $ nextButtonBy (-1) d+            _ -> return d  -- | Create a dialog. dialog :: Name@@ -106,7 +106,7 @@ -- | Render a dialog with the specified body widget. renderDialog :: Dialog a -> Widget -> Widget renderDialog d body =-    let buttonPadding = "  "+    let buttonPadding = str "   "         mkButton (i, (s, _)) = let att = if Just i == d^.dialogSelectedIndexL                                          then buttonSelectedAttr                                          else buttonAttr
src/Brick/Widgets/Edit.hs view
@@ -1,8 +1,8 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-}--- | This module provides a basic single-line text editor widget. You'll--- need to embed an 'Editor' in your application state and transform it--- with 'handleEvent' when relevant events arrive. To get the contents+-- | This module provides a basic text editor widget. You'll need to+-- embed an 'Editor' in your application state and transform it with+-- 'handleEvent' when relevant events arrive. To get the contents -- of the editor, just use 'getEditContents'. To modify it, use the -- 'Z.TextZipper' interface with 'applyEdit'. --@@ -72,7 +72,7 @@                   EvKey KRight [] -> Z.moveRight                   EvKey KBS [] -> Z.deletePrevChar                   _ -> id-        in applyEdit f ed+        in return $ applyEdit f ed  -- | Construct an editor. editor :: Name
src/Brick/Widgets/Internal.hs view
@@ -1,226 +1,22 @@-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TypeSynonymInstances #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE TupleSections #-}-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE BangPatterns #-} module Brick.Widgets.Internal-  ( Result(..)-  , visibilityRequestsL-  , imageL-  , cursorsL-  , addResultOffset--  , VisibilityRequest(..)-  , vrPositionL-  , vrSizeL--  , RenderState(..)-  , ScrollRequest(..)-  , Direction(..)--  , renderFinal-  , Widget(..)-  , Size(..)-  , RenderM--  , Context(ctxAttrName, availWidth, availHeight, ctxBorderStyle, ctxAttrMap)-  , lookupAttrName-  , getContext-  , attrL-  , availWidthL-  , availHeightL-  , ctxAttrMapL-  , ctxAttrNameL-  , ctxBorderStyleL+  ( renderFinal   , cropToContext--  , withBorderStyle--  , ViewportType(..)--  , txt-  , str-  , fill--  , Padding(..)-  , padLeft-  , padRight-  , padTop-  , padBottom-  , padLeftRight-  , padTopBottom-  , padAll--  , emptyWidget-  , hBox-  , vBox-  , (<=>)-  , (<+>)--  , hLimit-  , vLimit-  , withDefAttr-  , withAttr-  , forceAttr-  , updateAttrMap-  , raw-  , translateBy-  , cropLeftBy-  , cropRightBy-  , cropTopBy-  , cropBottomBy-  , showCursor-  , viewport-  , visible-  , visibleRegion+  , cropResultToContext   ) where  import Control.Applicative-import Control.Lens (makeLenses, (^.), (.~), (&), (%~), to, _1, _2, each, to, ix)-import Control.Monad (when)+import Control.Lens ((^.), (&), (%~)) import Control.Monad.Trans.State.Lazy import Control.Monad.Trans.Reader-import Control.Monad.Trans.Class (lift)-import qualified Data.Text as T import Data.Default-import Data.Functor.Contravariant-import Data.Monoid ((<>), mempty)-import qualified Data.Map as M-import qualified Data.Function as DF-import Data.List (sortBy, partition)-import Control.Lens (Lens')-import Data.String (IsString(..)) import qualified Graphics.Vty as V  import Brick.Types-import Brick.Widgets.Border.Style-import Brick.Util (clOffset)+import Brick.Types.Internal import Brick.AttrMap-import Brick.Util (clamp) -data VisibilityRequest =-    VR { vrPosition :: Location-       , vrSize :: V.DisplayRegion-       }-       deriving Show---- | The type of viewports that indicates the direction(s) in which a--- viewport is scrollable.-data ViewportType = Vertical-                  -- ^ Viewports of this type are scrollable only vertically.-                  | Horizontal-                  -- ^ Viewports of this type are scrollable only horizontally.-                  | Both-                  -- ^ Viewports of this type are scrollable vertically and horizontally.-                  deriving Show--data Viewport =-    VP { _vpLeft :: Int-       , _vpTop :: Int-       , _vpSize :: V.DisplayRegion-       }-       deriving Show---- | The type of result returned by a widget's rendering function. The--- result provides the image, cursor positions, and visibility requests--- that resulted from the rendering process.-data Result =-    Result { image :: V.Image-           -- ^ The final rendered image for a widget-           , cursors :: [CursorLocation]-           -- ^ The list of reported cursor positions for the-           -- application to choose from-           , visibilityRequests :: [VisibilityRequest]-           -- ^ The list of visibility requests made by widgets rendered-           -- while rendering this one (used by viewports)-           }-           deriving Show---- | The rendering context. This tells widgets how to render: how much--- space they have in which to render, which attribute they should use--- to render, which bordring style should be used, and the attribute map--- available for rendering.-data Context =-    Context { ctxAttrName :: AttrName-            , availWidth :: Int-            , availHeight :: Int-            , ctxBorderStyle :: BorderStyle-            , ctxAttrMap :: AttrMap-            }---- | The type of the rendering monad. This monad is used by the--- library's rendering routines to manage rendering state and--- communicate rendering parameters to widgets' rendering functions.-type RenderM a = ReaderT Context (State RenderState) a---- | Widget growth policies. These policies communicate to layout--- algorithms how a widget uses space when being rendered. These--- policies influence rendering order and space allocation in the box--- layout algorithm.-data Size = Fixed-          -- ^ Fixed widgets take up the same amount of space no matter-          -- how much they are given (non-greedy).-          | Greedy-          -- ^ Greedy widgets take up all the space they are given.-          deriving (Show, Eq, Ord)---- | The type of widgets.-data Widget =-    Widget { hSize :: Size-           -- ^ This widget's horizontal growth policy-           , vSize :: Size-           -- ^ This widget's vertical growth policy-           , render :: RenderM Result-           -- ^ This widget's rendering function-           }---- | Scrolling direction.-data Direction = Up-               -- ^ Up/left-               | Down-               -- ^ Down/right--data ScrollRequest = HScrollBy Int-                   | HScrollPage Direction-                   | HScrollToBeginning-                   | HScrollToEnd-                   | VScrollBy Int-                   | VScrollPage Direction-                   | VScrollToBeginning-                   | VScrollToEnd--data RenderState =-    RS { viewportMap :: M.Map Name Viewport-       , scrollRequests :: [(Name, ScrollRequest)]-       }--suffixLenses ''Result-suffixLenses ''Context-suffixLenses ''VisibilityRequest-suffixLenses ''RenderState--makeLenses ''Viewport--instance IsString Widget where-    fromString = str--instance Default Result where-    def = Result V.emptyImage [] []---- | Get the current rendering context.-getContext :: RenderM Context-getContext = ask---- | When rendering the specified widget, use the specified border style--- for any border rendering.-withBorderStyle :: BorderStyle -> Widget -> Widget-withBorderStyle bs p = Widget (hSize p) (vSize p) $ withReaderT (& ctxBorderStyleL .~ bs) (render p)---- | The empty widget.-emptyWidget :: Widget-emptyWidget = raw V.emptyImage- renderFinal :: AttrMap             -> [Widget]             -> V.DisplayRegion@@ -229,7 +25,7 @@             -> (RenderState, V.Picture, Maybe CursorLocation) renderFinal aMap layerRenders sz chooseCursor rs = (newRS, pic, theCursor)     where-        (layerResults, newRS) = flip runState rs $ sequence $+        (layerResults, !newRS) = flip runState rs $ sequence $             (\p -> runReaderT p ctx) <$>             (render <$> cropToContext <$> layerRenders)         ctx = Context def (fst sz) (snd sz) def aMap@@ -237,641 +33,13 @@         layerCursors = (^.cursorsL) <$> layerResults         theCursor = chooseCursor $ concat layerCursors --- | 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--- 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--- something and then translate it or otherwise offset it from its--- original origin.-addResultOffset :: Location -> Result -> Result-addResultOffset off = addCursorOffset off . addVisibilityOffset off--addVisibilityOffset :: Location -> Result -> Result-addVisibilityOffset off r = r & visibilityRequestsL.each.vrPositionL %~ (off <>)--addCursorOffset :: Location -> Result -> Result-addCursorOffset off r =-    let onlyVisible = filter isVisible-        isVisible l = l^.columnL >= 0 && l^.rowL >= 0-    in r & cursorsL %~ (\cs -> onlyVisible $ (`clOffset` off) <$> cs)--unrestricted :: Int-unrestricted = 100000---- | The rendering context's current drawing attribute.-attrL :: (Contravariant f, Functor f) => (V.Attr -> f V.Attr) -> Context -> f Context-attrL = to (\c -> attrMapLookup (c^.ctxAttrNameL) (c^.ctxAttrMapL))---- | Given an attribute name, obtain the attribute for the attribute--- name by consulting the context's attribute map.-lookupAttrName :: AttrName -> RenderM V.Attr-lookupAttrName n = do-    c <- getContext-    return $ attrMapLookup n (c^.ctxAttrMapL)---- | Build a widget from a 'String'. Breaks newlines up and space-pads--- short lines out to the length of the longest line.-str :: String -> Widget-str s =-    Widget Fixed Fixed $ do-      c <- getContext-      let theLines = lines s-          fixEmpty [] = " "-          fixEmpty l = l-      case fixEmpty <$> theLines of-          [] -> return def-          [one] -> return $ def & imageL .~ (V.string (c^.attrL) one)-          multiple ->-              let maxLength = maximum $ length <$> multiple-                  lineImgs = lineImg <$> multiple-                  lineImg lStr = V.string (c^.attrL) (lStr ++ replicate (maxLength - length lStr) ' ')-              in return $ def & imageL .~ (V.vertCat lineImgs)---- | Build a widget from a 'T.Text' value.  Behaves the same as 'str'.-txt :: T.Text -> Widget-txt = str . T.unpack---- | The type of padding.-data Padding = Pad Int-             -- ^ Pad by the specified number of rows or columns.-             | Max-             -- ^ Pad up to the number of available rows or columns.---- | Pad the specified widget on the left.-padLeft :: Padding -> Widget -> Widget-padLeft padding p =-    let (f, sz) = case padding of-          Max -> (id, Greedy)-          Pad i -> (hLimit i, hSize p)-    in Widget sz (vSize p) $ do-        result <- render p-        render $ (f $ vLimit (result^.imageL.to V.imageHeight) $ fill ' ') <+>-                 (Widget Fixed Fixed $ return result)---- | Pad the specified widget on the right.-padRight :: Padding -> Widget -> Widget-padRight padding p =-    let (f, sz) = case padding of-          Max -> (id, Greedy)-          Pad i -> (hLimit i, hSize p)-    in Widget sz (vSize p) $ do-        result <- render p-        render $ (Widget Fixed Fixed $ return result) <+>-                 (f $ vLimit (result^.imageL.to V.imageHeight) $ fill ' ')---- | Pad the specified widget on the top.-padTop :: Padding -> Widget -> Widget-padTop padding p =-    let (f, sz) = case padding of-          Max -> (id, Greedy)-          Pad i -> (vLimit i, vSize p)-    in Widget (hSize p) sz $ do-        result <- render p-        render $ (f $ hLimit (result^.imageL.to V.imageWidth) $ fill ' ') <=>-                 (Widget Fixed Fixed $ return result)---- | Pad the specified widget on the bottom.-padBottom :: Padding -> Widget -> Widget-padBottom padding p =-    let (f, sz) = case padding of-          Max -> (id, Greedy)-          Pad i -> (vLimit i, vSize p)-    in Widget (hSize p) sz $ do-        result <- render p-        render $ (Widget Fixed Fixed $ return result) <=>-                 (f $ hLimit (result^.imageL.to V.imageWidth) $ fill ' ')---- | Pad a widget on the left and right.-padLeftRight :: Int -> Widget -> Widget-padLeftRight c w = padLeft (Pad c) $ padRight (Pad c) w---- | Pad a widget on the top and bottom.-padTopBottom :: Int -> Widget -> Widget-padTopBottom r w = padTop (Pad r) $ padBottom (Pad r) w---- | Pad a widget on all sides.-padAll :: Int -> Widget -> Widget-padAll v w = padLeftRight v $ padTopBottom v w---- | Fill all available space with the specified character. Grows both--- horizontally and vertically.-fill :: Char -> Widget-fill ch =-    Widget Greedy Greedy $ do-      c <- getContext-      return $ def & imageL .~ (V.charFill (c^.attrL) ch (c^.availWidthL) (c^.availHeightL))---- | Vertical box layout: put the specified widgets one above the other--- in the specified order (uppermost first). Defers growth policies to--- the growth policies of both widgets.-vBox :: [Widget] -> Widget-vBox [] = emptyWidget-vBox pairs = renderBox vBoxRenderer pairs---- | Horizontal box layout: put the specified widgets next to each other--- in the specified order (leftmost first). Defers growth policies to--- the growth policies of both widgets.-hBox :: [Widget] -> Widget-hBox [] = emptyWidget-hBox pairs = renderBox hBoxRenderer pairs---- | The process of rendering widgets in a box layout is exactly the--- same except for the dimension under consideration (width vs. height),--- in which case all of the same operations that consider one dimension--- in the layout algorithm need to be switched to consider the other.--- Because of this we fill a BoxRenderer with all of the functions--- needed to consider the "primary" dimension (e.g. vertical if the--- box layout is vertical) as well as the "secondary" dimension (e.g.--- horizontal if the box layout is vertical). Doing this permits us to--- have one implementation for box layout and parameterizing on the--- orientation of all of the operations.-data BoxRenderer =-    BoxRenderer { contextPrimary :: Lens' Context Int-                , contextSecondary :: Lens' Context Int-                , imagePrimary :: V.Image -> Int-                , imageSecondary :: V.Image -> Int-                , limitPrimary :: Int -> Widget -> Widget-                , limitSecondary :: Int -> Widget -> Widget-                , primaryWidgetSize :: Widget -> Size-                , concatenatePrimary :: [V.Image] -> V.Image-                , locationFromOffset :: Int -> Location-                , padImageSecondary :: Int -> V.Image -> V.Attr -> V.Image-                }--vBoxRenderer :: BoxRenderer-vBoxRenderer =-    BoxRenderer { contextPrimary = availHeightL-                , contextSecondary = availWidthL-                , imagePrimary = V.imageHeight-                , imageSecondary = V.imageWidth-                , limitPrimary = vLimit-                , limitSecondary = hLimit-                , primaryWidgetSize = vSize-                , concatenatePrimary = V.vertCat-                , locationFromOffset = Location . (0 ,)-                , padImageSecondary = \amt img a ->-                    let p = V.charFill a ' ' amt (V.imageHeight img)-                    in V.horizCat [img, p]-                }--hBoxRenderer :: BoxRenderer-hBoxRenderer =-    BoxRenderer { contextPrimary = availWidthL-                , contextSecondary = availHeightL-                , imagePrimary = V.imageWidth-                , imageSecondary = V.imageHeight-                , limitPrimary = hLimit-                , limitSecondary = vLimit-                , primaryWidgetSize = hSize-                , concatenatePrimary = V.horizCat-                , locationFromOffset = Location . (, 0)-                , padImageSecondary = \amt img a ->-                    let p = V.charFill a ' ' (V.imageWidth img) amt-                    in V.vertCat [img, p]-                }---- | Render a series of widgets in a box layout in the order given.------ The growth policy of a box layout is the most unrestricted of the--- growth policies of the widgets it contains, so to determine the hSize--- and vSize of the box we just take the maximum (using the Ord instance--- for Size) of all of the widgets to be rendered in the box.------ Then the box layout algorithm proceeds as follows. We'll use--- the vertical case to concretely describe the algorithm, but the--- horizontal case can be envisioned just by exchanging all--- "vertical"/"horizontal" and "rows"/"columns", etc., in the--- description.------ The growth policies of the child widgets determine the order in which--- they are rendered, i.e., the order in which space in the box is--- allocated to widgets as the algorithm proceeds. This is because order--- matters: if we render greedy widgets first, there will be no space--- left for non-greedy ones.------ So we render all widgets with size 'Fixed' in the vertical dimension--- first. Each is rendered with as much room as the overall box has, but--- we assume that they will not be greedy and use it all. If they do,--- maybe it's because the terminal is small and there just isn't enough--- room to render everything.------ Then the remaining height is distributed evenly amongst all remaining--- (greedy) widgets and they are rendered in sub-boxes that are as high--- as this even slice of rows and as wide as the box is permitted to be.--- We only do this step at all if rendering the non-greedy widgets left--- us any space, i.e., if there were any rows left.------ After rendering the non-greedy and then greedy widgets, their images--- are sorted so that they are stored in the order the original widgets--- were given. All cursor locations and visibility requests in each--- sub-widget are translated according to the position of the sub-widget--- in the box.------ All images are padded to be as wide as the widest sub-widget to--- prevent attribute over-runs. Without this step the attribute used by--- a sub-widget may continue on in an undesirable fashion until it hits--- something with a different attribute. To prevent this and to behave--- in the least surprising way, we pad the image on the right with--- whitespace using the context's current attribute.------ Finally, the padded images are concatenated together vertically and--- returned along with the translated cursor positions and visibility--- requests.-renderBox :: BoxRenderer -> [Widget] -> Widget-renderBox br ws = do-    Widget (maximum $ hSize <$> ws) (maximum $ vSize <$> ws) $ do-      c <- getContext--      let pairsIndexed = zip [(0::Int)..] ws-          (his, lows) = partition (\p -> (primaryWidgetSize br $ snd p) == Fixed) pairsIndexed--      renderedHis <- mapM (\(i, prim) -> (i,) <$> render prim) his--      renderedLows <- case lows of-          [] -> return []-          ls -> do-              let remainingPrimary = c^.(contextPrimary br) - (sum $ (^._2.imageL.(to $ imagePrimary br)) <$> renderedHis)-                  primaryPerLow = remainingPrimary `div` length ls-                  padFirst = remainingPrimary - (primaryPerLow * length ls)-                  secondaryPerLow = c^.(contextSecondary br)-                  primaries = replicate (length ls) primaryPerLow & ix 0 %~ (+ padFirst)--              let renderLow ((i, prim), pri) =-                      (i,) <$> (render $ limitPrimary br pri-                                       $ limitSecondary br secondaryPerLow-                                       $ cropToContext prim)--              if remainingPrimary > 0 then mapM renderLow (zip ls primaries) else return []--      let rendered = sortBy (compare `DF.on` fst) $ renderedHis ++ renderedLows-          allResults = snd <$> rendered-          allImages = (^.imageL) <$> allResults-          allPrimaries = imagePrimary br <$> allImages-          allTranslatedResults = (flip map) (zip [0..] allResults) $ \(i, result) ->-              let off = locationFromOffset br offPrimary-                  offPrimary = sum $ take i allPrimaries-              in addResultOffset off result-          -- Determine the secondary dimension value to pad to. In a-          -- vertical box we want all images to be the same width to-          -- avoid attribute over-runs or blank spaces with the wrong-          -- attribute. In a horizontal box we want all images to have-          -- the same height for the same reason.-          maxSecondary = maximum $ imageSecondary br <$> allImages-          padImage img = padImageSecondary br (maxSecondary - imageSecondary br img) img (c^.attrL)-          paddedImages = padImage <$> allImages--      cropResultToContext $ Result (concatenatePrimary br paddedImages)-                            (concat $ cursors <$> allTranslatedResults)-                            (concat $ visibilityRequests <$> allTranslatedResults)---- | Limit the space available to the specified widget to the specified--- number of columns. This is important for constraining the horizontal--- growth of otherwise-greedy widgets.-hLimit :: Int -> Widget -> Widget-hLimit w p =-    Widget Fixed (vSize p) $ do-      withReaderT (& availWidthL .~ w) $ render $ cropToContext p---- | Limit the space available to the specified widget to the specified--- number of rows. This is important for constraining the vertical--- growth of otherwise-greedy widgets.-vLimit :: Int -> Widget -> Widget-vLimit h p =-    Widget (hSize p) Fixed $ do-      withReaderT (& availHeightL .~ h) $ render $ cropToContext p---- | When drawing the specified widget, set the current attribute used--- for drawing to the one with the specified name. Note that the widget--- may use further calls to 'withAttr' to override this; if you really--- want to prevent that, use 'forceAttr'. Attributes used this way still--- get merged hierarchically and still fall back to the attribute map's--- default attribute. If you want to change the default attribute, use--- 'withDefAttr'.-withAttr :: AttrName -> Widget -> Widget-withAttr an p =-    Widget (hSize p) (vSize p) $ do-      withReaderT (& ctxAttrNameL .~ an) (render p)---- | Update the attribute map while rendering the specified widget: set--- its new default attribute to the one that we get by looking up the--- specified attribute name in the map.-withDefAttr :: AttrName -> Widget -> Widget-withDefAttr an p =-    Widget (hSize p) (vSize p) $ do-        c <- getContext-        withReaderT (& ctxAttrMapL %~ (setDefault (attrMapLookup an (c^.ctxAttrMapL)))) (render p)---- | When rendering the specified widget, update the attribute map with--- the specified transformation.-updateAttrMap :: (AttrMap -> AttrMap) -> Widget -> Widget-updateAttrMap f p =-    Widget (hSize p) (vSize p) $ do-        withReaderT (& ctxAttrMapL %~ f) (render p)---- | When rendering the specified widget, force all attribute lookups--- in the attribute map to use the value currently assigned to the--- specified attribute name.-forceAttr :: AttrName -> Widget -> Widget-forceAttr an p =-    Widget (hSize p) (vSize p) $ do-        c <- getContext-        withReaderT (& ctxAttrMapL .~ (forceAttrMap (attrMapLookup an (c^.ctxAttrMapL)))) (render p)---- | Build a widget directly from a raw Vty image.-raw :: V.Image -> Widget-raw img = Widget Fixed Fixed $ return $ def & imageL .~ img---- | Translate the specified widget by the specified offset amount.-translateBy :: Location -> Widget -> Widget-translateBy off p =-    Widget (hSize p) (vSize p) $ do-      result <- render p-      return $ addResultOffset off-             $ result & imageL %~ (V.translate (off^.columnL) (off^.rowL))--cropResultToContext :: Result -> RenderM Result-cropResultToContext result = do-    c <- getContext-    return $ result & imageL %~ (V.crop (c^.availWidthL) (c^.availHeightL))- -- | After rendering the specified widget, crop its result image to the -- dimensions in the rendering context. cropToContext :: Widget -> Widget cropToContext p =     Widget (hSize p) (vSize p) $ (render p >>= cropResultToContext) --- | Crop the specified widget on the left by the specified number of--- columns.-cropLeftBy :: Int -> Widget -> Widget-cropLeftBy cols p =-    Widget (hSize p) (vSize p) $ do-      result <- render p-      let amt = V.imageWidth (result^.imageL) - cols-          cropped img = if amt < 0 then V.emptyImage else V.cropLeft amt img-      return $ addResultOffset (Location (-1 * cols, 0))-             $ result & imageL %~ cropped---- | Crop the specified widget on the right by the specified number of--- columns.-cropRightBy :: Int -> Widget -> Widget-cropRightBy cols p =-    Widget (hSize p) (vSize p) $ do-      result <- render p-      let amt = V.imageWidth (result^.imageL) - cols-          cropped img = if amt < 0 then V.emptyImage else V.cropRight amt img-      return $ result & imageL %~ cropped---- | Crop the specified widget on the top by the specified number of--- rows.-cropTopBy :: Int -> Widget -> Widget-cropTopBy rows p =-    Widget (hSize p) (vSize p) $ do-      result <- render p-      let amt = V.imageHeight (result^.imageL) - rows-          cropped img = if amt < 0 then V.emptyImage else V.cropTop amt img-      return $ addResultOffset (Location (0, -1 * rows))-             $ result & imageL %~ cropped---- | Crop the specified widget on the bottom by the specified number of--- rows.-cropBottomBy :: Int -> Widget -> Widget-cropBottomBy rows p =-    Widget (hSize p) (vSize p) $ do-      result <- render p-      let amt = V.imageHeight (result^.imageL) - rows-          cropped img = if amt < 0 then V.emptyImage else V.cropBottom amt img-      return $ result & imageL %~ cropped---- | When rendering the specified widget, also register a cursor--- positioning request using the specified name and location.-showCursor :: Name -> Location -> Widget -> Widget-showCursor n cloc p =-    Widget (hSize p) (vSize p) $ do-      result <- render p-      return $ result & cursorsL %~ (CursorLocation cloc (Just n):)--hRelease :: Widget -> Maybe Widget-hRelease p =-    case hSize p of-        Fixed -> Just $ Widget Greedy (vSize p) $ withReaderT (& availWidthL .~ unrestricted) (render p)-        Greedy -> Nothing--vRelease :: Widget -> Maybe Widget-vRelease p =-    case vSize p of-        Fixed -> Just $ Widget (hSize p) Greedy $ withReaderT (& availHeightL .~ unrestricted) (render p)-        Greedy -> Nothing---- | Render the specified widget in a named viewport with the--- specified type. This permits widgets to be scrolled without being--- scrolling-aware. To make the most use of viewports, the specified--- widget should use the 'visible' combinator to make a "visibility--- request". This viewport combinator will then translate the resulting--- rendering to make the requested region visible. In addition, the--- 'Brick.Main.EventM' monad provides primitives to scroll viewports--- created by this function if 'visible' is not what you want.------ If a viewport receives more than one visibility request, only the--- first is honored. If a viewport receives more than one scrolling--- request from 'Brick.Main.EventM', all are honored in the order in--- which they are received.-viewport :: Name-         -- ^ The name of the viewport (must be unique and stable for-         -- reliable behavior)-         -> ViewportType-         -- ^ The type of viewport (indicates the permitted scrolling-         -- direction)-         -> Widget-         -- ^ The widget to be rendered in the scrollable viewport-         -> Widget-viewport vpname typ p =-    Widget Greedy Greedy $ do-      -- First, update the viewport size.-      c <- getContext-      let newVp = VP 0 0 newSize-          newSize = (c^.availWidthL, c^.availHeightL)-          doInsert (Just vp) = Just $ vp & vpSize .~ newSize-          doInsert Nothing = Just newVp--      lift $ modify (& viewportMapL %~ (M.alter doInsert vpname))--      -- Then render the sub-rendering with the rendering layout-      -- constraint released (but raise an exception if we are asked to-      -- render an infinitely-sized widget in the viewport's scrolling-      -- dimension)-      let Name vpn = vpname-          release = case typ of-            Vertical -> vRelease-            Horizontal -> hRelease-            Both -> \w -> vRelease w >>= hRelease-          released = case release p of-            Just w -> w-            Nothing -> case typ of-                Vertical -> error $ "tried to embed an infinite-height widget in vertical viewport " <> (show vpn)-                Horizontal -> error $ "tried to embed an infinite-width widget in horizontal viewport " <> (show vpn)-                Both -> error $ "tried to embed an infinite-width or infinite-height widget in 'Both' type viewport " <> (show vpn)--      initialResult <- render released--      -- If the sub-rendering requested visibility, update the scroll-      -- state accordingly-      when (not $ null $ initialResult^.visibilityRequestsL) $ do-          Just vp <- lift $ gets $ (^.viewportMapL.to (M.lookup vpname))-          let rq = head $ initialResult^.visibilityRequestsL-              updatedVp = case typ of-                  Both -> scrollToView Horizontal rq $ scrollToView Vertical rq vp-                  Horizontal -> scrollToView typ rq vp-                  Vertical -> scrollToView typ rq vp-          lift $ modify (& viewportMapL %~ (M.insert vpname updatedVp))--      -- If the rendering state includes any scrolling requests for this-      -- viewport, apply those-      reqs <- lift $ gets $ (^.scrollRequestsL)-      let relevantRequests = snd <$> filter (\(n, _) -> n == vpname) reqs-      when (not $ null relevantRequests) $ do-          Just vp <- lift $ gets $ (^.viewportMapL.to (M.lookup vpname))-          let updatedVp = applyRequests relevantRequests vp-              applyRequests [] v = v-              applyRequests (rq:rqs) v =-                  case typ of-                      Horizontal -> scrollTo typ rq (initialResult^.imageL) $ applyRequests rqs v-                      Vertical -> scrollTo typ rq (initialResult^.imageL) $ applyRequests rqs v-                      Both -> scrollTo Horizontal rq (initialResult^.imageL) $-                              scrollTo Vertical rq (initialResult^.imageL) $-                              applyRequests rqs v-          lift $ modify (& viewportMapL %~ (M.insert vpname updatedVp))-          return ()--      -- Get the viewport state now that it has been updated.-      Just vp <- lift $ gets (M.lookup vpname . (^.viewportMapL))--      -- Then perform a translation of the sub-rendering to fit into the-      -- viewport-      translated <- render $ translateBy (Location (-1 * vp^.vpLeft, -1 * vp^.vpTop))-                           $ Widget Fixed Fixed $ return initialResult--      -- Return the translated result with the visibility requests-      -- discarded-      let translatedSize = ( translated^.imageL.to V.imageWidth-                           , translated^.imageL.to V.imageHeight-                           )-      case translatedSize of-          (0, 0) -> return $ translated & imageL .~ (V.charFill (c^.attrL) ' ' (c^.availWidthL) (c^.availHeightL))-                                        & visibilityRequestsL .~ mempty-          _ -> render $ cropToContext-                      $ padBottom Max-                      $ padRight Max-                      $ Widget Fixed Fixed $ return $ translated & visibilityRequestsL .~ mempty--scrollTo :: ViewportType -> ScrollRequest -> V.Image -> Viewport -> Viewport-scrollTo Both _ _ _ = error "BUG: called scrollTo on viewport type 'Both'"-scrollTo Vertical req img vp = vp & vpTop .~ newVStart-    where-        newVStart = clamp 0 (V.imageHeight img - vp^.vpSize._2) adjustedAmt-        adjustedAmt = case req of-            VScrollBy amt -> vp^.vpTop + amt-            VScrollPage Up -> vp^.vpTop - vp^.vpSize._2-            VScrollPage Down -> vp^.vpTop + vp^.vpSize._2-            VScrollToBeginning -> 0-            VScrollToEnd -> V.imageHeight img - vp^.vpSize._2-            _ -> vp^.vpTop-scrollTo Horizontal req img vp = vp & vpLeft .~ newHStart-    where-        newHStart = clamp 0 (V.imageWidth img - vp^.vpSize._1) adjustedAmt-        adjustedAmt = case req of-            HScrollBy amt -> vp^.vpLeft + amt-            HScrollPage Up -> vp^.vpLeft - vp^.vpSize._1-            HScrollPage Down -> vp^.vpLeft + vp^.vpSize._1-            HScrollToBeginning -> 0-            HScrollToEnd -> V.imageWidth img - vp^.vpSize._1-            _ -> vp^.vpLeft--scrollToView :: ViewportType -> VisibilityRequest -> Viewport -> Viewport-scrollToView Both _ _ = error "BUG: called scrollToView on 'Both' type viewport"-scrollToView Vertical rq vp = vp & vpTop .~ newVStart-    where-        curStart = vp^.vpTop-        curEnd = curStart + vp^.vpSize._2-        reqStart = rq^.vrPositionL.rowL--        reqEnd = rq^.vrPositionL.rowL + rq^.vrSizeL._2-        newVStart :: Int-        newVStart = if reqStart < curStart-                   then reqStart-                   else if reqStart > curEnd || reqEnd > curEnd-                        then reqEnd - vp^.vpSize._2-                        else curStart-scrollToView Horizontal rq vp = vp & vpLeft .~ newHStart-    where-        curStart = vp^.vpLeft-        curEnd = curStart + vp^.vpSize._1-        reqStart = rq^.vrPositionL.columnL--        reqEnd = rq^.vrPositionL.columnL + rq^.vrSizeL._1-        newHStart :: Int-        newHStart = if reqStart < curStart-                   then reqStart-                   else if reqStart > curEnd || reqEnd > curEnd-                        then reqEnd - vp^.vpSize._1-                        else curStart---- | Request that the specified widget be made visible when it is--- rendered inside a viewport. This permits widgets (whose sizes and--- positions cannot be known due to being embedded in arbitrary layouts)--- to make a request for a parent viewport to locate them and scroll--- enough to put them in view. This, together with 'viewport', is what--- makes the text editor and list widgets possible without making them--- deal with the details of scrolling state management.------ This does nothing if not rendered in a viewport.-visible :: Widget -> Widget-visible p =-    Widget (hSize p) (vSize p) $ do-      result <- render p-      let imageSize = ( result^.imageL.to V.imageWidth-                      , result^.imageL.to V.imageHeight-                      )-      -- The size of the image to be made visible in a viewport must have-      -- non-zero size in both dimensions.-      return $ if imageSize^._1 > 0 && imageSize^._2 > 0-               then result & visibilityRequestsL %~ (VR (Location (0, 0)) imageSize :)-               else result---- | Similar to 'visible', request that a region (with the specified--- 'Location' as its origin and 'V.DisplayRegion' as its size) be made--- visible when it is rendered inside a viewport. The 'Location' is--- relative to the specified widget's upper-left corner of (0, 0).------ This does nothing if not rendered in a viewport.-visibleRegion :: Location -> V.DisplayRegion -> Widget -> Widget-visibleRegion vrloc sz p =-    Widget (hSize p) (vSize p) $ do-      result <- render p-      -- The size of the image to be made visible in a viewport must have-      -- non-zero size in both dimensions.-      return $ if sz^._1 > 0 && sz^._2 > 0-               then result & visibilityRequestsL %~ (VR vrloc sz :)-               else result---- | Horizontal box layout: put the specified widgets next to each other--- in the specified order. Defers growth policies to the growth policies--- of both widgets.  This operator is a binary version of 'hBox'.-(<+>) :: Widget-      -- ^ Left-      -> Widget-      -- ^ Right-      -> Widget-(<+>) a b = hBox [a, b]---- | Vertical box layout: put the specified widgets one above the other--- in the specified order. Defers growth policies to the growth policies--- of both widgets.  This operator is a binary version of 'vBox'.-(<=>) :: Widget-      -- ^ Top-      -> Widget-      -- ^ Bottom-      -> Widget-(<=>) a b = vBox [a, b]+cropResultToContext :: Result -> RenderM Result+cropResultToContext result = do+    c <- getContext+    return $ result & imageL %~ (V.crop (c^.availWidthL) (c^.availHeightL))
src/Brick/Widgets/List.hs view
@@ -4,7 +4,7 @@ -- | This module provides a scrollable list type and functions for -- manipulating and rendering it. module Brick.Widgets.List-  ( List(listElements, listSelected, listName, listElementDraw)+  ( List(listElements, listSelected, listName, listItemHeight)    -- * Consructing a list   , list@@ -16,6 +16,7 @@   , listElementsL   , listSelectedL   , listNameL+  , listItemHeightL    -- * Manipulating a list   , listMoveBy@@ -34,13 +35,15 @@ where  import Control.Applicative ((<$>))-import Control.Lens ((^.), (&), (.~))+import Control.Lens ((^.), (&), (.~), _2) import Data.Monoid ((<>)) import Data.Maybe (fromMaybe) import qualified Data.Algorithm.Diff as D import Graphics.Vty (Event(..), Key(..))+import qualified Data.Vector as V  import Brick.Types+import Brick.Main (lookupViewport) import Brick.Widgets.Core import Brick.Util (clamp) import Brick.AttrMap@@ -49,22 +52,38 @@ -- by the list.  Lists handle the following events by default: -- -- * Up/down arrow keys: move cursor of selected item+-- * Page up / page down keys: move cursor of selected item by one page+--   at a time (based on the number of items shown)+-- * Home/end keys: move cursor of selected item to beginning or end of+--   list data List e =-    List { listElements :: ![e]-         , listElementDraw :: Bool -> e -> Widget+    List { listElements :: !(V.Vector e)          , listSelected :: !(Maybe Int)          , listName :: Name+         , listItemHeight :: Int          }  suffixLenses ''List  instance HandleEvent (List e) where-    handleEvent e theList = f theList+    handleEvent e theList = f         where             f = case e of-                  EvKey KUp [] -> listMoveUp-                  EvKey KDown [] -> listMoveDown-                  _ -> id+                  EvKey KUp [] -> return $ listMoveUp theList+                  EvKey KDown [] -> return $ listMoveDown theList+                  EvKey KHome [] -> return $ listMoveTo 0 theList+                  EvKey KEnd [] -> return $ listMoveTo (V.length $ listElements theList) theList+                  EvKey KPageDown [] -> do+                      v <- lookupViewport (theList^.listNameL)+                      case v of+                          Nothing -> return theList+                          Just vp -> return $ listMoveBy (vp^.vpSize._2 `div` theList^.listItemHeightL) theList+                  EvKey KPageUp [] -> do+                      v <- lookupViewport (theList^.listNameL)+                      case v of+                          Nothing -> return theList+                          Just vp -> return $ listMoveBy (negate $ vp^.vpSize._2 `div` theList^.listItemHeightL) theList+                  _ -> return theList  -- | The top-level attribute used for the entire list. listAttr :: AttrName@@ -78,35 +97,51 @@ -- | Construct a list in terms of an element type 'e'. list :: Name      -- ^ The list name (must be unique)-     -> (Bool -> e -> Widget)-     -- ^ The item rendering function (takes the item and whether it is-     -- currently selected)-     -> [e]+     -> V.Vector e      -- ^ The initial list contents+     -> Int+     -- ^ The list item height in rows (all list item widgets must be+     -- this high)      -> List e-list name draw es =-    let selIndex = if null es then Nothing else Just 0-    in List es draw selIndex name+list name es h =+    let selIndex = if V.null es then Nothing else Just 0+    in List es selIndex name h --- | Turn a list state value into a widget.-renderList :: List e -> Widget-renderList l = withDefAttr listAttr $-               viewport (l^.listNameL) Vertical $-               vBox $-               drawListElements l+-- | Turn a list state value into a widget given an item drawing+-- function.+renderList :: List e -> (Bool -> e -> Widget) -> Widget+renderList l drawElem =+    withDefAttr listAttr $+    drawListElements l drawElem -drawListElements :: List e -> [Widget]-drawListElements l = drawnElements-    where-        es = l^.listElementsL-        drawnElements = (flip map) (zip [0..] es) $ \(i, e) ->-            let isSelected = Just i == l^.listSelectedL-                elemWidget = (l^.listElementDrawL) isSelected e-                makeVisible = if isSelected-                              then (visible . withDefAttr listSelectedAttr)-                              else id-            in makeVisible elemWidget+drawListElements :: List e -> (Bool -> e -> Widget) -> Widget+drawListElements l drawElem =+    Widget Fixed Fixed $ do+        c <- getContext +        let es = V.slice start num (l^.listElementsL)+            idx = case l^.listSelectedL of+                Nothing -> 0+                Just i -> i++            start = max 0 $ idx - numPerHeight + 1+            num = min (numPerHeight * 2) (V.length (l^.listElementsL) - start)+            numPerHeight = (c^.availHeightL) `div` (l^.listItemHeightL)++            off = start * (l^.listItemHeightL)++            drawnElements = (flip V.imap) es $ \i e ->+                let isSelected = Just (i + start) == l^.listSelectedL+                    elemWidget = drawElem isSelected e+                    makeVisible = if isSelected+                                  then (visible . withDefAttr listSelectedAttr)+                                  else id+                in makeVisible elemWidget++        render $ viewport (l^.listNameL) Vertical $+                 translateBy (Location (0, off)) $+                 vBox $ V.toList drawnElements+ -- | Insert an item into a list at the specified position. listInsert :: Int            -- ^ The position at which to insert (0 <= i <= size)@@ -115,24 +150,24 @@            -> List e            -> List e listInsert pos e l =-    let safePos = clamp 0 (length es) pos+    let safePos = clamp 0 (V.length es) pos         es = l^.listElementsL         newSel = case l^.listSelectedL of           Nothing -> 0           Just s -> if safePos < s                     then s + 1                     else s-        (front, back) = splitAt safePos es+        (front, back) = V.splitAt safePos es     in l & listSelectedL .~ Just newSel-         & listElementsL .~ (front ++ (e : back))+         & listElementsL .~ (front V.++ (e `V.cons` back))  -- | Remove an element from a list at the specified position. listRemove :: Int            -- ^ The position at which to remove an element (0 <= i < size)            -> List e            -> List e-listRemove pos l | null (l^.listElementsL) = l-                 | pos /= clamp 0 (length (l^.listElementsL) - 1) pos = l+listRemove pos l | V.null (l^.listElementsL) = l+                 | pos /= clamp 0 (V.length (l^.listElementsL) - 1) pos = l                  | otherwise =     let newSel = case l^.listSelectedL of           Nothing -> 0@@ -143,22 +178,22 @@                           else if pos < s                                then s - 1                                else s-        (front, back) = splitAt pos es-        es' = front ++ tail back+        (front, back) = V.splitAt pos es+        es' = front V.++ V.tail back         es = l^.listElementsL-    in l & listSelectedL .~ (if null es' then Nothing else Just newSel)+    in l & listSelectedL .~ (if V.null es' then Nothing else Just newSel)          & listElementsL .~ es'  -- | Replace the contents of a list with a new set of elements but -- preserve the currently selected index.-listReplace :: Eq e => [e] -> List e -> List e+listReplace :: Eq e => V.Vector e -> List e -> List e listReplace es' l | es' == l^.listElementsL = l                   | otherwise =     let sel = fromMaybe 0 (l^.listSelectedL)-        getNewSel es = case (null es, null es') of+        getNewSel es = case (V.null es, V.null es') of           (_, True)      -> Nothing           (True, False)  -> Just 0-          (False, False) -> Just (maintainSel es es' sel)+          (False, False) -> Just (maintainSel (V.toList es) (V.toList es') sel)         newSel = getNewSel (l^.listElementsL)      in l & listSelectedL .~ newSel@@ -178,14 +213,14 @@ -- validation. listMoveBy :: Int -> List e -> List e listMoveBy amt l =-    let newSel = clamp 0 (length (l^.listElementsL) - 1) <$> (amt +) <$> (l^.listSelectedL)+    let newSel = clamp 0 (V.length (l^.listElementsL) - 1) <$> (amt +) <$> (l^.listSelectedL)     in l & listSelectedL .~ newSel  -- | Set the selected index for a list to the specified index, subject -- to validation. listMoveTo :: Int -> List e -> List e listMoveTo pos l =-    let len = length (l^.listElementsL)+    let len = V.length (l^.listElementsL)         newSel = clamp 0 (len - 1) $ if pos < 0 then (len - pos) else pos     in l & listSelectedL .~ if len > 0                             then Just newSel@@ -195,7 +230,7 @@ listSelectedElement :: List e -> Maybe (Int, e) listSelectedElement l = do   sel <- l^.listSelectedL-  return (sel, (l^.listElementsL) !! sel)+  return (sel, (l^.listElementsL) V.! sel)  -- Assuming `xs` is an existing list that we want to update to match the -- state of `ys`. Given a selected index in `xs`, the goal is to compute
src/Brick/Widgets/ProgressBar.hs view
@@ -11,6 +11,7 @@ import Control.Lens ((^.)) import Data.Monoid +import Brick.Types import Brick.AttrMap import Brick.Widgets.Core