@@ -15,6 +15,7 @@ module Reflex.Vty.Widget
1515 , HasDisplaySize(..)
1616 , HasFocus(..)
1717 , HasVtyInput(..)
18+ , HasVtyWidgetCtx(..)
1819 , Region(..)
1920 , regionSize
2021 , regionBlankImage
@@ -57,7 +58,8 @@ module Reflex.Vty.Widget
5758import Control.Applicative (liftA2)
5859import Control.Monad.Fix (MonadFix)
5960import Control.Monad.IO.Class (MonadIO)
60−import Control.Monad.Reader (ReaderT, ask, asks, runReaderT)
61+import Control.Monad.NodeId
62+import Control.Monad.Reader (ReaderT, ask, asks, local, runReaderT)
6163import Control.Monad.Trans (MonadTrans, lift)
6264import Data.Default (Default(..))
6365import Data.Set (Set)
@@ -70,11 +72,8 @@ import qualified Graphics.Vty as V
7072import Reflex
7173import Reflex.Class ()
7274import Reflex.Host.Class (MonadReflexCreateTrigger)
73−
7475import Reflex.Vty.Host
7576
76−import Control.Monad.NodeId
77−
7877-- | The context within which a 'VtyWidget' runs
7978data VtyWidgetCtx t = VtyWidgetCtx
8079 { _vtyWidgetCtx_width :: Dynamic t Int
@@ -131,6 +130,19 @@ instance MonadNodeId m => MonadNodeId (VtyWidget t m) where
131130 getNextNodeId = VtyWidget $ do
132131 lift $ lift getNextNodeId
133132
133+class HasVtyWidgetCtx t m | m -> t where
134+ askCtx :: m (VtyWidgetCtx t)
135+ default askCtx :: (f m' ~ m, Monad m', MonadTrans f, HasVtyWidgetCtx t m') => m (VtyWidgetCtx t)
136+ askCtx = lift askCtx
137+ localCtx :: (VtyWidgetCtx t -> VtyWidgetCtx t) -> (Behavior t [Image] -> Behavior t [Image]) -> m a -> m a
138+
139+instance (Monad m, Reflex t) => HasVtyWidgetCtx t (VtyWidget t m) where
140+ askCtx = VtyWidget $ lift ask
141+ localCtx f g (VtyWidget x) = VtyWidget $ do
142+ (a, images) :: (a, Behavior t [Image]) <- lift $ local f $ runBehaviorWriterT x
143+ tellImages $ g images
144+ pure a
145+
134146-- | Runs a 'VtyWidget' with a given context
135147runVtyWidget
136148 :: (Reflex t, MonadNodeId m)
@@ -208,6 +220,8 @@ instance (Reflex t, Monad m) => HasVtyInput t (VtyWidget t m) where
208220-- | A class for things that can dynamically gain and lose focus
209221class HasFocus t m | m -> t where
210222 focus :: m (Dynamic t Bool)
223+ default focus :: (f m' ~ m, Monad m', MonadTrans f, HasFocus t m') => m (Dynamic t Bool)
224+ focus = lift focus
211225
212226instance (Reflex t, Monad m) => HasFocus t (VtyWidget t m) where
213227 focus = VtyWidget . lift $ asks _vtyWidgetCtx_focus
@@ -216,6 +230,8 @@ instance (Reflex t, Monad m) => HasFocus t (VtyWidget t m) where
216230class (Reflex t, Monad m) => ImageWriter t m | m -> t where
217231 -- | Send images upstream for rendering
218232 tellImages :: Behavior t [Image] -> m ()
233+ default tellImages :: (f m' ~ m, Monad m', MonadTrans f, ImageWriter t m') => Behavior t [Image] -> m ()
234+ tellImages = lift . tellImages
219235
220236instance (Monad m, Reflex t) => ImageWriter t (BehaviorWriterT t [Image] m) where
221237 tellImages = tellBehavior
@@ -255,18 +271,16 @@ withinImage (Region left top width height)
255271-- * mouse inputs outside the region are ignored
256272-- * mouse inputs inside the region have their coordinates translated such
257273-- that (0,0) is the top-left corner of the region
258−-- TODO make this polymorphic
259274pane
260− :: (Reflex t, Monad m, MonadNodeId m)
275+ :: (Reflex t, Monad m, MonadNodeId m, HasVtyWidgetCtx t m)
261276 => Dynamic t Region
262277 -> Dynamic t Bool -- ^ Whether the widget should be focused when the parent is.
263− -> VtyWidget t m a
264− -> VtyWidget t m a
265−pane dr foc child = VtyWidget $ do
266− ctx <- lift ask
278+ -> m a
279+ -> m a
280+pane dr foc child = do
267281 let reg = current dr
268− let ctx' = VtyWidgetCtx
269− { _vtyWidgetCtx_input = leftmost -- TODO: think about this leftmost more.
282+ let subContext ctx = VtyWidgetCtx
283+ { _vtyWidgetCtx_input = leftmost
270284 [ fmapMaybe id $
271285 attachWith (\(r,f) e -> filterInput r f e)
272286 (liftA2 (,) reg (current foc))
@@ -276,10 +290,8 @@ pane dr foc child = VtyWidget $ do
276290 , _vtyWidgetCtx_width = _region_width <$> dr
277291 , _vtyWidgetCtx_height = _region_height <$> dr
278292 }
279− (result, images) <- lift . lift $ runVtyWidget ctx' child -- TODO can we use a local-based implementation?
280− let images' = liftA2 (\r is -> map (withinImage r) is) reg images
281− tellImages images'
282− return result
293+ let imagesWithinRegion images = liftA2 (\is r -> map (withinImage r) is) images reg
294+ localCtx subContext imagesWithinRegion child
283295 where
284296 filterInput :: Region -> Bool -> VtyEvent -> Maybe VtyEvent
285297 filterInput (Region l t w h) focused e = case e of
@@ -424,16 +436,16 @@ keyCombos ks = do
424436
425437-- | A plain split of the available space into vertically stacked panes.
426438-- No visual separator is built in here.
427−splitV :: (Reflex t, Monad m, MonadNodeId m)
439+splitV :: (Reflex t, Monad m, MonadNodeId m, HasVtyWidgetCtx t m, HasDisplaySize t m)
428440 => Dynamic t (Int -> Int)
429441 -- ^ Function used to determine size of first pane based on available size
430442 -> Dynamic t (Bool, Bool)
431443 -- ^ How to focus the two sub-panes, given that we are focused.
432− -> VtyWidget t m a
444+ -> m a
433445 -- ^ Widget for first pane
434− -> VtyWidget t m b
446+ -> m b
435447 -- ^ Widget for second pane
436− -> VtyWidget t m (a,b)
448+ -> m (a,b)
437449splitV sizeFunD focD wA wB = do
438450 dw <- displayWidth
439451 dh <- displayHeight
@@ -445,16 +457,16 @@ splitV sizeFunD focD wA wB = do
445457
446458-- | A plain split of the available space into horizontally stacked panes.
447459-- No visual separator is built in here.
448−splitH :: (Reflex t, Monad m, MonadNodeId m)
460+splitH :: (Reflex t, Monad m, MonadNodeId m, HasDisplaySize t m, HasVtyWidgetCtx t m)
449461 => Dynamic t (Int -> Int)
450462 -- ^ Function used to determine size of first pane based on available size
451463 -> Dynamic t (Bool, Bool)
452464 -- ^ How to focus the two sub-panes, given that we are focused.
453− -> VtyWidget t m a
465+ -> m a
454466 -- ^ Widget for first pane
455− -> VtyWidget t m b
467+ -> m b
456468 -- ^ Widget for second pane
457− -> VtyWidget t m (a,b)
469+ -> m (a,b)
458470splitH sizeFunD focD wA wB = do
459471 dw <- displayWidth
460472 dh <- displayHeight
@@ -465,11 +477,11 @@ splitH sizeFunD focD wA wB = do
465477-- | A split of the available space into two parts with a draggable separator.
466478-- Starts with half the space allocated to each, and the first pane has focus.
467479-- Clicking in a pane switches focus.
468−splitVDrag :: (Reflex t, MonadFix m, MonadHold t m, MonadNodeId m)
469− => VtyWidget t m ()
470− -> VtyWidget t m a
471− -> VtyWidget t m b
472− -> VtyWidget t m (a,b)
480+splitVDrag :: (Reflex t, MonadFix m, MonadHold t m, MonadNodeId m, HasDisplaySize t m, HasVtyInput t m, HasVtyWidgetCtx t m)
481+ => m ()
482+ -> m a
483+ -> m b
484+ -> m (a,b)
473485splitVDrag wS wA wB = do
474486 dh <- displayHeight
475487 dw <- displayWidth
@@ -503,7 +515,7 @@ splitVDrag wS wA wB = do
503515 return (m, x')
504516
505517-- | Fill the background with a particular character.
506−fill :: (Reflex t, Monad m) => Char -> VtyWidget t m ()
518+fill :: (HasDisplaySize t m, ImageWriter t m) => Char -> m ()
507519fill c = do
508520 dw <- displayWidth
509521 dh <- displayHeight
@@ -511,7 +523,7 @@ fill c = do
511523 tellImages fillImg
512524
513525-- | Fill the background with the bottom
514−hRule :: (Reflex t, Monad m) => BoxStyle -> VtyWidget t m ()
526+hRule :: (HasDisplaySize t m, ImageWriter t m) => BoxStyle -> m ()
515527hRule boxStyle = fill (_boxStyle_s boxStyle)
516528
517529-- | Defines a set of symbols to use to draw the outlines of boxes
@@ -552,11 +564,11 @@ roundedBoxStyle :: BoxStyle
552564roundedBoxStyle = BoxStyle '╭' '─' '╮' '│' '╯' '─' '╰' '│'
553565
554566-- | Draws a titled box in the provided style and a child widget inside of that box
555−boxTitle :: (Monad m, Reflex t, MonadNodeId m)
567+boxTitle :: (Monad m, Reflex t, MonadNodeId m, HasDisplaySize t m, ImageWriter t m, HasVtyWidgetCtx t m)
556568 => Behavior t BoxStyle
557569 -> Text
558− -> VtyWidget t m a
559− -> VtyWidget t m a
570+ -> m a
571+ -> m a
560572boxTitle boxStyle title child = do
561573 dh <- displayHeight
562574 dw <- displayWidth
@@ -604,18 +616,18 @@ boxTitle boxStyle title child = do
604616 right = mkHalf delta
605617
606618-- | A box without a title
607−box :: (Monad m, Reflex t, MonadNodeId m)
619+box :: (Monad m, Reflex t, MonadNodeId m, HasDisplaySize t m, ImageWriter t m, HasVtyWidgetCtx t m)
608620 => Behavior t BoxStyle
609− -> VtyWidget t m a
610− -> VtyWidget t m a
621+ -> m a
622+ -> m a
611623box boxStyle = boxTitle boxStyle mempty
612624
613625-- | A box whose style is static
614626boxStatic
615− :: (Reflex t, Monad m, MonadNodeId m)
627+ :: (Monad m, Reflex t, MonadNodeId m, HasDisplaySize t m, ImageWriter t m, HasVtyWidgetCtx t m)
616628 => BoxStyle
617− -> VtyWidget t m a
618− -> VtyWidget t m a
629+ -> m a
630+ -> m a
619631boxStatic = box . pure
620632
621633-- | Configuration options for displaying "rich" text
@@ -628,10 +640,10 @@ instance Reflex t => Default (RichTextConfig t) where
628640
629641-- | A widget that displays text with custom time-varying attributes
630642richText
631− :: (Reflex t, Monad m)
643+ :: (Reflex t, Monad m, HasDisplaySize t m, ImageWriter t m)
632644 => RichTextConfig t
633645 -> Behavior t Text
634− -> VtyWidget t m ()
646+ -> m ()
635647richText cfg t = do
636648 dw <- displayWidth
637649 let img = (\w a s -> [wrapText w a s])
@@ -646,19 +658,19 @@ richText cfg t = do
646658
647659-- | Renders text, wrapped to the container width
648660text
649− :: (Reflex t, Monad m)
661+ :: (Reflex t, Monad m, HasDisplaySize t m, ImageWriter t m)
650662 => Behavior t Text
651− -> VtyWidget t m ()
663+ -> m ()
652664text = richText def
653665
654666-- | Scrollable text widget. The output pair exposes the current scroll position and total number of lines (including those
655667-- that are hidden)
656668scrollableText
657− :: forall t m. (Reflex t, MonadHold t m, MonadFix m)
669+ :: forall t m. (Reflex t, MonadHold t m, MonadFix m, HasDisplaySize t m, HasVtyInput t m, ImageWriter t m)
658670 => Event t Int
659671 -- ^ Number of lines to scroll by
660672 -> Behavior t Text
661− -> VtyWidget t m (Behavior t (Int, Int))
673+ -> m (Behavior t (Int, Int))
662674 -- ^ (Current scroll position, total number of lines)
663675scrollableText scrollBy t = do
664676 dw <- displayWidth
@@ -686,11 +698,11 @@ scrollableText scrollBy t = do
686698-- | Renders any behavior whose value can be converted to
687699-- 'String' as text
688700display
689− :: (Reflex t, Monad m, Show a)
701+ :: (Reflex t, Monad m, Show a, HasDisplaySize t m, ImageWriter t m)
690702 => Behavior t a
691− -> VtyWidget t m ()
703+ -> m ()
692704display a = text $ T.pack . show <$> a
693705
694706-- | A widget that draws nothing
695−blank :: Monad m => VtyWidget t m ()
707+blank :: Monad m => m ()
696708blank = return ()