packages feed

hum-0.2.0.0: src/Hum/Views/Common.hs

-- | Module    : Hum.Views.Library
-- Copyright   : (c) Itai Y. Efrat 2020-2021
-- License     : GPLv2-or-later (see LICENSE)
-- Maintainer  : Itai Y. Efrat <itai3397@gmail.com>
--
-- Functions for the Help view.

module Hum.Views.Common where
import           Hum.Types
import           Brick.Types
import           Brick.Widgets.Core
import           Brick.Widgets.Center
import           Brick.Widgets.Edit
import           Brick.Widgets.Border
import           Hum.Attributes
import           Hum.Utils
import qualified Network.MPD                   as MPD
import qualified Data.Text                     as T
import           Brick.Widgets.List
import           Control.Lens

-- | Draw Now Playing box.
drawNowPlaying :: HumState -> Widget Name
drawNowPlaying st = reportExtent NowPlaying $ vLimit 5 . center $ maybe
  (txt "nothing.")
  nowPlaying
  (currentSong st)
 where
  nowPlaying song =
    txt "\n"
      <=> hCenter title
      <=> hCenter (artist <+> txt " - " <+> album)
      <=> progbar
      <=> (padRight Max playing <+> padLeft Max mode)
   where
    title = withAttr titleAttr $ txt $ meta "<no title>" MPD.Title song
    album =
      withAttr albumAttr (txt $ meta "<no album>" MPD.Album song)
        <+> txt " ("
        <+> withAttr dateAttr (txt $ meta "????" MPD.Date song)
        <+> txt ")"
    artist  = withAttr artistAttr $ txt $ meta "<no one>" MPD.Artist song
    progbar = withAttr timeAttr $ drawProgressBar st
    playing = txt $ maybe
      "[       ]"
      ((\t -> "[" <> t <> "]") . T.toLower . show . MPD.stState)
      (status st)
    formatMode t modeFun = maybe
      "-"
      ( (\case
          False -> "-"
          True  -> t
        )
      . modeFun
      )
      (status st)
    repeatmpd = formatMode "r" MPD.stRepeat
    random    = formatMode "z" MPD.stRandom
    single    = formatMode "s" MPD.stSingle
    consume   = formatMode "c" MPD.stConsume
    crossfade = formatMode "x" ((/= 0) . MPD.stXFadeWidth)
    mode =
      txt $ "[" <> repeatmpd <> random <> single <> consume <> crossfade <> "]"

-- | Draw progress bar for song time.
drawProgressBar :: HumState -> Widget Name
drawProgressBar st =
  Widget Fixed Fixed  $ do
    ctx <- getContext
    let width = ctx ^. windowWidthL
    render $ bar width
 where
  songTime = fromMaybe (0, 1) (MPD.stTime =<< status st)
  timeText =
    toString
      . (\(i, j) -> secondsToTime (round i) <> "/" <> secondsToTime (round j))
      $ songTime
  completed width = (\w (i, j) -> round ((i / j) * fromIntegral w)) width songTime
  bar width     = str
    (zipWith
      (\a b -> if a `elem` ("1234567890/:" :: String) then a else b)
      (replicate (-5 + div width 2) ' ' ++ timeText ++ replicate
        (-3 + div width 2)
        ' '
      )
      (replicate (completed width) '=' ++ replicate (width - (completed width)) ' ')
    )

-- | Either a number or a percent. To be used for widget horizontal size.
data PerCol =
    Per Int -- ^ percent size
  | Col Int -- ^ column number size

-- | Helper function for drawing column rows.
column
  :: Maybe PerCol -- ^ Maximum width, greedy if Nothing.
  -> Padding -- ^ Left padding
  -> Padding -- ^ Right padding
  -> Widget n
  -> Widget n
column maxWidth left right w = case maxWidth of
  Nothing      -> wpad
  Just (Per m) -> hLimitPercent m wpad
  Just (Col m) -> hLimit m wpad
  where wpad = padLeft left . padRight right $ w

-- | Returns True if text is substring of one of the given tags of the given song.
songSearch :: Text -> [MPD.Metadata] -> MPD.Song -> Bool
songSearch text metadata song =
  let mtags = (T.toLower <$>) . (`mmeta` song) <$> metadata
  in  or $ fromMaybe False <$> (T.isInfixOf (T.toLower text) <<$>> mtags)

-- | Returns True if text is substring of the given strings.
stringySearch :: MPD.ToString a => Text -> a -> Bool
stringySearch text value =
  T.isInfixOf (T.toLower text) (T.toLower . MPD.toText $ value)

-- | Draws a prompt.
drawPrompt :: HumState -> Widget Name
drawPrompt st = case st ^. promptsL . currentPromptL of
  PlSelectPrompt ->
    centerLayer
      .   border
      .   setAvailableSize (30, 10)
      .   center
      $   (hCenter . txt $ st ^. promptsL . promptTitleL)
      <=> hBorder
      <=> renderListWithIndex drawPlSelectRow
                              True
                              (st ^. promptsL . plSelectPromptL)
  TextPrompt ->
    centerLayer
      .   border
      .   setAvailableSize (30, 3)
      .   center
      $   (hCenter . txt $ st ^. promptsL . promptTitleL)
      <=> hBorder
      <=> (center . setAvailableSize (25, 1) . withDefAttr editorAttr)
            (renderEditor (txt . T.unlines) True (st ^. promptsL . textPromptL))
  YNPrompt ->
    centerLayer
      .   border
      .   hLimit 30
      $   (hCenter . txt $ st ^. promptsL . promptTitleL)
      <=> (hCenter . txt $ "[y/n]")

-- | Draw row in playlist select prompt.
drawPlSelectRow :: Int -> Bool -> Maybe MPD.PlaylistName -> Widget n
drawPlSelectRow i _ pl = if i==0 then
  str "New Playlist" <=> modifyDefAttr (const wobAttr) hBorder
  else str (MPD.toString $ fromMaybe "<error getting playlist name>" pl)