kino/src/Kino/UI.hs

164 lines
5.3 KiB
Haskell
Raw Normal View History

2021-09-20 13:21:48 +02:00
{-# LANGUAGE OverloadedStrings #-}
{-|
Module : Kino.UI
Description : This is the code which interacts with Brick
This is the code which interacts with Brick
-}
2021-09-06 21:02:39 +02:00
module Kino.UI (runApp) where
2021-09-06 21:02:39 +02:00
2021-09-14 17:06:34 +02:00
import Brick hiding (Direction(..))
import Brick.Widgets.Edit (handleEditorEvent, editorText, editAttr, getEditContents)
import Control.Monad.IO.Class (liftIO)
2021-09-14 17:06:34 +02:00
import Graphics.Vty (defAttr)
import Graphics.Vty.Attributes (withStyle, reverseVideo)
2021-09-20 13:21:48 +02:00
import Graphics.Vty.Attributes.Color (brightBlack)
import Graphics.Vty.Input.Events
2021-09-14 17:06:34 +02:00
import Lens.Micro
import System.Clipboard
import qualified Data.Text as T
2021-09-06 21:02:39 +02:00
import Kino.Types
import Kino.Request
import Kino.Misc
import Kino.UI.Widgets
import Kino.Torrent
2021-09-14 17:06:34 +02:00
-- | The initial state of our application
2021-09-14 17:06:34 +02:00
initialState :: AppS
initialState = AppS
{ _appMode = Browse
, _appCursor = 0
, _appExpanded = False
, _appPage = 1
2021-09-20 13:21:48 +02:00
, _appListing = JSONListMovies 0 0 0 []
2021-09-14 17:06:34 +02:00
, _appDetails = Nothing
, _appMessage = Nothing
, _appContinue = True
2021-09-20 13:21:48 +02:00
, _appEditor = editorText Input (Just 1) mempty
2021-09-14 17:06:34 +02:00
}
2021-09-06 21:02:39 +02:00
-- | Our brick app specification
2021-09-13 19:00:26 +02:00
app :: App AppS () Ident
2021-09-06 21:02:39 +02:00
app = App
{ appDraw = draw
, appChooseCursor = chooseCursor
2021-09-13 19:00:26 +02:00
, appHandleEvent = eventHandler
, appStartEvent = startEvent
, appAttrMap = attributeMap
2021-09-06 21:02:39 +02:00
}
-- | A small wrapper to run the brick app
2021-09-13 19:00:26 +02:00
runApp :: IO AppS
runApp = defaultMain app initialState
2021-09-06 21:02:39 +02:00
-- | Updates the state given a way to request a new movie listing
2021-09-20 13:21:48 +02:00
setMovies :: AppS -> IO (Either T.Text JSONListMovies) -> IO AppS
setMovies s mb = do
m <- mb
case m of
(Left t) -> pure (displayMessage s True (T.unpack t))
(Right l) -> pure (s & appListing .~ l
2021-09-20 13:21:48 +02:00
& appCursor .~ 0
& appDetails .~ (moviesMovies l !? 0))
2021-09-20 13:21:48 +02:00
-- | The starting event which grabs the inital listing
2021-09-14 17:06:34 +02:00
startEvent :: AppS -> EventM Ident AppS
2021-09-14 18:24:05 +02:00
startEvent s = do
2021-09-14 17:06:34 +02:00
-- todo move unwrapping our response structure into a function
2021-09-20 13:21:48 +02:00
pure =<< liftIO (setMovies s getMovies)
2021-09-13 19:00:26 +02:00
-- | The drawing function which defers to the proper function
-- from UI.Widgets
2021-09-13 19:00:26 +02:00
draw :: AppS -> [Widget Ident]
2021-09-14 17:06:34 +02:00
draw s = pure $ case s ^. appMode of
Browse -> browseWidget s
Search -> searchWidget s
Message -> messageWidget s
2021-09-06 21:02:39 +02:00
-- | Currently just showFirstCursor
2021-09-13 19:00:26 +02:00
chooseCursor :: AppS -> [CursorLocation Ident] -> Maybe (CursorLocation Ident)
chooseCursor = showFirstCursor
2021-09-06 21:02:39 +02:00
-- | Scrolls our cursor and updates the app details
2021-09-14 17:06:34 +02:00
scroll :: ScrollDirection -> AppS -> AppS
scroll d s = s & appCursor .~ newCursor
2021-09-14 17:06:34 +02:00
& appExpanded .~ False
2021-09-20 13:21:48 +02:00
& appDetails .~ (moviesMovies (s ^. appListing) !? newCursor)
2021-09-14 17:06:34 +02:00
where
2021-09-20 13:21:48 +02:00
upperLimit = length (moviesMovies (s ^. appListing)) - 1
new = case d of { Up -> subtract 1; Down -> (+1) }
newCursor = max 0 (min upperLimit (new (s ^. appCursor)))
-- | Given a String, modify our state so that string
2021-09-20 13:21:48 +02:00
-- is displayed as a message, alternatively forcing
-- an exit after the message is aknowledged.
displayMessage :: AppS -> Bool -> String -> AppS
displayMessage s fatal msg = s & appMode .~ Message
& appMessage ?~ msg
2021-09-20 13:21:48 +02:00
& appContinue .~ not fatal
-- | Copy the magnet link of the focused movie at
-- the given index
copyMagnet :: AppS -> Int -> EventM Ident (Next AppS)
copyMagnet s i = case (do
m <- s ^. appDetails
_ <- movieTorrents m !? i - 1
toMagnets m !? i - 1) of
Nothing -> continue s
(Just magnet) -> do
liftIO (setClipboardString magnet)
2021-09-20 13:21:48 +02:00
continue (displayMessage s False "Copied magnet link to clipboard!")
-- | The event handler, takes care of keyboard events.
2021-09-14 17:06:34 +02:00
eventHandler :: AppS -> BrickEvent Ident () -> EventM Ident (Next AppS)
2021-09-20 13:21:48 +02:00
eventHandler s (VtyEvent e@(EvKey k _)) =
2021-09-14 17:06:34 +02:00
case s ^. appMode of
Message -> if s ^. appContinue
then continue (s & appMode .~ Browse
& appMessage .~ Nothing)
else halt s
2021-09-20 13:21:48 +02:00
Search -> case k of
(KEnter) -> do
let queryText = T.unlines (getEditContents (s ^. appEditor))
newS <- liftIO (setMovies s (queryMovies queryText))
continue (newS & appMode .~ Browse)
_ -> continue =<< handleEventLensed s appEditor handleEditorEvent e
Browse -> case k of
2021-09-14 17:06:34 +02:00
(KChar 'q') -> halt s
2021-09-14 18:24:05 +02:00
(KEsc) -> halt s
2021-09-14 17:06:34 +02:00
(KChar 'j') -> continue (scroll Down s)
(KChar 'k') -> continue (scroll Up s)
2021-09-14 18:24:05 +02:00
(KDown) -> continue (scroll Down s)
(KUp) -> continue (scroll Up s)
2021-09-14 17:06:34 +02:00
(KChar ' ') -> continue (s & appExpanded %~ not)
2021-09-14 18:24:05 +02:00
(KEnter) -> continue (s & appExpanded %~ not)
(KChar '1') -> copyMagnet s 1
(KChar '2') -> copyMagnet s 2
(KChar '3') -> copyMagnet s 3
(KChar '4') -> copyMagnet s 4
(KChar '5') -> copyMagnet s 5
(KChar '6') -> copyMagnet s 6
(KChar '7') -> copyMagnet s 7
(KChar '8') -> copyMagnet s 8
(KChar '9') -> copyMagnet s 9
2021-09-14 18:24:05 +02:00
(KChar '/') -> continue (s & appMode .~ Search)
(KChar 's') -> continue (s & appMode .~ Search)
2021-09-20 13:21:48 +02:00
_ -> continueWithoutRedraw s
2021-09-14 17:06:34 +02:00
eventHandler s _ = continue s
2021-09-06 21:02:39 +02:00
-- | The attribute map, currently not dependant on state
2021-09-14 17:06:34 +02:00
attributeMap :: AppS -> AttrMap
2021-09-20 13:21:48 +02:00
attributeMap = const $ attrMap defAttr [ (attrName "selected", withStyle defAttr reverseVideo)
, (editAttr, fg brightBlack) ]