mushu-0.1.0: app/UI/Widgets/Library.hs
{-# LANGUAGE TemplateHaskell #-}
module UI.Widgets.Library
( mkState
, mkWidget
, LibraryState
, handleEvent
, applyFilter
, resetFilter
, attrs
) where
import ClassyPrelude hiding ((<>), on)
import Data.Monoid ((<>))
import TH (makeSuffixLenses)
import Lens.Micro.Platform ((^.), (&), (.~), (%~))
import Brick.Widgets.Core ((<+>), (<=>), str, padLeft, padRight, withAttr)
import Brick.Widgets.Center (hCenter)
import Brick.Widgets.Border (vBorder, hBorder)
import Brick.Types (EventM, Widget, Padding(..))
import Brick.AttrMap (AttrName)
import Brick.Widgets.List (List(..), list, listMoveDown, listMoveUp, listReplace, listSelectedElement, renderList, listAttr, listSelectedAttr, listSelectedFocusedAttr)
import Brick.Util (fg, on)
import qualified Graphics.Vty as Vty
import Data.Map.Strict (elemAt)
import qualified Data.Vector as V
import qualified Data.Set as Set
import Network.MPD (Metadata(..))
import MPD (tag, addArtistToPlaylist, addAlbumToPlaylist, addSongToPlaylist)
import qualified MPD
import UI.Utils (listGetSelected)
import Library
data LibraryColumn = ArtistsColumn | AlbumsColumn | SongsColumn deriving (Show, Eq)
data LibraryMode = ArtistsAlbumsSongsMode | AlbumsSongsMode deriving (Show, Eq)
data LibraryState n = LibraryState
{ _library :: Library
, _filteredLibrary :: Library
, _libraryArtists :: List n Text
, _libraryAlbums :: List n Text
, _librarySongs :: List n Text
, _libraryActiveColumn :: LibraryColumn
, _libraryMode :: LibraryMode
, _libraryFilter :: Maybe ([Text] -> [Text])
}
makeSuffixLenses ''LibraryState
mkWidget :: (Show n, Ord n) => LibraryState n -> Widget n
mkWidget state = columns
where
columns = case state^.libraryModeL of
ArtistsAlbumsSongsMode -> column "Artists" True artistsWidget <+> column "Albums" True albumsWidget <+> column "Songs" False songsWidget
AlbumsSongsMode -> column "Albums" True albumsWidget <+> column "Songs" False songsWidget
-- SongsMode -> column "Songs" False songsWidget
column name bBorder widget = title name <=> if bBorder then widget <+> vBorder else widget
title t = padRight Max (str t) <=> hBorder
artistsWidget = columnWidget ArtistsColumn (state^.libraryArtistsL)
albumsWidget = columnWidget AlbumsColumn (state^.libraryAlbumsL)
songsWidget = columnWidget SongsColumn (state^.librarySongsL)
columnWidget col = renderList (listDrawElement col activeColumn) False
activeColumn = state^.libraryActiveColumnL
listDrawElement :: LibraryColumn -> LibraryColumn -> Bool -> Text -> Widget n
listDrawElement column activeColumn sel el = hCenter $ formatListElement column activeColumn sel $ pad $ str (unpack el)
where
pad w = padLeft Max $ padRight Max w
formatListElement :: LibraryColumn -> LibraryColumn -> Bool -> Widget n -> Widget n
formatListElement column activeColumn sel = withAttr attr
where
attr
| column == activeColumn =
if sel then selActiveColAttrName else activeColAttrName
| columnIsBefore column activeColumn =
if sel then listSelAttrName else listAttrName
| otherwise = listAttrName
-- TODO: find a better way to do this
-- column != activeColumn must be true before calling this function
columnIsBefore :: LibraryColumn -> LibraryColumn -> Bool
columnIsBefore column activeColumn = case activeColumn of
ArtistsColumn -> False
SongsColumn -> True
AlbumsColumn -> case column of
ArtistsColumn -> True
AlbumsColumn -> False
SongsColumn -> False
listAttrName :: AttrName
listAttrName = listAttr <> "custom"
listSelAttrName :: AttrName
listSelAttrName = listSelectedAttr <> "custom-selected"
activeColAttrName :: AttrName
activeColAttrName = listAttrName <> "active-column"
selActiveColAttrName :: AttrName
selActiveColAttrName = listSelAttrName <> "selected-active-column"
attrs :: [(AttrName, Vty.Attr)]
attrs = [ (listAttrName, Vty.white `on` Vty.black)
, (listSelAttrName, Vty.black `on` Vty.yellow)
, (activeColAttrName, fg Vty.white)
, (selActiveColAttrName, Vty.black `on` Vty.green)
, (listSelectedFocusedAttr, fg Vty.red)
]
mkState :: n -> n -> n -> Library -> LibraryState n
mkState artistsName albumsName songsName library = LibraryState
{ _library = library
, _filteredLibrary = library
, _libraryArtists = list artistsName (fromList (keys artists)) 1
, _libraryAlbums = list albumsName firstArtistAlbums 1
, _librarySongs = list songsName V.empty 1
, _libraryActiveColumn = ArtistsColumn
, _libraryMode = ArtistsAlbumsSongsMode
, _libraryFilter = Nothing
}
where
artists = library^.artistsL
firstArtistAlbums = V.fromList . Set.toAscList $ snd $ elemAt 0 artists
handleEvent :: Vty.Event -> LibraryState n -> EventM n (LibraryState n)
handleEvent event state = case event of
(Vty.EvKey (Vty.KChar 'j') []) -> case activeColumn of
ArtistsColumn -> return $ nextArtist state
AlbumsColumn -> return $ nextAlbum state
SongsColumn -> return $ nextSong state
(Vty.EvKey (Vty.KChar 'k') []) -> case activeColumn of
ArtistsColumn -> return $ previousArtist state
AlbumsColumn -> return $ previousAlbum state
SongsColumn -> return $ previousSong state
(Vty.EvKey (Vty.KChar 'l') []) -> return $ nextColumn state
(Vty.EvKey (Vty.KChar 'h') []) -> return $ previousColumn state
(Vty.EvKey (Vty.KChar 'a') []) -> void (liftIO (addToPlaylist state False)) >> return (next state)
(Vty.EvKey Vty.KEnter []) -> void (liftIO (addToPlaylist state True)) >> return (next state)
(Vty.EvKey (Vty.KChar 't') []) -> return $ toggleMode state
_ -> return state
where activeColumn = state^.libraryActiveColumnL
next :: LibraryState n -> LibraryState n
next state = case state^.libraryActiveColumnL of
ArtistsColumn -> nextArtist state
AlbumsColumn -> nextAlbum state
SongsColumn -> nextSong state
nextArtist :: LibraryState n -> LibraryState n
nextArtist state = updateArtistAlbumSongs $ updateArtistAlbums (state & libraryArtistsL %~ listMoveDown)
previousArtist :: LibraryState n -> LibraryState n
previousArtist state = updateArtistAlbumSongs $ updateArtistAlbums (state & libraryArtistsL %~ listMoveUp)
nextAlbum :: LibraryState n -> LibraryState n
nextAlbum state = updateFn $ state & libraryAlbumsL %~ listMoveDown
where updateFn = case state^.libraryModeL of
ArtistsAlbumsSongsMode -> updateArtistAlbumSongs
AlbumsSongsMode -> updateAlbumSongs
previousAlbum :: LibraryState n -> LibraryState n
previousAlbum state = updateFn $ state & libraryAlbumsL %~ listMoveUp
where updateFn = case state^.libraryModeL of
ArtistsAlbumsSongsMode -> updateArtistAlbumSongs
AlbumsSongsMode -> updateAlbumSongs
nextSong :: LibraryState n -> LibraryState n
nextSong state = state & librarySongsL %~ listMoveDown
previousSong :: LibraryState n -> LibraryState n
previousSong state = state & librarySongsL %~ listMoveUp
update :: LibraryState n -> LibraryState n
update state = updateFn state
where updateFn = case state^.libraryModeL of
ArtistsAlbumsSongsMode -> updateArtistAlbums . updateArtistAlbumSongs
AlbumsSongsMode -> updateAlbumSongs
updateArtistAlbums :: LibraryState n -> LibraryState n
updateArtistAlbums state = state & libraryAlbumsL %~ listReplace newAlbums (Just 0)
where
selArtist = snd <$> listSelectedElement (state^.libraryArtistsL)
newAlbums = case selArtist of
Just a -> fromMaybe V.empty ((V.fromList . Set.toAscList) <$> lookup a (state^.filteredLibraryL.artistsL))
Nothing -> V.empty
updateArtistAlbumSongs :: LibraryState n -> LibraryState n
updateArtistAlbumSongs state = state & librarySongsL %~ listReplace (toTxt newSongsFiltered) (Just 0)
where
selAlbum = snd <$> listSelectedElement (state^.libraryAlbumsL)
selArtist = snd <$> listSelectedElement (state^.libraryArtistsL)
toTxt = map (tag Title "<no title>")
newSongsFiltered = case selArtist of
Just a -> filter (\s -> tag Artist "" s == a) newSongs
Nothing -> newSongs
newSongs = case selAlbum of
Just a -> fromMaybe V.empty (lookup a (state^.filteredLibraryL.albumsL))
Nothing -> V.empty
updateAlbumSongs :: LibraryState n -> LibraryState n
updateAlbumSongs state = state & librarySongsL %~ listReplace (toTxt newSongs) (Just 0)
where
selAlbum = snd <$> listSelectedElement (state^.libraryAlbumsL)
toTxt = map (tag Title "<no title>")
newSongs = case selAlbum of
Just a -> fromMaybe V.empty (lookup a (state^.filteredLibraryL.albumsL))
Nothing -> V.empty
nextColumn :: LibraryState n -> LibraryState n
nextColumn state = state & libraryActiveColumnL .~ nextCol
where
activeColumn = state^.libraryActiveColumnL
nextCol = case state^.libraryModeL of
ArtistsAlbumsSongsMode -> case activeColumn of
ArtistsColumn -> AlbumsColumn
AlbumsColumn -> SongsColumn
SongsColumn -> SongsColumn
AlbumsSongsMode -> SongsColumn
previousColumn :: LibraryState n -> LibraryState n
previousColumn state = state & libraryActiveColumnL .~ prevCol
where
activeColumn = state^.libraryActiveColumnL
prevCol = case state^.libraryModeL of
ArtistsAlbumsSongsMode -> case activeColumn of
ArtistsColumn -> ArtistsColumn
AlbumsColumn -> ArtistsColumn
SongsColumn -> AlbumsColumn
AlbumsSongsMode -> AlbumsColumn
getSelected :: LibraryState n -> (Maybe Text, Maybe Text, Maybe Text)
getSelected state = (artist, album, title)
where
activeColumn = state^.libraryActiveColumnL
artist = case state^.libraryModeL of
ArtistsAlbumsSongsMode -> listGetSelected $ state^.libraryArtistsL
AlbumsSongsMode -> Nothing
album = if activeColumn == AlbumsColumn then
listGetSelected $ state^.libraryAlbumsL else Nothing
title = if activeColumn == SongsColumn then
listGetSelected $ state^.librarySongsL else Nothing
addToPlaylist :: LibraryState n -> Bool -> IO ()
addToPlaylist state = addSelectionToPlaylist (getSelected state)
addSelectionToPlaylist :: (Maybe Text, Maybe Text, Maybe Text) -> Bool -> IO ()
addSelectionToPlaylist (Just artist, Nothing, Nothing) play = addArtistToPlaylist artist play
addSelectionToPlaylist (maybeArtist, Just album, Nothing) play = addAlbumToPlaylist maybeArtist album play
addSelectionToPlaylist (_, Just album, Just song) play = addSongToPlaylist album song play
addSelectionToPlaylist _ _ = putStrLn "This shouldn't happen"
toggleMode :: LibraryState n -> LibraryState n
toggleMode state = update $ case state^.libraryFilterL of
Nothing -> newModeState
Just f -> applyFilter f newModeState
where
newModeState = case state^.libraryModeL of
ArtistsAlbumsSongsMode -> setMode AlbumsSongsMode state
AlbumsSongsMode -> setMode ArtistsAlbumsSongsMode state
setMode :: LibraryMode -> LibraryState n -> LibraryState n
setMode mode state = case mode of
ArtistsAlbumsSongsMode -> updateArtistAlbumSongs $ updateArtistAlbums $ state & libraryModeL .~ ArtistsAlbumsSongsMode
& libraryActiveColumnL .~ ArtistsColumn
AlbumsSongsMode -> updateAlbumSongs $ state & libraryModeL .~ AlbumsSongsMode
& libraryActiveColumnL .~ AlbumsColumn
& libraryAlbumsL %~ listReplace (fromList (keys (state^.libraryL.albumsL))) (Just 0)
-- TODO: modify this to filter depending on the mode (artists/albums or albums)
-- Only filter on artists atm
-- filter :: (Text -> Text) -> LibraryState -> LibraryState
applyFilter :: ([Text] -> [Text]) -> LibraryState n -> LibraryState n
applyFilter f state = newState & libraryFilterL .~ Just f
where
newState = case state^.libraryModeL of
ArtistsAlbumsSongsMode -> filterArtists f state
AlbumsSongsMode -> filterAlbums f state
filterArtists :: ([Text] -> [Text]) -> LibraryState n -> LibraryState n
filterArtists f state = state & libraryArtistsL %~ listReplace (V.fromList filteredArtists) (Just 0)
where
filteredArtists = f (keys (state^.libraryL.artistsL))
filterAlbums :: ([Text] -> [Text]) -> LibraryState n -> LibraryState n
filterAlbums f state = state & libraryAlbumsL %~ listReplace (V.fromList filteredAlbums) (Just 0)
where
filteredAlbums = f (keys (state^.libraryL.albumsL))
resetFilter :: LibraryState n -> LibraryState n
resetFilter state = newState & libraryFilterL .~ Nothing
where
newState = case state^.libraryModeL of
ArtistsAlbumsSongsMode -> updateArtistAlbumSongs $ updateArtistAlbums $ state & libraryArtistsL %~ listReplace artists (Just 0)
where artists = V.fromList . keys $ state^.libraryL.artistsL
AlbumsSongsMode -> updateAlbumSongs $ state & libraryAlbumsL %~ listReplace albums (Just 0)
where albums = V.fromList . keys $ state^.libraryL.albumsL