@@ -15,6 +15,7 @@ module Reflex.Vty.Widget
15 , HasDisplaySize(..)
15 , HasDisplaySize(..)
16 , HasFocus(..)
16 , HasFocus(..)
17 , HasVtyInput(..)
17 , HasVtyInput(..)
18+ , HasVtyWidgetCtx(..)
18 , Region(..)
19 , Region(..)
19 , regionSize
20 , regionSize
20 , regionBlankImage
21 , regionBlankImage
@@ -57,7 +58,8 @@ module Reflex.Vty.Widget
57import Control.Applicative (liftA2)
58import Control.Applicative (liftA2)
58import Control.Monad.Fix (MonadFix)
59import Control.Monad.Fix (MonadFix)
59import Control.Monad.IO.Class (MonadIO)
60import Control.Monad.IO.Class (MonadIO)
61+import Control.Monad.NodeId
60−import Control.Monad.Reader (ReaderT, ask, asks, runReaderT)
62+import Control.Monad.Reader (ReaderT, ask, asks, local, runReaderT)
61import Control.Monad.Trans (MonadTrans, lift)
63import Control.Monad.Trans (MonadTrans, lift)
62import Data.Default (Default(..))
64import Data.Default (Default(..))
63import Data.Set (Set)
65import Data.Set (Set)
@@ -70,11 +72,8 @@ import qualified Graphics.Vty as V
70import Reflex
72import Reflex
71import Reflex.Class ()
73import Reflex.Class ()
72import Reflex.Host.Class (MonadReflexCreateTrigger)
74import Reflex.Host.Class (MonadReflexCreateTrigger)
74import Reflex.Vty.Host
75import Reflex.Vty.Host
76−import Control.Monad.NodeId
78-- | The context within which a 'VtyWidget' runs
77-- | The context within which a 'VtyWidget' runs
79data VtyWidgetCtx t = VtyWidgetCtx
78data VtyWidgetCtx t = VtyWidgetCtx
80 { _vtyWidgetCtx_width :: Dynamic t Int
79 { _vtyWidgetCtx_width :: Dynamic t Int
@@ -131,6 +130,19 @@ instance MonadNodeId m => MonadNodeId (VtyWidget t m) where
131 getNextNodeId = VtyWidget $ do
130 getNextNodeId = VtyWidget $ do
132 lift $ lift getNextNodeId
131 lift $ lift getNextNodeId
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
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
134-- | Runs a 'VtyWidget' with a given context
146-- | Runs a 'VtyWidget' with a given context
135runVtyWidget
147runVtyWidget
136 :: (Reflex t, MonadNodeId m)
148 :: (Reflex t, MonadNodeId m)
@@ -208,6 +220,8 @@ instance (Reflex t, Monad m) => HasVtyInput t (VtyWidget t m) where
208-- | A class for things that can dynamically gain and lose focus
220-- | A class for things that can dynamically gain and lose focus
209class HasFocus t m | m -> t where
221class HasFocus t m | m -> t where
210 focus :: m (Dynamic t Bool)
222 focus :: m (Dynamic t Bool)
223+ default focus :: (f m' ~ m, Monad m', MonadTrans f, HasFocus t m') => m (Dynamic t Bool)
212instance (Reflex t, Monad m) => HasFocus t (VtyWidget t m) where
226instance (Reflex t, Monad m) => HasFocus t (VtyWidget t m) where
213 focus = VtyWidget . lift $ asks _vtyWidgetCtx_focus
227 focus = VtyWidget . lift $ asks _vtyWidgetCtx_focus
@@ -216,6 +230,8 @@ instance (Reflex t, Monad m) => HasFocus t (VtyWidget t m) where
216class (Reflex t, Monad m) => ImageWriter t m | m -> t where
230class (Reflex t, Monad m) => ImageWriter t m | m -> t where
217 -- | Send images upstream for rendering
231 -- | Send images upstream for rendering
218 tellImages :: Behavior t [Image] -> m ()
232 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
220instance (Monad m, Reflex t) => ImageWriter t (BehaviorWriterT t [Image] m) where
236instance (Monad m, Reflex t) => ImageWriter t (BehaviorWriterT t [Image] m) where
221 tellImages = tellBehavior
237 tellImages = tellBehavior
@@ -255,18 +271,16 @@ withinImage (Region left top width height)
255-- * mouse inputs outside the region are ignored
271-- * mouse inputs outside the region are ignored
256-- * mouse inputs inside the region have their coordinates translated such
272-- * mouse inputs inside the region have their coordinates translated such
257-- that (0,0) is the top-left corner of the region
273-- that (0,0) is the top-left corner of the region
258−-- TODO make this polymorphic
260− :: (Reflex t, Monad m, MonadNodeId m)
275+ :: (Reflex t, Monad m, MonadNodeId m, HasVtyWidgetCtx t m)
261 => Dynamic t Region
276 => Dynamic t Region
262 -> Dynamic t Bool -- ^ Whether the widget should be focused when the parent is.
277 -> Dynamic t Bool -- ^ Whether the widget should be focused when the parent is.
263− -> VtyWidget t m a
278+ -> m a
264− -> VtyWidget t m a
279+ -> m a
265−pane dr foc child = VtyWidget $ do
280+pane dr foc child = do
267 let reg = current dr
281 let reg = current dr
268− let ctx' = VtyWidgetCtx
282+ let subContext ctx = VtyWidgetCtx
269− { _vtyWidgetCtx_input = leftmost -- TODO: think about this leftmost more.
283+ { _vtyWidgetCtx_input = leftmost
270 [ fmapMaybe id $
284 [ fmapMaybe id $
271 attachWith (\(r,f) e -> filterInput r f e)
285 attachWith (\(r,f) e -> filterInput r f e)
272 (liftA2 (,) reg (current foc))
286 (liftA2 (,) reg (current foc))
@@ -276,10 +290,8 @@ pane dr foc child = VtyWidget $ do
276 , _vtyWidgetCtx_width = _region_width <$> dr
290 , _vtyWidgetCtx_width = _region_width <$> dr
277 , _vtyWidgetCtx_height = _region_height <$> dr
291 , _vtyWidgetCtx_height = _region_height <$> dr
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
293+ let imagesWithinRegion images = liftA2 (\is r -> map (withinImage r) is) images reg
281− tellImages images'
294+ localCtx subContext imagesWithinRegion child
284 filterInput :: Region -> Bool -> VtyEvent -> Maybe VtyEvent
296 filterInput :: Region -> Bool -> VtyEvent -> Maybe VtyEvent
285 filterInput (Region l t w h) focused e = case e of
297 filterInput (Region l t w h) focused e = case e of
@@ -424,16 +436,16 @@ keyCombos ks = do
425-- | A plain split of the available space into vertically stacked panes.
437-- | A plain split of the available space into vertically stacked panes.
426-- No visual separator is built in here.
438-- 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)
428 => Dynamic t (Int -> Int)
440 => Dynamic t (Int -> Int)
429 -- ^ Function used to determine size of first pane based on available size
441 -- ^ Function used to determine size of first pane based on available size
430 -> Dynamic t (Bool, Bool)
442 -> Dynamic t (Bool, Bool)
431 -- ^ How to focus the two sub-panes, given that we are focused.
443 -- ^ How to focus the two sub-panes, given that we are focused.
432− -> VtyWidget t m a
444+ -> m a
433 -- ^ Widget for first pane
445 -- ^ Widget for first pane
434− -> VtyWidget t m b
446+ -> m b
435 -- ^ Widget for second pane
447 -- ^ Widget for second pane
436− -> VtyWidget t m (a,b)
448+ -> m (a,b)
437splitV sizeFunD focD wA wB = do
449splitV sizeFunD focD wA wB = do
438 dw <- displayWidth
450 dw <- displayWidth
439 dh <- displayHeight
451 dh <- displayHeight
@@ -445,16 +457,16 @@ splitV sizeFunD focD wA wB = do
446-- | A plain split of the available space into horizontally stacked panes.
458-- | A plain split of the available space into horizontally stacked panes.
447-- No visual separator is built in here.
459-- 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)
449 => Dynamic t (Int -> Int)
461 => Dynamic t (Int -> Int)
450 -- ^ Function used to determine size of first pane based on available size
462 -- ^ Function used to determine size of first pane based on available size
451 -> Dynamic t (Bool, Bool)
463 -> Dynamic t (Bool, Bool)
452 -- ^ How to focus the two sub-panes, given that we are focused.
464 -- ^ How to focus the two sub-panes, given that we are focused.
453− -> VtyWidget t m a
465+ -> m a
454 -- ^ Widget for first pane
466 -- ^ Widget for first pane
455− -> VtyWidget t m b
467+ -> m b
456 -- ^ Widget for second pane
468 -- ^ Widget for second pane
457− -> VtyWidget t m (a,b)
469+ -> m (a,b)
458splitH sizeFunD focD wA wB = do
470splitH sizeFunD focD wA wB = do
459 dw <- displayWidth
471 dw <- displayWidth
460 dh <- displayHeight
472 dh <- displayHeight
@@ -465,11 +477,11 @@ splitH sizeFunD focD wA wB = do
465-- | A split of the available space into two parts with a draggable separator.
477-- | A split of the available space into two parts with a draggable separator.
466-- Starts with half the space allocated to each, and the first pane has focus.
478-- Starts with half the space allocated to each, and the first pane has focus.
467-- Clicking in a pane switches focus.
479-- Clicking in a pane switches focus.
468−splitVDrag :: (Reflex t, MonadFix m, MonadHold t m, MonadNodeId m)
480+splitVDrag :: (Reflex t, MonadFix m, MonadHold t m, MonadNodeId m, HasDisplaySize t m, HasVtyInput t m, HasVtyWidgetCtx t m)
469− => VtyWidget t m ()
481+ => m ()
470− -> VtyWidget t m a
482+ -> m a
471− -> VtyWidget t m b
483+ -> m b
472− -> VtyWidget t m (a,b)
484+ -> m (a,b)
473splitVDrag wS wA wB = do
485splitVDrag wS wA wB = do
474 dh <- displayHeight
486 dh <- displayHeight
475 dw <- displayWidth
487 dw <- displayWidth
@@ -503,7 +515,7 @@ splitVDrag wS wA wB = do
503 return (m, x')
515 return (m, x')
505-- | Fill the background with a particular character.
517-- | 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 ()
507fill c = do
519fill c = do
508 dw <- displayWidth
520 dw <- displayWidth
509 dh <- displayHeight
521 dh <- displayHeight
@@ -511,7 +523,7 @@ fill c = do
511 tellImages fillImg
523 tellImages fillImg
513-- | Fill the background with the bottom
525-- | 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 ()
515hRule boxStyle = fill (_boxStyle_s boxStyle)
527hRule boxStyle = fill (_boxStyle_s boxStyle)
517-- | Defines a set of symbols to use to draw the outlines of boxes
529-- | Defines a set of symbols to use to draw the outlines of boxes
@@ -552,11 +564,11 @@ roundedBoxStyle :: BoxStyle
552roundedBoxStyle = BoxStyle '╭' '─' '╮' '│' '╯' '─' '╰' '│'
564roundedBoxStyle = BoxStyle '╭' '─' '╮' '│' '╯' '─' '╰' '│'
554-- | Draws a titled box in the provided style and a child widget inside of that box
566-- | 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)
556 => Behavior t BoxStyle
568 => Behavior t BoxStyle
558− -> VtyWidget t m a
570+ -> m a
559− -> VtyWidget t m a
571+ -> m a
560boxTitle boxStyle title child = do
572boxTitle boxStyle title child = do
561 dh <- displayHeight
573 dh <- displayHeight
562 dw <- displayWidth
574 dw <- displayWidth
@@ -604,18 +616,18 @@ boxTitle boxStyle title child = do
604 right = mkHalf delta
616 right = mkHalf delta
606-- | A box without a title
618-- | 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)
608 => Behavior t BoxStyle
620 => Behavior t BoxStyle
609− -> VtyWidget t m a
621+ -> m a
610− -> VtyWidget t m a
622+ -> m a
611box boxStyle = boxTitle boxStyle mempty
623box boxStyle = boxTitle boxStyle mempty
613-- | A box whose style is static
625-- | A box whose style is static
615− :: (Reflex t, Monad m, MonadNodeId m)
627+ :: (Monad m, Reflex t, MonadNodeId m, HasDisplaySize t m, ImageWriter t m, HasVtyWidgetCtx t m)
616 => BoxStyle
628 => BoxStyle
617− -> VtyWidget t m a
629+ -> m a
618− -> VtyWidget t m a
630+ -> m a
619boxStatic = box . pure
631boxStatic = box . pure
621-- | Configuration options for displaying "rich" text
633-- | Configuration options for displaying "rich" text
@@ -628,10 +640,10 @@ instance Reflex t => Default (RichTextConfig t) where
629-- | A widget that displays text with custom time-varying attributes
641-- | A widget that displays text with custom time-varying attributes
631− :: (Reflex t, Monad m)
643+ :: (Reflex t, Monad m, HasDisplaySize t m, ImageWriter t m)
632 => RichTextConfig t
644 => RichTextConfig t
633 -> Behavior t Text
645 -> Behavior t Text
634− -> VtyWidget t m ()
646+ -> m ()
635richText cfg t = do
647richText cfg t = do
636 dw <- displayWidth
648 dw <- displayWidth
637 let img = (\w a s -> [wrapText w a s])
649 let img = (\w a s -> [wrapText w a s])
@@ -646,19 +658,19 @@ richText cfg t = do
647-- | Renders text, wrapped to the container width
659-- | Renders text, wrapped to the container width
649− :: (Reflex t, Monad m)
661+ :: (Reflex t, Monad m, HasDisplaySize t m, ImageWriter t m)
650 => Behavior t Text
662 => Behavior t Text
651− -> VtyWidget t m ()
663+ -> m ()
652text = richText def
664text = richText def
654-- | Scrollable text widget. The output pair exposes the current scroll position and total number of lines (including those
666-- | Scrollable text widget. The output pair exposes the current scroll position and total number of lines (including those
655-- that are hidden)
667-- that are hidden)
656scrollableText
668scrollableText
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)
658 => Event t Int
670 => Event t Int
659 -- ^ Number of lines to scroll by
671 -- ^ Number of lines to scroll by
660 -> Behavior t Text
672 -> Behavior t Text
661− -> VtyWidget t m (Behavior t (Int, Int))
673+ -> m (Behavior t (Int, Int))
662 -- ^ (Current scroll position, total number of lines)
674 -- ^ (Current scroll position, total number of lines)
663scrollableText scrollBy t = do
675scrollableText scrollBy t = do
664 dw <- displayWidth
676 dw <- displayWidth
@@ -686,11 +698,11 @@ scrollableText scrollBy t = do
686-- | Renders any behavior whose value can be converted to
698-- | Renders any behavior whose value can be converted to
687-- 'String' as text
699-- 'String' as text
689− :: (Reflex t, Monad m, Show a)
701+ :: (Reflex t, Monad m, Show a, HasDisplaySize t m, ImageWriter t m)
690 => Behavior t a
702 => Behavior t a
691− -> VtyWidget t m ()
703+ -> m ()
692display a = text $ T.pack . show <$> a
704display a = text $ T.pack . show <$> a
694-- | A widget that draws nothing
706-- | A widget that draws nothing
695−blank :: Monad m => VtyWidget t m ()
707+blank :: Monad m => m ()
696blank = return ()
708blank = return ()