diff --git a/src/Brick/Main.hs b/src/Brick/Main.hs index e90cbb0..5f4c125 100644 --- a/src/Brick/Main.hs +++ b/src/Brick/Main.hs @@ -5,12 +5,14 @@ module Brick.Main , simpleMain , resizeOrQuit + -- * Event handler functions , EventM , Next , continue , halt , suspendAndResume + -- ** Viewport scrolling , viewportScroll , ViewportScroll , scrollBy @@ -18,6 +20,7 @@ module Brick.Main , scrollToBeginning , scrollToEnd + -- * Cursor management functions , neverShowCursor , showFirstCursor ) @@ -49,29 +52,69 @@ import Brick.Widgets.Internal (renderFinal, RenderState(..), ScrollRequest(..), import Brick.Core (row, column, CursorLocation(..), Name(..)) import Brick.AttrMap +-- | The type of actions to take in an event handler. data Next a = Continue a | SuspendAndResume (IO a) | Halt a +-- | The library application abstraction. Your application's operations +-- are represented here and passed to one of the various main functions +-- in this module. An application is in terms of an application state +-- type 'a' and an application event type 'e'. In the simplest case 'e' is +-- vty's 'Event' type, but you may define your own event type, permitted +-- that it has a constructor for wrapping Vty events, so that Vty events +-- can be handled by your event loop. data App a e = App { appDraw :: a -> [Widget] + -- ^ This function turns your application state into a list of + -- widget layers. The layers are listed topmost first. , appChooseCursor :: a -> [CursorLocation] -> Maybe CursorLocation + -- ^ This function chooses which of the zero or more cursor + -- locations reported by the rendering process should be + -- selected as the one to use to place the cursor. If this + -- returns 'Nothing', no cursor is placed. The rationale here + -- is that many widgets may request a cursor placement but your + -- application state is what you probably want to use to decide + -- which one wins. , appHandleEvent :: e -> a -> EventM (Next a) + -- ^ This function takes an event and your application state + -- and returns an action to be taken. Possible options are + -- 'continue', 'suspendAndResume', and 'halt'. , appStartEvent :: a -> EventM a + -- ^ This function gets called once just prior to the first + -- drawing of your application. Here is where you can make + -- initial scrolling requests, for example. , appAttrMap :: a -> AttrMap + -- ^ The attribute map that should be used during rendering. , appMakeVtyEvent :: Event -> e + -- ^ The event constructor to use to wrap Vty events in your own + -- event type. For example, if the application's event type is + -- 'Event', this is just 'id'. } +-- | The monad in which event handlers run. type EventM a = StateT EventState IO a type EventState = [(Name, ScrollRequest)] -defaultMain :: App a Event -> a -> IO a +-- | The default main entry point which takes an application and an +-- initial state and returns the final state returned by a 'halt' +-- operation. +defaultMain :: App a Event + -- ^ The application. + -> a + -- ^ The initial application state. + -> IO a defaultMain app st = do chan <- newChan customMain (mkVty def) chan app st -simpleMain :: Widget -> IO () +-- | A simple main entry point which takes a widget and renders it. This +-- event loop terminates when the user presses any key, but terminal +-- resize events cause redraws. +simpleMain :: Widget + -- ^ The widget to draw. + -> IO () simpleMain w = let app = App { appDraw = const [w] , appHandleEvent = resizeOrQuit @@ -82,6 +125,11 @@ simpleMain w = } in defaultMain app () +-- | An event-handling function which continues execution of the event +-- loop only when resize events occur; all other types of events trigger +-- a halt. This is a convenience function useful as an 'appHandleEvent' +-- value for simple applications using the 'Event' type that do not need +-- to get more sophisticated user input. resizeOrQuit :: Event -> a -> EventM (Next a) resizeOrQuit e a = case e of @@ -107,7 +155,21 @@ runWithNewVty buildVty chan app initialRS initialSt = do Continue s -> runInner newRS s runInner initialRS initialSt -customMain :: IO Vty -> Chan e -> App a e -> a -> IO a +-- | The custom event loop entry point to use when the simpler ones +-- don't permit enough control. +customMain :: IO Vty + -- ^ An IO action to build a Vty handle. This is used to + -- build a Vty handle whenever the event loop begins or is + -- resumed after suspension. + -> Chan e + -- ^ An event channel for sending custom events to the event + -- loop (you write to this channel, the event loop reads from + -- it). + -> App a e + -- ^ The application. + -> a + -- ^ The initial application state. + -> IO a customMain buildVty chan app initialAppState = do let run rs st = do result <- runWithNewVty buildVty chan app rs st @@ -155,12 +217,22 @@ renderApp vty app appState rs = do return newRS +-- | Ignore all requested cursor positions returned by the rendering +-- process. This is a convenience function useful as an +-- 'appChooseCursor' value when a simple application has no need to +-- position the cursor. neverShowCursor :: a -> [CursorLocation] -> Maybe CursorLocation neverShowCursor = const $ const Nothing +-- | Always show the first cursor, if any, returned by the rendering +-- process. This is a convenience function useful as an +-- 'appChooseCursor' value when a simple program has zero or more +-- widgets that advertise a cursor position. showFirstCursor :: a -> [CursorLocation] -> Maybe CursorLocation showFirstCursor = const $ listToMaybe +-- | A viewport scrolling handle for managing the scroll state of +-- viewports. data ViewportScroll = ViewportScroll { viewportName :: Name , scrollPage :: Direction -> EventM () @@ -169,6 +241,7 @@ data ViewportScroll = , scrollToEnd :: EventM () } +-- | Build a viewport scroller for the viewport with the specified name. viewportScroll :: Name -> ViewportScroll viewportScroll n = ViewportScroll { viewportName = n @@ -178,11 +251,19 @@ viewportScroll n = , scrollToEnd = modify ((n, ScrollToEnd) :) } +-- | Continue running the event loop with the specified application +-- state. continue :: a -> EventM (Next a) continue = return . Continue +-- | Halt the event loop and return the specified application state as +-- the final state value. halt :: a -> EventM (Next a) halt = return . Halt +-- | Suspend the event loop, save the terminal state, and run the +-- specified action. When it returns an application state value, restore +-- the terminal state, and resume the event loop with the returned +-- application state. suspendAndResume :: IO a -> EventM (Next a) suspendAndResume = return . SuspendAndResume