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 +23/−0
- README.md +14/−8
- brick.cabal +35/−1
- docs/guide.rst +6/−6
- programs/EditorLineNumbersDemo.hs +135/−0
- programs/TabularListDemo.hs +146/−0
- src/Brick/Main.hs +1/−0
- src/Brick/Widgets/Core.hs +1/−0
- src/Brick/Widgets/Internal.hs +1/−0
- src/Brick/Widgets/Table.hs +130/−72
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 =