hbro-1.3.0.0: library/Hbro/Keys.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
-- | Key bindings model.
-- Designed to be imported as @qualified@.
module Hbro.Keys (
-- * Modifiers
Modifier(..)
, _Alt
, _Control
, _Shift
, modifier
-- * Mode
, Mode(..)
-- * KeyMap implementation
, KeyStroke
, keyStrokes
, KeyMap
-- * Interface
, KeyPressed(..)
, KeyMapPressed(..)
, bindKeys
) where
-- {{{ Imports
import Hbro.Error
import Hbro.Event
import Hbro.Gdk.KeyVal
import Hbro.Keys.Model ((.|))
import qualified Hbro.Keys.Model as Model
import Hbro.Logger
import Hbro.Prelude hiding (isPrefixOf)
import Control.Concurrent.Async.Lifted
import Control.Monad.Trans.Resource
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Graphics.UI.Gtk.Gdk.EventM as Gdk
import Text.Parsec hiding (many)
import Text.Parsec.Text
-- }}}
-- {{{ Modifiers
instance Describable Gdk.Modifier where
describe Gdk.Control = "C-"
describe Gdk.Shift = "S-"
describe Gdk.Alt = "M-"
describe _ = ""
deriving instance Ord Gdk.Modifier
instance Describable (Modifier, KeyVal) where
describe (m, k) = describe m ++ describe k
newtype Modifier = Modifier (Set Gdk.Modifier) deriving(Eq)
instance Monoid Modifier where
mempty = Modifier mempty
(Modifier a) `mappend` (Modifier b) = Modifier (a `mappend` b)
instance Describable Modifier where
describe (Modifier x) = concatMap describe $ Set.toList x
instance Ord Modifier where compare = comparing describe
_Alt, _Control, _Shift :: Modifier
_Alt = Modifier $ Set.singleton Gdk.Alt
_Control = Modifier $ Set.singleton Gdk.Control
_Shift = Modifier $ Set.singleton Gdk.Shift
modifier :: Parser Modifier
modifier = spaces *> (mconcat <$> many elementModifier)
elementModifier :: Parser Modifier
elementModifier = choice [ string "C-" >> return _Control
, string "M-" >> return _Alt
-- , string "S-" >> return _Shift
]
-- }}}
-- {{{ Key mode (à la vi)
data Mode = Normal | Insert deriving(Eq, Ord)
instance Default Mode where def = Normal
-- }}}
-- {{{ KeyMap implementation
type KeyStroke = Model.KeyStroke Modifier KeyVal
instance Describable KeyStroke where
describe (Model.KeyStroke m k) = describe m ++ describe k
keyStrokes :: Parser KeyStroke
keyStrokes = do
spaces
m <- fromMaybe (Modifier Set.empty) <$> optionMaybe modifier
k <- keyVal
return $ m .| k
-- type Binding m = Model.Binding Hbro.Keys.Stroke (m ())
type KeyMap m = Model.KeyMap KeyStroke (m ())
-- }}}
data KeyPressed = KeyPressed deriving(Show)
instance Event KeyPressed where
type Input KeyPressed = KeyStroke
describeInput _ stroke = Just $ "Key pressed: " ++ describe stroke
data KeyMapPressed = KeyMapPressed deriving(Show)
instance Event KeyMapPressed where
type Input KeyMapPressed = ([KeyStroke], Bool)
describeInput _ (strokes, _bound) = Just . unwords $ "Key pressed: " : (describe <$> strokes)
bindKeys :: (ControlIO m, MonadLogger m, MonadError Text m, MonadResource m) => Signal KeyPressed -> Signal KeyMapPressed -> KeyMap m -> m ReleaseKey
bindKeys input output keyMap = addRecursiveHandler input empty $ \previousStrokes newStroke -> do
let k = Map.keys keyMap
strokes = previousStrokes |: newStroke
strokesL = NonEmpty.toList strokes
found = Map.lookup strokes keyMap
reset = isJust found || all (not . NonEmpty.isPrefixOf strokesL) k
debug $ "Accumulated: " ++ unwords (map describe strokesL)
emit output (strokesL, isJust found)
async . logErrors $ fromMaybe doNothing found
return $ empty <| reset |> strokesL