terminal-0.2.0.0: src/System/Terminal/MonadInput.hs
module System.Terminal.MonadInput where
import Control.Applicative ((<|>))
import Control.Monad.IO.Class
import Control.Monad.STM
import Data.Bits
import Data.List
import System.Terminal.MonadScreen
-- | This monad describes an environment that maintains a stream of `Event`s
-- and offers out-of-band signaling for interrupts.
--
-- * An interrupt shall occur if the user either presses CTRL+C
-- or any other mechanism the environment designates for that purpose.
-- * Implementations shall maintain an interrupt flag that is set
-- when an interrupt occurs. Computations in this monad shall check and
-- reset this flag regularly. If the execution environment finds this
-- flag still set when trying to signal another interrupt, it shall
-- throw `Control.Exception.AsyncException.UserInterrupt` to the
-- seemingly unresponsive computation.
class (MonadIO m) => MonadInput m where
-- | Wait for the next interrupt or next event transformed by a given mapper.
--
-- * The first mapper parameter is a transaction that succeeds as
-- soon as an interrupt occurs. Executing this transaction
-- resets the interrupt flag. When a second interrupt occurs before
-- the interrupt flag has been reset, the current thread shall
-- receive an `Control.Exception.AsyncException.UserInterrupt`.
-- * The second mapper parameter is a transaction that succeeds as
-- as soon as the next event arrives and removes that event from the
-- stream of events. It shall be executed at most once within a single
-- transaction or the transaction would block until the requested number
-- of events is available.
-- * The mapper may also be used in order to additionally wait on external
-- events (like an `Control.Monad.Async.Async` to complete).
awaitWith :: (STM Interrupt -> STM Event -> STM a) -> m a
-- | Wait for the next event.
--
-- * Returns as soon as an interrupt or a regular event occurs.
-- * This operation resets the interrupt flag, signaling responsiveness to
-- the execution environment.
awaitEvent :: MonadInput m => m (Either Interrupt Event)
awaitEvent = awaitWith$ \intr ev ->
(Left <$> intr) <|> (Right <$> ev)
-- | Check whether an interrupt is pending.
--
-- * This operation resets the interrupt flag, signaling responsiveness
-- to the execution environment.
checkInterrupt :: MonadInput m => m Bool
checkInterrupt = awaitWith $ \intr _ ->
(intr >> pure True) <|> pure False
-- | Events emitted by the terminal.
--
-- * Event decoding might be ambique. In case of ambiqueness all
-- possible meaning shall be emitted. The user is advised to only
-- match on events expected in a certain context and ignore all
-- others.
-- * Key events are highly ambique: I.e. when the user presses @space@
-- it might either be meant as a regular text element (like @a@,@b@,@c@)
-- or the focus is on the key itself (like in "Press space to continue...").
-- * The story is even more complicated: Depending on terminal type and
-- @termios@ settings, certain control codes have special meaning or not
-- (@Ctrl+C@ sometimes means interrupt, but not if the environment supports
-- delivering it as a signal). Don't wait for @Ctrl+C@ when you mean `Interrupt`!
-- Example: The tab key will likely emit @KeyEvent (CharKey 'I') ctrlKey@ and
-- @KeyEvent TabKey mempty@ in most settings.
data Event
= KeyEvent Key Modifiers
| MouseEvent MouseEvent
| WindowEvent WindowEvent
| DeviceEvent DeviceEvent
| OtherEvent String
deriving (Eq,Ord,Show)
-- | Events triggered by key press.
data Key
= CharKey Char
| TabKey
| SpaceKey
| BackspaceKey
| EnterKey
| InsertKey
| DeleteKey
| HomeKey
| BeginKey
| EndKey
| PageUpKey
| PageDownKey
| EscapeKey
| PrintKey
| PauseKey
| ArrowKey Direction
| FunctionKey Int
deriving (Eq,Ord,Show)
-- | Modifier keys.
newtype Modifiers = Modifiers Int
deriving (Eq, Ord, Bits)
instance Semigroup Modifiers where
Modifiers a <> Modifiers b = Modifiers (a .|. b)
instance Monoid Modifiers where
mempty = Modifiers 0
instance Show Modifiers where
show (Modifiers 0) = "mempty"
show (Modifiers 1) = "shiftKey"
show (Modifiers 2) = "ctrlKey"
show (Modifiers 4) = "altKey"
show (Modifiers 8) = "metaKey"
show i = "(" ++ intercalate " <> " ls ++ ")"
where
ls = foldl (\acc x-> if x .&. i /= mempty then show x:acc else acc) []
[metaKey, altKey, ctrlKey, shiftKey]
shiftKey, ctrlKey, altKey, metaKey :: Modifiers
shiftKey = Modifiers 1
ctrlKey = Modifiers 2
altKey = Modifiers 4
metaKey = Modifiers 8
-- | Events triggered by mouse action.
--
-- * Mouse event reporting must be activated before (TODO).
data MouseEvent
= MouseMoved Position
| MouseButtonPressed Position MouseButton
| MouseButtonReleased Position MouseButton
| MouseButtonClicked Position MouseButton
| MouseWheeled Position Direction
deriving (Eq,Ord,Show)
data MouseButton
= LeftMouseButton
| RightMouseButton
| OtherMouseButton
deriving (Eq,Ord,Show)
data Direction
= Upwards
| Downwards
| Leftwards
| Rightwards
deriving (Eq,Ord,Show)
data WindowEvent
= WindowLostFocus
| WindowGainedFocus
| WindowSizeChanged
deriving (Eq, Ord, Show)
data DeviceEvent
= DeviceAttributesReport String
| CursorPositionReport Position
deriving (Eq, Ord, Show)
-- | Interrupt is a special type of event that needs
-- to be treated with priority. It is therefor not
-- included in the regular event stream.
data Interrupt
= Interrupt
deriving (Eq, Ord, Show)