tinyapp-0.2.0.0: src/TinyApp/Interactive.hs
-- | Build interactice apps that reacts to each keystroke and renders text
-- Requires `ghc-options: -threaded`
module TinyApp.Interactive
( Event (..),
Key (..),
Modifier (..),
Sandbox (..),
ContinueExit (..),
runInteractive,
runInteractive',
)
where
import Brick qualified as B
import Brick.Main qualified as BM
import Brick.Types qualified as BT
import Brick.Widgets.Core qualified as BWC
import Control.Monad
import Control.Monad.State
import Graphics.Vty qualified as V
import Graphics.Vty.Input.Events qualified as VE
-- | Representes keys that can be pressed
data Key
= KEsc
| KChar Char
| KBS
| KEnter
| KLeft
| KRight
| KUp
| KDown
| KUpLeft
| KUpRight
| KDownLeft
| KDownRight
| KCenter
| KFun Int
| KBackTab
| KPrtScr
| KPause
| KIns
| KHome
| KPageUp
| KDel
| KEnd
| KPageDown
| KBegin
| KMenu
deriving (Eq, Show, Read, Ord)
fromVKey :: V.Key -> Key
fromVKey V.KEsc = KEsc
fromVKey (V.KChar char) = KChar char
fromVKey V.KBS = KBS
fromVKey V.KEnter = KEnter
fromVKey V.KLeft = KLeft
fromVKey V.KRight = KRight
fromVKey V.KUp = KUp
fromVKey V.KDown = KDown
fromVKey V.KUpLeft = KUpLeft
fromVKey V.KUpRight = KUpRight
fromVKey V.KDownLeft = KDownLeft
fromVKey V.KDownRight = KDownRight
fromVKey V.KCenter = KCenter
fromVKey (V.KFun int) = KFun int
fromVKey V.KBackTab = KBackTab
fromVKey V.KPrtScr = KPrtScr
fromVKey V.KPause = KPause
fromVKey V.KIns = KIns
fromVKey V.KHome = KHome
fromVKey V.KPageUp = KPageUp
fromVKey V.KDel = KDel
fromVKey V.KEnd = KEnd
fromVKey V.KPageDown = KPageDown
fromVKey V.KBegin = KBegin
fromVKey V.KMenu = KMenu
-- | Modifiers keys
data Modifier = MShift | MCtrl | MMeta | MAlt
deriving (Eq, Show, Read, Ord)
fromVModifier :: V.Modifier -> Modifier
fromVModifier V.MShift = MShift
fromVModifier V.MCtrl = MCtrl
fromVModifier V.MMeta = MMeta
fromVModifier V.MAlt = MAlt
-- | Event the application can receive
data Event = Key Key [Modifier]
-- | Signals whether the application should continue waiting input from the user or exit.
data ContinueExit = Continue | Exit
deriving (Eq, Show)
-- | Defines an interactive application that is not allowed to perform arbitrary IO while executing.
data Sandbox state = Sandbox
{ -- | Initial state
initialize :: state,
-- | What to draw based on the current state.
-- The screen is cleared between render calls. Usually use '\n' or *Prelude.unlines* to render multiple lines.
render :: state -> String,
-- | Process the event given the current state
-- Returns the next state and whether to continue or not the program
update :: Event -> state -> (state, ContinueExit)
}
-- | Executes the application.
runInteractive :: Sandbox s -> IO ()
runInteractive = Control.Monad.void . runInteractive'
-- | Executes the application returning its final state.
runInteractive' :: forall s. Sandbox s -> IO s
runInteractive' config =
let app :: BM.App s e ()
app =
BM.App
{ BM.appDraw = \s -> [BWC.str (config.render s)],
BM.appChooseCursor = \_ _ -> Nothing,
BM.appHandleEvent = \e -> do
s <- get
case e of
BT.VtyEvent (VE.EvKey k ms) ->
case config.update (Key (fromVKey k) (map fromVModifier ms)) s of
(s', Continue) -> do
put s'
(s', Exit) -> do
put s'
BM.halt
_ ->
return (),
BM.appStartEvent = return (),
BM.appAttrMap = \_ -> B.attrMap V.defAttr []
}
in BM.defaultMain app config.initialize