mushu-0.1.0: app/UI.hs
module UI
( start
) where
import ClassyPrelude hiding (catch)
import Library
import UI.Types
import Control.Concurrent.Async (async)
import Lens.Micro.Platform ((^.), (&), (.~), (%~))
import qualified UI.Utils as Utils
import qualified UI.Views.Playlist as PlaylistView
import qualified UI.Views.Library as LibraryView
import qualified UI.Views.Help as HelpView
import qualified UI.Widgets.Status as Status
import qualified UI.Widgets.Playlist as Playlist
import qualified UI.Widgets.Filter as Filter
import qualified UI.Widgets.Notification as Notification
import qualified UI.Widgets.Library as LibraryWidget
import UI.Widgets.Filter (isFocusedL)
import Brick.Types (Widget, EventM, Next, BrickEvent(..))
import Brick.AttrMap (AttrMap, attrMap)
import Brick.BChan (BChan, newBChan, writeBChan)
import Brick.Util (clamp)
import qualified Brick.Main as M
import qualified Graphics.Vty as Vty
import Graphics.Vty.Config (defaultConfig)
import Network.MPD (Song, idle, Subsystem(..), Status(..))
import MPD (togglePlayPause, fetchPlaylist, fetchStatus, clearPlaylist, mpdReq, setVolume, currentSong)
type NextState n = EventM n (Next (AppState n))
drawUI :: (Show n, Ord n) => AppState n -> [Widget n]
drawUI state = if state^.helpActive
then HelpView.draw
else case state^.activeView of
PlaylistView -> PlaylistView.draw state
LibraryView -> LibraryView.draw state
appEvent :: AppState UIName -> BrickEvent UIName MPDEvent -> NextState UIName
appEvent state event = case event of
AppEvent MPDPlaylistEvent -> updatePlaylist state
AppEvent MPDStatusEvent -> updateStatus state
vtyEvent -> case state^.filterStateL.isFocusedL of
True -> handleViewEvent state vtyEvent
False -> case vtyEvent of
VtyEvent (Vty.EvKey (Vty.KChar '?') []) -> M.continue $ state & helpActive %~ not
VtyEvent (Vty.EvKey (Vty.KChar 'p') []) -> void (liftIO togglePlayPause) >> M.continue state
VtyEvent (Vty.EvKey (Vty.KChar 'c') []) -> void (liftIO clearPlaylist) >> M.continue state
VtyEvent (Vty.EvKey (Vty.KChar '1') []) -> M.continue $ state & activeView .~ PlaylistView
VtyEvent (Vty.EvKey (Vty.KChar '2') []) -> M.continue $ state & activeView .~ LibraryView
VtyEvent (Vty.EvKey (Vty.KChar '-') []) -> void (liftIO (decreaseVolume state)) >> M.continue state
VtyEvent (Vty.EvKey (Vty.KChar '+') []) -> void (liftIO (increaseVolume state)) >> M.continue state
ev -> handleViewEvent state ev
handleViewEvent :: AppState UIName -> BrickEvent UIName e -> NextState UIName
handleViewEvent state event = case state^.activeView of
PlaylistView -> PlaylistView.event state event
LibraryView -> LibraryView.event state event
updatePlaylist :: AppState UIName -> NextState UIName
updatePlaylist state = do
songs <- liftIO fetchPlaylist
M.continue $ state & playlistStateL %~ Playlist.update songs
updateStatus :: AppState n -> NextState n
updateStatus state = do
st <- liftIO fetchStatus
song <- liftIO currentSong
M.continue $ state & status .~ st
& playlistStateL . Playlist.playingSongL .~ song
increaseVolume :: AppState n -> IO ()
increaseVolume = changeVolume (+3)
decreaseVolume :: AppState n -> IO ()
decreaseVolume = changeVolume (\i -> i-3)
changeVolume :: (Int -> Int) -> AppState n -> IO ()
changeVolume f state = case volume of
Nothing -> return ()
Just v -> setVolume $ clamp (f v) 0 100
where volume = stVolume (state^.status)
initialState :: [Song] -> Library -> Status -> AppState UIName
initialState playlist library st = AppState
{ _playlistStateL = Playlist.mkState (UIName "playlist") playlist
, _filterStateL = Filter.mkState (UIName "filter")
, _activeView = LibraryView
, _libraryStateL = LibraryWidget.mkState (UIName "artists") (UIName "albums") (UIName "songs") library
, _status = st
, _helpActive = False
, _notificationState = Notification.mkState
}
attributesMap :: AttrMap
attributesMap = attrMap Vty.defAttr $ concat
[ Utils.attrs
, Playlist.attrs
, Status.attrs
, LibraryWidget.attrs
, Filter.attrs
]
app :: M.App (AppState UIName) MPDEvent UIName
app = M.App
{ M.appDraw = drawUI
, M.appChooseCursor = M.showFirstCursor
, M.appHandleEvent = appEvent
, M.appStartEvent = return
, M.appAttrMap = const attributesMap
}
toEvent :: Subsystem -> MPDEvent
toEvent UpdateS = MPDDatabaseEvent
toEvent PlaylistS = MPDPlaylistEvent
toEvent PlayerS = MPDStatusEvent
toEvent OptionsS = MPDStatusEvent
toEvent MixerS = MPDStatusEvent
toEvent _ = MPDUnknownEvent
mpdLoop :: BChan MPDEvent -> IO ()
mpdLoop chan = forever $ do
subsChanges <- mpdReq $ idle [UpdateS, PlaylistS, OptionsS, PlayerS, MixerS]
mapM_ (writeBChan chan.toEvent) subsChanges
start :: IO ()
start = do
playlist <- fetchPlaylist
library <- fetchLibrary
st <- fetchStatus
mpdEventChan <- newBChan 10
let initState = initialState playlist library st
song <- currentSong
let appState = initState & playlistStateL . Playlist.playingSongL .~ song
_ <- async $ mpdLoop mpdEventChan
void $ M.customMain (Vty.mkVty defaultConfig)
(Just mpdEventChan) app appState