hum-0.2.0.0: src/Hum/Views/Playlists.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 Playlist view.
module Hum.Views.Playlists where
import Hum.Types
import Brick.Types
import Brick.Widgets.Edit hiding ( decodeUtf8 )
import Graphics.Vty.Input.Events
import Brick.Main
import Brick.Widgets.Core
import Brick.Widgets.Center
import Brick.Widgets.Border
import Brick.Widgets.Border.Style
import Control.Lens
import Brick.Widgets.List
import Hum.Attributes
import Hum.Views.Common
import Hum.Rebuild
import qualified Data.Text as T
import qualified Data.Text.Zipper as Z
import Network.MPD ( withMPD )
import qualified Network.MPD as MPD
import Hum.Utils
-- | Draw left column in Playlist view.
drawPlaylistLeft :: HumState -> Widget Name
drawPlaylistLeft st = reportExtent PlaylistLeft $
Widget Greedy Greedy $ do
ctx <- getContext
let vsize = ctx ^. windowHeightL - 6 -- HACK Don't hardcode nowplaying size?
render $ hCenter
( visible
. vLimit vsize
. center
$ hBorder
<=> hCenter
(renderList (const $ playlistRow st)
((focPlay . focus $ st) == FocPlaylists)
(MPD.toText <$> st ^. playlistsL . plListL)
)
)
-- | Draw right column in Playlist view.
drawPlaylistRight :: HumState -> Widget Name
drawPlaylistRight st = reportExtent PlaylistRight $
Widget Greedy Greedy $ do
ctx <- getContext
let vsize = ctx ^. windowHeightL - 6 -- HACK Don't hardcode nowplaying size?
render $ hCenter
( visible
. vLimit vsize
. center
$ (if st ^. editableL
then
withDefAttr listHighlightedAttr
. withBorderStyle (borderStyleFromChar '=')
$ hBorderWithLabel
$ txt "editing"
else hBorder
)
<=> hCenter
(renderList (const $ playlistSongRow st)
((focPlay . focus $ st) == FocPSongs)
(st ^. playlistsL . plSongsL)
)
)
-- | Draw row in playlist column in Playlist view.
playlistRow :: HumState -> T.Text -> Widget n -- TODO rename?
playlistRow _ val =
withAttr albumAttr $ column Nothing (Pad 1) Max $ txt val
-- | Draw row in song column in Playlist view.
playlistSongRow :: HumState -> (MPD.Song,Highlight) -> Widget n
playlistSongRow st (song,hl) =
let pathsInQueue =
(MPD.sgFilePath <$>) . (fst <$>) . listElements . queue $ st
in (if hl then highlightOverAttrs else id) . withAttr
(if MPD.sgFilePath song `elem` pathsInQueue
then titleBoldAttr
else titleAttr
)
$ column Nothing (Pad 1) Max
$ txt (meta (MPD.toText . MPD.sgFilePath $ song) MPD.Title song)
-- | Draw Playlist view.
drawViewPlaylists :: HumState -> Widget Name
drawViewPlaylists st =
hLimitPercent 25 (drawPlaylistLeft st) <+> drawPlaylistRight st
-- | Move focused playlist column by given function
playlistsMove
:: (forall e . List Name e -> List Name e) -- ^ Function to move the focused column with
-> HumState
-> EventM Name HumState
playlistsMove moveFunc s =
let playfoc = s ^. focusL . focPlayL
in case playfoc of
FocPlaylists -> rebuildPlList $ s & playlistsL . plListL %~ moveFunc
FocPSongs -> do
pure $ s & playlistsL . plSongsL %~ moveFunc
-- | Add selected element in Playlist view to queue.
-- If the element is a playlist adds entire playlist.
playlistsAddtoQ
:: Bool -- ^ Play first item added to queue
-> HumState
-> EventM Name HumState
playlistsAddtoQ play s =
let playfoc = s ^. focusL . focPlayL
in case playfoc of
FocPlaylists -> do
songBulkAddtoQ play (fst <$> listElements (s ^. playlistsL . plSongsL)) s
FocPSongs -> do
let maybeFilePath = MPD.sgFilePath . fst . snd <$> listSelectedElement
(s ^. playlistsL . plSongsL)
traverse_
(\sel -> liftIO
(withMPD $ MPD.addId sel Nothing >>= if play
then MPD.playId
else const pass
)
)
maybeFilePath
song <- liftIO (withMPD MPD.currentSong)
pure s { currentSong = fromRight Nothing song, queue = queue s }
-- | Search focused playlist column for next instance of last search.
playlistsSearch
:: Bool -- ^ Search direction, True for forward.
-> HumState
-> EventM Name HumState
playlistsSearch direction s =
let playfoc = s ^. focusL . focPlayL
dir = if direction then id else listReverse
searchkey = fromMaybe "" ((s ^. exL . searchHistoryL) !!? 0)
in if searchkey == ""
then pure s
else case playfoc of
FocPlaylists -> do
rebuildPlList
$ s
& playlistsL
. plListL
%~ (dir . listFindBy (stringySearch searchkey) . dir)
FocPSongs -> do
pure
$ s
& playlistsL
. plSongsL
%~ (dir . listFindBy (songSearch searchkey [MPD.Title] . fst) . dir)
-- | Rename the given playlist with input from the text prompt.
renamePl
:: T.Text -> Bool -> HumState -> EventM n HumState
renamePl oldname bl st = if bl
then
let newname = fromString (toString $ st ^. promptsL . textPromptL . editContentsL & Z.currentLine)
oldname' = fromString . T.unpack $ oldname
in do
_ <- liftIO . withMPD $MPD.rename oldname' newname
rebuildPl st
else pure st
-- | handle key inputs for Playlist view.
handleEventPlaylists
:: HumState -> BrickEvent Name HumEvent -> EventM Name (Next HumState)
handleEventPlaylists s e = case e of
VtyEvent vtye -> case vtye of
EvKey (KChar 'j') [] -> continue =<< playlistsMove listMoveDown s
EvKey (KChar 'k') [] -> continue =<< playlistsMove listMoveUp s
EvKey (KChar 'l') [] -> continue $ s & focusL . focPlayL .~ FocPSongs
EvKey (KChar 'h') [] -> continue $ s & focusL . focPlayL .~ FocPlaylists
EvKey (KChar 'n') [] ->
continue =<< playlistsSearch (s ^. exL . searchDirectionL) s
EvKey (KChar 'N') [] ->
continue =<< playlistsSearch (s ^. exL . searchDirectionL & not) s
EvKey KEnter [] ->
continue =<< playlistsMove listMoveDown =<< playlistsAddtoQ True s
EvKey (KChar ' ') [] -> if s ^. editableL then
continue $ s & playlistsL . plSongsL %~ (listMoveDown . listToggleHighlight)
else
continue =<< playlistsMove listMoveDown =<< playlistsAddtoQ False s
EvKey (KChar 'd') []
| s ^. editableL
-> continue $ deleteHighlighted s (playlistsL . plSongsL)
| s ^. focusL . focPlayL == FocPlaylists
-> continue $ s & modeL .~ PromptMode
& promptsL . currentPromptL .~ YNPrompt
& promptsL . promptTitleL .~ ("DELETE " <> selectedPl <> "?\nYou can't paste it back yet")
& promptsL . exitPromptFuncL .~ deleteSelectedPl
| otherwise -> continue s
EvKey (KChar 'y') []
| s ^. editableL
-> continue $ s & clipboardL . clSongsL .~ (s ^. playlistsL . plSongsL & getHighlighted)
| s ^. focusL . focPlayL == FocPlaylists
-> continue $ s & clipboardL . clPlNameL .~ (s ^. playlistsL . plListL & listSelectedElement <&> snd)
| otherwise -> continue s
EvKey (KChar 'p') []
| s ^. editableL
-> continue $ s & playlistsL . plSongsL %~ listPaste (s^. clipboardL. clSongsL)
| s ^. focusL . focPlayL == FocPlaylists
-> continue =<< pastePlaylist s
| otherwise -> continue s
EvKey (KChar 'G') [] -> continue =<< playlistsMove (listMoveTo (-1)) s
EvKey (KChar 'g') [] -> continue =<< playlistsMove (listMoveTo 0) s -- TODO change this to 'gg', somehow
EvKey (KChar 'e') [] -> case s ^. focusL . focPlayL of
FocPlaylists
-> continue $ s & modeL .~ PromptMode
& promptsL . currentPromptL .~ TextPrompt
& promptsL . promptTitleL .~ ("Rename " <> selectedPl <> " to:")
& promptsL . exitPromptFuncL .~ renamePl selectedPl
FocPSongs -> if s ^. editableL then
continue $ s & editableL %~ not
& modeL .~ PromptMode
& promptsL . currentPromptL .~ YNPrompt
& promptsL . promptTitleL .~ ("Save changes to " <> selectedPl <> "?")
& promptsL . exitPromptFuncL .~ saveEditedPl
else continue =<< reloadPlList (s & editableL %~ not)
_ -> continue s
_ -> continue s
where selectedPl = s ^. playlistsL . plListL & listSelectedElement ? maybe "<error>" snd ? MPD.toText