packages feed

zwirn-0.2.3.1: app/zwirnmill/UI/Event.hs

{- HLINT ignore "Use tuple-section" -}
module UI.Event where

import Brick hiding (on, str)
import Brick.Keybindings
import Control.Monad (unless, void)
import Control.Monad.RWS
import Data.List (intercalate, uncons)
import qualified Data.Map as Map
import qualified Data.Text as T
import Data.Time
import Docs.Event (handleDocEvent)
import Editor.Core (OutputType (..))
import Editor.Event (editorToAppEvent)
import EnvBrowser.Event (handleEnvBrowserEvent, updateEnvBrowser)
import qualified Graphics.Vty as V
import Keyboard.Event (handleKeyboardEvent)
import Keymap
import SampleBrowser.Event (handleSampleBrowserEvent)
import Session (quitEvent)
import Sound.Doux.Engine (activeVoices, douxVersion, load, memory, peakVoices, scheduleDepth)
import UI.Core
import UI.Window
import Zwirn.Doux.Types (Stream (..))
import Zwirn.Doux.UI (streamGetBPM, streamGetCPS, streamGetCycle)
import Zwirn.Language.Compiler (compilerInterpreterBasic, runCIEnv)

handleEvent :: BrickEvent Name AppEvent -> EventM Name AppState ()
handleEvent ev = do
  handledd <- handleGlobalEvent ev
  unless handledd $ do
    active <- gets getActiveWindow
    case active of
      Just (name, win) -> do
        win' <- handleWindowEvent (name, win) ev
        wm <- gets asWindows
        modify $ \as -> as {asWindows = Map.insert name win' wm}
      Nothing -> return ()

getActiveWindow :: AppState -> Maybe (Name, Window)
getActiveWindow as = (\val -> (asActiveWindow as, val)) <$> Map.lookup (asActiveWindow as) (asWindows as)

handleWindowEvent :: (Name, Window) -> BrickEvent Name AppEvent -> EventM Name AppState Window
handleWindowEvent (name, Window p s (EditorContent es) h l) ev = editorToAppEvent name es ev >>= \es' -> return $ Window p s (EditorContent es') h l
handleWindowEvent (_, Window p s (DocumentationContent d c m) h l) ev = handleDocEvent d c m ev >>= \(d', f) -> return (Window p s (DocumentationContent d' f m) h l)
handleWindowEvent (_, Window p s (SampleBrowserContent sb) h l) ev = handleSampleBrowserEvent sb ev >>= \sb' -> return (Window p s (SampleBrowserContent sb') h l)
handleWindowEvent (_, Window p s (EnvBrowserContent sb) h l) ev = handleEnvBrowserEvent sb ev >>= \sb' -> return (Window p s (EnvBrowserContent sb') h l)
handleWindowEvent (_, Window p s (KeyboardContent k) h l) ev = handleKeyboardEvent k ev >>= \k' -> return (Window p s (KeyboardContent k') h l)
handleWindowEvent (_, win) _ = return win

handleGlobalEvent :: BrickEvent Name AppEvent -> EventM Name AppState Bool
handleGlobalEvent (VtyEvent (V.EvKey key mods)) = gets asKeyConfig >>= \conf -> handleKey (globalKeyDispatcher $ kGlobal conf) key mods
handleGlobalEvent (AppEvent UpdateStatus) = handled updateStatus
handleGlobalEvent (AppEvent UpdateEnv) = handled updateEnvBrowser
handleGlobalEvent (AppEvent (AnimationUpdate a)) = handled a
handleGlobalEvent (AppEvent (UpdateOutput o)) = handled $ updateOutput o
handleGlobalEvent (MouseUp _ (Just V.BLeft) _) = handled $ modify $ \as -> as {asDragging = Nothing}
handleGlobalEvent (MouseDown name V.BRight _ (Location pos)) = handled $ rightClickWindow name pos
handleGlobalEvent (MouseDown OptionWin V.BLeft _ (Location pos)) = handled $ clickOptionWindow pos
handleGlobalEvent (MouseDown name V.BLeft _ (Location (mx, my))) = handled $ do
  closeOptions
  drag <- gets asDragging
  case drag of
    Just (pos, dragged, action) -> do
      ab <- getAbsolutePos (name, dragged) (mx, my)
      dragWindow dragged ab pos action
    Nothing -> clickWindow name (mx, my) >> modify (\as -> as {asActiveWindow = parentWindow name})
handleGlobalEvent _ = return False

handled :: (Monad m) => m a -> m Bool
handled x = x >> return True

hushEvent :: EventM Name AppState ()
hushEvent = do
  env <- gets asEnvironment
  void $ liftIO $ runCIEnv env (compilerInterpreterBasic "hush")
  updateOutput (OutputInfo, "Hush.")

panicEvent :: EventM Name AppState ()
panicEvent = do
  env <- gets asEnvironment
  void $ liftIO $ runCIEnv env (compilerInterpreterBasic "panic")
  updateOutput (OutputInfo, "Panic.")

customActionEvent :: T.Text -> EventM Name AppState ()
customActionEvent exe = do
  env <- gets asEnvironment
  x <- liftIO $ runCIEnv env (compilerInterpreterBasic exe)
  case x of
    Right _ -> updateOutput (OutputInfo, "Ran custom action: " <> T.unpack exe)
    Left _ -> updateOutput (OutputError, "Failed to run custom action.")

updateStatus :: EventM Name AppState ()
updateStatus = do
  wm <- gets asWindows
  case Map.lookup Status wm of
    Nothing -> return ()
    Just (Window _ _ _ True _) -> return ()
    Just (Window _ _ _ False _) -> do
      str <- gets asStream
      mx <- gets asMaxVoices
      ver <- liftIO douxVersion
      ld <- liftIO $ realToFrac <$> load (sDoux str)
      voices <- liftIO $ activeVoices (sDoux str)
      peak <- liftIO $ peakVoices (sDoux str)
      schedule <- liftIO $ scheduleDepth (sDoux str)
      samples <- liftIO $ realToFrac <$> memory (sDoux str)
      bpm <- liftIO $ streamGetBPM str
      cps <- liftIO $ streamGetCPS str
      cyc <- liftIO $ streamGetCycle str
      let f (Just (Window p s _ False l)) = Just $ Window p s (StatusContent ver bpm cps cyc ld (voices, mx) peak schedule samples) False l
          f _ = Nothing
          wm' = Map.alter f Status wm
      modify $ \as -> as {asWindows = wm'}

updateOutput :: (OutputType, String) -> EventM Name AppState ()
updateOutput (t, cont) = do
  wm <- gets asWindows
  case Map.lookup Output wm of
    Just (Window _ _ (OutputContent os) False _) -> do
      time <- liftIO getZonedTime
      let f Nothing = Nothing
          f (Just (Window p s _ h l)) = Just $ Window p s (OutputContent $ (t, prettyShowContent time cont) : take 20 os) h l
          wm' = Map.alter f Output wm
      modify $ \as -> as {asWindows = wm'}
    _ -> return ()

showTime :: ZonedTime -> String
showTime = take 8 . show . localTimeOfDay . zonedTimeToLocalTime

prettyShowContent :: ZonedTime -> String -> String
prettyShowContent time cont = case uncons $ lines cont of
  Just (x, xs) -> showTime time <> " - " <> intercalate "\n" (x : map (\y -> replicate 11 ' ' <> y) xs)
  Nothing -> showTime time <> " - " <> cont

globalEventHandlerConstructor :: GlobalAction -> (T.Text, EventM Name AppState ())
globalEventHandlerConstructor Quit = ("exit ziwnrmill", quitEvent)
globalEventHandlerConstructor Hush = ("stop playing sound", hushEvent)
globalEventHandlerConstructor Panic = ("stop playing sound immediately", panicEvent)
globalEventHandlerConstructor (CustomAction x) = ("execute custom action", customActionEvent x)

globalEventHandlers :: KeyConfig GlobalAction -> [KeyEventHandler GlobalAction (EventM Name AppState)]
globalEventHandlers conf = map (\a -> toEventHandler a (globalEventHandlerConstructor a)) as
  where
    toEventHandler a (t, ev) = onEvent a t ev
    as = map snd $ keyEventsList $ keyConfigEvents conf

globalKeyDispatcher :: KeyConfig GlobalAction -> KeyDispatcher GlobalAction (EventM Name AppState)
globalKeyDispatcher conf = case keyDispatcher conf (globalEventHandlers conf) of
  Left err -> error $ show (map fst err)
  Right dis -> dis