packages feed

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