packages feed

tinytools-vty-0.1.0.7: src/Potato/Flow/Vty/PotatoReader.hs

{-# Language UndecidableInstances #-}

module Potato.Flow.Vty.PotatoReader where

import           Relude

import Potato.Flow

import Data.Default
import Control.Monad.Fix (MonadFix)
import Control.Monad.IO.Class (MonadIO)
--import Control.Monad.Morph
import Control.Monad.NodeId
import Control.Monad.Reader (ReaderT, ask, local, runReaderT)
import Control.Monad.Ref
import Control.Monad.Trans (MonadTrans, lift)

import qualified System.FilePath as FP

import qualified Graphics.Vty as V
import Reflex.Host.Class (MonadReflexCreateTrigger)
import           Reflex
import           Reflex.Vty

import Potato.Flow.Vty.Attrs


-- TODO move elsewhere
kTinyToolsFileExtension :: (IsString a) => a
kTinyToolsFileExtension = ".tt"

-- TODO move elsewhere
addTinyToolsFileExtensionIfNecessary :: FP.FilePath -> FP.FilePath
addTinyToolsFileExtensionIfNecessary fp = if FP.takeExtension fp == ""
  then fp <> kTinyToolsFileExtension
  else fp



data PotatoStyle = PotatoStyle {

  -- TODO you can DELETE this now prob
  _potatoStyle_canvasCursor :: V.Attr

  , _potatoStyle_makeCanvasManipulator :: RenderHandleColor -> V.Attr
  , _potatoStyle_normal :: V.Attr

  , _potatoStyle_selected :: V.Attr
  , _potatoStyle_layers_softSelected :: V.Attr -- color of parent(s) when child is selected
  , _potatoStyle_canvas_oob :: V.Attr

  , _potatoStyle_textfield_normal :: V.Attr
  , _potatoStyle_textfield_modifying :: V.Attr
  , _potatoStyle_textfield_cursor :: V.Attr
}


instance Default PotatoStyle where
  def = PotatoStyle {
      _potatoStyle_normal = lg_default

      ,_potatoStyle_canvasCursor = lg_canvas_cursor
      , _potatoStyle_makeCanvasManipulator = lg_make_canvas_cursor
      , _potatoStyle_canvas_oob = lg_canvas_oob

      , _potatoStyle_selected = lg_layer_selected
      , _potatoStyle_layers_softSelected = lg_layer_inheritselect

      , _potatoStyle_textfield_normal = lg_textfield_normal
      , _potatoStyle_textfield_modifying = lg_textfield_modifying
      , _potatoStyle_textfield_cursor = lg_textfield_cursor
    }

data PotatoConfig t = PotatoConfig {
  _potatoConfig_style :: Behavior t PotatoStyle

  -- TODO these need to be per document if you ever want MDI
  , _potatoConfig_appCurrentOpenFile :: Behavior t (Maybe FP.FilePath)
  , _potatoConfig_appCurrentDirectory :: Behavior t FP.FilePath
  , _potatoConfig_appPrintFile :: Behavior t (Maybe FP.FilePath)
  -- TODO
  --, _potatoConfig_unsavedChanges :: Behavior t Bool
}

instance (Reflex t) =>  Default (PotatoConfig t) where
  def = PotatoConfig {
      _potatoConfig_style = constant def
      , _potatoConfig_appCurrentOpenFile = constant Nothing
      , _potatoConfig_appPrintFile = constant Nothing
    }

-- | A class for things that can dynamically gain and lose focus
class (Reflex t, Monad m) => HasPotato t m | m -> t where
  askPotato :: m (PotatoConfig t)

instance (HasInput t m, Monad m) => HasInput t (ReaderT r m)


-- TODO it's better to do this using
-- default input :: (f m' ~ m, Monad m', MonadTrans f, HasInput t m') => ...
-- inside of HasFocus class
instance (Reflex t, HasFocus t m, Monad m) => HasFocus t (ReaderT r m) where
  makeFocus = lift makeFocus
  requestFocus = lift . requestFocus
  isFocused = lift . isFocused
  --subFoci :: m a -> m (a, Dynamic t FocusSet)
  subFoci x = ReaderT $ \r -> subFoci (runReaderT x r)
  focusedId = lift focusedId


instance HasPotato t m => HasPotato t (ReaderT x m)
instance HasPotato t m => HasPotato t (BehaviorWriterT t x m)
instance HasPotato t m => HasPotato t (DynamicWriterT t x m)
instance HasPotato t m => HasPotato t (EventWriterT t x m)
instance HasPotato t m => HasPotato t (NodeIdT m)
instance HasPotato t m => HasPotato t (Input t m)
instance HasPotato t m => HasPotato t (ImageWriter t m)
instance HasPotato t m => HasPotato t (DisplayRegion t m)
instance HasPotato t m => HasPotato t (FocusReader t m)
instance HasPotato t m => HasPotato t (Focus t m) where
  askPotato = lift askPotato
instance HasPotato t m => HasPotato t (Layout t m) where
  askPotato = lift askPotato


-- | A widget that has access to information about whether it is focused
newtype PotatoReader t m a = PotatoReader
  { unPotatoReader :: ReaderT (PotatoConfig t) m a }
  deriving
    ( Functor
    , Applicative
    , Monad
    , MonadFix
    , MonadHold t
    , MonadIO
    , MonadRef
    , MonadSample t
    )

instance (Monad m, Reflex t) => HasPotato t (PotatoReader t m) where
  askPotato = PotatoReader ask

deriving instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (PotatoReader t m)
deriving instance NotReady t m => NotReady t (PotatoReader t m)
deriving instance PerformEvent t m => PerformEvent t (PotatoReader t m)
deriving instance PostBuild t m => PostBuild t (PotatoReader t m)
deriving instance TriggerEvent t m => TriggerEvent t (PotatoReader t m)
deriving instance (HasInput t m, Monad m) => HasInput t (PotatoReader t m)
deriving instance HasFocus t m => HasFocus t (PotatoReader t m)
deriving instance HasFocusReader t m => HasFocusReader t (PotatoReader t m)
deriving instance HasTheme t m => HasTheme t (PotatoReader t m)
deriving instance HasDisplayRegion t m => HasDisplayRegion t (PotatoReader t m)

-- can't seem to include Control.Monad.Morph :(
--instance HasImageWriter t m => HasImageWriter t (PotatoReader t m)
--instance MFunctor (PotatoReader t) where
--  hoist f = PotatoReader . hoist f . unPotatoReader

instance HasImageWriter t m => HasImageWriter t (PotatoReader t m) where
  tellImages = lift . tellImages
  mapImages f = hoistpotato (mapImages f) where
    hoistpotato g = PotatoReader . (hoist g) . unPotatoReader
    hoist nat m = ReaderT (\i -> nat (runReaderT m i))

-- TODO it's better to do this using
-- default input :: (f m' ~ m, Monad m', MonadTrans f, HasInput t m') => ...
-- inside of HasLayout class
instance (Reflex t, HasLayout t m) => HasLayout t (PotatoReader t m) where
  axis a b c = PotatoReader . ReaderT $ \pcfg -> axis a b (runPotatoReader c pcfg)
  region = lift . region
  askOrientation = lift askOrientation

instance (Adjustable t m, MonadFix m, MonadHold t m) => Adjustable t (PotatoReader t m) where
  runWithReplace (PotatoReader a) e = PotatoReader $ runWithReplace a $ fmap unPotatoReader e
  traverseIntMapWithKeyWithAdjust f m e = PotatoReader $ traverseIntMapWithKeyWithAdjust (\k v -> unPotatoReader $ f k v) m e
  traverseDMapWithKeyWithAdjust f m e = PotatoReader $ traverseDMapWithKeyWithAdjust (\k v -> unPotatoReader $ f k v) m e
  traverseDMapWithKeyWithAdjustWithMove f m e = PotatoReader $ traverseDMapWithKeyWithAdjustWithMove (\k v -> unPotatoReader $ f k v) m e

instance MonadTrans (PotatoReader t) where
  lift = PotatoReader . lift


instance MonadNodeId m => MonadNodeId (PotatoReader t m)


-- | Run a 'FocusReader' action with the given focus value
-- TODO flip arg order to match ReaderT oops...
runPotatoReader
  :: (Reflex t, Monad m)
  => PotatoReader t m a
  -> PotatoConfig t
  -> m a
runPotatoReader a b = flip runReaderT b $ unPotatoReader a