packages feed

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 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