{-# LANGUAGE LambdaCase #-} module Hum.UI where import Brick.Main import Brick.Types import qualified Brick.BChan as BC import Brick.Widgets.Core import Brick.Widgets.List import Brick.Widgets.Edit import Graphics.Vty.Input.Events import Network.MPD ( withMPD ) import qualified Network.MPD as MPD import Hum.Types import qualified Data.Vector as V import qualified Data.Text as T import Hum.Attributes import Hum.Views import Hum.Modes import Hum.Utils import Hum.Rebuild import qualified Data.Map.Strict as Map import Control.Lens import System.Directory app :: App HState HumEvent Name app = App { appDraw = drawUI , appChooseCursor = chooseCursor , appHandleEvent = handleEvent , appStartEvent = humStartEvent , appAttrMap = const humAttrMap } drawUI :: HState -> [Widget Name] drawUI st = [if st ^. modeL == PromptMode then drawPrompt st else emptyWidget, drawNowPlaying st <=> (case hview st of QueueView -> drawViewQueue st LibraryView -> drawViewLibrary st PlaylistsView -> drawViewPlaylists st HelpView -> drawViewHelp st ) <=> if st ^. focusL . focExL then txt (st ^. exL . exPrefixL & exPrefixTxt) <+> renderEditor (txt . T.unlines) (st ^. focusL . focExL) (st ^. exL . exEditorL) else txt " " ] chooseCursor :: HState -> [CursorLocation Name] -> Maybe (CursorLocation Name) chooseCursor st ls | st ^. focusL . focExL = find (isCurrent ExEditor) ls | st ^. promptsL . currentPromptL == TextPrompt = find (isCurrent TextPromptEditor) ls | otherwise = Nothing where isCurrent n cl = cl ^. cursorLocationNameL == Just n buildInitialState :: BC.BChan HumEvent -> IO HState buildInitialState chan = do configDir <- getXdgDirectory XdgConfig "hum" _ <- createDirectoryIfMissing True configDir let mode = NormalMode let ex = ExState { exPrefix = Cmd , exEditor = editorText ExEditor (Just 1) "" , searchDirection = True , searchHistory = [] , cmdHistory = [] } currentSong <- fromRight Nothing <$> withMPD MPD.currentSong status <- fromRight Nothing <$> (Just <<$>> withMPD MPD.status) let hview = QueueView let extentMap = Map.empty let focus = Focus { focQueue = FocQueue , focLib = FocArtists , focPlay = FocPlaylists , focEx = False } let clipboard = Clipboard { clSongs = list ClSongs V.empty 1 , clPlName = Nothing} queueVec <- V.fromList . fromRight [] <$> withMPD (MPD.playlistInfo Nothing) let queue = (, False) <$> list QueueList queueVec 1 artistsVec <- V.fromList . fromRight [] <$> withMPD (MPD.list MPD.AlbumArtist Nothing) let artists = list ArtistsList artistsVec 1 albumsVec <- albumsOfArtist (snd <$> listSelectedElement artists) let albums = list AlbumsList albumsVec 1 songsVec <- songsOfAlbum (snd <$> listSelectedElement albums) let songs = list SongsList songsVec 1 plListVec <- V.fromList . fromRight [] <$> withMPD MPD.listPlaylists let plList = list PlaylistList plListVec 1 plSongsVec <- V.fromList . fromRight [] <$> withMPD ( MPD.listPlaylistInfo $ maybe "" snd (listSelectedElement plList) ) let plSongs = (, False) <$> list PlaylistSongs plSongsVec 1 let library = LibraryState { artists, albums, songs } let playlists = PlaylistsState { plList, plSongs } let editable = False let prompts = Prompts { currentPrompt = PlSelectPrompt , promptTitle = "" , textPrompt = editorText TextPromptEditor (Just 1) "" , plSelectPrompt = listInsert 0 Nothing (Just <$> plList) , exitPrompt = \s -> pure $ s & modeL .~ NormalMode } let helpScreen = 0 pure HState { chan , hview , mode , ex , status , currentSong , queue , extentMap , clipboard , library , playlists , focus , editable , prompts , helpScreen } humStartEvent :: HState -> EventM Name HState humStartEvent = pure hBoxPad :: Padding -> [Widget n] -> Widget n hBoxPad _ [] = emptyWidget hBoxPad _ [w ] = w hBoxPad p (w : ws) = padRight p w <+> hBoxPad p ws seekCurEventM :: MPD.FractionalSeconds -> HState -> EventM Name HState seekCurEventM i s = do _ <- liftIO (withMPD $ MPD.seekCur False i) status <- liftIO (fromRight Nothing <$> (Just <<$>> withMPD MPD.status)) song <- liftIO (withMPD MPD.currentSong) pure s { currentSong = fromRight Nothing song, status } handleEvent :: HState -> BrickEvent Name HumEvent -> EventM Name (Next HState) handleEvent s e = case e of VtyEvent vtye -> case s ^. modeL of ExMode -> handleExEvent s e PromptMode -> handlePromptEvent s e NormalMode -> case vtye of EvKey (KChar 'q') [] -> halt s EvKey (KChar 't') [] -> do st <- liftIO ((MPD.stState <$>) <$> withMPD MPD.status) _ <- case st of Left _ -> liftIO (withMPD $ MPD.pause True) Right MPD.Paused -> liftIO (withMPD $ MPD.play Nothing) Right MPD.Stopped -> liftIO (withMPD $ MPD.play Nothing) Right MPD.Playing -> liftIO (withMPD $ MPD.pause True) continue s EvKey (KChar 's') [] -> do _ <- liftIO (withMPD $ MPD.single (maybe False (not . MPD.stSingle) (status s))) status <- liftIO (fromRight Nothing <$> (Just <<$>> withMPD MPD.status)) continue s { status } EvKey (KChar 'c') [] -> do _ <- liftIO ( withMPD $ MPD.consume (maybe False (not . MPD.stConsume) (status s)) ) status <- liftIO (fromRight Nothing <$> (Just <<$>> withMPD MPD.status)) continue s { status } EvKey (KChar 'x') [] -> do _ <- liftIO (withMPD $ MPD.crossfade ( (\case 0 -> 5 _ -> 0 ) $ maybe 0 MPD.stXFadeWidth (status s) ) ) -- TODO status <- liftIO (fromRight Nothing <$> (Just <<$>> withMPD MPD.status)) continue s { status } EvKey (KChar 'r') [] -> do _ <- liftIO (withMPD $ MPD.repeat (maybe False (not . MPD.stRepeat) (status s))) status <- liftIO (fromRight Nothing <$> (Just <<$>> withMPD MPD.status)) continue s { status } EvKey (KChar 'z') [] -> do _ <- liftIO (withMPD $ MPD.random (maybe False (not . MPD.stRandom) (status s))) status <- liftIO (fromRight Nothing <$> (Just <<$>> withMPD MPD.status)) continue s { status } EvKey (KChar '/') [] -> continue $ s & modeL .~ ExMode & exL . exPrefixL .~ FSearch & exL . searchDirectionL .~ True & focusL . focExL .~ True EvKey (KChar '?') [] -> continue $ s & modeL .~ ExMode & exL . exPrefixL .~ BSearch & exL . searchDirectionL .~ False & focusL . focExL .~ True EvKey (KChar ':') [] -> continue $ s & modeL .~ ExMode & exL . exPrefixL .~ Cmd & focusL . focExL .~ True EvKey (KChar '.') [] -> do _ <- liftIO (withMPD MPD.next) song <- liftIO (withMPD MPD.currentSong) continue s { currentSong = fromRight Nothing song } EvKey (KChar ',') [] -> do _ <- liftIO (withMPD MPD.previous) song <- liftIO (withMPD MPD.currentSong) continue s { currentSong = fromRight Nothing song } EvKey (KChar ']') [] -> continue =<< seekCurEventM 5 s EvKey (KChar '[') [] -> continue =<< seekCurEventM (-5) s EvKey (KChar '}') [] -> continue =<< seekCurEventM 30 s EvKey (KChar '{') [] -> continue =<< seekCurEventM (-30) s EvKey (KChar '1') [] -> do _ <- liftIO (BC.writeBChan (chan s) (Left Tick)) continue $ s & editableL .~ False & hviewL .~ QueueView EvKey (KChar '2') [] -> do _ <- liftIO (BC.writeBChan (chan s) (Left Tick)) continue $ s & editableL .~ False & hviewL .~ LibraryView EvKey (KChar '3') [] -> do _ <- liftIO (BC.writeBChan (chan s) (Left Tick)) continue $ s & editableL .~ False & hviewL .~ PlaylistsView EvResize _ _ -> do extentMap <- updateExtentMap continue s { extentMap } _ -> case hview s of QueueView -> handleEventQueue s e LibraryView -> handleEventLibrary s e PlaylistsView -> handleEventPlaylists s e HelpView -> handleEventHelp s e (AppEvent (Left Tick)) -> do extentMap <- updateExtentMap status <- liftIO (fromRight Nothing <$> (Just <<$>> withMPD MPD.status)) continue s { status, extentMap } (AppEvent (Right (Right _))) -> do currentSong <- liftIO (fromRight Nothing <$> withMPD MPD.currentSong) status <- liftIO (fromRight Nothing <$> (Just <<$>> withMPD MPD.status)) queueVec <- liftIO (V.fromList . fromRight [] <$> withMPD (MPD.playlistInfo Nothing)) let queueUnmoved = (, False) <$> list QueueList queueVec 1 let queueNew = case listSelected (queue s) of Nothing -> queueUnmoved Just i -> listMoveTo i queueUnmoved continue s { currentSong, status, queue = queueNew } _ -> continue s