module Brick.Main
( App(..)
, defaultMain
, customMain
, simpleMain
, resizeOrQuit
, simpleApp
, continue
, halt
, suspendAndResume
, lookupViewport
, lookupExtent
, findClickedExtents
, clickedExtent
, getVtyHandle
, viewportScroll
, ViewportScroll
, vScrollBy
, vScrollPage
, vScrollToBeginning
, vScrollToEnd
, hScrollBy
, hScrollPage
, hScrollToBeginning
, hScrollToEnd
, setTop
, setLeft
, neverShowCursor
, showFirstCursor
, showCursorNamed
, invalidateCacheEntry
, invalidateCache
, renderFinal
, getRenderState
, resetRenderState
)
where
import Control.Exception (finally)
import Lens.Micro ((^.), (&), (.~), (%~), _1, _2)
import Control.Monad (forever)
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.State
import Control.Monad.Trans.Reader
import Control.Concurrent (forkIO, killThread)
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative ((<$>))
import Data.Monoid (mempty)
#endif
import qualified Data.Foldable as F
import Data.Maybe (listToMaybe)
import qualified Data.Map as M
import qualified Data.Set as S
import Graphics.Vty
( Vty
, Picture(..)
, Cursor(..)
, Event(..)
, update
, outputIface
, displayBounds
, shutdown
, nextEvent
, mkVty
, defaultConfig
)
import Graphics.Vty.Attributes (defAttr)
import Brick.BChan (BChan, newBChan, readBChan, readBChan2, writeBChan)
import Brick.Types (Widget, EventM(..))
import Brick.Types.Internal
import Brick.Widgets.Internal
import Brick.AttrMap
data App s e n =
App { appDraw :: s -> [Widget n]
, appChooseCursor :: s -> [CursorLocation n] -> Maybe (CursorLocation n)
, appHandleEvent :: s -> BrickEvent n e -> EventM n (Next s)
, appStartEvent :: s -> EventM n s
, appAttrMap :: s -> AttrMap
}
defaultMain :: (Ord n)
=> App s e n
-> s
-> IO s
defaultMain app st = do
customMain (mkVty defaultConfig) Nothing app st
simpleMain :: (Ord n)
=> Widget n
-> IO ()
simpleMain w = defaultMain (simpleApp w) ()
simpleApp :: Widget n -> App s e n
simpleApp w =
App { appDraw = const [w]
, appHandleEvent = resizeOrQuit
, appStartEvent = return
, appAttrMap = const $ attrMap defAttr []
, appChooseCursor = neverShowCursor
}
resizeOrQuit :: s -> BrickEvent n e -> EventM n (Next s)
resizeOrQuit s (VtyEvent (EvResize _ _)) = continue s
resizeOrQuit s _ = halt s
data InternalNext n a = InternalSuspendAndResume (RenderState n) (IO a)
| InternalHalt a
readBrickEvent :: BChan (BrickEvent n e) -> BChan e -> IO (BrickEvent n e)
readBrickEvent brickChan userChan = either id AppEvent <$> readBChan2 brickChan userChan
runWithNewVty :: (Ord n)
=> Vty
-> BChan (BrickEvent n e)
-> Maybe (BChan e)
-> App s e n
-> RenderState n
-> s
-> IO (InternalNext n s)
runWithNewVty buildVty brickChan mUserChan app initialRS initialSt =
withVty buildVty $ \vty -> do
pid <- forkIO $ supplyVtyEvents vty brickChan
let readEvent = case mUserChan of
Nothing -> readBChan brickChan
Just uc -> readBrickEvent brickChan uc
runInner rs st = do
(result, newRS) <- runVty vty readEvent app st (resetRenderState rs)
case result of
SuspendAndResume act -> do
killThread pid
return $ InternalSuspendAndResume newRS act
Halt s -> do
killThread pid
return $ InternalHalt s
Continue s -> runInner newRS s
runInner initialRS initialSt
customMain :: (Ord n)
=> IO Vty
-> Maybe (BChan e)
-> App s e n
-> s
-> IO s
customMain buildVty mUserChan app initialAppState = do
initialVty <- buildVty
let run vty rs st brickChan = do
result <- runWithNewVty vty brickChan mUserChan app rs st
case result of
InternalHalt s -> return s
InternalSuspendAndResume newRS action -> do
newAppState <- action
newVty <- buildVty
run newVty (newRS { renderCache = mempty }) newAppState brickChan
emptyES = ES [] mempty
emptyRS = RS M.empty mempty S.empty mempty mempty
eventRO = EventRO M.empty initialVty mempty emptyRS
(st, eState) <- runStateT (runReaderT (runEventM (appStartEvent app initialAppState)) eventRO) emptyES
let initialRS = RS M.empty (esScrollRequests eState) S.empty mempty []
brickChan <- newBChan 20
run initialVty initialRS st brickChan
supplyVtyEvents :: Vty -> BChan (BrickEvent n e) -> IO ()
supplyVtyEvents vty chan =
forever $ do
e <- nextEvent vty
writeBChan chan $ VtyEvent e
runVty :: (Ord n)
=> Vty
-> IO (BrickEvent n e)
-> App s e n
-> s
-> RenderState n
-> IO (Next s, RenderState n)
runVty vty readEvent app appState rs = do
(firstRS, exts) <- renderApp vty app appState rs
e <- readEvent
(e', nextRS, nextExts) <- case e of
VtyEvent (EvResize _ _) -> do
(rs', exts') <- renderApp vty app appState $ firstRS & observedNamesL .~ S.empty
return (e, rs', exts')
VtyEvent (EvMouseDown c r button mods) -> do
let matching = findClickedExtents_ (c, r) exts
case matching of
(Extent n (Location (ec, er)) _ (Location (oC, oR)):_) ->
case n `elem` firstRS^.clickableNamesL of
True -> do
let localCoords = Location (lc, lr)
lc = c - ec + oC
lr = r - er + oR
newCoords = case M.lookup n (viewportMap firstRS) of
Nothing -> localCoords
Just vp -> localCoords & _1 %~ (+ (vp^.vpLeft))
& _2 %~ (+ (vp^.vpTop))
return (MouseDown n button mods newCoords, firstRS, exts)
False -> return (e, firstRS, exts)
_ -> return (e, firstRS, exts)
VtyEvent (EvMouseUp c r button) -> do
let matching = findClickedExtents_ (c, r) exts
case matching of
(Extent n (Location (ec, er)) _ (Location (oC, oR)):_) ->
case n `elem` firstRS^.clickableNamesL of
True -> do
let localCoords = Location (lc, lr)
lc = c - ec + oC
lr = r - er + oR
newCoords = case M.lookup n (viewportMap firstRS) of
Nothing -> localCoords
Just vp -> localCoords & _1 %~ (+ (vp^.vpLeft))
& _2 %~ (+ (vp^.vpTop))
return (MouseUp n button newCoords, firstRS, exts)
False -> return (e, firstRS, exts)
_ -> return (e, firstRS, exts)
_ -> return (e, firstRS, exts)
let emptyES = ES [] mempty
eventRO = EventRO (viewportMap nextRS) vty nextExts nextRS
(next, eState) <- runStateT (runReaderT (runEventM (appHandleEvent app appState e'))
eventRO) emptyES
return (next, nextRS { rsScrollRequests = esScrollRequests eState
, renderCache = applyInvalidations (cacheInvalidateRequests eState) $
renderCache nextRS
})
applyInvalidations :: (Ord n) => S.Set (CacheInvalidateRequest n) -> M.Map n v -> M.Map n v
applyInvalidations ns cache =
if InvalidateEntire `S.member` ns
then mempty
else foldr (.) id (mkFunc <$> F.toList ns) cache
where
mkFunc InvalidateEntire = const mempty
mkFunc (InvalidateSingle n) = M.delete n
lookupViewport :: (Ord n) => n -> EventM n (Maybe Viewport)
lookupViewport n = EventM $ asks (M.lookup n . eventViewportMap)
clickedExtent :: (Int, Int) -> Extent n -> Bool
clickedExtent (c, r) (Extent _ (Location (lc, lr)) (w, h) _) =
c >= lc && c < (lc + w) &&
r >= lr && r < (lr + h)
lookupExtent :: (Eq n) => n -> EventM n (Maybe (Extent n))
lookupExtent n = EventM $ asks (listToMaybe . filter f . latestExtents)
where
f (Extent n' _ _ _) = n == n'
findClickedExtents :: (Int, Int) -> EventM n [Extent n]
findClickedExtents pos = EventM $ asks (findClickedExtents_ pos . latestExtents)
findClickedExtents_ :: (Int, Int) -> [Extent n] -> [Extent n]
findClickedExtents_ pos = reverse . filter (clickedExtent pos)
getVtyHandle :: EventM n Vty
getVtyHandle = EventM $ asks eventVtyHandle
invalidateCacheEntry :: (Ord n) => n -> EventM n ()
invalidateCacheEntry n = EventM $ do
lift $ modify (\s -> s { cacheInvalidateRequests = S.insert (InvalidateSingle n) $ cacheInvalidateRequests s })
invalidateCache :: (Ord n) => EventM n ()
invalidateCache = EventM $ do
lift $ modify (\s -> s { cacheInvalidateRequests = S.insert InvalidateEntire $ cacheInvalidateRequests s })
withVty :: Vty -> (Vty -> IO a) -> IO a
withVty vty useVty = do
useVty vty `finally` shutdown vty
getRenderState :: EventM n (RenderState n)
getRenderState = EventM $ asks oldState
resetRenderState :: RenderState n -> RenderState n
resetRenderState s =
s & observedNamesL .~ S.empty
& clickableNamesL .~ mempty
renderApp :: Vty -> App s e n -> s -> RenderState n -> IO (RenderState n, [Extent n])
renderApp vty app appState rs = do
sz <- displayBounds $ outputIface vty
let (newRS, pic, theCursor, exts) = renderFinal (appAttrMap app appState)
(appDraw app appState)
sz
(appChooseCursor app appState)
rs
picWithCursor = case theCursor of
Nothing -> pic { picCursor = NoCursor }
Just cloc -> pic { picCursor = AbsoluteCursor (cloc^.locationColumnL)
(cloc^.locationRowL)
}
update vty picWithCursor
return (newRS, exts)
neverShowCursor :: s -> [CursorLocation n] -> Maybe (CursorLocation n)
neverShowCursor = const $ const Nothing
showFirstCursor :: s -> [CursorLocation n] -> Maybe (CursorLocation n)
showFirstCursor = const listToMaybe
showCursorNamed :: (Eq n) => n -> [CursorLocation n] -> Maybe (CursorLocation n)
showCursorNamed name locs =
let matches l = l^.cursorLocationNameL == Just name
in listToMaybe $ filter matches locs
data ViewportScroll n =
ViewportScroll { viewportName :: n
, hScrollPage :: Direction -> EventM n ()
, hScrollBy :: Int -> EventM n ()
, hScrollToBeginning :: EventM n ()
, hScrollToEnd :: EventM n ()
, vScrollPage :: Direction -> EventM n ()
, vScrollBy :: Int -> EventM n ()
, vScrollToBeginning :: EventM n ()
, vScrollToEnd :: EventM n ()
, setTop :: Int -> EventM n ()
, setLeft :: Int -> EventM n ()
}
addScrollRequest :: (n, ScrollRequest) -> EventM n ()
addScrollRequest req = EventM $ do
lift $ modify (\s -> s { esScrollRequests = req : esScrollRequests s })
viewportScroll :: n -> ViewportScroll n
viewportScroll n =
ViewportScroll { viewportName = n
, hScrollPage = \dir -> addScrollRequest (n, HScrollPage dir)
, hScrollBy = \i -> addScrollRequest (n, HScrollBy i)
, hScrollToBeginning = addScrollRequest (n, HScrollToBeginning)
, hScrollToEnd = addScrollRequest (n, HScrollToEnd)
, vScrollPage = \dir -> addScrollRequest (n, VScrollPage dir)
, vScrollBy = \i -> addScrollRequest (n, VScrollBy i)
, vScrollToBeginning = addScrollRequest (n, VScrollToBeginning)
, vScrollToEnd = addScrollRequest (n, VScrollToEnd)
, setTop = \i -> addScrollRequest (n, SetTop i)
, setLeft = \i -> addScrollRequest (n, SetLeft i)
}
continue :: s -> EventM n (Next s)
continue = return . Continue
halt :: s -> EventM n (Next s)
halt = return . Halt
suspendAndResume :: IO s -> EventM n (Next s)
suspendAndResume = return . SuspendAndResume