packages feed

brick-tabular-list-0.2.0.1: demo/Internal/MixedTabularList.hs

{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedLabels #-}
module Internal.MixedTabularList (
  runMain
)
where

import Brick.Widgets.TabularList.Mixed hiding (sizes, contents)
-- base
import GHC.Generics (Generic)
import Control.Monad (void)
-- Third party libraries
import Optics.Core
import Data.Sequence (Seq(..))
import qualified Data.Sequence as S
-- brick
import Brick.Main
import Brick.AttrMap
import Brick.Types
import Brick.Widgets.Core
import Brick.Widgets.Center
import Brick.Widgets.Border
import Brick.Util
import Brick.Widgets.List
import Brick.Widgets.Border.Style
import Graphics.Vty (defAttr, Event(..), Key(..), Modifier(..), black, white, blue, red)

data Song = Song {
  artist :: String
, title :: String
, album :: String
, time :: Int
} deriving Generic

data LibraryEntry = LibFolder String | LibSong Song deriving Generic

data LibCell = StringCell String | FolderCell String | TimeCell Int deriving Generic

data Name = TheList deriving (Eq, Ord, Show)

type LibraryList = MixedTabularList Name LibraryEntry LibCell Int String
type LibraryRenderers = MixedRenderers Name LibraryEntry LibCell Int String
type LibraryContents = MixedContents LibraryEntry LibCell Int String
type LibraryEventHandler = Event -> EventM Name LibraryList ()

libraryEntries :: Seq LibraryEntry
libraryEntries = let
  folders = [LibFolder "[.]", LibFolder "[..]"]
  songs = map (\n -> LibSong (Song ("Artist " <> show n) ("Title " <> show n) ("Album " <> show n) n)) [1..1000]
  in S.fromList $ folders ++ songs

contents :: LibraryContents
contents = let
  getCell (LibFolder s) 0 = Just $ FolderCell $ "Folder: " <> s
  getCell (LibFolder _) _ = Nothing
  getCell (LibSong s) 0 = Just $ StringCell $ s ^. #artist
  getCell (LibSong s) 1 = Just $ StringCell $ s ^. #title
  getCell (LibSong s) 2 = Just $ StringCell $ s ^. #album
  getCell (LibSong s) 3 = Just $ TimeCell $ s ^. #time
  getCell (LibSong _) _ = Nothing
  getColumnHeader 0 = Just "Artist"
  getColumnHeader 1 = Just "Title"
  getColumnHeader 2 = Just "Album"
  getColumnHeader 3 = Just "Time"
  getColumnHeader _ = Nothing
  getRowHeader _ n = Just (n+1)
  in MixedContents {
    cell = getCell
  , rowHdr = Just getRowHeader
  , colHdr = Just getColumnHeader
  }

data AppState = AppState {
  libList :: LibraryList
, libRenderers :: LibraryRenderers
, listWidth :: Int
} deriving Generic

handleLibEvent :: LibraryEventHandler -> BrickEvent Name () -> EventM Name AppState ()
handleLibEvent eh e = case e of
  VtyEvent (EvKey KEsc []) -> halt
  VtyEvent (EvKey (KChar 'q') []) -> halt
  VtyEvent (EvKey (KChar 'c') []) -> zoom (#libRenderers . #drawColHdr) $ modify $ \case
    Nothing -> Just dch
    Just _ -> Nothing
  VtyEvent (EvKey (KChar 'r') []) -> zoom (#libRenderers . #drawRowHdr) $ modify $ \case
    Nothing -> Just drh
    Just _ -> Nothing
  VtyEvent (EvKey (KChar '-') []) -> zoom #listWidth $ modify $ max 1 . subtract 1
  VtyEvent (EvKey (KChar '=') []) -> zoom #listWidth $ modify (+1)
  VtyEvent e -> zoom #libList $ eh e
  _ -> return ()
  where abc = []

drawUi :: [String] -> AppState -> [Widget Name]
drawUi msgs s = let
  theList = renderMixedTabularList (s ^. #libRenderers) True (s ^. #libList)
  in [vCenter $ vBox $ hCenter (border $ vLimit 15 $ hLimit (s ^. #listWidth) theList)
                     : hCenter (str "Press Esc or q to exit" )
                     : hCenter (str "Press c to toggle column headers")
                     : hCenter (str "Press r to toggle row headaers")
                     : hCenter (str "Press - to shorten the list and = to widen the list")
                     : hCenter (str " ")
                     : map (hCenter . str) msgs ]

columnHdrAttr :: AttrName
columnHdrAttr = attrName "columnHeader"
rowHdrAttr :: AttrName
rowHdrAttr = attrName "rowHeader"

getApp :: [String] -> LibraryEventHandler -> App AppState () Name
getApp msgs eh = App {
  appDraw = drawUi msgs
, appChooseCursor = neverShowCursor
, appHandleEvent = handleLibEvent eh
, appStartEvent = return ()
, appAttrMap = const $ attrMap defAttr [ (listSelectedAttr, black `on` white)
                                       , (columnHdrAttr, fg blue)
                                       , (rowHdrAttr, fg red)]
}

dch lf ci = \case
  Nothing -> hCenter $ str " "
  Just ch -> withAttr columnHdrAttr $ padRight Max (str ch) <+> str " "

drh lf wd (Position i f) row = \case
  Nothing -> hCenter $ str " "
  Just rh ->
    let attrFn = if f
          then id
          else withAttr rowHdrAttr
    in attrFn $ padRight (Pad $ if wd > 0 then 0 else 1) $ padLeft Max (str $ show rh)

renderers :: LibraryRenderers
renderers = MixedRenderers {
  drawCell = \lf (Position i f) row mc -> case mc of
    Nothing -> hCenter $ str " "
    Just (StringCell s) -> padRight Max (str s) <+> str " "
    Just (FolderCell s) -> padRight Max $ str s
    Just (TimeCell time) -> let
      (min, sec) = time `divMod` 60
      time' = case min of
        0 -> show sec
        _ -> show min <> ":" <> show sec
      in padRight Max (str time') <+> str " "
, drawRowHdr = Just drh
, drawColHdr = Just dch
-- This is the same as Nothing.
, drawColHdrRowHdr = Just $ \_ _ -> fill ' '
}

runMain :: [String] -> LibraryEventHandler -> IO ()
runMain msgs eh = do
  let sizes = MixedSizes {
        rowHdr = Just $ VisibleRowHeaders $ \_ rowHs -> (+2) $ maximum $ map (length . show) rowHs
      , colSizes = AvailWidth $ \aW -> let
          artist = max 7 $ (aW * 30) `div` 100
          title = max 6 $ (aW * 30) `div` 100
          time = 7
          album = aW - artist - title - time
          songWidths = [artist, title, album, time]
          in ColSizes {
            rowKind = \case
              LibFolder _ -> [aW]
              LibSong _ -> songWidths
          , colHdr = Just (songWidths, 1)
          }
      }
      appState = AppState {
        libList = mixedTabularList TheList libraryEntries 1 sizes contents
      , libRenderers = renderers
      , listWidth = 80
      }
  void $ defaultMain (getApp msgs eh) appState