@@ -2,6 +2,7 @@
2Module: Reflex.Vty.Widget
2Module: Reflex.Vty.Widget
3Description: Basic set of widgets and building blocks for reflex-vty applications
3Description: Basic set of widgets and building blocks for reflex-vty applications
5+{-# LANGUAGE DefaultSignatures #-}
5{-# LANGUAGE DeriveFunctor #-}
6{-# LANGUAGE DeriveFunctor #-}
6{-# LANGUAGE FlexibleContexts #-}
7{-# LANGUAGE FlexibleContexts #-}
7{-# LANGUAGE FlexibleInstances #-}
8{-# LANGUAGE FlexibleInstances #-}
@@ -17,6 +18,7 @@ Description: Basic set of widgets and building blocks for reflex-vty application
17{-# LANGUAGE TupleSections #-}
18{-# LANGUAGE TupleSections #-}
18{-# LANGUAGE TypeFamilies #-}
19{-# LANGUAGE TypeFamilies #-}
19{-# LANGUAGE UndecidableInstances #-}
20{-# LANGUAGE UndecidableInstances #-}
20module Reflex.Vty.Widget
22module Reflex.Vty.Widget
21 ( VtyWidgetCtx(..)
23 ( VtyWidgetCtx(..)
22 , VtyWidget(..)
24 , VtyWidget(..)
@@ -25,14 +27,6 @@ module Reflex.Vty.Widget
25 , runVtyWidget
27 , runVtyWidget
26 , mainWidget
28 , mainWidget
27 , mainWidgetWithHandle
29 , mainWidgetWithHandle
36 , HasDisplaySize(..)
30 , HasDisplaySize(..)
37 , HasFocus(..)
31 , HasFocus(..)
38 , HasVtyInput(..)
32 , HasVtyInput(..)
@@ -68,15 +62,30 @@ module Reflex.Vty.Widget
70 , keyCombos
64 , keyCombos
73import Control.Applicative (liftA2)
82import Control.Applicative (liftA2)
74−import Control.Monad (ap)
83+import Control.Monad.TimeMachine
75import Control.Monad.Fix (MonadFix)
84import Control.Monad.Fix (MonadFix)
85+import Control.Monad.Trans.Class
76import Control.Monad.Trans (lift)
86import Control.Monad.Trans (lift)
77−import Control.Monad.Trans.Reader (ReaderT, runReaderT, asks, ask)
87+import Control.Monad.Reader
78import Data.Default (Default(..))
88import Data.Default (Default(..))
79−import Data.Monoid (Sum(..))
80import Data.Set (Set)
89import Data.Set (Set)
81import qualified Data.Set as Set
90import qualified Data.Set as Set
82import Data.Text (Text)
91import Data.Text (Text)
@@ -85,6 +94,7 @@ import qualified Data.Text.Zipper as TZ
85import Graphics.Vty (Image)
94import Graphics.Vty (Image)
86import qualified Graphics.Vty as V
95import qualified Graphics.Vty as V
87import Reflex
96import Reflex
97+import Reflex.Class ()
88import Reflex.Class.Orphans ()
98import Reflex.Class.Orphans ()
89import Reflex.NotReady.Class
99import Reflex.NotReady.Class
90import Reflex.NotReady.Class.Orphans ()
100import Reflex.NotReady.Class.Orphans ()
@@ -125,7 +135,7 @@ instance (Adjustable t m, MonadHold t m, Reflex t) => Adjustable t (VtyWidget t
125newtype VtyWidget t m a = VtyWidget
135newtype VtyWidget t m a = VtyWidget
126 { unVtyWidget :: BehaviorWriterT t [Image] (ReaderT (VtyWidgetCtx t) m) a
136 { unVtyWidget :: BehaviorWriterT t [Image] (ReaderT (VtyWidgetCtx t) m) a
128− deriving (Functor, Applicative, Monad, MonadSample t, MonadHold t, MonadFix, NotReady t, ImageWriter t)
138+ deriving (Functor, Applicative, Monad, MonadSample t, MonadHold t, MonadFix, NotReady t, ImageWriter t, PostBuild t)
130-- | Runs a 'VtyWidget' with a given context
140-- | Runs a 'VtyWidget' with a given context
131runVtyWidget
141runVtyWidget
@@ -174,13 +184,23 @@ mainWidget child = do
174class (Reflex t, Monad m) => HasDisplaySize t m | m -> t where
184class (Reflex t, Monad m) => HasDisplaySize t m | m -> t where
175 -- | Retrieve the display width (columns)
185 -- | Retrieve the display width (columns)
176 displayWidth :: m (Dynamic t Int)
186 displayWidth :: m (Dynamic t Int)
187+ default displayWidth :: (f m' ~ m, MonadTrans f, HasDisplaySize t m') => m (Dynamic t Int)
188+ displayWidth = lift displayWidth
177 -- | Retrieve the display height (rows)
189 -- | Retrieve the display height (rows)
178 displayHeight :: m (Dynamic t Int)
190 displayHeight :: m (Dynamic t Int)
179−
191+ default displayHeight :: (f m' ~ m, MonadTrans f, HasDisplaySize t m') => m (Dynamic t Int)
192+ displayHeight = lift displayHeight
180instance (Reflex t, Monad m) => HasDisplaySize t (VtyWidget t m) where
193instance (Reflex t, Monad m) => HasDisplaySize t (VtyWidget t m) where
181 displayWidth = VtyWidget . lift $ asks _vtyWidgetCtx_width
194 displayWidth = VtyWidget . lift $ asks _vtyWidgetCtx_width
182 displayHeight = VtyWidget . lift $ asks _vtyWidgetCtx_height
195 displayHeight = VtyWidget . lift $ asks _vtyWidgetCtx_height
197+instance HasDisplaySize t m => HasDisplaySize t (ReaderT x m)
198+instance HasDisplaySize t m => HasDisplaySize t (BehaviorWriterT t x m)
199+instance HasDisplaySize t m => HasDisplaySize t (DynamicWriterT t x m)
200+instance HasDisplaySize t m => HasDisplaySize t (EventWriterT t x m)
202+instance (MonadFix m, HasDisplaySize t m) => HasDisplaySize t (TimeMachineT back fwd m)
184-- | A class for things that can receive vty events as input
204-- | A class for things that can receive vty events as input
185class HasVtyInput t m | m -> t where
205class HasVtyInput t m | m -> t where
186 input :: m (Event t VtyEvent)
206 input :: m (Event t VtyEvent)
@@ -290,8 +310,6 @@ pane dr foc child = VtyWidget $ do
290 | otherwise =
310 | otherwise =
291 Just (con (x - l) (y - t))
311 Just (con (x - l) (y - t))
295-- | Information about a drag operation
313-- | Information about a drag operation
296data Drag = Drag
314data Drag = Drag
297 { _drag_from :: (Int, Int) -- ^ Where the drag began
315 { _drag_from :: (Int, Int) -- ^ Where the drag began
@@ -371,14 +389,18 @@ data MouseUp = MouseUp
372 deriving (Eq, Ord, Show)
390 deriving (Eq, Ord, Show)
392+-- | Type synonym for a key and modifier combination
374type KeyCombo = (V.Key, [V.Modifier])
393type KeyCombo = (V.Key, [V.Modifier])
395+-- | Emits an event that fires on a particular key press (without modifiers)
376key :: (Monad m, Reflex t) => V.Key -> VtyWidget t m (Event t KeyCombo)
396key :: (Monad m, Reflex t) => V.Key -> VtyWidget t m (Event t KeyCombo)
377key = keyCombos . Set.singleton . (,[])
397key = keyCombos . Set.singleton . (,[])
399+-- | Emits an event that fires on particular key presses (without modifiers)
379keys :: (Monad m, Reflex t) => [V.Key] -> VtyWidget t m (Event t KeyCombo)
400keys :: (Monad m, Reflex t) => [V.Key] -> VtyWidget t m (Event t KeyCombo)
380keys = keyCombos . Set.fromList . fmap (,[])
401keys = keyCombos . Set.fromList . fmap (,[])
403+-- | Emit an event that fires whenever any of the provided key combinations occur
383 :: (Reflex t, Monad m)
405 :: (Reflex t, Monad m)
384 => Set KeyCombo
406 => Set KeyCombo
@@ -600,65 +622,169 @@ display
600 -> VtyWidget t m ()
622 -> VtyWidget t m ()
601display a = text $ T.pack . show <$> a
623display a = text $ T.pack . show <$> a
625+-- | The main-axis orientation of a 'StackWidget' widget
603data Direction = Direction_Column
626data Direction = Direction_Column
604 | Direction_Row
627 | Direction_Row
605 deriving (Show, Read, Eq, Ord)
628 deriving (Show, Read, Eq, Ord)
607−newtype StackWidget t m a = StackWidget { unStackWidget :: ReaderT Direction (DynamicWriterT t (Sum Int) (VtyWidget t m)) a }
630+-- | Convenience type synonym for the 'TimeMachine' used to manage 'StackWidget'
608− deriving (Functor, MonadSample t, MonadHold t, MonadFix, NotReady t)
631+-- focus and offset states. The first parameter is the "focus previous" event and
609−
632+-- the second parameter is a tuple of "focus next" and the total size.
610−instance (Reflex t, MonadFix m) => Applicative (StackWidget t m) where
633+type StackT t = TimeMachineT (Event t ()) (Event t (), Dynamic t Int)
635+-- | A 'VtyWidget' that can be stacked automatically
636+newtype StackWidget t m a
641+ (ReaderT (StackConfig, Event t ())
655+-- | Retrieve the 'Direction'/orientation of the current 'StackWidget'
656+askDirection :: MonadFix m => StackWidget t m Direction
657+askDirection = StackWidget $ asks $ _stackConfig_direction . fst
659+-- | Retrieve the offset reported by the previous 'tile'
660+askOffset :: Monad m => StackWidget t m (Dynamic t Int)
661+askOffset = StackWidget $ snd <$> recvFromPast
663+-- | Compute the remaining main-axis space available
664+askRemainingSpace :: (Reflex t, MonadFix m) => StackWidget t m (Dynamic t Int)
665+askRemainingSpace = do
669+ Direction_Column -> displayHeight
670+ Direction_Row -> displayWidth
611− pure = return
671+ return $ sz - offset
613−
673+-- | A 'VtyWidget' and its actual main-axis size
674+type SizedWidget t m a = VtyWidget t m (Dynamic t Int, a)
676+-- | Runs a 'StackWidget' with the provided configuration and initial offset
614runStackWidget
677runStackWidget
615− :: (Reflex t, MonadFix m)
678+ :: (MonadFix m, PostBuild t m)
616− => Direction
679+ => StackConfig
617 -> Dynamic t Int
680 -> Dynamic t Int
618 -> StackWidget t m a
681 -> StackWidget t m a
619− -> VtyWidget t m (Dynamic t (Sum Int), a)
682+ -> SizedWidget t m a
620−runStackWidget dir offset x = do
683+runStackWidget cfg offset x = do
621− mkRegion <- case dir of
684+ pb <- getPostBuild
685+ rec ((back, (fwd, sz), a), unfocus) <- runReaderT (runEventWriterT (runTimeMachineT (unStackWidget x) back (fwd <> pb, offset))) (cfg, unfocus)
688+-- | Turns a 'SizedWidget' into a 'StackWidget'. Wires up the focus-switching
689+-- and offset propagation.
691+ :: (Reflex t, MonadHold t m, MonadFix m)
692+ => SizedWidget t m a
693+ -> StackWidget t m a
694+tile s = StackWidget $ do
695+ (focusTriggerP, offset) <- recvFromPast
696+ rec sendToPast focusBackward
697+ (cfg, unfocus) <- ask
698+ let bk = Set.difference (_stackConfig_focusPrevKey cfg) fk
699+ fk = _stackConfig_focusNextKey cfg
700+ focused <- holdDyn False $ leftmost
701+ [ True <$ (focusTriggerF <> focusTriggerP)
703+ , False <$ unfocus -- NB: unfocus must come after events that focus the current widget
709+ reg <- case _stackConfig_direction cfg of
622− Direction_Column -> (\w sz -> DynRegion 0 offset w sz) <$> displayWidth
710+ Direction_Column -> do
712+ return $ DynRegion 0 offset w sz
623− Direction_Row -> (\h sz -> DynRegion offset 0 sz h) <$> displayHeight
714+ h <- displayHeight
624− rec (a, h) <- pane (mkRegion h') (pure True) $ runDynamicWriterT $ runReaderT (unStackWidget x) dir
715+ return $ DynRegion offset 0 sz h
625− let h' = getSum <$> h
716+ (click, keypress, (sz, r)) <- lift $ lift $ lift $ pane reg focused $ do
717+ e <- mouseDown V.BLeft
718+ ek <- keyCombos $ Set.union bk fk
626− return (h, a)
720+ return (e, ek, v)
627−
721+ let focusForward = () <$ ffilter (flip Set.member fk) keypress
628−instance (MonadFix m, Reflex t) => Monad (StackWidget t m) where
722+ focusBackward = () <$ ffilter (flip Set.member bk) keypress
629− w >>= g = StackWidget $ do
723+ focusTriggerF <- recvFromFuture
630− dir <- ask
724+ sendToFuture (focusForward, sz + offset)
631− (sz, r) <- lift $ lift $ runStackWidget dir 0 w
725+ return r
633− (sz', r') <- lift $ lift $ runStackWidget dir (getSum <$> sz) (g r)
727+-- | Configuration for a 'StackWidget'
634− tellDyn sz'
728+data StackConfig = StackConfig
635− return r'
729+ { _stackConfig_direction :: Direction
636− return x = StackWidget $ return x
730+ , _stackConfig_focusNextKey :: Set KeyCombo
731+ , _stackConfig_focusPrevKey :: Set KeyCombo
734+instance Default StackConfig where
736+ { _stackConfig_direction = Direction_Column
737+ , _stackConfig_focusNextKey = Set.singleton (V.KChar '\t', [])
738+ , _stackConfig_focusPrevKey = Set.singleton (V.KBackTab, [])
741+-- | Runs a 'StackWidget' with the provided configuration
639− :: (Reflex t, MonadFix m)
743+ :: (MonadFix m, PostBuild t m)
640− => Direction
744+ => StackConfig
641 -> StackWidget t m a
745 -> StackWidget t m a
642 -> VtyWidget t m a
746 -> VtyWidget t m a
643−stack dir w = fmap snd $ runStackWidget dir 0 w
747+stack cfg w = fmap snd $ runStackWidget cfg 0 w
749+-- | Runs a vertically-oriented 'StackWidget' (i.e., a column)
646− :: (Reflex t, MonadFix m)
751+ :: (MonadFix m, PostBuild t m)
647 => StackWidget t m a
752 => StackWidget t m a
648 -> VtyWidget t m a
753 -> VtyWidget t m a
649−col = stack Direction_Column
754+col = stack def
756+-- | Runs a horizontally-oriented 'StackWidget' (i.e., a row)
652− :: (Reflex t, MonadFix m)
758+ :: (MonadFix m, PostBuild t m)
653 => StackWidget t m a
759 => StackWidget t m a
654 -> VtyWidget t m a
760 -> VtyWidget t m a
655−row = stack Direction_Row
761+row = stack $ def { _stackConfig_direction = Direction_Row }
657−sized
763+-- | A 'tile' of a fixed size
658− :: (Reflex t, MonadFix m)
765+ :: (Reflex t, MonadFix m, MonadHold t m)
659 => Dynamic t Int
766 => Dynamic t Int
660 -> VtyWidget t m a
767 -> VtyWidget t m a
661 -> StackWidget t m a
768 -> StackWidget t m a
662−sized sz w = StackWidget $ do
769+fixed sz w = tile $ (sz,) <$> w
663− tellDyn $ Sum <$> sz
770+
664− lift $ lift w
771+-- | A 'tile' that takes up the given fraction of the remaining
774+ :: (Reflex t, MonadFix m, MonadHold t m)
775+ => Dynamic t Rational
777+ -> StackWidget t m a
779+ s <- askRemainingSpace
780+ let sz = fmap round $ n * fmap fromIntegral s
783+-- | A 'tile' that takes up all of the remaining main-axis space
785+ :: (Reflex t, MonadHold t m, MonadFix m)
787+ -> StackWidget t m a
789+ s <- askRemainingSpace