brick 0.9 → 0.10
raw patch · 9 files changed
+303/−62 lines, 9 filesdep ~microlensnew-component:exe:brick-cache-demoPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: microlens
API changes (from Hackage documentation)
- Brick.Types: instance Data.Default.Class.Default (Brick.Types.Result n)
- Brick.Types: instance GHC.Show.Show n => GHC.Show.Show (Brick.Types.Result n)
+ Brick.Main: invalidateCache :: EventM n ()
+ Brick.Main: invalidateCacheEntry :: n -> EventM n ()
+ Brick.Main: setLeft :: ViewportScroll n -> Int -> EventM n ()
+ Brick.Main: setTop :: ViewportScroll n -> Int -> EventM n ()
+ Brick.Widgets.Core: cached :: (Ord n) => n -> Widget n -> Widget n
- Brick.Main: customMain :: IO Vty -> Chan e -> App s e n -> s -> IO s
+ Brick.Main: customMain :: (Ord n) => IO Vty -> Chan e -> App s e n -> s -> IO s
- Brick.Main: defaultMain :: App s Event n -> s -> IO s
+ Brick.Main: defaultMain :: (Ord n) => App s Event n -> s -> IO s
- Brick.Main: simpleMain :: Widget n -> IO ()
+ Brick.Main: simpleMain :: (Ord n) => Widget n -> IO ()
- Brick.Types: cursorLocationL :: forall n_atbu. Lens' (CursorLocation n_atbu) Location
+ Brick.Types: cursorLocationL :: forall n_aoPe. Lens' (CursorLocation n_aoPe) Location
- Brick.Types: cursorLocationNameL :: forall n_atbu n_atm0. Lens (CursorLocation n_atbu) (CursorLocation n_atm0) (Maybe n_atbu) (Maybe n_atm0)
+ Brick.Types: cursorLocationNameL :: forall n_aoPe n_apGg. Lens (CursorLocation n_aoPe) (CursorLocation n_apGg) (Maybe n_aoPe) (Maybe n_apGg)
- Brick.Types: cursorsL :: forall n_axYw n_ayv0. Lens (Result n_axYw) (Result n_ayv0) [CursorLocation n_axYw] [CursorLocation n_ayv0]
+ Brick.Types: cursorsL :: forall n_aoO9 n_apyF. Lens (Result n_aoO9) (Result n_apyF) [CursorLocation n_aoO9] [CursorLocation n_apyF]
- Brick.Types: imageL :: forall n_axYw. Lens' (Result n_axYw) Image
+ Brick.Types: imageL :: forall n_aoO9. Lens' (Result n_aoO9) Image
- Brick.Types: visibilityRequestsL :: forall n_axYw. Lens' (Result n_axYw) [VisibilityRequest]
+ Brick.Types: visibilityRequestsL :: forall n_aoO9. Lens' (Result n_aoO9) [VisibilityRequest]
- Brick.Widgets.Dialog: dialogButtonsL :: forall a_aXP7 a_aXPH. Lens (Dialog a_aXP7) (Dialog a_aXPH) [(String, a_aXP7)] [(String, a_aXPH)]
+ Brick.Widgets.Dialog: dialogButtonsL :: forall a_aY6y a_aY78. Lens (Dialog a_aY6y) (Dialog a_aY78) [(String, a_aY6y)] [(String, a_aY78)]
- Brick.Widgets.Dialog: dialogSelectedIndexL :: forall a_aXP7. Lens' (Dialog a_aXP7) (Maybe Int)
+ Brick.Widgets.Dialog: dialogSelectedIndexL :: forall a_aY6y. Lens' (Dialog a_aY6y) (Maybe Int)
- Brick.Widgets.Dialog: dialogTitleL :: forall a_aXP7. Lens' (Dialog a_aXP7) (Maybe String)
+ Brick.Widgets.Dialog: dialogTitleL :: forall a_aY6y. Lens' (Dialog a_aY6y) (Maybe String)
- Brick.Widgets.Dialog: dialogWidthL :: forall a_aXP7. Lens' (Dialog a_aXP7) Int
+ Brick.Widgets.Dialog: dialogWidthL :: forall a_aY6y. Lens' (Dialog a_aY6y) Int
- Brick.Widgets.Edit: editContentsL :: forall t_aZzU n_aZzV. Lens' (Editor t_aZzU n_aZzV) (TextZipper t_aZzU)
+ Brick.Widgets.Edit: editContentsL :: forall t_aZQf n_aZQg. Lens' (Editor t_aZQf n_aZQg) (TextZipper t_aZQf)
- Brick.Widgets.Edit: editDrawContentsL :: forall t_aZzU n_aZzV. Lens' (Editor t_aZzU n_aZzV) ([t_aZzU] -> Widget n_aZzV)
+ Brick.Widgets.Edit: editDrawContentsL :: forall t_aZQf n_aZQg. Lens' (Editor t_aZQf n_aZQg) ([t_aZQf] -> Widget n_aZQg)
- Brick.Widgets.List: listElementsL :: forall n_a158S e_a158T e_a15ey. Lens (List n_a158S e_a158T) (List n_a158S e_a15ey) (Vector e_a158T) (Vector e_a15ey)
+ Brick.Widgets.List: listElementsL :: forall n_a11mR e_a11mS e_a11vs. Lens (List n_a11mR e_a11mS) (List n_a11mR e_a11vs) (Vector e_a11mS) (Vector e_a11vs)
- Brick.Widgets.List: listItemHeightL :: forall n_a158S e_a158T. Lens' (List n_a158S e_a158T) Int
+ Brick.Widgets.List: listItemHeightL :: forall n_a11mR e_a11mS. Lens' (List n_a11mR e_a11mS) Int
- Brick.Widgets.List: listNameL :: forall n_a158S e_a158T n_a15ez. Lens (List n_a158S e_a158T) (List n_a15ez e_a158T) n_a158S n_a15ez
+ Brick.Widgets.List: listNameL :: forall n_a11mR e_a11mS n_a11vt. Lens (List n_a11mR e_a11mS) (List n_a11vt e_a11mS) n_a11mR n_a11vt
- Brick.Widgets.List: listSelectedL :: forall n_a158S e_a158T. Lens' (List n_a158S e_a158T) (Maybe Int)
+ Brick.Widgets.List: listSelectedL :: forall n_a11mR e_a11mS. Lens' (List n_a11mR e_a11mS) (Maybe Int)
Files
- CHANGELOG.md +28/−0
- brick.cabal +16/−1
- programs/CacheDemo.hs +80/−0
- programs/LayerDemo.hs +1/−1
- src/Brick/Main.hs +99/−27
- src/Brick/Types.hs +1/−21
- src/Brick/Types/Internal.hs +45/−10
- src/Brick/Widgets/Core.hs +29/−1
- src/Brick/Widgets/Dialog.hs +4/−1
CHANGELOG.md view
@@ -2,6 +2,34 @@ Brick changelog --------------- +0.10+----++New features:+ * Added a rendering cache. To use the rendering cache, use the 'cached'+ widget combinator. This causes drawings of the specified widget to+ re-use a cached rendering until the rendering cache is invalidated+ with 'invalidateCacheEntry' or 'invalidateCache'. This change also+ includes programs/CacheDemo.hs. This change introduced an Ord+ constraint on the name type variable 'n'.+ * Added setTop and setLeft for setting viewport offsets directly in+ EventM.+ * Dialog event handlers now support left and right arrow keys (thanks+ Grégoire Charvet)++Library changes:+ * On resizes brick now draws the application twice before handling the+ resize event. This change makes it possible for event handlers to+ get the latest viewport states on a resize rather than getting the+ most recent (but stale) versions as before, at the cost of a second+ redraw.++Bug fixes:+ * We now use the most recent rendering state when setting up event handler+ viewport data. This mostly won't matter to anyone except in cases+ where a viewport name was expected to be in the viewport map but+ wasn't due to using stale rendering state to set up EventM.+ 0.9 ---
brick.cabal view
@@ -1,5 +1,5 @@ name: brick-version: 0.9+version: 0.10 synopsis: A declarative terminal user interface library description: Write terminal applications painlessly with 'brick'! You write an@@ -91,6 +91,21 @@ text-zipper >= 0.7.1, template-haskell, deepseq >= 1.3 && < 1.5++executable brick-cache-demo+ if !flag(demos)+ Buildable: False+ hs-source-dirs: programs+ ghc-options: -threaded -Wall -fno-warn-unused-do-bind -O3+ default-language: Haskell2010+ default-extensions: CPP+ main-is: CacheDemo.hs+ build-depends: base,+ brick,+ vty >= 5.5.0,+ text,+ microlens >= 0.3.0.0,+ microlens-th executable brick-visibility-demo if !flag(demos)
+ programs/CacheDemo.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE OverloadedStrings #-}+module Main where++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif++import Control.Monad (void)+import Data.Monoid ((<>))+import qualified Graphics.Vty as V++import qualified Brick.Types as T+import qualified Brick.Main as M+import qualified Brick.Widgets.Center as C+import Brick.Types+ ( Widget+ )+import Brick.Widgets.Core+ ( vBox+ , padTopBottom+ , withDefAttr+ , cached+ , padBottom+ , str+ )+import Brick (on)+import Brick.Widgets.Center+ ( hCenter+ )+import Brick.AttrMap+ ( AttrName+ , attrMap+ )++data Name = ExpensiveWidget+ deriving (Ord, Show, Eq)++drawUi :: Int -> [Widget Name]+drawUi i = [ui]+ where+ ui = C.vCenter $+ vBox $ hCenter <$>+ [ str "This demo shows how cached widgets behave. The top widget below"+ , str "is cacheable, so once it's rendered, brick re-uses the rendering"+ , str "each time it is drawn. The bottom widget is not cacheable so it is"+ , str "drawn on every request. Brick supports cache invalidation to force"+ , str "a redraw of cached widgets; we can trigger that here with 'i'. Notice"+ , str "how state changes with '+' aren't reflected in the cached widget"+ , str "until the cache is invalidated with 'i'."+ , padTopBottom 1 $+ cached ExpensiveWidget $+ withDefAttr emphAttr $ str $ "This widget is cached (state = " <> show i <> ")"+ , padBottom (T.Pad 1) $+ withDefAttr emphAttr $ str $ "This widget is not cached (state = " <> show i <> ")"+ , hCenter $ str "Press 'i' to invalidate the cache,"+ , str "'+' to change the state value, and"+ , str "'Esc' to quit."+ ]++appEvent :: Int -> V.Event -> T.EventM Name (T.Next Int)+appEvent i (V.EvKey (V.KChar '+') []) = M.continue $ i + 1+appEvent i (V.EvKey (V.KChar 'i') []) = M.invalidateCacheEntry ExpensiveWidget >> M.continue i+appEvent i (V.EvKey V.KEsc []) = M.halt i+appEvent i _ = M.continue i++emphAttr :: AttrName+emphAttr = "emphasis"++app :: M.App Int V.Event Name+app =+ M.App { M.appDraw = drawUi+ , M.appStartEvent = return+ , M.appHandleEvent = appEvent+ , M.appAttrMap = const $ attrMap V.defAttr [(emphAttr, V.white `on` V.blue)]+ , M.appLiftVtyEvent = id+ , M.appChooseCursor = M.neverShowCursor+ }++main :: IO ()+main = void $ M.defaultMain app 0
programs/LayerDemo.hs view
@@ -28,7 +28,7 @@ drawUi :: St -> [Widget ()] drawUi st = [ C.centerLayer $- B.border $ str "This layer is centered but other\nlayers are visible underneath it."+ B.border $ str "This layer is centered but other\nlayers are placed underneath it." , topLayer st , bottomLayer st ]
src/Brick/Main.hs view
@@ -22,11 +22,17 @@ , hScrollPage , hScrollToBeginning , hScrollToEnd+ , setTop+ , setLeft -- * Cursor management functions , neverShowCursor , showFirstCursor , showCursorNamed++ -- * Rendering cache management+ , invalidateCacheEntry+ , invalidateCache ) where @@ -37,6 +43,10 @@ import Control.Monad.Trans.State import Control.Monad.Trans.Reader import Control.Concurrent (forkIO, Chan, newChan, readChan, writeChan, killThread)+#if !MIN_VERSION_base(4,8,0)+import Control.Applicative ((<$>))+import Data.Monoid (mempty)+#endif import Data.Default import Data.Maybe (listToMaybe) import qualified Data.Map as M@@ -55,7 +65,7 @@ ) import Brick.Types (Viewport, Direction, Widget, rowL, columnL, CursorLocation(..), cursorLocationNameL, EventM(..))-import Brick.Types.Internal (ScrollRequest(..), RenderState(..), observedNamesL, Next(..))+import Brick.Types.Internal (ScrollRequest(..), RenderState(..), observedNamesL, Next(..), EventState(..), CacheInvalidateRequest(..)) import Brick.Widgets.Internal (renderFinal) import Brick.AttrMap @@ -101,7 +111,8 @@ -- | 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 s Event n+defaultMain :: (Ord n)+ => App s Event n -- ^ The application. -> s -- ^ The initial application state.@@ -113,7 +124,8 @@ -- | 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 n+simpleMain :: (Ord n)+ => Widget n -- ^ The widget to draw. -> IO () simpleMain w =@@ -138,10 +150,16 @@ data InternalNext n a = InternalSuspendAndResume (RenderState n) (IO a) | InternalHalt a -runWithNewVty :: IO Vty -> Chan e -> App s e n -> RenderState n -> s -> IO (InternalNext n s)+runWithNewVty :: (Ord n)+ => IO Vty+ -> Chan (Either Event e)+ -> App s e n+ -> RenderState n+ -> s+ -> IO (InternalNext n s) runWithNewVty buildVty chan app initialRS initialSt = withVty buildVty $ \vty -> do- pid <- forkIO $ supplyVtyEvents vty (appLiftVtyEvent app) chan+ pid <- forkIO $ supplyVtyEvents vty chan let runInner rs st = do (result, newRS) <- runVty vty chan app st (rs & observedNamesL .~ S.empty) case result of@@ -156,7 +174,8 @@ -- | The custom event loop entry point to use when the simpler ones -- don't permit enough control.-customMain :: IO Vty+customMain :: (Ord n)+ => 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.@@ -169,32 +188,65 @@ -> s -- ^ The initial application state. -> IO s-customMain buildVty chan app initialAppState = do- let run rs st = do+customMain buildVty userChan app initialAppState = do+ let run rs st chan = do result <- runWithNewVty buildVty chan app rs st case result of InternalHalt s -> return s InternalSuspendAndResume newRS action -> do newAppState <- action- run newRS newAppState+ run newRS newAppState chan - (st, initialScrollReqs) <- runStateT (runReaderT (runEventM (appStartEvent app initialAppState)) M.empty) []- let initialRS = RS M.empty initialScrollReqs S.empty- run initialRS st+ emptyES = ES [] []+ (st, eState) <- runStateT (runReaderT (runEventM (appStartEvent app initialAppState)) M.empty) emptyES+ let initialRS = RS M.empty (esScrollRequests eState) S.empty mempty+ chan <- newChan+ forkIO $ forever $ readChan userChan >>= (\userEvent -> writeChan chan (Right userEvent))+ run initialRS st chan -supplyVtyEvents :: Vty -> (Event -> e) -> Chan e -> IO ()-supplyVtyEvents vty mkEvent chan =+supplyVtyEvents :: Vty -> Chan (Either Event e) -> IO ()+supplyVtyEvents vty chan = forever $ do e <- nextEvent vty- writeChan chan $ mkEvent e+ writeChan chan $ Left e -runVty :: Vty -> Chan e -> App s e n -> s -> RenderState n -> IO (Next s, RenderState n)+runVty :: (Ord n)+ => Vty+ -> Chan (Either Event e)+ -> App s e n+ -> s+ -> RenderState n+ -> IO (Next s, RenderState n) runVty vty chan app appState rs = do firstRS <- renderApp vty app appState rs e <- readChan chan- (next, scrollReqs) <- runStateT (runReaderT (runEventM (appHandleEvent app appState e)) (viewportMap rs)) []- return (next, firstRS { scrollRequests = scrollReqs }) + -- If the event was a resize, redraw the UI to update the viewport+ -- states before we invoke the event handler since we want the event+ -- handler to have access to accurate viewport information.+ nextRS <- case e of+ Left (EvResize _ _) ->+ renderApp vty app appState $ firstRS & observedNamesL .~ S.empty+ _ -> return firstRS++ let emptyES = ES [] []+ userEvent = case e of+ Left e' -> appLiftVtyEvent app e'+ Right e' -> e'++ (next, eState) <- runStateT (runReaderT (runEventM (appHandleEvent app appState userEvent))+ (viewportMap nextRS)) emptyES+ return (next, nextRS { rsScrollRequests = esScrollRequests eState+ , renderCache = applyInvalidations (cacheInvalidateRequests eState) $+ renderCache nextRS+ })++applyInvalidations :: (Ord n) => [CacheInvalidateRequest n] -> M.Map n v -> M.Map n v+applyInvalidations ns cache = foldr (.) id (mkFunc <$> ns) cache+ where+ mkFunc InvalidateEntire = const mempty+ mkFunc (InvalidateSingle n) = M.delete n+ -- | Given a viewport name, get the viewport's size and offset -- information from the most recent rendering. Returns 'Nothing' if -- no such state could be found, either because the name was invalid@@ -203,6 +255,16 @@ lookupViewport :: (Ord n) => n -> EventM n (Maybe Viewport) lookupViewport = EventM . asks . M.lookup +-- | Invalidate the rendering cache entry with the specified name.+invalidateCacheEntry :: n -> EventM n ()+invalidateCacheEntry n = EventM $ do+ lift $ modify (\s -> s { cacheInvalidateRequests = InvalidateSingle n : cacheInvalidateRequests s })++-- | Invalidate the entire rendering cache.+invalidateCache :: EventM n ()+invalidateCache = EventM $ do+ lift $ modify (\s -> s { cacheInvalidateRequests = InvalidateEntire : cacheInvalidateRequests s })+ withVty :: IO Vty -> (Vty -> IO a) -> IO a withVty buildVty useVty = do vty <- buildVty@@ -274,20 +336,30 @@ -- ^ Scroll vertically to the beginning of the viewport. , vScrollToEnd :: EventM n () -- ^ Scroll vertically to the end of the viewport.+ , setTop :: Int -> EventM n ()+ -- ^ Set the top row offset of the viewport.+ , setLeft :: Int -> EventM n ()+ -- ^ Set the left column offset of the viewport. } +addScrollRequest :: (n, ScrollRequest) -> EventM n ()+addScrollRequest req = EventM $ do+ lift $ modify (\s -> s { esScrollRequests = req : esScrollRequests s })+ -- | Build a viewport scroller for the viewport with the specified name. viewportScroll :: n -> ViewportScroll n viewportScroll n =- ViewportScroll { viewportName = n- , hScrollPage = \dir -> EventM $ lift $ modify ((n, HScrollPage dir) :)- , hScrollBy = \i -> EventM $ lift $ modify ((n, HScrollBy i) :)- , hScrollToBeginning = EventM $ lift $ modify ((n, HScrollToBeginning) :)- , hScrollToEnd = EventM $ lift $ modify ((n, HScrollToEnd) :)- , vScrollPage = \dir -> EventM $ lift $ modify ((n, VScrollPage dir) :)- , vScrollBy = \i -> EventM $ lift $ modify ((n, VScrollBy i) :)- , vScrollToBeginning = EventM $ lift $ modify ((n, VScrollToBeginning) :)- , vScrollToEnd = EventM $ lift $ modify ((n, VScrollToEnd) :)+ 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 running the event loop with the specified application
src/Brick/Types.hs view
@@ -74,8 +74,7 @@ import Lens.Micro.Type (Getting) import Control.Monad.Trans.State.Lazy import Control.Monad.Trans.Reader-import Graphics.Vty (Event, Image, emptyImage, Attr)-import Data.Default (Default(..))+import Graphics.Vty (Event, Attr) import qualified Data.Map as M import Control.Monad.IO.Class @@ -142,30 +141,11 @@ -- communicate rendering parameters to widgets' rendering functions. type RenderM n a = ReaderT Context (State (RenderState n)) a --- | The type of result returned by a widget's rendering function. The--- result provides the image, cursor positions, and visibility requests--- that resulted from the rendering process.-data Result n =- Result { image :: Image- -- ^ The final rendered image for a widget- , cursors :: [CursorLocation n]- -- ^ The list of reported cursor positions for the- -- application to choose from- , visibilityRequests :: [VisibilityRequest]- -- ^ The list of visibility requests made by widgets rendered- -- while rendering this one (used by viewports)- }- deriving Show--instance Default (Result n) where- def = Result emptyImage [] []- -- | Get the current rendering context. getContext :: RenderM n Context getContext = ask suffixLenses ''Context-suffixLenses ''Result -- | The rendering context's current drawing attribute. attrL :: forall r. Getting r Context Attr
src/Brick/Types/Internal.hs view
@@ -18,15 +18,21 @@ , cursorLocationL , cursorLocationNameL , Context(..)- , EventState+ , EventState(..) , Next(..)+ , Result(..)+ , CacheInvalidateRequest(..) - , scrollRequestsL+ , rsScrollRequestsL , viewportMapL+ , renderCacheL , observedNamesL , vpSize , vpLeft , vpTop+ , imageL+ , cursorsL+ , visibilityRequestsL ) where @@ -39,18 +45,13 @@ import Lens.Micro.Internal (Field1, Field2) import qualified Data.Set as S import qualified Data.Map as M-import Graphics.Vty (DisplayRegion)+import Graphics.Vty (DisplayRegion, Image, emptyImage)+import Data.Default (Default(..)) import Brick.Types.TH import Brick.AttrMap (AttrName, AttrMap) import Brick.Widgets.Border.Style (BorderStyle) -data RenderState n =- RS { viewportMap :: M.Map n Viewport- , scrollRequests :: [(n, ScrollRequest)]- , observedNames :: !(S.Set n)- }- data ScrollRequest = HScrollBy Int | HScrollPage Direction | HScrollToBeginning@@ -59,6 +60,8 @@ | VScrollPage Direction | VScrollToBeginning | VScrollToEnd+ | SetTop Int+ | SetLeft Int data VisibilityRequest = VR { vrPosition :: Location@@ -88,8 +91,13 @@ -- ^ Viewports of this type are scrollable vertically and horizontally. deriving (Show, Eq) -type EventState n = [(n, ScrollRequest)]+data CacheInvalidateRequest n = InvalidateSingle n+ | InvalidateEntire +data EventState n = ES { esScrollRequests :: [(n, ScrollRequest)]+ , cacheInvalidateRequests :: [CacheInvalidateRequest n]+ }+ -- | The type of actions to take upon completion of an event handler. data Next a = Continue a | SuspendAndResume (IO a)@@ -148,6 +156,33 @@ -- ^ The name of the widget associated with the location } deriving Show++-- | The type of result returned by a widget's rendering function. The+-- result provides the image, cursor positions, and visibility requests+-- that resulted from the rendering process.+data Result n =+ Result { image :: Image+ -- ^ The final rendered image for a widget+ , cursors :: [CursorLocation n]+ -- ^ The list of reported cursor positions for the+ -- application to choose from+ , visibilityRequests :: [VisibilityRequest]+ -- ^ The list of visibility requests made by widgets rendered+ -- while rendering this one (used by viewports)+ }+ deriving Show++suffixLenses ''Result++instance Default (Result n) where+ def = Result emptyImage [] []++data RenderState n =+ RS { viewportMap :: M.Map n Viewport+ , rsScrollRequests :: [(n, ScrollRequest)]+ , observedNames :: !(S.Set n)+ , renderCache :: M.Map n (Result n)+ } -- | The rendering context. This tells widgets how to render: how much -- space they have in which to render, which attribute they should use
src/Brick/Widgets/Core.hs view
@@ -60,6 +60,7 @@ , visible , visibleRegion , unsafeLookupViewport+ , cached -- ** Adding offsets to cursor positions and visibility requests , addResultOffset@@ -555,6 +556,31 @@ Fixed -> Just $ Widget (hSize p) Greedy $ withReaderT (& availHeightL .~ unrestricted) (render p) Greedy -> Nothing +-- | Render the specified widget. If the widget has an entry in the+-- rendering cache using the specified name as the cache key, use the+-- rendered version from the cache instead. If not, render the widget+-- and update the cache.+--+-- See also 'invalidateCacheEntry'.+cached :: (Ord n) => n -> Widget n -> Widget n+cached n w =+ Widget (hSize w) (vSize w) $ do+ result <- cacheLookup n+ case result of+ Just prevResult -> return prevResult+ Nothing -> do+ wResult <- render w+ cacheUpdate n wResult+ return wResult++cacheLookup :: (Ord n) => n -> RenderM n (Maybe (Result n))+cacheLookup n = do+ cache <- lift $ gets (^.renderCacheL)+ return $ M.lookup n cache++cacheUpdate :: (Ord n) => n -> Result n -> RenderM n ()+cacheUpdate n r = lift $ modify (& renderCacheL %~ M.insert n r)+ -- | Render the specified widget in a named viewport with the -- specified type. This permits widgets to be scrolled without being -- scrolling-aware. To make the most use of viewports, the specified@@ -623,7 +649,7 @@ -- If the rendering state includes any scrolling requests for this -- viewport, apply those- reqs <- lift $ gets $ (^.scrollRequestsL)+ reqs <- lift $ gets $ (^.rsScrollRequestsL) let relevantRequests = snd <$> filter (\(n, _) -> n == vpname) reqs when (not $ null relevantRequests) $ do Just vp <- lift $ gets $ (^.viewportMapL.to (M.lookup vpname))@@ -710,6 +736,7 @@ VScrollPage Down -> vp^.vpTop + vp^.vpSize._2 VScrollToBeginning -> 0 VScrollToEnd -> V.imageHeight img - vp^.vpSize._2+ SetTop i -> i _ -> vp^.vpTop scrollTo Horizontal req img vp = vp & vpLeft .~ newHStart where@@ -720,6 +747,7 @@ HScrollPage Down -> vp^.vpLeft + vp^.vpSize._1 HScrollToBeginning -> 0 HScrollToEnd -> V.imageWidth img - vp^.vpSize._1+ SetLeft i -> i _ -> vp^.vpLeft scrollToView :: ViewportType -> VisibilityRequest -> Viewport -> Viewport
src/Brick/Widgets/Dialog.hs view
@@ -45,7 +45,8 @@ -- | Dialogs present a window with a title (optional), a body, and -- buttons (optional). They provide a 'HandleEvent' instance that knows--- about Tab and Shift-Tab for changing which button is active. Dialog+-- about Tab and Shift-Tab as well as ArrowLeft and ArrowRight+-- for changing which button is active. Dialog -- buttons are labeled with strings and map to values of type 'a', which -- you choose. --@@ -71,6 +72,8 @@ return $ case ev of EvKey (KChar '\t') [] -> nextButtonBy 1 d EvKey KBackTab [] -> nextButtonBy (-1) d+ EvKey KRight [] -> nextButtonBy 1 d+ EvKey KLeft [] -> nextButtonBy (-1) d _ -> d -- | Create a dialog.