packages feed

brick 1.5 → 1.6

raw patch · 10 files changed

+492/−87 lines, 10 filesdep ~basedep ~microlensdep ~vtynew-component:exe:brick-editor-line-numbers-demonew-component:exe:brick-tabular-list-demoPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: base, microlens, vty

API changes (from Hackage documentation)

+ Brick.Widgets.Table: BorderConfiguration :: Bool -> Bool -> Bool -> BorderConfiguration
+ Brick.Widgets.Table: RenderedTableCells :: [[Widget n]] -> [Int] -> [Int] -> BorderConfiguration -> RenderedTableCells n
+ Brick.Widgets.Table: [borderConfiguration] :: RenderedTableCells n -> BorderConfiguration
+ Brick.Widgets.Table: [drawColumnBorders] :: BorderConfiguration -> Bool
+ Brick.Widgets.Table: [drawRowBorders] :: BorderConfiguration -> Bool
+ Brick.Widgets.Table: [drawSurroundingBorder] :: BorderConfiguration -> Bool
+ Brick.Widgets.Table: [renderedTableColumnWidths] :: RenderedTableCells n -> [Int]
+ Brick.Widgets.Table: [renderedTableRowHeights] :: RenderedTableCells n -> [Int]
+ Brick.Widgets.Table: [renderedTableRows] :: RenderedTableCells n -> [[Widget n]]
+ Brick.Widgets.Table: addBorders :: RenderedTableCells n -> RenderM n (Widget n)
+ Brick.Widgets.Table: alignColumns :: [ColumnAlignment] -> [Int] -> [Widget n] -> [Widget n]
+ Brick.Widgets.Table: data BorderConfiguration
+ Brick.Widgets.Table: data RenderedTableCells n
+ Brick.Widgets.Table: tableCellLayout :: Table n -> RenderM n (RenderedTableCells n)
- Brick.BorderMap: eBottomL :: forall a_aeIX. Lens' (Edges a_aeIX) a_aeIX
+ Brick.BorderMap: eBottomL :: forall a_aeHn. Lens' (Edges a_aeHn) a_aeHn
- Brick.BorderMap: eLeftL :: forall a_aeIX. Lens' (Edges a_aeIX) a_aeIX
+ Brick.BorderMap: eLeftL :: forall a_aeHn. Lens' (Edges a_aeHn) a_aeHn
- Brick.BorderMap: eRightL :: forall a_aeIX. Lens' (Edges a_aeIX) a_aeIX
+ Brick.BorderMap: eRightL :: forall a_aeHn. Lens' (Edges a_aeHn) a_aeHn
- Brick.BorderMap: eTopL :: forall a_aeIX. Lens' (Edges a_aeIX) a_aeIX
+ Brick.BorderMap: eTopL :: forall a_aeHn. Lens' (Edges a_aeHn) a_aeHn
- Brick.Types: availHeightL :: forall n_azep. Lens' (Context n_azep) Int
+ Brick.Types: availHeightL :: forall n_azcT. Lens' (Context n_azcT) Int
- Brick.Types: availWidthL :: forall n_azep. Lens' (Context n_azep) Int
+ Brick.Types: availWidthL :: forall n_azcT. Lens' (Context n_azcT) Int
- Brick.Types: bordersL :: forall n_azet. Lens' (Result n_azet) (BorderMap DynBorder)
+ Brick.Types: bordersL :: forall n_azcX. Lens' (Result n_azcX) (BorderMap DynBorder)
- Brick.Types: ctxAttrMapL :: forall n_azep. Lens' (Context n_azep) AttrMap
+ Brick.Types: ctxAttrMapL :: forall n_azcT. Lens' (Context n_azcT) AttrMap
- Brick.Types: ctxAttrNameL :: forall n_azep. Lens' (Context n_azep) AttrName
+ Brick.Types: ctxAttrNameL :: forall n_azcT. Lens' (Context n_azcT) AttrName
- Brick.Types: ctxBorderStyleL :: forall n_azep. Lens' (Context n_azep) BorderStyle
+ Brick.Types: ctxBorderStyleL :: forall n_azcT. Lens' (Context n_azcT) BorderStyle
- Brick.Types: ctxDynBordersL :: forall n_azep. Lens' (Context n_azep) Bool
+ Brick.Types: ctxDynBordersL :: forall n_azcT. Lens' (Context n_azcT) Bool
- Brick.Types: ctxHScrollBarOrientationL :: forall n_azep. Lens' (Context n_azep) (Maybe HScrollBarOrientation)
+ Brick.Types: ctxHScrollBarOrientationL :: forall n_azcT. Lens' (Context n_azcT) (Maybe HScrollBarOrientation)
- Brick.Types: ctxHScrollBarRendererL :: forall n_azep. Lens' (Context n_azep) (Maybe (ScrollbarRenderer n_azep))
+ Brick.Types: ctxHScrollBarRendererL :: forall n_azcT. Lens' (Context n_azcT) (Maybe (ScrollbarRenderer n_azcT))
- Brick.Types: ctxVScrollBarOrientationL :: forall n_azep. Lens' (Context n_azep) (Maybe VScrollBarOrientation)
+ Brick.Types: ctxVScrollBarOrientationL :: forall n_azcT. Lens' (Context n_azcT) (Maybe VScrollBarOrientation)
- Brick.Types: ctxVScrollBarRendererL :: forall n_azep. Lens' (Context n_azep) (Maybe (ScrollbarRenderer n_azep))
+ Brick.Types: ctxVScrollBarRendererL :: forall n_azcT. Lens' (Context n_azcT) (Maybe (ScrollbarRenderer n_azcT))
- Brick.Types: cursorLocationL :: forall n_azeu. Lens' (CursorLocation n_azeu) Location
+ Brick.Types: cursorLocationL :: forall n_azcY. Lens' (CursorLocation n_azcY) Location
- Brick.Types: cursorLocationNameL :: forall n_azeu n_aBEl. Lens (CursorLocation n_azeu) (CursorLocation n_aBEl) (Maybe n_azeu) (Maybe n_aBEl)
+ Brick.Types: cursorLocationNameL :: forall n_azcY n_aBCP. Lens (CursorLocation n_azcY) (CursorLocation n_aBCP) (Maybe n_azcY) (Maybe n_aBCP)
- Brick.Types: cursorsL :: forall n_azet. Lens' (Result n_azet) [CursorLocation n_azet]
+ Brick.Types: cursorsL :: forall n_azcX. Lens' (Result n_azcX) [CursorLocation n_azcX]
- Brick.Types: eBottomL :: forall a_aeIX. Lens' (Edges a_aeIX) a_aeIX
+ Brick.Types: eBottomL :: forall a_aeHn. Lens' (Edges a_aeHn) a_aeHn
- Brick.Types: eLeftL :: forall a_aeIX. Lens' (Edges a_aeIX) a_aeIX
+ Brick.Types: eLeftL :: forall a_aeHn. Lens' (Edges a_aeHn) a_aeHn
- Brick.Types: eRightL :: forall a_aeIX. Lens' (Edges a_aeIX) a_aeIX
+ Brick.Types: eRightL :: forall a_aeHn. Lens' (Edges a_aeHn) a_aeHn
- Brick.Types: eTopL :: forall a_aeIX. Lens' (Edges a_aeIX) a_aeIX
+ Brick.Types: eTopL :: forall a_aeHn. Lens' (Edges a_aeHn) a_aeHn
- Brick.Types: extentsL :: forall n_azet. Lens' (Result n_azet) [Extent n_azet]
+ Brick.Types: extentsL :: forall n_azcX. Lens' (Result n_azcX) [Extent n_azcX]
- Brick.Types: imageL :: forall n_azet. Lens' (Result n_azet) Image
+ Brick.Types: imageL :: forall n_azcX. Lens' (Result n_azcX) Image
- Brick.Types: visibilityRequestsL :: forall n_azet. Lens' (Result n_azet) [VisibilityRequest]
+ Brick.Types: visibilityRequestsL :: forall n_azcX. Lens' (Result n_azcX) [VisibilityRequest]
- Brick.Types: windowHeightL :: forall n_azep. Lens' (Context n_azep) Int
+ Brick.Types: windowHeightL :: forall n_azcT. Lens' (Context n_azcT) Int
- Brick.Types: windowWidthL :: forall n_azep. Lens' (Context n_azep) Int
+ Brick.Types: windowWidthL :: forall n_azcT. Lens' (Context n_azcT) Int
- Brick.Widgets.Dialog: dialogButtonsL :: forall a_a1kLb n_a1kLc a_a1kLZ. Lens (Dialog a_a1kLb n_a1kLc) (Dialog a_a1kLZ n_a1kLc) [(String, n_a1kLc, a_a1kLb)] [(String, n_a1kLc, a_a1kLZ)]
+ Brick.Widgets.Dialog: dialogButtonsL :: forall a_a1kJo n_a1kJp a_a1kKc. Lens (Dialog a_a1kJo n_a1kJp) (Dialog a_a1kKc n_a1kJp) [(String, n_a1kJp, a_a1kJo)] [(String, n_a1kJp, a_a1kKc)]
- Brick.Widgets.Dialog: dialogTitleL :: forall a_a1kLb n_a1kLc. Lens' (Dialog a_a1kLb n_a1kLc) (Maybe (Widget n_a1kLc))
+ Brick.Widgets.Dialog: dialogTitleL :: forall a_a1kJo n_a1kJp. Lens' (Dialog a_a1kJo n_a1kJp) (Maybe (Widget n_a1kJp))
- Brick.Widgets.Dialog: dialogWidthL :: forall a_a1kLb n_a1kLc. Lens' (Dialog a_a1kLb n_a1kLc) Int
+ Brick.Widgets.Dialog: dialogWidthL :: forall a_a1kJo n_a1kJp. Lens' (Dialog a_a1kJo n_a1kJp) Int
- Brick.Widgets.Edit: editContentsL :: forall t_a1ecn n_a1eco t_a1ed2. Lens (Editor t_a1ecn n_a1eco) (Editor t_a1ed2 n_a1eco) (TextZipper t_a1ecn) (TextZipper t_a1ed2)
+ Brick.Widgets.Edit: editContentsL :: forall t_a1eaQ n_a1eaR t_a1ebv. Lens (Editor t_a1eaQ n_a1eaR) (Editor t_a1ebv n_a1eaR) (TextZipper t_a1eaQ) (TextZipper t_a1ebv)
- Brick.Widgets.FileBrowser: fileBrowserEntriesG :: forall n_a1vUC. SimpleGetter (FileBrowser n_a1vUC) (List n_a1vUC FileInfo)
+ Brick.Widgets.FileBrowser: fileBrowserEntriesG :: forall n_a1vT1. SimpleGetter (FileBrowser n_a1vT1) (List n_a1vT1 FileInfo)
- Brick.Widgets.FileBrowser: fileBrowserEntryFilterG :: forall n_a1vUC. SimpleGetter (FileBrowser n_a1vUC) (Maybe (FileInfo -> Bool))
+ Brick.Widgets.FileBrowser: fileBrowserEntryFilterG :: forall n_a1vT1. SimpleGetter (FileBrowser n_a1vT1) (Maybe (FileInfo -> Bool))
- Brick.Widgets.FileBrowser: fileBrowserExceptionG :: forall n_a1vUC. SimpleGetter (FileBrowser n_a1vUC) (Maybe IOException)
+ Brick.Widgets.FileBrowser: fileBrowserExceptionG :: forall n_a1vT1. SimpleGetter (FileBrowser n_a1vT1) (Maybe IOException)
- Brick.Widgets.FileBrowser: fileBrowserLatestResultsG :: forall n_a1vUC. SimpleGetter (FileBrowser n_a1vUC) [FileInfo]
+ Brick.Widgets.FileBrowser: fileBrowserLatestResultsG :: forall n_a1vT1. SimpleGetter (FileBrowser n_a1vT1) [FileInfo]
- Brick.Widgets.FileBrowser: fileBrowserNameG :: forall n_a1vUC. SimpleGetter (FileBrowser n_a1vUC) n_a1vUC
+ Brick.Widgets.FileBrowser: fileBrowserNameG :: forall n_a1vT1. SimpleGetter (FileBrowser n_a1vT1) n_a1vT1
- Brick.Widgets.FileBrowser: fileBrowserSearchStringG :: forall n_a1vUC. SimpleGetter (FileBrowser n_a1vUC) (Maybe Text)
+ Brick.Widgets.FileBrowser: fileBrowserSearchStringG :: forall n_a1vT1. SimpleGetter (FileBrowser n_a1vT1) (Maybe Text)
- Brick.Widgets.FileBrowser: fileBrowserSelectableG :: forall n_a1vUC. SimpleGetter (FileBrowser n_a1vUC) (FileInfo -> Bool)
+ Brick.Widgets.FileBrowser: fileBrowserSelectableG :: forall n_a1vT1. SimpleGetter (FileBrowser n_a1vT1) (FileInfo -> Bool)
- Brick.Widgets.FileBrowser: fileBrowserSelectableL :: forall n_a1vUC. Lens' (FileBrowser n_a1vUC) (FileInfo -> Bool)
+ Brick.Widgets.FileBrowser: fileBrowserSelectableL :: forall n_a1vT1. Lens' (FileBrowser n_a1vT1) (FileInfo -> Bool)
- Brick.Widgets.FileBrowser: fileBrowserSelectedFilesG :: forall n_a1vUC. SimpleGetter (FileBrowser n_a1vUC) (Set String)
+ Brick.Widgets.FileBrowser: fileBrowserSelectedFilesG :: forall n_a1vT1. SimpleGetter (FileBrowser n_a1vT1) (Set String)
- Brick.Widgets.FileBrowser: fileBrowserWorkingDirectoryG :: forall n_a1vUC. SimpleGetter (FileBrowser n_a1vUC) FilePath
+ Brick.Widgets.FileBrowser: fileBrowserWorkingDirectoryG :: forall n_a1vT1. SimpleGetter (FileBrowser n_a1vT1) FilePath
- Brick.Widgets.List: listElementsL :: forall n_a1qnZ t_a1qo0 e_a1qo1 t_a1qDb e_a1qDc. Lens (GenericList n_a1qnZ t_a1qo0 e_a1qo1) (GenericList n_a1qnZ t_a1qDb e_a1qDc) (t_a1qo0 e_a1qo1) (t_a1qDb e_a1qDc)
+ Brick.Widgets.List: listElementsL :: forall n_a1qmo t_a1qmp e_a1qmq t_a1qBA e_a1qBB. Lens (GenericList n_a1qmo t_a1qmp e_a1qmq) (GenericList n_a1qmo t_a1qBA e_a1qBB) (t_a1qmp e_a1qmq) (t_a1qBA e_a1qBB)
- Brick.Widgets.List: listItemHeightL :: forall n_a1qnZ t_a1qo0 e_a1qo1. Lens' (GenericList n_a1qnZ t_a1qo0 e_a1qo1) Int
+ Brick.Widgets.List: listItemHeightL :: forall n_a1qmo t_a1qmp e_a1qmq. Lens' (GenericList n_a1qmo t_a1qmp e_a1qmq) Int
- Brick.Widgets.List: listNameL :: forall n_a1qnZ t_a1qo0 e_a1qo1 n_a1qDd. Lens (GenericList n_a1qnZ t_a1qo0 e_a1qo1) (GenericList n_a1qDd t_a1qo0 e_a1qo1) n_a1qnZ n_a1qDd
+ Brick.Widgets.List: listNameL :: forall n_a1qmo t_a1qmp e_a1qmq n_a1qBC. Lens (GenericList n_a1qmo t_a1qmp e_a1qmq) (GenericList n_a1qBC t_a1qmp e_a1qmq) n_a1qmo n_a1qBC
- Brick.Widgets.List: listSelectedL :: forall n_a1qnZ t_a1qo0 e_a1qo1. Lens' (GenericList n_a1qnZ t_a1qo0 e_a1qo1) (Maybe Int)
+ Brick.Widgets.List: listSelectedL :: forall n_a1qmo t_a1qmp e_a1qmq. Lens' (GenericList n_a1qmo t_a1qmp e_a1qmq) (Maybe Int)

Files

CHANGELOG.md view
@@ -2,6 +2,29 @@ Brick changelog --------------- +1.6+---++Package changes:+* Support `mtl` 2.3 (thanks Daniel Firth)++API changes:+* `Brick.Widgets.Table` got a new `alignColumns` function that can be+  used to do column layout of a list of widgets using `ColumnAlignment`+  values from the table API.+* `Brick.Widgets.Table` got a new low-level table-rendering API for use+  in applications that want to use the table layout machinery without+  using `Table` itself. This includes:+  * `tableCellLayout` - does table cell layout using table configuration+    settings,+  * `addBorders` - adds row, column, and surrounding borders using table+    border-drawing settings, and+  * `RenderedTableCells` and `BorderConfiguration` - the low-level types+    used for the new functions.++Other changes:+* Added a new `EditorLineNumbersDemo` demo program.+ 1.5 --- 
README.md view
@@ -6,9 +6,10 @@ you provide a state transformation function to handle events.  `brick` exposes a declarative API. Unlike most GUI toolkits which-require you to write a long and tedious sequence of "create a widget,-now bind an event handler", `brick` just requires you to describe your-interface using a set of declarative layout combinators.+require you to write a long and tedious sequence of widget creations+and layout setup, `brick` just requires you to describe your interface+using a set of declarative layout combinators. Event-handling is done by+pattern-matching on incoming events and updating your application state.  Under the hood, this library builds upon [vty](http://hackage.haskell.org/package/vty), so some knowledge of Vty@@ -87,6 +88,10 @@ | [`htyper`](https://github.com/Simon-Hostettler/htyper) | A typing speed test program | | [`ullekha`](https://github.com/ajithnn/ullekha) | An interactive terminal notes/todo app with file/redis persistence | | [`mywork`](https://github.com/kquick/mywork) [[Hackage]](https://hackage.haskell.org/package/mywork) | A tool to keep track of the projects you are working on |+| [`hic-hac-hoe`](https://github.com/blastwind/hic-hac-hoe) | Play tic tac toe in terminal! |+| [`babel-cards`](https://github.com/srhoulam/babel-cards) | A TUI spaced-repetition memorization tool. Similar to Anki. |+| [`codenames-haskell`](https://github.com/VigneshN1997/codenames-haskell) | An implementation of the Codenames game |+| [`haradict`](https://github.com/srhoulam/haradict) | A TUI Arabic dictionary powered by [ElixirFM](https://github.com/otakar-smrz/elixir-fm) |  These third-party packages also extend `brick`: @@ -144,13 +149,14 @@  * A filesystem browser for file and directory selection  * Borders can be configured to automatically connect! -Brick-Users Discussion-----------------------+Brick Discussion+---------------- -The `brick-users` Google Group / e-mail list is a place to discuss-library changes, give feedback, and ask questions. You can subscribe at:+There are two forums for discussing brick-related things: -[https://groups.google.com/group/brick-users](https://groups.google.com/group/brick-users)+1. The [Discussions page](https://github.com/jtdaugherty/brick/discussions) on the github repo, and+1. The `brick-users` Google Group / e-mail list. You can subscribe+   [here](https://groups.google.com/group/brick-users).  Status ------
brick.cabal view
@@ -1,5 +1,5 @@ name:                brick-version:             1.5+version:             1.6 synopsis:            A declarative terminal user interface library description:   Write terminal user interfaces (TUIs) painlessly with 'brick'! You@@ -415,6 +415,23 @@                        text,                        microlens +executable brick-tabular-list-demo+  if !flag(demos)+    Buildable: False+  hs-source-dirs:      programs+  ghc-options:         -threaded -Wall -Wcompat -O2+  default-language:    Haskell2010+  main-is:             TabularListDemo.hs+  build-depends:       base,+                       brick,+                       vty,+                       text,+                       microlens >= 0.3.0.0,+                       microlens-mtl,+                       microlens-th,+                       mtl,+                       vector+ executable brick-list-demo   if !flag(demos)     Buildable: False@@ -495,6 +512,23 @@   ghc-options:         -threaded -Wall -Wcompat -O2   default-language:    Haskell2010   main-is:             EditDemo.hs+  build-depends:       base,+                       brick,+                       vty,+                       text,+                       vector,+                       mtl,+                       microlens >= 0.3.0.0,+                       microlens-th,+                       microlens-mtl++executable brick-editor-line-numbers-demo+  if !flag(demos)+    Buildable: False+  hs-source-dirs:      programs+  ghc-options:         -threaded -Wall -Wcompat -O2+  default-language:    Haskell2010+  main-is:             EditorLineNumbersDemo.hs   build-depends:       base,                        brick,                        vty,
docs/guide.rst view
@@ -214,8 +214,8 @@    appHandleEvent :: BrickEvent n e -> EventM n s ()  ``appHandleEvent`` is responsible for deciding how to change the state-based on the event. The single parameter to the event handler is the-event to be handled. Its type variables ``n`` and ``e`` correspond+based on incoming events. The single parameter to the event handler is+the event to be handled. Its type variables ``n`` and ``e`` correspond to the *resource name type* and *event type* of your application, respectively, and must match the corresponding types in ``App`` and ``EventM``.@@ -254,11 +254,11 @@ in this monad by using ``liftIO``. Keep in mind, however, that event handlers should execute as quickly as possible to avoid introducing screen redraw latency. Consider using background threads to work-asynchronously when it would otherwise cause redraw latency.+asynchronously when handling an event would otherwise cause redraw+latency. -Beyond I/O, ``EventM`` is used to make scrolling requests to the-renderer (see `Viewports`_), obtain named extents (see `Extents`_), and-other duties.+``EventM`` is also used to make scrolling requests to the renderer (see+`Viewports`_), obtain named extents (see `Extents`_), and other duties.  Event Handlers for Component State **********************************
+ programs/EditorLineNumbersDemo.hs view
@@ -0,0 +1,135 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE CPP #-}+module Main where++import Control.Monad (void)+import Lens.Micro+import Lens.Micro.TH+import Lens.Micro.Mtl+import qualified Graphics.Vty as V+#if !(MIN_VERSION_base(4,11,0))+import Data.Semigroup ((<>))+#endif++import qualified Brick.Main as M+import qualified Brick.Types as T+import Brick.Widgets.Core+  ( (<+>)+  , vBox+  , hLimit+  , vLimit+  , str+  , visible+  , viewport+  , withDefAttr+  )+import qualified Brick.Widgets.Center as C+import qualified Brick.Widgets.Edit as E+import qualified Brick.AttrMap as A+import Brick.Util (on, fg)++data Name = Edit+          | EditLines+          deriving (Ord, Show, Eq)++data St =+    St { _edit :: E.Editor String Name+       }++makeLenses ''St++drawUI :: St -> [T.Widget Name]+drawUI st = [ui]+    where+        e = renderWithLineNumbers (st^.edit)+        ui = C.center $ hLimit 50 $ vLimit 10 e++-- | Given an editor, render the editor with line numbers to the left of+-- the editor.+--+-- This essentially exploits knowledge of how the editor is implemented:+-- we make a viewport containing line numbers that is just as high as+-- the editor, then request that the line number associated with the+-- editor's current line position be made visible, thus scrolling it+-- into view. This is slightly brittle, however, because it relies on+-- essentially keeping the line number viewport and the editor viewport+-- in the same vertical scrolling state; with direct scrolling requests+-- from EventM it is easily possible to put the two viewports into a+-- state where they do not have the same vertical scrolling offset. That+-- means that visibility requests made with 'visible' won't necessarily+-- have the same effect in each viewport in that case. So this is+-- only really usable in the case where you're sure that the editor's+-- viewport and the line number viewports will not be managed by direct+-- viewport operations in EventM. That's what I'd recommend anyway, but+-- still, this is an important caveat.+--+-- There's another important caveat here: this particular implementation+-- has @O(n)@ performance for editor height @n@ because we generate+-- the entire list of line numbers on each rendering depending on the+-- height of the editor. That means that for sufficiently large files,+-- it will get more expensive to render the line numbers. There is a way+-- around this problem, which is to take the approach that the @List@+-- implementation takes: only render a region of visible line numbers+-- around the currently-edited line that is just large enough to be+-- guaranteed to fill the viewport, then translate that so that it+-- appears at the right viewport offset, thus faking a viewport filled+-- with line numbers when in fact we'd only ever render at most @2 * K ++-- 1@ line numbers for a viewport height of @K@. That's more involved,+-- so I didn't do it here, but that would be the way to go for a Real+-- Application.+renderWithLineNumbers :: E.Editor String Name -> T.Widget Name+renderWithLineNumbers e =+    lineNumbersVp <+> editorVp+    where+        lineNumbersVp = hLimit (maxNumWidth + 1) $ viewport EditLines T.Vertical body+        editorVp = E.renderEditor (str . unlines) True e+        body = withDefAttr lineNumberAttr $ vBox numWidgets+        numWidgets = mkNumWidget <$> numbers+        mkNumWidget i = maybeVisible i $ str $ show i+        maybeVisible i+            | i == curLine + 1 =+                visible . withDefAttr currentLineNumberAttr+            | otherwise =+                id+        numbers = [1..h]+        contents = E.getEditContents e+        h = length contents+        curLine = fst $ E.getCursorPosition e+        maxNumWidth = length $ show h++appEvent :: T.BrickEvent Name e -> T.EventM Name St ()+appEvent (T.VtyEvent (V.EvKey V.KEsc [])) =+    M.halt+appEvent ev = do+    zoom edit $ E.handleEditorEvent ev++initialState :: St+initialState =+    St (E.editor Edit Nothing "")++lineNumberAttr :: A.AttrName+lineNumberAttr = A.attrName "lineNumber"++currentLineNumberAttr :: A.AttrName+currentLineNumberAttr = lineNumberAttr <> A.attrName "current"++theMap :: A.AttrMap+theMap = A.attrMap V.defAttr+    [ (E.editAttr,              V.white `on` V.blue)+    , (E.editFocusedAttr,       V.black `on` V.yellow)+    , (lineNumberAttr,          fg V.cyan)+    , (currentLineNumberAttr,   V.defAttr `V.withStyle` V.bold)+    ]++theApp :: M.App St e Name+theApp =+    M.App { M.appDraw = drawUI+          , M.appChooseCursor = const $ M.showCursorNamed Edit+          , M.appHandleEvent = appEvent+          , M.appStartEvent = return ()+          , M.appAttrMap = const theMap+          }++main :: IO ()+main = do+    void $ M.defaultMain theApp initialState
+ programs/TabularListDemo.hs view
@@ -0,0 +1,146 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE CPP #-}+module Main where++import Lens.Micro ((^.))+import Lens.Micro.Mtl+import Lens.Micro.TH+import Control.Monad (void)+#if !(MIN_VERSION_base(4,11,0))+import Data.Monoid+#endif+import qualified Graphics.Vty as V++import qualified Brick.Main as M+import qualified Brick.Types as T+import qualified Brick.Widgets.Border as B+import qualified Brick.Widgets.List as L+import qualified Brick.Widgets.Center as C+import qualified Brick.Widgets.Table as Table+import qualified Brick.AttrMap as A+import qualified Data.Vector as Vec+import Brick.Types+  ( Widget+  )+import Brick.Widgets.Core+  ( (<=>)+  , str+  , vLimit+  , hLimit+  , vBox+  , hBox+  , withDefAttr+  )+import Brick.Util (on)++data Row = Row String String String++data AppState =+    AppState { _tabularList :: L.List () Row+             , _colIndex :: Int+             }++makeLenses ''AppState++drawUI :: AppState -> [Widget ()]+drawUI s = [ui]+    where+        l = s^.tabularList+        label = str $ "Row " <> cur <> " / col " <> show (s^.colIndex + 1)+        cur = case l^.(L.listSelectedL) of+                Nothing -> "-"+                Just i -> show (i + 1)+        box = B.borderWithLabel label $+              hLimit totalWidth $+              vLimit 15 $+              listDrawElement 0 False headerRow <=>+              L.renderList (listDrawElement (s^.colIndex)) True l+        ui = C.vCenter $ vBox [ C.hCenter box+                              , str " "+                              , C.hCenter $ str "Press +/- to add/remove list elements."+                              , C.hCenter $ str "Use arrow keys to change selection."+                              , C.hCenter $ str "Press Esc to exit."+                              ]++appEvent :: T.BrickEvent () e -> T.EventM () AppState ()+appEvent (T.VtyEvent e) =+    case e of+        V.EvKey (V.KChar '+') [] -> do+            els <- use (tabularList.L.listElementsL)+            let el = Row (show pos) (show $ pos * 3) (show $ pos * 9)+                pos = Vec.length els+            tabularList %= L.listInsert pos el++        V.EvKey (V.KChar '-') [] -> do+            sel <- use (tabularList.L.listSelectedL)+            case sel of+                Nothing -> return ()+                Just i -> tabularList %= L.listRemove i++        V.EvKey V.KLeft [] ->+            colIndex %= (\i -> max 0 (i - 1))+        V.EvKey V.KRight [] ->+            colIndex %= (\i -> min (length columnAlignments - 1) (i + 1))++        V.EvKey V.KEsc [] -> M.halt++        ev -> T.zoom tabularList $ L.handleListEvent ev+appEvent _ = return ()++listDrawElement :: Int -> Bool -> Row -> Widget ()+listDrawElement colIdx sel (Row a b c) =+    let ws = [str a, str b, str c]+        maybeSelect es = selectCell <$> zip [0..] es+        selectCell (i, w) = if sel && i == colIdx+                            then withDefAttr selectedCellAttr w+                            else w+    in hLimit totalWidth $+       hBox $+       maybeSelect $+       Table.alignColumns columnAlignments columnWidths ws++initialState :: AppState+initialState =+    AppState { _tabularList = L.list () (Vec.fromList initialRows) 1+             , _colIndex = 0+             }++selectedCellAttr :: A.AttrName+selectedCellAttr = A.attrName "selectedCell"++theMap :: A.AttrMap+theMap = A.attrMap V.defAttr+    [ (L.listAttr,            V.white `on` V.blue)+    , (selectedCellAttr,      V.blue `on` V.white)+    ]++columnWidths :: [Int]+columnWidths = [10, 15, 20]++totalWidth :: Int+totalWidth = sum columnWidths++headerRow :: Row+headerRow = Row "Col 1" "Col 2" "Col 3"++columnAlignments :: [Table.ColumnAlignment]+columnAlignments = [Table.AlignLeft, Table.AlignCenter, Table.AlignRight]++initialRows :: [Row]+initialRows =+    [ Row "one" "two" "three"+    , Row "foo" "bar" "baz"+    , Row "stuff" "things" "blah"+    ]++theApp :: M.App AppState e ()+theApp =+    M.App { M.appDraw = drawUI+          , M.appChooseCursor = M.showFirstCursor+          , M.appHandleEvent = appEvent+          , M.appStartEvent = return ()+          , M.appAttrMap = const theMap+          }++main :: IO ()+main = void $ M.defaultMain theApp initialState
src/Brick/Main.hs view
@@ -54,6 +54,7 @@  import qualified Control.Exception as E import Lens.Micro ((^.), (&), (.~), (%~), _1, _2)+import Control.Monad import Control.Monad.State.Strict import Control.Monad.Reader import Control.Concurrent (forkIO, killThread)
src/Brick/Widgets/Core.hs view
@@ -122,6 +122,7 @@  import Lens.Micro ((^.), (.~), (&), (%~), to, _1, _2, each, to, Lens') import Lens.Micro.Mtl (use, (%=))+import Control.Monad import Control.Monad.State.Strict import Control.Monad.Reader import qualified Data.Foldable as F
src/Brick/Widgets/Internal.hs view
@@ -10,6 +10,7 @@  import Lens.Micro ((^.), (&), (%~)) import Lens.Micro.Mtl ((%=))+import Control.Monad import Control.Monad.State.Strict import Control.Monad.Reader import Data.Maybe (fromMaybe, mapMaybe)
src/Brick/Widgets/Table.hs view
@@ -31,6 +31,13 @@    -- * Rendering   , renderTable++  -- * Low-level API+  , RenderedTableCells(..)+  , BorderConfiguration(..)+  , tableCellLayout+  , addBorders+  , alignColumns   ) where @@ -90,11 +97,16 @@           , tableRows :: [[Widget n]]           , defaultColumnAlignment :: ColumnAlignment           , defaultRowAlignment :: RowAlignment-          , drawSurroundingBorder :: Bool-          , drawRowBorders :: Bool-          , drawColumnBorders :: Bool+          , tableBorderConfiguration :: BorderConfiguration           } +-- | A border configuration for a table.+data BorderConfiguration =+    BorderConfiguration { drawSurroundingBorder :: Bool+                        , drawRowBorders :: Bool+                        , drawColumnBorders :: Bool+                        }+ -- | Construct a new table. -- -- The argument is the list of rows with the topmost row first, with@@ -148,27 +160,29 @@         t = Table { columnAlignments = mempty                   , rowAlignments = mempty                   , tableRows = rows-                  , drawSurroundingBorder = True-                  , drawRowBorders = True-                  , drawColumnBorders = True                   , defaultColumnAlignment = AlignLeft                   , defaultRowAlignment = AlignTop+                  , tableBorderConfiguration =+                      BorderConfiguration { drawSurroundingBorder = True+                                          , drawRowBorders = True+                                          , drawColumnBorders = True+                                          }                   }  -- | Configure whether the table draws a border on its exterior. surroundingBorder :: Bool -> Table n -> Table n surroundingBorder b t =-    t { drawSurroundingBorder = b }+    t { tableBorderConfiguration = (tableBorderConfiguration t) { drawSurroundingBorder = b } }  -- | Configure whether the table draws borders between its rows. rowBorders :: Bool -> Table n -> Table n rowBorders b t =-    t { drawRowBorders = b }+    t { tableBorderConfiguration = (tableBorderConfiguration t) { drawRowBorders = b } }  -- | Configure whether the table draws borders between its columns. columnBorders :: Bool -> Table n -> Table n columnBorders b t =-    t { drawColumnBorders = b }+    t { tableBorderConfiguration = (tableBorderConfiguration t) { drawColumnBorders = b } }  -- | Align the specified column to the right. The argument is the column -- index, starting with zero. Silently does nothing if the index is out@@ -235,60 +249,104 @@ renderTable t =     joinBorders $     Widget Fixed Fixed $ do-        ctx <- getContext-        cellResults <- forM (tableRows t) $ mapM render+        tableCellLayout t >>= addBorders >>= render -        let maybeIntersperse f v = if f t then intersperse v else id-            rowHeights = rowHeight <$> cellResults-            colWidths = colWidth <$> byColumn-            allRowAligns = (\i -> M.findWithDefault (defaultRowAlignment t) i (rowAlignments t)) <$>-                           [0..length rowHeights - 1]-            allColAligns = (\i -> M.findWithDefault (defaultColumnAlignment t) i (columnAlignments t)) <$>-                           [0..length byColumn - 1]-            rowHeight = maximum . fmap (imageHeight . image)-            colWidth = maximum . fmap (imageWidth . image)-            byColumn = transpose cellResults-            toW = Widget Fixed Fixed . return-            fillEmptyCell w h result =-                if imageWidth (image result) == 0 && imageHeight (image result) == 0-                then result { image = charFill (ctx^.attrL) ' ' w h }-                else result-            mkColumn (hAlign, width, colCells) =-                let paddedCells = flip map (zip3 allRowAligns rowHeights colCells) $ \(vAlign, rHeight, cell) ->-                        applyColAlignment width hAlign $-                        applyRowAlignment rHeight vAlign $-                        toW $-                        fillEmptyCell width rHeight cell-                    maybeRowBorders = maybeIntersperse drawRowBorders (hLimit width hBorder)-                in vBox $ maybeRowBorders paddedCells+-- | The result of performing table cell intermediate rendering and+-- layout.+data RenderedTableCells n =+    RenderedTableCells { renderedTableRows :: [[Widget n]]+                       -- ^ The table's cells in row-major order.+                       , renderedTableColumnWidths :: [Int]+                       -- ^ The widths of the table's columns.+                       , renderedTableRowHeights :: [Int]+                       -- ^ The heights of the table's rows.+                       , borderConfiguration :: BorderConfiguration+                       -- ^ The border configuration to use.+                       } -            vBorders = mkVBorder <$> rowHeights-            hBorders = mkHBorder <$> colWidths-            mkHBorder w = hLimit w hBorder-            mkVBorder h = vLimit h vBorder-            topBorder =-                hBox $ maybeIntersperse drawColumnBorders topT hBorders-            bottomBorder =-                hBox $ maybeIntersperse drawColumnBorders bottomT hBorders-            leftBorder =-                vBox $ topLeftCorner : maybeIntersperse drawRowBorders leftT vBorders <> [bottomLeftCorner]-            rightBorder =-                vBox $ topRightCorner : maybeIntersperse drawRowBorders rightT vBorders <> [bottomRightCorner]+-- | Augment rendered table cells with borders according to the+-- border configuration accompanying the cells.+addBorders :: RenderedTableCells n -> RenderM n (Widget n)+addBorders r = do+    let cfg = borderConfiguration r+        rows = renderedTableRows r+        rowHeights = renderedTableRowHeights r+        colWidths = renderedTableColumnWidths r -            maybeWrap check f =-                if check t then f else id-            addSurroundingBorder body =-                leftBorder <+> (topBorder <=> body <=> bottomBorder) <+> rightBorder-            addColumnBorders =-                let maybeAddCrosses = maybeIntersperse drawRowBorders cross-                    columnBorder = vBox $ maybeAddCrosses vBorders-                in intersperse columnBorder+        contentWidth = sum colWidths+        contentHeight = sum rowHeights -        let columns = mkColumn <$> zip3 allColAligns colWidths byColumn-            body = hBox $-                   maybeWrap drawColumnBorders addColumnBorders columns-        render $ maybeWrap drawSurroundingBorder addSurroundingBorder body+        hBorderLength = contentWidth + if drawColumnBorders cfg+                                       then max (length colWidths - 1) 0+                                       else 0+        vBorderHeight = contentHeight + if drawRowBorders cfg+                                        then max (length rowHeights - 1) 0+                                        else 0+        horizBorder = hLimit hBorderLength hBorder+        vertBorder = vLimit vBorderHeight vBorder +        leftBorder =+            vBox [topLeftCorner, vertBorder, bottomLeftCorner]+        rightBorder =+            vBox [topRightCorner, vertBorder, bottomRightCorner]++        maybeWrap check f =+            if check cfg then f else id+        addSurroundingBorder b =+            leftBorder <+> (horizBorder <=> b <=> horizBorder) <+> rightBorder+        addRowBorders =+            intersperse horizBorder++        rowsWithColumnBorders = (\(h, row) -> hBox $ maybeColumnBorders h row) <$> zip rowHeights rows+        maybeColumnBorders height = maybeIntersperse cfg drawColumnBorders (vLimit height vBorder)+        body = vBox $+               maybeWrap drawRowBorders addRowBorders rowsWithColumnBorders++    return $ maybeWrap drawSurroundingBorder addSurroundingBorder body++tableCellLayout :: Table n -> RenderM n (RenderedTableCells n)+tableCellLayout t = do+    ctx <- getContext+    cellResults <- forM (tableRows t) $ mapM render++    let rowHeights = rowHeight <$> cellResults+        colWidths = colWidth <$> transpose cellResults+        numRows = length rowHeights+        numCols = if length cellResults >= 1+                     then length (cellResults !! 0)+                     else 0+        allRowAligns = (\i -> M.findWithDefault (defaultRowAlignment t) i (rowAlignments t)) <$>+                       [0..numRows - 1]+        allColAligns = (\i -> M.findWithDefault (defaultColumnAlignment t) i (columnAlignments t)) <$>+                       [0..numCols - 1]+        rowHeight = maximum . fmap (imageHeight . image)+        colWidth = maximum . fmap (imageWidth . image)++        toW = Widget Fixed Fixed . return+        fillEmptyCell w h result =+            if imageWidth (image result) == 0 && imageHeight (image result) == 0+            then result { image = charFill (ctx^.attrL) ' ' w h }+            else result+        mkRow (vAlign, height, rowCells) =+            let paddedCells = flip map (zip3 allColAligns colWidths rowCells) $ \(hAlign, width, cell) ->+                    applyColAlignment width hAlign $+                    applyRowAlignment height vAlign $+                    toW $+                    fillEmptyCell width height cell+            in paddedCells++    let rows = mkRow <$> zip3 allRowAligns rowHeights cellResults++    return $ RenderedTableCells { renderedTableRows = rows+                                , renderedTableColumnWidths = colWidths+                                , renderedTableRowHeights = rowHeights+                                , borderConfiguration = tableBorderConfiguration t+                                }++maybeIntersperse :: BorderConfiguration -> (BorderConfiguration -> Bool) -> Widget n -> [Widget n] -> [Widget n]+maybeIntersperse cfg f v | f cfg = intersperse v+                         | otherwise = id+ topLeftCorner :: Widget n topLeftCorner = joinableBorder $ Edges False True False True @@ -301,20 +359,20 @@ bottomRightCorner :: Widget n bottomRightCorner = joinableBorder $ Edges True False True False -cross :: Widget n-cross = joinableBorder $ Edges True True True True--leftT :: Widget n-leftT = joinableBorder $ Edges True True False True--rightT :: Widget n-rightT = joinableBorder $ Edges True True True False--topT :: Widget n-topT = joinableBorder $ Edges False True True True--bottomT :: Widget n-bottomT = joinableBorder $ Edges True False True True+-- | Given a "table row" of widgets, align each one according to the+-- list of specified column alignments in columns of the specified+-- widths.+alignColumns :: [ColumnAlignment]+             -- ^ The column alignments to use for each widget,+             -- respectively.+             -> [Int]+             -- ^ The width of each column in terminal columns,+             -- respectively.+             -> [Widget n]+             -- ^ The column cells to align.+             -> [Widget n]+alignColumns as widths cells =+    (\(w, a, c) -> applyColAlignment w a c) <$> zip3 widths as cells  applyColAlignment :: Int -> ColumnAlignment -> Widget n -> Widget n applyColAlignment width align w =