packages feed

mushu-0.1.0: app/UI/Widgets/Playlist.hs

{-# LANGUAGE TemplateHaskell #-}
module UI.Widgets.Playlist
( PlaylistState
, mkState
, mkWidget
, handleEvent
, update
, playingSongL
, attrs
) where

import ClassyPrelude hiding ((<>), on)
import Data.Monoid ((<>))
import TH (makeSuffixLenses)
import Lens.Micro.Platform ((^.), (%~), (&))

import Brick.Types (Widget(..), Padding(..), EventM)
import Brick.Widgets.List (List, list, renderList, listSelectedAttr, listAttr, listMoveDown, listMoveUp, listSelectedL, listElementsL, listReplace)
import Brick.AttrMap (AttrName)
import Brick.Widgets.Center (hCenter)
import Brick.Widgets.Core ((<=>), (<+>), str, withAttr, padLeft, padRight, hLimit)
import Brick.Widgets.Border (hBorder)
import Brick.Util (fg, on)

import Data.Vector ((!))

import qualified Graphics.Vty as Vty

import UI.Utils (secondsToTime)

import Network.MPD (Song(..), Metadata(..), withMPD, playId)
-- import Network.MPD (withMPD, Song(..), Id(..), playId)
import MPD (tag)

data PlaylistState n = PlaylistState
  { _songs :: List n Song
  , _playingSong :: Maybe Song
  }

mkState :: n -> [Song] -> PlaylistState n
mkState name songs = PlaylistState
  { _songs = list name (fromList songs) 1
  , _playingSong = Nothing
  }

makeSuffixLenses ''PlaylistState

-- mkWidget :: (Show n, Ord n) => List n Song -> Widget n
mkWidget :: (Show n, Ord n) => PlaylistState n -> Widget n
mkWidget state = header <=> hBorder <=> renderList listDrawElement True (state^.songsL)
  where
    header = artist <+> track <+> title <+> album <+> time
    artist = column (Just 25) (Pad 0) Max $ str "Artist"
    track = column (Just 5) Max (Pad 0) $ str "Track"
    title = column Nothing (Pad 2) Max $ str "Title"
    album = column (Just 35) (Pad 2) Max $ str "Album"
    time = column (Just 8) Max (Pad 1) $ str "Time"

update :: [Song] -> PlaylistState n -> PlaylistState n
update songs state = state & songsL %~ (listReplace (fromList songs) (Just 0))

handleEvent :: Vty.Event -> PlaylistState n -> EventM n (PlaylistState n)
handleEvent event state = case event of
  (Vty.EvKey (Vty.KChar 'j') []) -> return $ nextSong state
  (Vty.EvKey (Vty.KChar 'k') []) -> return $ previousSong state
  (Vty.EvKey Vty.KEnter []) -> play state
  _ -> return state

nextSong :: PlaylistState n -> PlaylistState n
nextSong state = state & songsL %~ listMoveDown

previousSong :: PlaylistState n -> PlaylistState n
previousSong state = state & songsL %~ listMoveUp

play :: PlaylistState n -> EventM n (PlaylistState n)
play state = case (state^.songsL.listSelectedL) of
  Nothing -> return state
  Just i -> do
    let selectedSong = (state^.songsL.listElementsL) ! i
    case (sgId selectedSong) of
      Nothing -> return state
      Just id -> do
        _ <- liftIO $ withMPD $ playId id
        return state

listDrawElement ::  Bool -> Song -> Widget n
listDrawElement sel song = hCenter $ formatListElement False sel $ artist <+> track <+> title <+> album <+> time
  where
    artist = column (Just 25) (Pad 0) Max $ str. unpack $ tag Artist "<unknown>" song
    track = column (Just 5) Max (Pad 0) $ str. unpack $ tag Track "?" song
    title = column Nothing (Pad 2) Max $ str . unpack $ tag Title "<no title>" song
    album = column (Just 35) (Pad 2) Max $ str . unpack $ tag Album "<no album>" song
    time = column (Just 8) Max (Pad 1) $ str . unpack $ secondsToTime $ sgLength song

-- Make a column considering a left padding, a right padding and an optional width
column :: Maybe Int -> Padding -> Padding -> Widget n -> Widget n
column maybeWidth left right widget = case maybeWidth of
  Nothing -> w
  Just wth -> hLimit wth w
  where w = padLeft left $ padRight right widget

formatListElement :: Bool -> Bool -> Widget n -> Widget n
formatListElement playing sel = withAttr attr
  where attr = case playing of
                True -> case sel of
                  True -> playlistSelPlayingAttrName
                  False -> playlistPlayingAttrName
                False -> case sel of
                  True -> playlistSelAttrName
                  False -> playlistListAttrName

playlistListAttrName :: AttrName
playlistListAttrName = listAttr <> "playlist"

playlistSelAttrName :: AttrName
playlistSelAttrName = listSelectedAttr <> "playlist-selected"

playlistPlayingAttrName :: AttrName
playlistPlayingAttrName = listAttr <> "playlist-playing"

playlistSelPlayingAttrName :: AttrName
playlistSelPlayingAttrName = listSelectedAttr <> "playlist-selected-playing"

attrs :: [(AttrName, Vty.Attr)]
attrs = [ (playlistListAttrName, fg Vty.white)
        , (playlistPlayingAttrName, Vty.withStyle (fg Vty.white) Vty.bold)
        , (playlistSelAttrName, Vty.withStyle (Vty.green `on` Vty.black) Vty.standout)
        , (playlistSelPlayingAttrName, Vty.withStyle (Vty.withStyle (Vty.green `on` Vty.black) Vty.standout) Vty.bold)
        ]