reflex-vty-1.2.0.0: src/Reflex/Vty/Widget/Input/Mouse.hs
-- |
-- Description: Mouse clicks, drags, and scrolls
module Reflex.Vty.Widget.Input.Mouse where
import Control.Monad.Fix
import qualified Graphics.Vty as V
import Reflex
import Reflex.Vty.Widget
-- | The phase of a drag operation. A drag is a sequence of events that begins
-- with a 'DragStart' (the button is pressed), continues with zero or more
-- 'Dragging' events (the mouse moves with the button held), and finishes with
-- a 'DragEnd' (the button is released).
data DragState
= -- | The button was just pressed; this is the first event of the drag.
DragStart
| -- | The mouse is moving with the button held down.
Dragging
| -- | The button was released; this is the last event of the drag.
DragEnd
deriving (Bounded, Enum, Eq, Ord, Read, Show)
-- | Information about a drag operation
data Drag = Drag
{ _drag_from :: (Int, Int)
-- ^ Where the drag began
, _drag_to :: (Int, Int)
-- ^ Where the mouse currently is
, _drag_button :: V.Button
-- ^ Which mouse button is dragging
, _drag_modifiers :: [V.Modifier]
-- ^ What modifiers are held
, _drag_state :: DragState
-- ^ Which phase of the drag this event represents. Use this to tell the
-- start of a drag ('DragStart') apart from its continuation ('Dragging')
-- and its end ('DragEnd').
}
deriving (Eq, Ord, Show)
-- | Pure state transition driving 'drag'. Given the button being tracked, the
-- previous 'Drag' state (if any), and an incoming vty event, produce the next
-- 'Drag' or 'Nothing' if the event is irrelevant. The first relevant mouse-down
-- yields a 'DragStart', subsequent moves yield 'Dragging', and the button
-- release yields 'DragEnd'; a mouse-down after a 'DragEnd' starts a fresh drag.
stepDrag :: V.Button -> Maybe Drag -> V.Event -> Maybe Drag
stepDrag btn = \case
Nothing -> \case
V.EvMouseDown x y btn' mods
| btn == btn' -> Just $ Drag (x, y) (x, y) btn' mods DragStart
| otherwise -> Nothing
_ -> Nothing
Just (Drag from _ _ mods st) -> \case
V.EvMouseDown x y btn' mods'
| st == DragEnd && btn == btn' -> Just $ Drag (x, y) (x, y) btn' mods' DragStart
| btn == btn' -> Just $ Drag from (x, y) btn mods' Dragging
| otherwise -> Nothing -- Ignore other buttons.
V.EvMouseUp x y (Just btn')
| st == DragEnd -> Nothing
| btn == btn' -> Just $ Drag from (x, y) btn mods DragEnd
| otherwise -> Nothing
V.EvMouseUp x y Nothing -- Terminal doesn't specify mouse up button,
-- assume it's the right one.
| st == DragEnd -> Nothing
| otherwise -> Just $ Drag from (x, y) btn mods DragEnd
_ -> Nothing
-- | Converts raw vty mouse drag events into an event stream of 'Drag's
drag
:: (Reflex t, MonadFix m, MonadHold t m, HasInput t m)
=> V.Button
-> m (Event t Drag)
drag btn = do
inp <- input
rec let newDrag = attachWithMaybe (stepDrag btn) (current dragD) inp
dragD <- holdDyn Nothing $ Just <$> newDrag
return (fmapMaybe id $ updated dragD)
-- | Mouse down events for a particular mouse button
mouseDown
:: (Reflex t, Monad m, HasInput t m)
=> V.Button
-> m (Event t MouseDown)
mouseDown btn = do
i <- input
return $ fforMaybe i $ \case
V.EvMouseDown x y btn' mods ->
if btn == btn'
then Just $ MouseDown btn' (x, y) mods
else Nothing
_ -> Nothing
-- | Mouse up events for a particular mouse button
mouseUp
:: (Reflex t, Monad m, HasInput t m)
=> m (Event t MouseUp)
mouseUp = do
i <- input
return $ fforMaybe i $ \case
V.EvMouseUp x y btn' -> Just $ MouseUp btn' (x, y)
_ -> Nothing
-- | Information about a mouse down event
data MouseDown = MouseDown
{ _mouseDown_button :: V.Button
, _mouseDown_coordinates :: (Int, Int)
, _mouseDown_modifiers :: [V.Modifier]
}
deriving (Eq, Ord, Show)
-- | Information about a mouse up event
data MouseUp = MouseUp
{ _mouseUp_button :: Maybe V.Button
, _mouseUp_coordinates :: (Int, Int)
}
deriving (Eq, Ord, Show)
-- | Mouse scroll direction
data ScrollDirection = ScrollDirection_Up | ScrollDirection_Down
deriving (Eq, Ord, Show)
-- | Produce an event that fires when the mouse wheel is scrolled
mouseScroll
:: (Reflex t, Monad m, HasInput t m)
=> m (Event t ScrollDirection)
mouseScroll = do
up <- mouseDown V.BScrollUp
down <- mouseDown V.BScrollDown
return $
leftmost
[ ScrollDirection_Up <$ up
, ScrollDirection_Down <$ down
]
-- | Track the last known mouse position from any mouse event (down or up).
-- Note: true hover (motion without a button held) requires terminal mode
-- 1003, which vty 6.2 does not expose. Position updates only on click/release.
mousePosition
:: (Reflex t, MonadHold t m, HasInput t m)
=> m (Dynamic t (Int, Int))
mousePosition = do
inp <- input
let posEvent = fforMaybe inp $ \case
V.EvMouseDown x y _ _ -> Just (x, y)
V.EvMouseUp x y _ -> Just (x, y)
_ -> Nothing
holdDyn (0, 0) posEvent