@@ -8,6 +8,7 @@
8{-# LANGUAGE FunctionalDependencies #-}
8{-# LANGUAGE FunctionalDependencies #-}
9{-# LANGUAGE GeneralizedNewtypeDeriving #-}
9{-# LANGUAGE GeneralizedNewtypeDeriving #-}
10{-# LANGUAGE ConstraintKinds #-}
10{-# LANGUAGE ConstraintKinds #-}
11+{-# LANGUAGE RecursiveDo #-}
12{-# OPTIONS_GHC -Wall #-}
13{-# OPTIONS_GHC -Wall #-}
@@ -18,16 +19,20 @@ module Reflex.Vty
18 , runVtyApp
19 , runVtyApp
19 , runVtyAppWith
20 , runVtyAppWith
20 , HasDisplaySize(..)
21 , HasDisplaySize(..)
21 , HasFocus(..)
24 , HasFocus(..)
22 , HasVtyInput(..)
25 , HasVtyInput(..)
23 , Region(..)
26 , Region(..)
24− , regionToDisplayRegion
27+ , regionSize
25− , region
28+ , regionBlankImage
27 , runVtyWidget
30 , runVtyWidget
28 , VtyWidgetCtx(..)
31 , VtyWidgetCtx(..)
29 , VtyWidget(..)
32 , VtyWidget(..)
30 , VtyWidgetOut(..)
33 , VtyWidgetOut(..)
32 , modifyImages
37 , modifyImages
33 , mainVtyWidget
38 , mainVtyWidget
@@ -35,6 +40,10 @@ module Reflex.Vty
35 , tellImages
40 , tellImages
36 , tellShutdown
41 , tellShutdown
37 , wrapString
42 , wrapString
40import Control.Applicative
49import Control.Applicative
@@ -258,41 +267,40 @@ data Region = Region
259 deriving (Show, Read, Eq, Ord)
268 deriving (Show, Read, Eq, Ord)
261−regionToDisplayRegion :: Region -> DisplayRegion
270+regionSize :: Region -> (Int, Int)
262−regionToDisplayRegion (Region _ _ w h) = (w, h)
271+regionSize (Region _ _ w h) = (w, h)
264−region :: Region -> (DisplayRegion -> Image) -> [Image]
273+regionBlankImage :: Region -> Image
265−region r@(Region _ _ width height) i =
274+regionBlankImage r@(Region _ _ width height) =
266− [ within r $ V.resize width height $ i (width, height)
267− , within r $ wrapString width V.defAttr $ replicate (width * height) ' '
275+ withinImage r $ wrapString width V.defAttr $ replicate (width * height) ' '
270−within :: Region -> Image -> Image
277+withinImage :: Region -> Image -> Image
271−within (Region left top width height) =
278+withinImage (Region left top width height)
279+ | width < 0 || height < 0 = withinImage (Region left top 0 0)
272− V.translate left top . V.crop width height
280+ | otherwise = V.translate left top . V.crop width height
274wrapString :: Int -> Attr -> String -> Image
282wrapString :: Int -> Attr -> String -> Image
275wrapString maxWidth attrs = V.vertCat . concatMap (fmap (V.string attrs) . fmap (take maxWidth) . takeWhile (not . null) . iterate (drop maxWidth)) . lines
283wrapString maxWidth attrs = V.vertCat . concatMap (fmap (V.string attrs) . fmap (take maxWidth) . takeWhile (not . null) . iterate (drop maxWidth)) . lines
277−box :: Region -> (DisplayRegion -> Image) -> [Image]
285+boxImages :: Region -> [Image]
278−box r@(Region left top width height) i =
286+boxImages r@(Region left top width height) =
279 let hBorder = V.string mempty $ replicate width '-'
287 let hBorder = V.string mempty $ replicate width '-'
280 vBorder = wrapString 1 mempty $ replicate (height - 2) '|'
288 vBorder = wrapString 1 mempty $ replicate (height - 2) '|'
282− { _region_left = left + 1
283− , _region_top = top + 1
284− , _region_width = width - 2
285− , _region_height = height - 2
287− in [ within (r { _region_height = 1 }) hBorder
289+ in [ withinImage (r { _region_height = 1 }) hBorder
288− , within (Region left (top + 1) 1 (height - 2)) vBorder
290+ , withinImage (Region left (top + 1) 1 (height - 2)) vBorder
289− , within (Region (left + width - 1) (top + 1) 1 (height - 2)) vBorder
291+ , withinImage (Region (left + width - 1) (top + 1) 1 (height - 2)) vBorder
290− , within (r { _region_top = top + height - 1 }) hBorder
292+ , withinImage (r { _region_top = top + height - 1 }) hBorder
291− ] ++ region internal i
293+ ]
293−class HasDisplaySize t m | m -> t where
295+class (Reflex t, Monad m) => HasDisplaySize t m | m -> t where
294 displaySize :: m (Dynamic t (Int, Int))
296 displaySize :: m (Dynamic t (Int, Int))
298+displayWidth :: HasDisplaySize t m => m (Dynamic t Int)
299+displayWidth = fmap fst <$> displaySize
301+displayHeight :: HasDisplaySize t m => m (Dynamic t Int)
302+displayHeight = fmap snd <$> displaySize
296class HasVtyInput t m | m -> t where
304class HasVtyInput t m | m -> t where
297 input :: m (Event t VtyEvent)
305 input :: m (Event t VtyEvent)
@@ -324,9 +332,9 @@ instance (Reflex t) => Monoid (VtyWidgetOut t) where
324 mappend wo wo' = wo <> wo'
332 mappend wo wo' = wo <> wo'
326newtype VtyWidget t m a = VtyWidget { unVtyWidget :: WriterT (VtyWidgetOut t) (ReaderT (VtyWidgetCtx t) m) a }
334newtype VtyWidget t m a = VtyWidget { unVtyWidget :: WriterT (VtyWidgetOut t) (ReaderT (VtyWidgetCtx t) m) a }
327− deriving (Functor, Applicative, Monad, MonadSample t, MonadHold t)
335+ deriving (Functor, Applicative, Monad, MonadSample t, MonadHold t, MonadFix)
329−runVtyWidget :: (MonadFix m, Reflex t)
337+runVtyWidget :: (Reflex t)
330 => VtyWidgetCtx t
338 => VtyWidgetCtx t
331 -> VtyWidget t m a
339 -> VtyWidget t m a
332 -> m (a, VtyWidgetOut t)
340 -> m (a, VtyWidgetOut t)
@@ -345,7 +353,7 @@ mainVtyWidget child =
346 ((), wo) <- runVtyWidget ctx child
354 ((), wo) <- runVtyWidget ctx child
347 return $ VtyResult
355 return $ VtyResult
348− { _vtyResult_picture = fmap V.picForLayers (_vtyWidgetOut_images wo)
356+ { _vtyResult_picture = fmap (V.picForLayers . reverse) (_vtyWidgetOut_images wo)
349 , _vtyResult_shutdown = _vtyWidgetOut_shutdown wo
357 , _vtyResult_shutdown = _vtyWidgetOut_shutdown wo
@@ -370,38 +378,133 @@ tellImages imgs = VtyWidget $ tell (mempty { _vtyWidgetOut_images = imgs })
370tellShutdown :: (Reflex t, Monad m) => Event t () -> VtyWidget t m ()
378tellShutdown :: (Reflex t, Monad m) => Event t () -> VtyWidget t m ()
371tellShutdown sd = VtyWidget $ tell (mempty { _vtyWidgetOut_shutdown = sd })
379tellShutdown sd = VtyWidget $ tell (mempty { _vtyWidgetOut_shutdown = sd })
373−pane :: (MonadFix m, Reflex t)
381+data Drag = Drag
382+ { _drag_from :: (Int, Int) -- ^ Where the drag began
383+ , _drag_to :: (Int, Int) -- ^ Where the mouse currently is
384+ , _drag_button :: V.Button -- ^ Which mouse button is dragging
385+ , _drag_modifiers :: [V.Modifier] -- ^ What modifiers are held
386+ , _drag_end :: Bool -- ^ Whether the drag ended (the mouse button was released)
388+ deriving (Eq, Ord, Show)
390+drag :: (Reflex t, MonadFix m, MonadHold t m) => V.Button -> VtyWidget t m (Event t Drag)
393+ let f :: Drag -> V.Event -> Maybe Drag
394+ f (Drag from _ _ mods end) = \case
395+ V.EvMouseDown x y btn' mods'
396+ | end -> Just $ Drag (x,y) (x,y) btn' mods' False
397+ | btn == btn' -> Just $ Drag from (x,y) btn mods' False
398+ | otherwise -> Nothing -- Ignore other buttons.
399+ V.EvMouseUp x y (Just btn')
401+ | btn == btn' -> Just $ Drag from (x,y) btn mods True
402+ | otherwise -> Nothing
403+ V.EvMouseUp x y Nothing -- Terminal doesn't specify mouse up button,
404+ -- assume it's the right one.
406+ | otherwise -> Just $ Drag from (x,y) btn mods True
408+ rec let newDrag = attachWithMaybe f (current dragD) inp
409+ dragD <- holdDyn (Drag (0,0) (0,0) V.BLeft [] True) -- gross, but ok.
411+ return (updated dragD)
413+pane :: (Reflex t, Monad m)
374 => Dynamic t Region -- ^ Region into which we should draw the widget (in coordinates relative to our own)
414 => Dynamic t Region -- ^ Region into which we should draw the widget (in coordinates relative to our own)
375− -> Dynamic t Bool -- ^ Whether the widget is focused
415+ -> Dynamic t Bool -- ^ Whether the widget should be focused when the parent is.
376 -> VtyWidget t m a
416 -> VtyWidget t m a
377 -> VtyWidget t m a
417 -> VtyWidget t m a
378−pane reg foc w = VtyWidget $ do
418+pane reg foc child = VtyWidget $ do
379 ctx <- lift ask
419 ctx <- lift ask
380 let ctx' = VtyWidgetCtx
420 let ctx' = VtyWidgetCtx
421+ { _vtyWidgetCtx_input = leftmost -- TODO: think about this leftmost more.
422+ [ ffor (updated reg) $ \(Region _ _ w h) -> V.EvResize w h
381− { _vtyWidgetCtx_input = fmapMaybe id $
423+ , fmapMaybe id $
382− attachWith (\(r,f) e -> filterInput r f e)
424+ attachWith (\(r,f) e -> filterInput r f e)
383− (liftA2 (,) (current reg) (current foc))
425+ (liftA2 (,) (current reg) (current foc))
384− (_vtyWidgetCtx_input ctx)
426+ (_vtyWidgetCtx_input ctx)
385− , _vtyWidgetCtx_focus = foc
428+ , _vtyWidgetCtx_focus = liftA2 (&&) (_vtyWidgetCtx_focus ctx) foc
386− , _vtyWidgetCtx_size = fmap regionToDisplayRegion reg }
429+ , _vtyWidgetCtx_size = fmap regionSize reg }
387− (result, wo) <- lift . lift $ runVtyWidget ctx' w
430+ (result, wo) <- lift . lift $ runVtyWidget ctx' child
388 let images = _vtyWidgetOut_images wo
431 let images = _vtyWidgetOut_images wo
389− images' = liftA2 (\r is -> map (within r) is) (current reg) images
432+ images' = liftA2 (\r is -> map (withinImage r) is) (current reg) images
390 wo' = wo { _vtyWidgetOut_images = images' }
433 wo' = wo { _vtyWidgetOut_images = images' }
392 return result
435 return result
394filterInput :: Region -> Bool -> VtyEvent -> Maybe VtyEvent
437filterInput :: Region -> Bool -> VtyEvent -> Maybe VtyEvent
395−filterInput r focused e = case e of
438+filterInput (Region l t w h) focused e = case e of
396 V.EvKey _ _ | not focused -> Nothing
439 V.EvKey _ _ | not focused -> Nothing
397 V.EvMouseDown x y btn m -> mouse (\u v -> V.EvMouseDown u v btn m) x y
440 V.EvMouseDown x y btn m -> mouse (\u v -> V.EvMouseDown u v btn m) x y
398 V.EvMouseUp x y btn -> mouse (\u v -> V.EvMouseUp u v btn) x y
441 V.EvMouseUp x y btn -> mouse (\u v -> V.EvMouseUp u v btn) x y
399 _ -> Just e
442 _ -> Just e
401 mouse con x y
444 mouse con x y
402− | or [ x < 0
445+ | or [ x < l
404− , x >= _region_width r
447+ , x >= l + w
405− , y >= _region_height r ] = Nothing
448+ , y >= t + h ] = Nothing
406 | otherwise =
449 | otherwise =
407− Just (con (x + _region_left r) (y + _region_top r))
450+ Just (con (x - l) (y - t))
452+-- | A plain split of the available space into vertically stacked panes.
453+-- No visual separator is built in here.
454+splitV :: (Reflex t, Monad m)
455+ => Dynamic t (Int -> Int)
456+ -- ^ Function used to determine size of first pane based on available size
457+ -> Dynamic t (Bool, Bool)
458+ -- ^ How to focus the two sub-panes, given that we are focused.
460+ -- ^ Widget for first pane
462+ -- ^ Widget for second pane
463+ -> VtyWidget t m (a,b)
464+splitV sizeFunD focD wA wB = do
466+ let regA = (\f (w,h) -> Region 0 0 w (f h)) <$> sizeFunD <*> sz
467+ regB = (\(w,h) (Region _ _ _ hA) -> Region 0 hA w (h - hA)) <$> sz <*> regA
468+ ra <- pane regA (fst <$> focD) wA
469+ rb <- pane regB (snd <$> focD) wB
474+-- | A split of the available space into two parts with a draggable separator.
475+-- Starts with half the space allocated to each, and the first pane has focus.
476+-- Clicking in a pane switches focus.
478+splitVDrag :: (Reflex t, Monad m)
481+ -> VtyWidget t m (a,b)
482+splitVDrag wA wB = do
484+ let splitterPos = ffor sz $ \(_,h) -> h `div` 2
485+ regA = (\(w,_) sp -> Region 0 0 w sp) <$> sz <*> splitterPos
486+ regS = (\(w,h) sp -> Region 0 sp w 1) <$> sz <*> splitterPos
487+ regB = (\(w,h) sp -> Region 0 (sp + 1) w (h - sp - 1)) <$> sz <*> splitterPos
488+ foc <- holdDyn False []
489+ (rA, inpA) <- pane wA
492+fractionSz :: Double -> Int -> Int
493+fractionSz x h = round (fromIntegral h * x)
495+box :: (Monad m, Reflex t)
500+ let boxReg = ffor (current sz) $ \(w,h) -> Region 0 0 w h
501+ innerReg = ffor sz $ \(w,h) -> Region 1 1 (w - 2) (h - 2)
502+ tellImages (fmap boxImages boxReg)
503+ tellImages (fmap (\r -> [regionBlankImage r]) (current innerReg))
504+ pane innerReg (pure True) child
506+string :: (Reflex t, Monad m) => Behavior t String -> VtyWidget t m ()
509+ let img = (\w s -> [wrapString w mempty s]) <$> current dw <*> msg