packages feed

typed-gui-0.1.0.0: src/TypedGUI.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE EmptyCase #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE QualifiedDo #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

module TypedGUI where

import Control.Concurrent (forkIO)
import Control.Concurrent.Chan (
  Chan,
  newChan,
  readChan,
  writeChan,
 )
import Control.Monad (void)
import Control.Monad.State (MonadIO (liftIO), StateT (runStateT))
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import Data.Singletons.Base.TH (SEq, Sing, SomeSing (..))
import Graphics.UI.Threepenny (Element, UI, Window, runUI)
import TypedFsm (
  AnyMsg (..),
  Result (..),
  SomeMsg (..),
  SomeOperate (SomeOperate),
  UnexpectMsg (..),
  UnexpectMsgHandler (IgnoreAndTrace),
  getSomeOperateSing,
  runOperate,
 )

{-

Control status : cs
Data status    : ds

-}

type family RecRenderOutVal (t :: ps)

type RenderSt cs ds =
  forall (t :: cs)
   . Sing t
  -> ds
  -> Chan (AnyMsg cs)
  -> Window
  -> UI cs t (Maybe (Element, IO (RecRenderOutVal t)))

data InternalStRef cs ds = InternalStRef
  { dsRef :: IORef ds
  , csStRef :: IORef (SomeSing cs)
  , anyMsgTChan :: Chan (AnyMsg cs)
  }

newInternalStRef
  :: Sing (t :: cs)
  -> ds
  -> IO (InternalStRef cs ds)
newInternalStRef sst ds = do
  a <- newIORef ds
  b <- newIORef (SomeSing sst)
  c <- newChan
  pure (InternalStRef a b c)

runHandler
  :: (SEq cs)
  => InternalStRef cs ds
  -> Result cs (UnexpectMsg cs) (StateT ds IO) a
  -> IO (Result cs (UnexpectMsg cs) (StateT ds IO) a)
runHandler
  InternalStRef
    { dsRef
    , csStRef
    , anyMsgTChan
    }
  result = case result of
    Finish a -> pure (Finish a)
    e@(ErrorInfo (UnexpectMsg _)) -> pure e
    Cont (SomeOperate cssing op) -> do
      anyMsg <- readChan anyMsgTChan
      ds <- readIORef dsRef
      (newResult, ds') <-
        runStateT
          ( runOperate
              ( IgnoreAndTrace
                  (\_ -> liftIO $ putStrLn "recive unexpect msg!!")
              )
              [anyMsg]
              cssing
              op
          )
          ds
      writeIORef dsRef ds'
      case newResult of
        Cont sop -> do
          let st = getSomeOperateSing sop
          writeIORef csStRef $ SomeSing st
        _ -> pure ()
      pure newResult

sendSomeMsg
  :: Chan (AnyMsg cs)
  -> Sing (t :: cs)
  -> SomeMsg cs t
  -> UI cs t ()
sendSomeMsg tchan sfrom (SomeMsg sto msg) =
  liftIO $ writeChan tchan (AnyMsg sfrom sto msg)

renderUI
  :: forall cs ds
   . InternalStRef cs ds
  -> RenderSt cs ds
  -> Window
  -> IO ()
renderUI
  InternalStRef{dsRef, csStRef, anyMsgTChan}
  renderStFun
  window =
    do
      (SomeSing sst, ds) <- do
        somesst <- readIORef csStRef
        ds <- readIORef dsRef
        pure (somesst, ds)
      void $ runUI window $ renderStFun sst ds anyMsgTChan window

uiSetup
  :: (SEq cs)
  => InternalStRef cs ds
  -> RenderSt cs ds
  -> Result cs (UnexpectMsg cs) (StateT ds IO) ()
  -> Window
  -> UI ps (t :: ps) ()
uiSetup interStRef renderSt sthandler window = do
  let loop result = do
        liftIO $ renderUI interStRef renderSt window
        newResult <- liftIO $ runHandler interStRef result
        loop newResult
  Control.Monad.void $ liftIO $ forkIO $ Control.Monad.void $ loop sthandler