brick 0.59 → 0.60
raw patch · 4 files changed
+336/−1 lines, 4 filesdep ~basedep ~vtynew-component:exe:brick-table-demoPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base, vty
API changes (from Hackage documentation)
+ Brick.Widgets.Table: AlignBottom :: RowAlignment
+ Brick.Widgets.Table: AlignCenter :: ColumnAlignment
+ Brick.Widgets.Table: AlignLeft :: ColumnAlignment
+ Brick.Widgets.Table: AlignMiddle :: RowAlignment
+ Brick.Widgets.Table: AlignRight :: ColumnAlignment
+ Brick.Widgets.Table: AlignTop :: RowAlignment
+ Brick.Widgets.Table: TEInvalidCellSizePolicy :: TableException
+ Brick.Widgets.Table: TEUnequalRowSizes :: TableException
+ Brick.Widgets.Table: alignBottom :: Int -> Table n -> Table n
+ Brick.Widgets.Table: alignCenter :: Int -> Table n -> Table n
+ Brick.Widgets.Table: alignLeft :: Int -> Table n -> Table n
+ Brick.Widgets.Table: alignMiddle :: Int -> Table n -> Table n
+ Brick.Widgets.Table: alignRight :: Int -> Table n -> Table n
+ Brick.Widgets.Table: alignTop :: Int -> Table n -> Table n
+ Brick.Widgets.Table: columnBorders :: Bool -> Table n -> Table n
+ Brick.Widgets.Table: data ColumnAlignment
+ Brick.Widgets.Table: data RowAlignment
+ Brick.Widgets.Table: data Table n
+ Brick.Widgets.Table: data TableException
+ Brick.Widgets.Table: instance GHC.Classes.Eq Brick.Widgets.Table.ColumnAlignment
+ Brick.Widgets.Table: instance GHC.Classes.Eq Brick.Widgets.Table.RowAlignment
+ Brick.Widgets.Table: instance GHC.Classes.Eq Brick.Widgets.Table.TableException
+ Brick.Widgets.Table: instance GHC.Exception.Type.Exception Brick.Widgets.Table.TableException
+ Brick.Widgets.Table: instance GHC.Read.Read Brick.Widgets.Table.ColumnAlignment
+ Brick.Widgets.Table: instance GHC.Read.Read Brick.Widgets.Table.RowAlignment
+ Brick.Widgets.Table: instance GHC.Read.Read Brick.Widgets.Table.TableException
+ Brick.Widgets.Table: instance GHC.Show.Show Brick.Widgets.Table.ColumnAlignment
+ Brick.Widgets.Table: instance GHC.Show.Show Brick.Widgets.Table.RowAlignment
+ Brick.Widgets.Table: instance GHC.Show.Show Brick.Widgets.Table.TableException
+ Brick.Widgets.Table: renderTable :: Table n -> Widget n
+ Brick.Widgets.Table: rowBorders :: Bool -> Table n -> Table n
+ Brick.Widgets.Table: setColAlignment :: ColumnAlignment -> Int -> Table n -> Table n
+ Brick.Widgets.Table: setDefaultColAlignment :: ColumnAlignment -> Table n -> Table n
+ Brick.Widgets.Table: setDefaultRowAlignment :: RowAlignment -> Table n -> Table n
+ Brick.Widgets.Table: setRowAlignment :: RowAlignment -> Int -> Table n -> Table n
+ Brick.Widgets.Table: surroundingBorder :: Bool -> Table n -> Table n
+ Brick.Widgets.Table: table :: [[Widget n]] -> Table n
- Brick.BorderMap: eBottomL :: forall a_acTf. Lens' (Edges a_acTf) a_acTf
+ Brick.BorderMap: eBottomL :: forall a_acUV. Lens' (Edges a_acUV) a_acUV
- Brick.BorderMap: eLeftL :: forall a_acTf. Lens' (Edges a_acTf) a_acTf
+ Brick.BorderMap: eLeftL :: forall a_acUV. Lens' (Edges a_acUV) a_acUV
- Brick.BorderMap: eRightL :: forall a_acTf. Lens' (Edges a_acTf) a_acTf
+ Brick.BorderMap: eRightL :: forall a_acUV. Lens' (Edges a_acUV) a_acUV
- Brick.BorderMap: eTopL :: forall a_acTf. Lens' (Edges a_acTf) a_acTf
+ Brick.BorderMap: eTopL :: forall a_acUV. Lens' (Edges a_acUV) a_acUV
- Brick.Types: bordersL :: forall n_axXs. Lens' (Result n_axXs) (BorderMap DynBorder)
+ Brick.Types: bordersL :: forall n_axZ8. Lens' (Result n_axZ8) (BorderMap DynBorder)
- Brick.Types: cursorLocationL :: forall n_awJY. Lens' (CursorLocation n_awJY) Location
+ Brick.Types: cursorLocationL :: forall n_awLE. Lens' (CursorLocation n_awLE) Location
- Brick.Types: cursorLocationNameL :: forall n_awJY n_ayAQ. Lens (CursorLocation n_awJY) (CursorLocation n_ayAQ) (Maybe n_awJY) (Maybe n_ayAQ)
+ Brick.Types: cursorLocationNameL :: forall n_awLE n_ayCw. Lens (CursorLocation n_awLE) (CursorLocation n_ayCw) (Maybe n_awLE) (Maybe n_ayCw)
- Brick.Types: cursorsL :: forall n_axXs. Lens' (Result n_axXs) [CursorLocation n_axXs]
+ Brick.Types: cursorsL :: forall n_axZ8. Lens' (Result n_axZ8) [CursorLocation n_axZ8]
- Brick.Types: eBottomL :: forall a_acTf. Lens' (Edges a_acTf) a_acTf
+ Brick.Types: eBottomL :: forall a_acUV. Lens' (Edges a_acUV) a_acUV
- Brick.Types: eLeftL :: forall a_acTf. Lens' (Edges a_acTf) a_acTf
+ Brick.Types: eLeftL :: forall a_acUV. Lens' (Edges a_acUV) a_acUV
- Brick.Types: eRightL :: forall a_acTf. Lens' (Edges a_acTf) a_acTf
+ Brick.Types: eRightL :: forall a_acUV. Lens' (Edges a_acUV) a_acUV
- Brick.Types: eTopL :: forall a_acTf. Lens' (Edges a_acTf) a_acTf
+ Brick.Types: eTopL :: forall a_acUV. Lens' (Edges a_acUV) a_acUV
- Brick.Types: extentsL :: forall n_axXs. Lens' (Result n_axXs) [Extent n_axXs]
+ Brick.Types: extentsL :: forall n_axZ8. Lens' (Result n_axZ8) [Extent n_axZ8]
- Brick.Types: imageL :: forall n_axXs. Lens' (Result n_axXs) Image
+ Brick.Types: imageL :: forall n_axZ8. Lens' (Result n_axZ8) Image
- Brick.Types: visibilityRequestsL :: forall n_axXs. Lens' (Result n_axXs) [VisibilityRequest]
+ Brick.Types: visibilityRequestsL :: forall n_axZ8. Lens' (Result n_axZ8) [VisibilityRequest]
- Brick.Widgets.Dialog: dialogButtonsL :: forall a_a18l2 a_a18lG. Lens (Dialog a_a18l2) (Dialog a_a18lG) [(String, a_a18l2)] [(String, a_a18lG)]
+ Brick.Widgets.Dialog: dialogButtonsL :: forall a_a18MC a_a18Ng. Lens (Dialog a_a18MC) (Dialog a_a18Ng) [(String, a_a18MC)] [(String, a_a18Ng)]
- Brick.Widgets.Dialog: dialogSelectedIndexL :: forall a_a18l2. Lens' (Dialog a_a18l2) (Maybe Int)
+ Brick.Widgets.Dialog: dialogSelectedIndexL :: forall a_a18MC. Lens' (Dialog a_a18MC) (Maybe Int)
- Brick.Widgets.Dialog: dialogTitleL :: forall a_a18l2. Lens' (Dialog a_a18l2) (Maybe String)
+ Brick.Widgets.Dialog: dialogTitleL :: forall a_a18MC. Lens' (Dialog a_a18MC) (Maybe String)
- Brick.Widgets.Dialog: dialogWidthL :: forall a_a18l2. Lens' (Dialog a_a18l2) Int
+ Brick.Widgets.Dialog: dialogWidthL :: forall a_a18MC. Lens' (Dialog a_a18MC) Int
- Brick.Widgets.Edit: editContentsL :: forall t_a13gx n_a13gy t_a13hc. Lens (Editor t_a13gx n_a13gy) (Editor t_a13hc n_a13gy) (TextZipper t_a13gx) (TextZipper t_a13hc)
+ Brick.Widgets.Edit: editContentsL :: forall t_a13id n_a13ie t_a13iS. Lens (Editor t_a13id n_a13ie) (Editor t_a13iS n_a13ie) (TextZipper t_a13id) (TextZipper t_a13iS)
- Brick.Widgets.FileBrowser: fileBrowserEntryFilterL :: forall n_a1itw. Lens' (FileBrowser n_a1itw) (Maybe (FileInfo -> Bool))
+ Brick.Widgets.FileBrowser: fileBrowserEntryFilterL :: forall n_a1iTq. Lens' (FileBrowser n_a1iTq) (Maybe (FileInfo -> Bool))
- Brick.Widgets.FileBrowser: fileBrowserSelectableL :: forall n_a1itw. Lens' (FileBrowser n_a1itw) (FileInfo -> Bool)
+ Brick.Widgets.FileBrowser: fileBrowserSelectableL :: forall n_a1iTq. Lens' (FileBrowser n_a1iTq) (FileInfo -> Bool)
- Brick.Widgets.List: listElementsL :: forall n_a1dip t_a1diq e_a1dir t_a1dx3 e_a1dx4. Lens (GenericList n_a1dip t_a1diq e_a1dir) (GenericList n_a1dip t_a1dx3 e_a1dx4) (t_a1diq e_a1dir) (t_a1dx3 e_a1dx4)
+ Brick.Widgets.List: listElementsL :: forall n_a1dIQ t_a1dIR e_a1dIS t_a1dWX e_a1dWY. Lens (GenericList n_a1dIQ t_a1dIR e_a1dIS) (GenericList n_a1dIQ t_a1dWX e_a1dWY) (t_a1dIR e_a1dIS) (t_a1dWX e_a1dWY)
- Brick.Widgets.List: listItemHeightL :: forall n_a1dip t_a1diq e_a1dir. Lens' (GenericList n_a1dip t_a1diq e_a1dir) Int
+ Brick.Widgets.List: listItemHeightL :: forall n_a1dIQ t_a1dIR e_a1dIS. Lens' (GenericList n_a1dIQ t_a1dIR e_a1dIS) Int
- Brick.Widgets.List: listNameL :: forall n_a1dip t_a1diq e_a1dir n_a1dx5. Lens (GenericList n_a1dip t_a1diq e_a1dir) (GenericList n_a1dx5 t_a1diq e_a1dir) n_a1dip n_a1dx5
+ Brick.Widgets.List: listNameL :: forall n_a1dIQ t_a1dIR e_a1dIS n_a1dWZ. Lens (GenericList n_a1dIQ t_a1dIR e_a1dIS) (GenericList n_a1dWZ t_a1dIR e_a1dIS) n_a1dIQ n_a1dWZ
- Brick.Widgets.List: listSelectedL :: forall n_a1dip t_a1diq e_a1dir. Lens' (GenericList n_a1dip t_a1diq e_a1dir) (Maybe Int)
+ Brick.Widgets.List: listSelectedL :: forall n_a1dIQ t_a1dIR e_a1dIS. Lens' (GenericList n_a1dIQ t_a1dIR e_a1dIS) (Maybe Int)
Files
- CHANGELOG.md +8/−0
- brick.cabal +15/−1
- programs/TableDemo.hs +63/−0
- src/Brick/Widgets/Table.hs +250/−0
CHANGELOG.md view
@@ -2,6 +2,14 @@ Brick changelog --------------- +0.60+----++New features:+ * Added `Brick.Widgets.Table` to support drawing basic tables. See+ `programs/TableDemo.hs` for a demonstration (`cabal new-run -f demos+ brick-table-demo`).+ 0.59 ----
brick.cabal view
@@ -1,5 +1,5 @@ name: brick-version: 0.59+version: 0.60 synopsis: A declarative terminal user interface library description: Write terminal user interfaces (TUIs) painlessly with 'brick'! You@@ -105,6 +105,7 @@ Brick.Widgets.FileBrowser Brick.Widgets.List Brick.Widgets.ProgressBar+ Brick.Widgets.Table Data.IMap Data.Text.Markup other-modules:@@ -138,6 +139,19 @@ word-wrap >= 0.2 if impl(ghc < 8.0) build-depends: semigroups++executable brick-table-demo+ if !flag(demos)+ Buildable: False+ hs-source-dirs: programs+ ghc-options: -threaded -Wall -Wcompat -O2+ default-language: Haskell2010+ default-extensions: CPP+ main-is: TableDemo.hs+ build-depends: base,+ brick,+ text,+ vty executable brick-tail-demo if !flag(demos)
+ programs/TableDemo.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE CPP #-}+module Main where++#if !(MIN_VERSION_base(4,11,0))+import Data.Monoid ((<>))+#endif+import Brick+import Brick.Widgets.Table+import Brick.Widgets.Center (center)++ui :: Widget ()+ui = center $ renderTable leftTable <+>+ padLeft (Pad 5) (renderTable rightTableA <=>+ renderTable rightTableB <=>+ renderTable rightTableC)++innerTable :: Table ()+innerTable =+ surroundingBorder False $+ table [ [txt "inner", txt "table"]+ , [txt "is", txt "here"]+ ]++leftTable :: Table ()+leftTable =+ alignCenter 1 $+ alignRight 2 $+ alignMiddle 2 $+ table [ [txt "Left", txt "Center", txt "Right"]+ , [txt "X", txt "Some things", txt "A"]+ , [renderTable innerTable, txt "are", txt "B"]+ , [txt "Z", txt "centered", txt "C"]+ ]++rightTableA :: Table ()+rightTableA =+ rowBorders False $+ setDefaultColAlignment AlignCenter $+ table [ [txt "A", txt "without"]+ , [txt "table", txt "row borders"]+ ]++rightTableB :: Table ()+rightTableB =+ columnBorders False $+ setDefaultColAlignment AlignCenter $+ table [ [txt "A", txt "table"]+ , [txt "without", txt "column borders"]+ ]++rightTableC :: Table ()+rightTableC =+ surroundingBorder False $+ rowBorders False $+ columnBorders False $+ setDefaultColAlignment AlignCenter $+ table [ [txt "A", txt "table"]+ , [txt "without", txt "any borders"]+ ]++main :: IO ()+main = simpleMain ui
+ src/Brick/Widgets/Table.hs view
@@ -0,0 +1,250 @@+-- | Support for basic table drawing.+module Brick.Widgets.Table+ (+ -- * Types+ Table+ , ColumnAlignment(..)+ , RowAlignment(..)+ , TableException(..)++ -- * Construction+ , table++ -- * Configuration+ , alignLeft+ , alignRight+ , alignCenter+ , alignTop+ , alignMiddle+ , alignBottom+ , setColAlignment+ , setRowAlignment+ , setDefaultColAlignment+ , setDefaultRowAlignment+ , surroundingBorder+ , rowBorders+ , columnBorders++ -- * Rendering+ , renderTable+ )+where++import Control.Monad (forM)+import qualified Control.Exception as E+import Data.List (transpose, intersperse, nub)+import qualified Data.Map as M+import Graphics.Vty (imageHeight, imageWidth)++import Brick.Types+import Brick.Widgets.Core+import Brick.Widgets.Center+import Brick.Widgets.Border++-- | Column alignment modes.+data ColumnAlignment =+ AlignLeft+ -- ^ Align all cells to the left.+ | AlignCenter+ -- ^ Center the content horizontally in all cells in the column.+ | AlignRight+ -- ^ Align all cells to the right.+ deriving (Eq, Show, Read)++-- | Row alignment modes.+data RowAlignment =+ AlignTop+ -- ^ Align all cells to the top.+ | AlignMiddle+ -- ^ Center the content vertically in all cells in the row.+ | AlignBottom+ -- ^ Align all cells to the bottom.+ deriving (Eq, Show, Read)++-- | A table creation exception.+data TableException =+ TEUnequalRowSizes+ -- ^ Rows did not all have the same number of cells.+ | TEInvalidCellSizePolicy+ -- ^ Some cells in the table did not use the 'Fixed' size policy for+ -- both horizontal and vertical sizing.+ deriving (Eq, Show, Read)++instance E.Exception TableException where++-- | A table data structure.+data Table n =+ Table { columnAlignments :: M.Map Int ColumnAlignment+ , rowAlignments :: M.Map Int RowAlignment+ , tableRows :: [[Widget n]]+ , defaultColumnAlignment :: ColumnAlignment+ , defaultRowAlignment :: RowAlignment+ , drawSurroundingBorder :: Bool+ , drawRowBorders :: Bool+ , drawColumnBorders :: Bool+ }++-- | Construct a new table.+--+-- The argument is the list of rows, with each element of the argument+-- list being the columns of the respective row.+--+-- By default, all columns are left-aligned. Use the alignment functions+-- in this module to change that behavior.+--+-- By default, all rows are top-aligned. Use the alignment functions in+-- this module to change that behavior.+--+-- By default, the table will draw borders between columns, between+-- rows, and around the outside of the table. Border-drawing behavior+-- can be configured with the API in this module. Note that tables+-- always draw with 'joinBorders' enabled.+--+-- All cells of all rows MUST use the 'Fixed' growth policy for both+-- horizontal and vertical growth. If the argument list contains+-- any cells that use the 'Greedy' policy, this will raise a+-- 'TableException'.+--+-- All rows must have the same number of cells. If not, this will raise+-- a 'TableException'.+table :: [[Widget n]] -> Table n+table rows =+ if not allFixed+ then E.throw TEInvalidCellSizePolicy+ else if not allSameLength+ then E.throw TEUnequalRowSizes+ else t+ where+ allSameLength = length (nub (length <$> rows)) == 1+ allFixed = all fixedRow rows+ fixedRow = all fixedCell+ fixedCell w = hSize w == Fixed && vSize w == Fixed+ t = Table { columnAlignments = mempty+ , rowAlignments = mempty+ , tableRows = rows+ , drawSurroundingBorder = True+ , drawRowBorders = True+ , drawColumnBorders = True+ , defaultColumnAlignment = AlignLeft+ , defaultRowAlignment = AlignTop+ }++-- | Configure whether the table draws a border on its exterior.+surroundingBorder :: Bool -> Table n -> Table n+surroundingBorder b t =+ t { drawSurroundingBorder = b }++-- | Configure whether the table draws borders between its rows.+rowBorders :: Bool -> Table n -> Table n+rowBorders b t =+ t { drawRowBorders = b }++-- | Configure whether the table draws borders between its columns.+columnBorders :: Bool -> Table n -> Table n+columnBorders b t =+ t { drawColumnBorders = b }++-- | Align the specified column to the right. The argument is the column+-- index, starting with zero.+alignRight :: Int -> Table n -> Table n+alignRight = setColAlignment AlignRight++-- | Align the specified column to the left. The argument is the column+-- index, starting with zero.+alignLeft :: Int -> Table n -> Table n+alignLeft = setColAlignment AlignLeft++-- | Align the specified column to center. The argument is the column+-- index, starting with zero.+alignCenter :: Int -> Table n -> Table n+alignCenter = setColAlignment AlignCenter++-- | Align the specified row to the top. The argument is the row index,+-- starting with zero.+alignTop :: Int -> Table n -> Table n+alignTop = setRowAlignment AlignTop++-- | Align the specified row to the middle. The argument is the row+-- index, starting with zero.+alignMiddle :: Int -> Table n -> Table n+alignMiddle = setRowAlignment AlignMiddle++-- | Align the specified row to bottom. The argument is the row index,+-- starting with zero.+alignBottom :: Int -> Table n -> Table n+alignBottom = setRowAlignment AlignBottom++-- | Set the alignment for the specified column index (starting at+-- zero).+setColAlignment :: ColumnAlignment -> Int -> Table n -> Table n+setColAlignment a col t =+ t { columnAlignments = M.insert col a (columnAlignments t) }++-- | Set the alignment for the specified row index (starting at+-- zero).+setRowAlignment :: RowAlignment -> Int -> Table n -> Table n+setRowAlignment a row t =+ t { rowAlignments = M.insert row a (rowAlignments t) }++-- | Set the default column alignment for columns with no explicitly+-- configured alignment.+setDefaultColAlignment :: ColumnAlignment -> Table n -> Table n+setDefaultColAlignment a t =+ t { defaultColumnAlignment = a }++-- | Set the default row alignment for rows with no explicitly+-- configured alignment.+setDefaultRowAlignment :: RowAlignment -> Table n -> Table n+setDefaultRowAlignment a t =+ t { defaultRowAlignment = a }++-- | Render the table.+renderTable :: Table n -> Widget n+renderTable t =+ joinBorders $+ (if drawSurroundingBorder t then border else id) $+ Widget Fixed Fixed $ do+ let rows = tableRows t+ cellResults <- forM rows $ mapM render+ let 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+ totalHeight = sum rowHeights+ applyColAlignment align width w =+ Widget Fixed Fixed $ do+ result <- render w+ case align of+ AlignLeft -> return result+ AlignCenter -> render $ hLimit width $ hCenter $ toW result+ AlignRight -> render $+ padLeft (Pad (width - imageWidth (image result))) $+ toW result+ applyRowAlignment rHeight align result =+ case align of+ AlignTop -> toW result+ AlignMiddle -> vLimit rHeight $ vCenter $ toW result+ AlignBottom -> vLimit rHeight $ padTop Max $ toW result+ mkColumn (hAlign, width, colCells) = do+ let paddedCells = flip map (zip3 allRowAligns rowHeights colCells) $ \(vAlign, rHeight, cell) ->+ applyColAlignment hAlign width $+ applyRowAlignment rHeight vAlign cell+ maybeRowBorders = if drawRowBorders t+ then intersperse (hLimit width hBorder)+ else id+ render $ vBox $ maybeRowBorders paddedCells+ columns <- mapM mkColumn $ zip3 allColAligns colWidths byColumn+ let maybeColumnBorders =+ if drawColumnBorders t+ then let rowBorderHeight = if drawRowBorders t+ then length rows - 1+ else 0+ in intersperse (vLimit (totalHeight + rowBorderHeight) vBorder)+ else id+ render $ hBox $ maybeColumnBorders $ toW <$> columns