From 157e153e59f076b75a394c8bb6fb74922a5a76f5 Mon Sep 17 00:00:00 2001
From: Jens Nolte <git@queezle.net>
Date: Tue, 21 Sep 2021 01:45:02 +0200
Subject: [PATCH] WIP

---
 src/Q/Interface.hs | 101 +++++++++++++++++++++++----------------------
 1 file changed, 52 insertions(+), 49 deletions(-)

diff --git a/src/Q/Interface.hs b/src/Q/Interface.hs
index 0474145..5945b1d 100644
--- a/src/Q/Interface.hs
+++ b/src/Q/Interface.hs
@@ -11,8 +11,10 @@ import Control.Monad.Catch (displayException)
 import Data.List (intersperse)
 import Data.Maybe (isJust)
 import qualified Graphics.Vty as Vty
+import Q.System
 import Quasar.Async
 import Quasar.Disposable
+import Quasar.Network
 import Quasar.Observable
 import Quasar.Prelude
 import Quasar.ResourceManager
@@ -39,7 +41,7 @@ import System.Random (randomRIO)
 type Key = Unique
 
 
-data UI = UI Layout
+data UIRoot = UIRoot Layout
 
 data Layout where
   SingletonLayout :: Element -> Layout
@@ -61,8 +63,8 @@ data Interactive where
 
 data AppState = AppState {
   lastEvent :: Maybe (BrickEvent Name StateEvent),
-  uiState :: State,
-  notifyChangedStateVar :: TMVar State
+  uiState :: UIState,
+  notifyChangedStateVar :: TMVar UIState
 }
 stepUIState :: MonadResourceManager m => AppState -> m AppState
 stepUIState appState = do
@@ -77,37 +79,39 @@ data Name = MainViewport
   deriving stock (Eq, Ord, Show)
 
 
-class IsNode a where
-  initialState :: MonadResourceManager m => a -> m State
+class IsUI a where
+  initialState :: MonadResourceManager m => a -> m UIState
 
-class IsState a where
-  toState :: a -> State
+class IsState s a | a -> s where
+  toState :: a -> State s
   toState = State
   hasUpdate :: a -> STM Bool
   stepState :: MonadResourceManager m => a -> m a
-  renderState :: a -> Widget Name
+  renderState :: a -> s
 
+type UIState = State (Widget Name)
 
 -- | Quantification wrapper for 'IsState'
-data State = forall a. IsState a => State a
-instance IsState State where
+data State s = forall a. IsState s a => State a
+instance IsState s (State s) where
   toState = id
   hasUpdate (State x) = hasUpdate x
   stepState (State x) = State <$> stepState x
   renderState (State x) = renderState x
 
-packState :: (IsState a, Monad m) => a -> m State
+packState :: (IsState s a, Monad m) => a -> m (State s)
 packState = pure . toState
 
+
 -- | State with a sub-'ResourceManager', i.e. a subtree that can be disposed.
-data SubState = forall a. IsState a => SubState ResourceManager a
-instance IsState SubState where
+data SubState s = forall a. IsState s a => SubState ResourceManager a
+instance IsState s (SubState s) where
   hasUpdate (SubState rm x) = hasUpdate x
   stepState (SubState rm x) = localResourceManager rm $ SubState rm <$> stepState x
   renderState (SubState rm x) = renderState x
-instance IsResourceManager SubState where
+instance IsResourceManager (SubState s) where
   toResourceManager (SubState rm _) = rm
-instance IsDisposable SubState where
+instance IsDisposable (SubState s) where
   toDisposable = toDisposable . toResourceManager
 
 --subState :: (IsState a, MonadResourceManager m) => a -> m State
@@ -116,22 +120,22 @@ instance IsDisposable SubState where
 --  pure $ State rm x
 
 
-instance IsNode UI where
-  initialState (UI layout) = initialState layout
+instance IsUI UIRoot where
+  initialState (UIRoot layout) = initialState layout
 
 -- ** Layout elements
 
-instance IsNode Layout where
+instance IsUI Layout where
   initialState (SingletonLayout element) = initialState element
   initialState (ListLayout elements) = toState . ListState <$> mapM initialState elements
 
-data ListState = ListState [State]
-instance IsState ListState where
+data ListState = ListState [UIState]
+instance IsState (Widget Name) ListState where
   hasUpdate (ListState states) = anyHasUpdates states
   stepState (ListState states) = ListState <$> mapM stepState states
   renderState (ListState states) = vBox $ map renderState states
 
-anyHasUpdates :: [State] -> STM Bool
+anyHasUpdates :: [State s] -> STM Bool
 anyHasUpdates [] = pure False
 anyHasUpdates (x:xs) = hasUpdate x >>= \case
   True -> pure True
@@ -140,22 +144,22 @@ anyHasUpdates (x:xs) = hasUpdate x >>= \case
 
 -- ** Elements
 
-instance IsNode Element where
+instance IsUI Element where
   initialState (ContentElement content) = initialState content
   initialState (InteractiveElement interactive) = initialState interactive
 
 -- ** Content elements
 
-instance IsNode Content where
+instance IsUI Content where
   initialState (Label observable) = do
     state <- newObservableState observable
     packState $ LabelState state
 
 newtype LabelState = LabelState (ObservableState String)
-instance IsState LabelState where
-  hasUpdate (LabelState state) = observableHasUpdate state
-  stepState (LabelState state) = LabelState <$> stepObservableState state
-  renderState (LabelState state) = strWrap $ observableMessageString $ lastObservableState state
+instance IsState (Widget Name) LabelState where
+  hasUpdate (LabelState state) = hasUpdate state
+  stepState (LabelState state) = LabelState <$> stepState state
+  renderState (LabelState state) = strWrap $ observableMessageString $ renderState state
 
 observableMessageString :: ObservableMessage String -> String
 observableMessageString ObservableLoading = "[loading]"
@@ -166,13 +170,13 @@ observableMessageString (ObservableNotAvailable ex) = displayException ex
 
 -- ** Interactive elements
 
-instance IsNode Interactive where
+instance IsUI Interactive where
   initialState (Button content action) = do
     contentState <- initialState content
     packState $ ButtonState contentState action
 
-data ButtonState = ButtonState State (IO ())
-instance IsState ButtonState where
+data ButtonState = ButtonState UIState (IO ())
+instance IsState (Widget Name) ButtonState where
   hasUpdate (ButtonState contentState action) = hasUpdate contentState
   stepState (ButtonState contentState action) = do
     contentState' <- stepState contentState
@@ -184,37 +188,36 @@ instance IsState ButtonState where
 
 data ObservableState a = ObservableState (TVar (Maybe (ObservableMessage a))) (ObservableMessage a)
 
+instance IsState (ObservableMessage a) (ObservableState a) where
+  hasUpdate :: ObservableState a -> STM Bool
+  hasUpdate (ObservableState var _) = isJust <$> readTVar var
+  stepState :: MonadResourceManager m => ObservableState a -> m (ObservableState a)
+  stepState state@(ObservableState var value) = do
+    liftIO $ atomically do
+      readTVar var >>= \case
+        Nothing -> pure state
+        Just next -> do
+          writeTVar var Nothing
+          pure $ ObservableState var next
+  renderState :: ObservableState a -> ObservableMessage a
+  renderState (ObservableState _ last) = last
+
 newObservableState :: MonadResourceManager m => Observable a -> m (ObservableState a)
 newObservableState observable = do
   var <- liftIO $ newTVarIO Nothing
   observe observable (liftIO . atomically . writeTVar var . Just)
   pure (ObservableState var ObservableLoading)
 
-observableHasUpdate :: ObservableState a -> STM Bool
-observableHasUpdate (ObservableState var _) = isJust <$> readTVar var
-
-stepObservableState :: MonadResourceManager m => ObservableState a -> m (ObservableState a)
-stepObservableState state@(ObservableState var value) = do
-  liftIO $ atomically do
-    readTVar var >>= \case
-      Nothing -> pure state
-      Just next -> do
-        writeTVar var Nothing
-        pure $ ObservableState var next
-
-lastObservableState :: ObservableState a -> ObservableMessage a
-lastObservableState (ObservableState _ last) = last
-
 
 
 -- * Example UI
 
-exampleUI :: MonadAsync m => Client SystemProtocol -> m UI
+exampleUI :: MonadAsync m => Client SystemProtocol -> m UIRoot
 exampleUI systemClient = do
   idle <- liftIO $ ContentElement . Label . fmap (("System idle: " <>) . show) <$> idle systemClient
   walkers <- replicateM 10 (ContentElement . Label . fmap show <$> randomWalkObservable)
   randoms <- replicateM 100 (ContentElement . Label <$> randomStringObservable)
-  pure $ UI $ ListLayout $ idle : walkers <> randoms
+  pure $ UIRoot $ ListLayout $ idle : walkers <> randoms
 
 randomStringObservable :: MonadAsync m => m (Observable String)
 randomStringObservable = do
@@ -241,7 +244,7 @@ main = withResourceManagerM $ runUnlimitedAsync do
   withSystemClient \client ->
     runUI =<< exampleUI client
 
-runUI :: MonadResourceManager m => UI -> m ()
+runUI :: MonadResourceManager m => UIRoot -> m ()
 runUI ui = do
   uiState <- initialState ui
   notifyChangedStateVar <- liftIO $ newEmptyTMVarIO
@@ -276,7 +279,7 @@ runUI ui = do
         Vty.setMode output Vty.Hyperlink True
       pure vty
 
-    notifyChangedStateThread :: TMVar State -> BChan StateEvent -> IO ()
+    notifyChangedStateThread :: TMVar (State s) -> BChan StateEvent -> IO ()
     notifyChangedStateThread stateVar eventChan = forever do
       atomically $ takeTMVar stateVar >>= hasUpdate >>= (`unless` retry)
       writeBChan eventChan StepStateEvent
-- 
GitLab