minilight 0.3.0 → 0.4.0
raw patch · 22 files changed
+945/−657 lines, 22 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Data.Component.AnimationLayer: instance MiniLight.Component.Types.ComponentUnit Data.Component.AnimationLayer.AnimationLayer
- Data.Component.Button: instance MiniLight.Component.Types.ComponentUnit Data.Component.Button.Button
- Data.Component.Layer: instance MiniLight.Component.Types.ComponentUnit Data.Component.Layer.Layer
- Data.Component.MessageEngine: [color] :: Config -> V4 Word8
- Data.Component.MessageEngine: [fontConf] :: Config -> FontDescriptor
- Data.Component.MessageEngine: [fontSize] :: Config -> Int
- Data.Component.MessageEngine: [rendered] :: MessageEngine -> Int
- Data.Component.MessageEngine: instance MiniLight.Component.Types.ComponentUnit Data.Component.MessageEngine.MessageEngine
- Data.Component.MessageLayer: instance MiniLight.Component.Types.ComponentUnit Data.Component.MessageLayer.MessageLayer
- Data.Component.Selection: instance MiniLight.Component.Types.ComponentUnit Data.Component.Selection.Selection
- MiniLight: instance MiniLight.Component.Loader.HasLoaderEnv MiniLight.LoopState
- MiniLight: instance MiniLight.Component.Loader.HasLoaderEnv env' => MiniLight.Component.Loader.HasLoaderEnv (env, env')
- MiniLight: instance MiniLight.Component.Types.HasComponentEnv (Data.Text.Internal.Text, env)
- MiniLight: instance MiniLight.Light.HasLightEnv env' => MiniLight.Light.HasLightEnv (env, env')
- MiniLight: instance MiniLight.Light.HasLoopEnv env' => MiniLight.Light.HasLoopEnv (env, env')
- MiniLight.Component.Internal.Resolver: Constant :: Value -> Expr
- MiniLight.Component.Internal.Resolver: Context :: Vector (Either Int Text) -> Object -> Value -> Context
- MiniLight.Component.Internal.Resolver: None :: Expr
- MiniLight.Component.Internal.Resolver: Op :: Text -> Expr -> Expr -> Expr
- MiniLight.Component.Internal.Resolver: Ref :: Text -> Expr
- MiniLight.Component.Internal.Resolver: Var :: Text -> Expr
- MiniLight.Component.Internal.Resolver: [path] :: Context -> Vector (Either Int Text)
- MiniLight.Component.Internal.Resolver: [target] :: Context -> Value
- MiniLight.Component.Internal.Resolver: [variables] :: Context -> Object
- MiniLight.Component.Internal.Resolver: convert :: Context -> Text -> Value
- MiniLight.Component.Internal.Resolver: convertPath :: Text -> [Either Int Text]
- MiniLight.Component.Internal.Resolver: data Context
- MiniLight.Component.Internal.Resolver: data Expr
- MiniLight.Component.Internal.Resolver: eval :: Context -> Expr -> Value
- MiniLight.Component.Internal.Resolver: getAt :: Value -> [Either Int Text] -> Value
- MiniLight.Component.Internal.Resolver: instance GHC.Classes.Eq MiniLight.Component.Internal.Resolver.Expr
- MiniLight.Component.Internal.Resolver: instance GHC.Show.Show MiniLight.Component.Internal.Resolver.Expr
- MiniLight.Component.Internal.Resolver: normalize :: Vector (Either Int Text) -> [Either Int Text] -> [Either Int Text]
- MiniLight.Component.Internal.Resolver: parseText :: Parser a -> Text -> Result a
- MiniLight.Component.Internal.Resolver: parser :: Parser Expr
- MiniLight.Component.Internal.Resolver: pattern Arithmetic :: Text -> Scientific -> Scientific -> Expr
- MiniLight.Component.Internal.Resolver: resolve :: Value -> Value
- MiniLight.Component.Internal.Types: AppConfig :: Vector ComponentConfig -> Vector Text -> AppConfig
- MiniLight.Component.Internal.Types: ComponentConfig :: Text -> Value -> ComponentConfig
- MiniLight.Component.Internal.Types: [app] :: AppConfig -> Vector ComponentConfig
- MiniLight.Component.Internal.Types: [name] :: ComponentConfig -> Text
- MiniLight.Component.Internal.Types: [properties] :: ComponentConfig -> Value
- MiniLight.Component.Internal.Types: [uuid] :: AppConfig -> Vector Text
- MiniLight.Component.Internal.Types: createComponentBy :: Resolver -> Maybe Text -> ComponentConfig -> MiniLight (Either String Component)
- MiniLight.Component.Internal.Types: data AppConfig
- MiniLight.Component.Internal.Types: data ComponentConfig
- MiniLight.Component.Internal.Types: instance Data.Aeson.Types.FromJSON.FromJSON MiniLight.Component.Internal.Types.AppConfig
- MiniLight.Component.Internal.Types: instance Data.Aeson.Types.FromJSON.FromJSON MiniLight.Component.Internal.Types.ComponentConfig
- MiniLight.Component.Internal.Types: instance Data.Aeson.Types.ToJSON.ToJSON MiniLight.Component.Internal.Types.ComponentConfig
- MiniLight.Component.Internal.Types: instance GHC.Classes.Eq MiniLight.Component.Internal.Types.AppConfig
- MiniLight.Component.Internal.Types: instance GHC.Classes.Eq MiniLight.Component.Internal.Types.ComponentConfig
- MiniLight.Component.Internal.Types: instance GHC.Generics.Generic MiniLight.Component.Internal.Types.ComponentConfig
- MiniLight.Component.Internal.Types: instance GHC.Show.Show MiniLight.Component.Internal.Types.AppConfig
- MiniLight.Component.Internal.Types: instance GHC.Show.Show MiniLight.Component.Internal.Types.ComponentConfig
- MiniLight.Component.Internal.Types: newUID :: MonadIO m => m Text
- MiniLight.Component.Internal.Types: type Resolver = Text " Component Type" -> Text " UID" -> Value " Component Property" -> MiniLight (Either String Component)
- MiniLight.Component.Loader: LoaderEnv :: Registry Component -> IORef AppConfig -> LoaderEnv
- MiniLight.Component.Loader: [appConfig] :: LoaderEnv -> IORef AppConfig
- MiniLight.Component.Loader: [registry] :: LoaderEnv -> Registry Component
- MiniLight.Component.Loader: _appConfig :: HasLoaderEnv c_aZhC => Lens' c_aZhC (IORef AppConfig)
- MiniLight.Component.Loader: _registry :: HasLoaderEnv c_aZhC => Lens' c_aZhC (Registry Component)
- MiniLight.Component.Loader: class HasLoaderEnv c_aZhC
- MiniLight.Component.Loader: data LoaderEnv
- MiniLight.Component.Loader: instance MiniLight.Component.Loader.HasLoaderEnv MiniLight.Component.Loader.LoaderEnv
- MiniLight.Component.Loader: loadAppConfig :: (HasLightEnv env, HasLoaderEnv env, MonadIO m, MonadCatch m) => FilePath -> Resolver -> LightT env m ()
- MiniLight.Component.Loader: loaderEnv :: HasLoaderEnv c_aZhC => Lens' c_aZhC LoaderEnv
- MiniLight.Component.Loader: patchAppConfig :: (HasLightEnv env, HasLoaderEnv env, MonadIO m, MonadCatch m) => FilePath -> Resolver -> LightT env m ()
- MiniLight.Component.Loader: resolveConfig :: MonadIO m => FilePath -> m (Either String AppConfig)
- MiniLight.Component.Types: beforeClearCache :: (ComponentUnit c, HasLightEnv env, MonadIO m, MonadMask m) => c -> [Figure] -> LightT env m ()
- MiniLight.Component.Types: class ComponentUnit c
- MiniLight.Component.Types: class HasComponentEnv env
- MiniLight.Component.Types: data Component
- MiniLight.Component.Types: draw :: (ComponentUnit c, HasLightEnv env, MonadIO m, MonadMask m) => c -> LightT env m ()
- MiniLight.Component.Types: emit :: (HasLoopEnv env, HasComponentEnv env, MonadIO m, EventType et) => et -> LightT env m ()
- MiniLight.Component.Types: figures :: (ComponentUnit c, HasLightEnv env, MonadIO m, MonadMask m) => c -> LightT env m [Figure]
- MiniLight.Component.Types: getComponentSize :: (ComponentUnit c, HasLightEnv env, MonadIO m, MonadMask m) => c -> LightT env m (Rectangle Int)
- MiniLight.Component.Types: getUID :: Component -> Text
- MiniLight.Component.Types: instance MiniLight.Component.Types.ComponentUnit MiniLight.Component.Types.Component
- MiniLight.Component.Types: newComponent :: (ComponentUnit c, HasLightEnv env, MonadIO m, MonadMask m) => Text -> c -> LightT env m Component
- MiniLight.Component.Types: onSignal :: (ComponentUnit c, HasLightEnv env, HasLoopEnv env, HasComponentEnv env, MonadIO m, MonadMask m) => Event -> c -> LightT env m c
- MiniLight.Component.Types: propagate :: Component -> Component
- MiniLight.Component.Types: uidL :: HasComponentEnv env => Lens' env Text
- MiniLight.Component.Types: update :: (ComponentUnit c, HasLightEnv env, HasLoopEnv env, HasComponentEnv env, MonadIO m, MonadMask m) => c -> LightT env m c
- MiniLight.Component.Types: useCache :: ComponentUnit c => c -> c -> Bool
- MiniLight.Event: Never :: Event
+ Control.Lens.TH.Rules: lensRules_ :: LensRules
+ Data.Component.AnimationLayer: instance MiniLight.Component.ComponentUnit Data.Component.AnimationLayer.AnimationLayer
+ Data.Component.Button: instance MiniLight.Component.ComponentUnit Data.Component.Button.Button
+ Data.Component.Layer: instance MiniLight.Component.ComponentUnit Data.Component.Layer.Layer
+ Data.Component.MessageEngine: NextPage :: EngineEvent
+ Data.Component.MessageEngine: [fontData] :: MessageEngine -> Font
+ Data.Component.MessageEngine: [page] :: MessageEngine -> Int
+ Data.Component.MessageEngine: [textCounter] :: MessageEngine -> Int
+ Data.Component.MessageEngine: _config :: Lens' MessageEngine Config
+ Data.Component.MessageEngine: _counter :: Lens' MessageEngine Int
+ Data.Component.MessageEngine: _finished :: Lens' MessageEngine Bool
+ Data.Component.MessageEngine: _font :: Lens' Config Config
+ Data.Component.MessageEngine: _fontData :: Lens' MessageEngine Font
+ Data.Component.MessageEngine: _messages :: Lens' Config (Vector Text)
+ Data.Component.MessageEngine: _page :: Lens' MessageEngine Int
+ Data.Component.MessageEngine: _static :: Lens' Config Bool
+ Data.Component.MessageEngine: _textCounter :: Lens' MessageEngine Int
+ Data.Component.MessageEngine: _textTexture :: Lens' MessageEngine Figure
+ Data.Component.MessageEngine: data EngineEvent
+ Data.Component.MessageEngine: instance MiniLight.Component.ComponentUnit Data.Component.MessageEngine.MessageEngine
+ Data.Component.MessageEngine: instance MiniLight.Event.EventType Data.Component.MessageEngine.EngineEvent
+ Data.Component.MessageEngine: wrapSignal :: (HasLightEnv env, HasLoopEnv env, HasComponentEnv env, MonadIO m, MonadMask m) => Lens' c MessageEngine -> (Event -> c -> LightT env m c) -> Event -> c -> LightT env m c
+ Data.Component.MessageLayer: _config :: Lens' MessageLayer Config
+ Data.Component.MessageLayer: _cursor :: Lens' MessageLayer AnimationLayer
+ Data.Component.MessageLayer: _layer :: Lens' MessageLayer Layer
+ Data.Component.MessageLayer: _messageEngine :: Lens' MessageLayer MessageEngine
+ Data.Component.MessageLayer: instance MiniLight.Component.ComponentUnit Data.Component.MessageLayer.MessageLayer
+ Data.Component.Selection: Select :: Int -> SelectionEvent
+ Data.Component.Selection: data SelectionEvent
+ Data.Component.Selection: instance MiniLight.Component.ComponentUnit Data.Component.Selection.Selection
+ Data.Component.Selection: instance MiniLight.Event.EventType Data.Component.Selection.SelectionEvent
+ Data.Config.Font: _color :: HasConfig c_a1cqt => Lens' c_a1cqt (V4 Word8)
+ Data.Config.Font: _descriptor :: HasConfig c_a1cqt => Lens' c_a1cqt FontDescriptor
+ Data.Config.Font: _size :: HasConfig c_a1cqt => Lens' c_a1cqt Int
+ Data.Config.Font: class HasConfig c_a1cqt
+ Data.Config.Font: config :: HasConfig c_a1cqt => Lens' c_a1cqt Config
+ Data.Config.Font: instance Data.Config.Font.HasConfig Data.Config.Font.Config
+ MiniLight: instance MiniLight.Component.HasComponentEnv (MiniLight.ComponentState env)
+ MiniLight: instance MiniLight.HasComponentState (MiniLight.ComponentState a) a
+ MiniLight: instance MiniLight.Light.HasLightEnv env => MiniLight.Light.HasLightEnv (MiniLight.ComponentState env)
+ MiniLight: instance MiniLight.Light.HasLoopEnv env => MiniLight.Light.HasLoopEnv (MiniLight.ComponentState env)
+ MiniLight: instance MiniLight.Loader.HasLoaderEnv MiniLight.LoopState
+ MiniLight: instance MiniLight.Loader.HasLoaderEnv env => MiniLight.Loader.HasLoaderEnv (MiniLight.ComponentState env)
+ MiniLight.Component: ComponentEnv :: Text -> Maybe HookMap -> ComponentEnv
+ MiniLight.Component: [callbacks] :: ComponentEnv -> Maybe HookMap
+ MiniLight.Component: [uid] :: ComponentEnv -> Text
+ MiniLight.Component: _callbacks :: HasComponentEnv c_aNwC => Lens' c_aNwC (Maybe HookMap)
+ MiniLight.Component: _uid :: HasComponentEnv c_aNwC => Lens' c_aNwC Text
+ MiniLight.Component: beforeClearCache :: (ComponentUnit c, HasLightEnv env, MonadIO m, MonadMask m) => c -> [Figure] -> LightT env m ()
+ MiniLight.Component: class ComponentUnit c
+ MiniLight.Component: class HasComponentEnv c_aNwC
+ MiniLight.Component: componentEnv :: HasComponentEnv c_aNwC => Lens' c_aNwC ComponentEnv
+ MiniLight.Component: data Component
+ MiniLight.Component: data ComponentEnv
+ MiniLight.Component: draw :: (ComponentUnit c, HasLightEnv env, MonadIO m, MonadMask m) => c -> LightT env m ()
+ MiniLight.Component: emit :: (HasLoopEnv env, HasComponentEnv env, MonadIO m, EventType et) => et -> LightT env m ()
+ MiniLight.Component: figures :: (ComponentUnit c, HasLightEnv env, MonadIO m, MonadMask m) => c -> LightT env m [Figure]
+ MiniLight.Component: getComponentSize :: (ComponentUnit c, HasLightEnv env, MonadIO m, MonadMask m) => c -> LightT env m (Rectangle Int)
+ MiniLight.Component: getHooks :: Component -> Maybe HookMap
+ MiniLight.Component: getUID :: Component -> Text
+ MiniLight.Component: instance MiniLight.Component.ComponentUnit MiniLight.Component.Component
+ MiniLight.Component: instance MiniLight.Component.HasComponentEnv MiniLight.Component.ComponentEnv
+ MiniLight.Component: newComponent :: (ComponentUnit c, HasLightEnv env, MonadIO m, MonadMask m) => Text -> c -> LightT env m Component
+ MiniLight.Component: onSignal :: (ComponentUnit c, HasLightEnv env, HasLoopEnv env, HasComponentEnv env, MonadIO m, MonadMask m) => Event -> c -> LightT env m c
+ MiniLight.Component: propagate :: Component -> Component
+ MiniLight.Component: setHooks :: Component -> Maybe HookMap -> Component
+ MiniLight.Component: type HookMap = HashMap Text (Text, Object -> Value)
+ MiniLight.Component: update :: (ComponentUnit c, HasLightEnv env, HasLoopEnv env, HasComponentEnv env, MonadIO m, MonadMask m) => c -> LightT env m c
+ MiniLight.Component: useCache :: ComponentUnit c => c -> c -> Bool
+ MiniLight.Event: GlobalSignal :: Text -> Value -> Event
+ MiniLight.Event: getEventProperties :: EventType e => e -> Object
+ MiniLight.Event: getEventType :: (EventType e, Show e) => e -> Text
+ MiniLight.Loader: LoaderEnv :: Registry Component -> IORef AppConfig -> LoaderEnv
+ MiniLight.Loader: [appConfig] :: LoaderEnv -> IORef AppConfig
+ MiniLight.Loader: [registry] :: LoaderEnv -> Registry Component
+ MiniLight.Loader: _appConfig :: HasLoaderEnv c_a14jy => Lens' c_a14jy (IORef AppConfig)
+ MiniLight.Loader: _registry :: HasLoaderEnv c_a14jy => Lens' c_a14jy (Registry Component)
+ MiniLight.Loader: class HasLoaderEnv c_a14jy
+ MiniLight.Loader: createComponentBy :: Resolver -> Maybe Text -> ComponentConfig -> MiniLight (Either String Component)
+ MiniLight.Loader: data LoaderEnv
+ MiniLight.Loader: instance MiniLight.Loader.HasLoaderEnv MiniLight.Loader.LoaderEnv
+ MiniLight.Loader: loadAppConfig :: (HasLightEnv env, HasLoaderEnv env, MonadIO m, MonadCatch m) => FilePath -> Resolver -> LightT env m ()
+ MiniLight.Loader: loaderEnv :: HasLoaderEnv c_a14jy => Lens' c_a14jy LoaderEnv
+ MiniLight.Loader: parseAppConfig :: Value -> Either Text AppConfig
+ MiniLight.Loader: patchAppConfig :: (HasLightEnv env, HasLoaderEnv env, MonadIO m, MonadCatch m) => FilePath -> Resolver -> LightT env m ()
+ MiniLight.Loader: resolve :: Value -> Value
+ MiniLight.Loader: resolveConfig :: MonadIO m => FilePath -> m (Either Text AppConfig)
+ MiniLight.Loader.Internal.Resolver: App :: Expr -> [Expr] -> Expr
+ MiniLight.Loader.Internal.Resolver: Constant :: Value -> Expr
+ MiniLight.Loader.Internal.Resolver: Context :: Vector (Either Int Text) -> Object -> HashMap Text Value -> Context
+ MiniLight.Loader.Internal.Resolver: None :: Expr
+ MiniLight.Loader.Internal.Resolver: Op :: Text -> Expr -> Expr -> Expr
+ MiniLight.Loader.Internal.Resolver: Ref :: Text -> Expr
+ MiniLight.Loader.Internal.Resolver: Symbol :: Text -> Expr
+ MiniLight.Loader.Internal.Resolver: Var :: Text -> Expr
+ MiniLight.Loader.Internal.Resolver: [path] :: Context -> Vector (Either Int Text)
+ MiniLight.Loader.Internal.Resolver: [values] :: Context -> HashMap Text Value
+ MiniLight.Loader.Internal.Resolver: [variables] :: Context -> Object
+ MiniLight.Loader.Internal.Resolver: convert :: Context -> Value -> Text -> Either Text Value
+ MiniLight.Loader.Internal.Resolver: convertPath :: Text -> [Either Int Text]
+ MiniLight.Loader.Internal.Resolver: data Context
+ MiniLight.Loader.Internal.Resolver: data Expr
+ MiniLight.Loader.Internal.Resolver: emptyContext :: Context
+ MiniLight.Loader.Internal.Resolver: eval :: Context -> Value -> Expr -> Either Text Value
+ MiniLight.Loader.Internal.Resolver: getAt :: Value -> [Either Int Text] -> Either Text Value
+ MiniLight.Loader.Internal.Resolver: instance Data.Aeson.Types.FromJSON.FromJSON MiniLight.Loader.Internal.Resolver.Expr
+ MiniLight.Loader.Internal.Resolver: instance Data.Aeson.Types.ToJSON.ToJSON MiniLight.Loader.Internal.Resolver.Expr
+ MiniLight.Loader.Internal.Resolver: instance GHC.Classes.Eq MiniLight.Loader.Internal.Resolver.Expr
+ MiniLight.Loader.Internal.Resolver: instance GHC.Generics.Generic MiniLight.Loader.Internal.Resolver.Expr
+ MiniLight.Loader.Internal.Resolver: instance GHC.Show.Show MiniLight.Loader.Internal.Resolver.Expr
+ MiniLight.Loader.Internal.Resolver: normalize :: Vector (Either Int Text) -> [Either Int Text] -> [Either Int Text]
+ MiniLight.Loader.Internal.Resolver: parseAppConfig :: Value -> Either Text AppConfig
+ MiniLight.Loader.Internal.Resolver: parseText :: Parser a -> Text -> Either Text a
+ MiniLight.Loader.Internal.Resolver: parser :: Parser Expr
+ MiniLight.Loader.Internal.Resolver: resolve :: Value -> Value
+ MiniLight.Loader.Internal.Resolver: resolveWith :: Context -> Value -> Either Text Value
+ MiniLight.Loader.Internal.Types: AppConfig :: Vector ComponentConfig -> Vector Text -> AppConfig
+ MiniLight.Loader.Internal.Types: ComponentConfig :: Text -> Value -> Maybe (HashMap Text Hook) -> ComponentConfig
+ MiniLight.Loader.Internal.Types: Hook :: Text -> Value -> Hook
+ MiniLight.Loader.Internal.Types: [app] :: AppConfig -> Vector ComponentConfig
+ MiniLight.Loader.Internal.Types: [hooks] :: ComponentConfig -> Maybe (HashMap Text Hook)
+ MiniLight.Loader.Internal.Types: [name] :: ComponentConfig -> Text
+ MiniLight.Loader.Internal.Types: [parameter] :: Hook -> Value
+ MiniLight.Loader.Internal.Types: [properties] :: ComponentConfig -> Value
+ MiniLight.Loader.Internal.Types: [signalName] :: Hook -> Text
+ MiniLight.Loader.Internal.Types: [uuid] :: AppConfig -> Vector Text
+ MiniLight.Loader.Internal.Types: data AppConfig
+ MiniLight.Loader.Internal.Types: data ComponentConfig
+ MiniLight.Loader.Internal.Types: data Hook
+ MiniLight.Loader.Internal.Types: instance Data.Aeson.Types.FromJSON.FromJSON MiniLight.Loader.Internal.Types.AppConfig
+ MiniLight.Loader.Internal.Types: instance Data.Aeson.Types.FromJSON.FromJSON MiniLight.Loader.Internal.Types.ComponentConfig
+ MiniLight.Loader.Internal.Types: instance Data.Aeson.Types.FromJSON.FromJSON MiniLight.Loader.Internal.Types.Hook
+ MiniLight.Loader.Internal.Types: instance Data.Aeson.Types.ToJSON.ToJSON MiniLight.Loader.Internal.Types.ComponentConfig
+ MiniLight.Loader.Internal.Types: instance Data.Aeson.Types.ToJSON.ToJSON MiniLight.Loader.Internal.Types.Hook
+ MiniLight.Loader.Internal.Types: instance GHC.Generics.Generic MiniLight.Loader.Internal.Types.Hook
+ MiniLight.Loader.Internal.Types: instance GHC.Show.Show MiniLight.Loader.Internal.Types.Hook
+ MiniLight.Loader.Internal.Types: newUID :: MonadIO m => m Text
+ MiniLight.Loader.Internal.Types: toHook :: Value -> Either Text Hook
+ MiniLight.Loader.Internal.Types: type Resolver = Text " Component Type" -> Text " UID" -> Value " Component Property" -> MiniLight (Either String Component)
- Data.Component.MessageEngine: Config :: Text -> Bool -> V4 Word8 -> FontDescriptor -> Int -> Config
+ Data.Component.MessageEngine: Config :: Vector Text -> Bool -> Config -> Config
- Data.Component.MessageEngine: MessageEngine :: Font -> Int -> Int -> Figure -> Bool -> Config -> MessageEngine
+ Data.Component.MessageEngine: MessageEngine :: Font -> Int -> Int -> Int -> Figure -> Bool -> Config -> MessageEngine
- Data.Component.MessageEngine: [font] :: MessageEngine -> Font
+ Data.Component.MessageEngine: [font] :: Config -> Config
- Data.Component.MessageEngine: [messages] :: Config -> Text
+ Data.Component.MessageEngine: [messages] :: Config -> Vector Text
- MiniLight.Light: _events :: HasLoopEnv c_aKBX => Lens' c_aKBX (MVar [Event])
+ MiniLight.Light: _events :: HasLoopEnv c_aFkk => Lens' c_aFkk (MVar [Event])
- MiniLight.Light: _fontCache :: HasLightEnv c_aKvN => Lens' c_aKvN FontMap
+ MiniLight.Light: _fontCache :: HasLightEnv c_aFfk => Lens' c_aFfk FontMap
- MiniLight.Light: _keyStates :: HasLoopEnv c_aKBX => Lens' c_aKBX (HashMap Scancode Int)
+ MiniLight.Light: _keyStates :: HasLoopEnv c_aFkk => Lens' c_aFkk (HashMap Scancode Int)
- MiniLight.Light: _logger :: HasLightEnv c_aKvN => Lens' c_aKvN LogQueue
+ MiniLight.Light: _logger :: HasLightEnv c_aFfk => Lens' c_aFfk LogQueue
- MiniLight.Light: _renderer :: HasLightEnv c_aKvN => Lens' c_aKvN Renderer
+ MiniLight.Light: _renderer :: HasLightEnv c_aFfk => Lens' c_aFfk Renderer
- MiniLight.Light: _signalQueue :: HasLoopEnv c_aKBX => Lens' c_aKBX (IORef [Event])
+ MiniLight.Light: _signalQueue :: HasLoopEnv c_aFkk => Lens' c_aFkk (IORef [Event])
- MiniLight.Light: class HasLightEnv c_aKvN
+ MiniLight.Light: class HasLightEnv c_aFfk
- MiniLight.Light: class HasLoopEnv c_aKBX
+ MiniLight.Light: class HasLoopEnv c_aFkk
- MiniLight.Light: lightEnv :: HasLightEnv c_aKvN => Lens' c_aKvN LightEnv
+ MiniLight.Light: lightEnv :: HasLightEnv c_aFfk => Lens' c_aFfk LightEnv
- MiniLight.Light: loopEnv :: HasLoopEnv c_aKBX => Lens' c_aKBX LoopEnv
+ MiniLight.Light: loopEnv :: HasLoopEnv c_aFkk => Lens' c_aFkk LoopEnv
Files
- CHANGELOG.md +7/−0
- README.md +1/−1
- minilight.cabal +6/−5
- src/Control/Lens/TH/Rules.hs +9/−0
- src/Data/Component/Basic.hs +4/−1
- src/Data/Component/Button.hs +2/−1
- src/Data/Component/Layer.hs +4/−0
- src/Data/Component/MessageEngine.hs +86/−37
- src/Data/Component/MessageLayer.hs +25/−12
- src/Data/Component/Selection.hs +15/−2
- src/Data/Config/Font.hs +9/−11
- src/Data/Vector/Mutable/PushBack.hs +1/−1
- src/MiniLight.hs +44/−25
- src/MiniLight/Component.hs +146/−36
- src/MiniLight/Component/Internal/Resolver.hs +0/−129
- src/MiniLight/Component/Internal/Types.hs +0/−50
- src/MiniLight/Component/Loader.hs +0/−217
- src/MiniLight/Component/Types.hs +0/−125
- src/MiniLight/Event.hs +13/−4
- src/MiniLight/Loader.hs +279/−0
- src/MiniLight/Loader/Internal/Resolver.hs +223/−0
- src/MiniLight/Loader/Internal/Types.hs +71/−0
CHANGELOG.md view
@@ -1,5 +1,12 @@ # Revision history for minilight +## 0.4 -- 2019-06-01++* Features:+ * Hooks feature in Component Loader+ * Improve MessageEngine component+ * Improve Loader functions+ ## 0.3 -- 2019-05-05 * New features:
README.md view
@@ -77,7 +77,7 @@ size: 22 ``` -For the configuration syntax, see [MiniLight.Component](https://hackage.haskell.org/package/minilight-0.2.0/docs/MiniLight-Component.html).+For the configuration syntax, see [MiniLight.Loader](https://hackage.haskell.org/package/minilight/docs/MiniLight-Loader.html). For the pre-defined components, see the modules under `Data.Component`.
minilight.cabal view
@@ -3,7 +3,7 @@ -- For further documentation, see http://haskell.org/cabal/users-guide/ name: minilight-version: 0.3.0+version: 0.4.0 synopsis: A SDL2-based graphics library, batteries-included. description: This package provides the wheel for a graphical application or a game.@@ -32,6 +32,7 @@ library exposed-modules:+ Control.Lens.TH.Rules Control.Monad.Caster Data.Vector.Mutable.PushBack Data.Registry@@ -42,10 +43,9 @@ MiniLight.Light MiniLight.Event MiniLight.Component- MiniLight.Component.Types- MiniLight.Component.Internal.Types- MiniLight.Component.Internal.Resolver- MiniLight.Component.Loader+ MiniLight.Loader.Internal.Types+ MiniLight.Loader.Internal.Resolver+ MiniLight.Loader Data.Config.Font Data.Component.Basic Data.Component.Layer@@ -97,6 +97,7 @@ RankNTypes Strict TemplateHaskell+ ViewPatterns ghc-options: -Wall -Wno-name-shadowing test-suite tests
+ src/Control/Lens/TH/Rules.hs view
@@ -0,0 +1,9 @@+module Control.Lens.TH.Rules where++import Control.Lens+import Language.Haskell.TH++-- | Custom rules of the lens. All lens are prefixed by '_'.+lensRules_ :: LensRules+lensRules_ =+ lensRules & lensField .~ \_ _ n -> [TopName (mkName ('_' : nameBase n))]
src/Data/Component/Basic.hs view
@@ -60,7 +60,10 @@ -> Signal deriving Typeable -instance EventType Signal+instance EventType Signal where+ getEventType (MousePressed _) = "mouse-pressed"+ getEventType (MouseReleased _) = "mouse-released"+ getEventType (MouseOver _) = "mouse-over" -- | This automatically applies basic configuration such as: position. wrapFigures :: Config -> [Figure] -> [Figure]
src/Data/Component/Button.hs view
@@ -17,7 +17,8 @@ data ButtonEvent = Click deriving Typeable -instance EventType ButtonEvent+instance EventType ButtonEvent where+ getEventType Click = "click" instance ComponentUnit Button where update = return
src/Data/Component/Layer.hs view
@@ -17,6 +17,10 @@ instance ComponentUnit Layer where figures comp = return $ Basic.wrapFigures (basic $ config comp) [layer comp] + onSignal = Basic.wrapSignal (basic . config) (\_ -> return)++ useCache _ _ = True+ data Config = Config { basic :: Basic.Config, image :: FilePath
src/Data/Component/MessageEngine.hs view
@@ -1,78 +1,127 @@ module Data.Component.MessageEngine where import Control.Lens+import Control.Lens.TH.Rules import Control.Monad.State-import Data.Aeson+import Control.Monad.Catch (MonadMask)+import Data.Aeson hiding ((.=))+import qualified Data.Config.Font as Font import qualified Data.Text as T-import Data.Word (Word8)+import qualified Data.Vector as V+import Data.Typeable import MiniLight import qualified SDL import qualified SDL.Font import qualified SDL.Vect as Vect +-- | 'MessageEngine' configuration. If @static@ enabled, only the first page will be rendered.+data Config = Config {+ messages :: V.Vector T.Text, -- ^ paged messages+ static :: Bool,+ font :: Font.Config+}++makeLensesWith lensRules_ ''Config++instance FromJSON Config where+ parseJSON = withObject "config" $ \v -> do+ messages <- v .: "messages"+ static <- v .:? "static" .!= False+ font <- v .: "font"++ return $ Config messages static font+ data MessageEngine = MessageEngine {- font :: SDL.Font.Font,+ fontData :: SDL.Font.Font, counter :: Int,- rendered :: Int,+ page :: Int,+ textCounter :: Int, textTexture :: Figure, finished :: Bool, config :: Config } +makeLensesWith lensRules_ ''MessageEngine++data EngineEvent = NextPage+ deriving Typeable++instance EventType EngineEvent where+ getEventType NextPage = "next-page"+ instance ComponentUnit MessageEngine where update = execStateT $ do- comp <- get-- unless (finished comp) $ do- when (counter comp `mod` 10 == 0) $ do- id %= (\c -> c { rendered = rendered c + 1 })+ fin <- use _finished+ unless fin $ do+ cnt <- use _counter+ when (cnt `mod` 10 == 0) $ do+ _textCounter %= (+1) - comp <- get- when (rendered comp == T.length (messages (config comp))) $ do- id %= (\c -> c { finished = True })+ tc <- use _textCounter+ p <- use _page+ messages <- use $ _config . _messages+ when (p == V.length messages - 1 && tc == T.length (messages V.! p)) $ do+ _finished .= True - id %= (\c -> c { counter = counter c + 1 })+ _counter %= (+1) figures comp = do- (w, h) <- SDL.Font.size (font comp) (T.take (rendered comp) $ messages (config comp))+ (w, h) <- SDL.Font.size (comp ^. _fontData) (T.take (comp ^. _textCounter) $ (config comp ^. _messages) V.! (comp ^. _page)) return [ clip (SDL.Rectangle 0 (Vect.V2 w h)) $ textTexture comp ] -data Config = Config {- messages :: T.Text,- static :: Bool,- color :: Vect.V4 Word8,- fontConf :: FontDescriptor,- fontSize :: Int-}+ useCache c1 c2 = page c1 == page c2 && textCounter c1 == textCounter c2 -instance FromJSON Config where- parseJSON = withObject "config" $ \v -> do- messages <- v .: "messages"- static <- v .:? "static" .!= False- [r,g,b,a] <- v .:? "color" .!= [255, 255, 255, 255]- (fontConf, size) <- (v .: "font" >>=) $ withObject "font" $ \v -> do- family <- v .: "family"- size <- v .: "size"- bold <- v .:? "bold" .!= False- italic <- v .:? "italic" .!= False+ onSignal ev c = view _uid >>= \u -> go (ev,u) c+ where+ go (uncurry asSignal -> Just NextPage) = execStateT $ do+ fin <- use _finished+ unless fin $ do+ _page %= (+1)+ _textCounter .= 0 - return $ (FontDescriptor family (FontStyle bold italic), size)+ font <- use _fontData+ fontColor <- use $ _config . _font . Font._color+ p <- use _page+ messages <- use $ _config . _messages+ tex <- lift $ liftMiniLight $ text font fontColor (messages V.! p)+ _textTexture .= tex - return $ Config messages static (Vect.V4 r g b a) fontConf size+ go _ = return new :: Config -> MiniLight MessageEngine new conf = do- font <- loadFont (fontConf conf) (fontSize conf)- textTexture <- text font (color conf) $ messages conf+ font <- Font.loadFontFrom (font conf)+ textTexture <- text font (conf ^. _font ^. Font._color) $ messages conf V.! 0 return $ MessageEngine- { font = font+ { fontData = font , counter = 0- , rendered = if static conf then T.length (messages conf) else 0+ , page = 0+ , textCounter = if static conf then T.length (messages conf V.! 0) else 0 , textTexture = textTexture , finished = static conf , config = conf }++wrapSignal+ :: ( HasLightEnv env+ , HasLoopEnv env+ , HasComponentEnv env+ , MonadIO m+ , MonadMask m+ )+ => (Lens' c MessageEngine)+ -> (Event -> c -> LightT env m c)+ -> (Event -> c -> LightT env m c)+wrapSignal lens f ev = execStateT $ do+ zoom lens $ do+ st <- use id+ st' <- lift $ onSignal ev st+ id .= st'++ st <- use id+ st' <- lift $ f ev st+ id .= st'
src/Data/Component/MessageLayer.hs view
@@ -1,6 +1,7 @@ module Data.Component.MessageLayer where import Control.Lens+import Control.Lens.TH.Rules import Control.Monad.State import Data.Aeson import Linear@@ -11,6 +12,20 @@ import qualified Data.Component.MessageEngine as CME import qualified SDL.Vect as Vect +data Config = Config {+ engine :: CME.Config,+ window :: CLayer.Config,+ next :: CAnim.Config+}++instance FromJSON Config where+ parseJSON = withObject "config" $ \v -> do+ layerConf <- parseJSON =<< v .: "window"+ nextConf <- parseJSON =<< v .: "next"+ messageEngineConf <- parseJSON =<< v .: "engine"++ return $ Config messageEngineConf layerConf nextConf+ data MessageLayer = MessageLayer { messageEngine :: CME.MessageEngine, layer :: CLayer.Layer,@@ -18,6 +33,8 @@ config :: Config } +makeLensesWith lensRules_ ''MessageLayer+ engineL :: Lens' MessageLayer CME.MessageEngine engineL = lens messageEngine (\s a -> s { messageEngine = a }) @@ -48,19 +65,15 @@ ++ map (translate (position + Vect.V2 20 10)) textLayer ++ map (translate (position + Vect.V2 ((windowSize ^. _x - cursorSize ^. _x) `div` 2) (windowSize ^. _y - cursorSize ^. _y))) cursorLayer -data Config = Config {- engine :: CME.Config,- window :: CLayer.Config,- next :: CAnim.Config-}--instance FromJSON Config where- parseJSON = withObject "config" $ \v -> do- layerConf <- parseJSON =<< v .: "window"- nextConf <- parseJSON =<< v .: "next"- messageEngineConf <- parseJSON =<< v .: "engine"+ onSignal+ = Basic.wrapSignal (CLayer.basic . CLayer.config . layer)+ $ CME.wrapSignal _messageEngine+ $ \ev c -> view _uid >>= \u -> go (ev,u) c+ where+ go (uncurry asSignal -> Just (Basic.MouseReleased _)) = execStateT $ do+ lift $ emit CME.NextPage - return $ Config messageEngineConf layerConf nextConf+ go _ = return new :: Config -> MiniLight MessageLayer new conf = do
src/Data/Component/Selection.hs view
@@ -4,7 +4,9 @@ import Control.Monad.State import Data.Aeson hiding ((.=)) import qualified Data.Config.Font as Font+import qualified Data.HashMap.Strict as HM import qualified Data.Text as T+import Data.Typeable (Typeable) import qualified Data.Vector as V import Linear import MiniLight@@ -39,11 +41,13 @@ useCache c1 c2 = hover c1 == hover c2 onSignal = Basic.wrapSignal (basic . conf) $ \ev sel -> flip execStateT sel $ do- uid <- view uidL+ uid <- view _uid case ev `asSignal` uid of Just (Basic.MouseOver pos) | (pos ^. _y) `div` 30 <= V.length (labels (conf sel)) - 1 -> do hoverL .= Just ((pos ^. _y) `div` 30)+ Just (Basic.MouseReleased pos) | (pos ^. _y) `div` 30 <= V.length (labels (conf sel)) - 1 -> do+ lift $ emit $ Select ((pos ^. _y) `div` 30) _ -> return () -- OMG@@ -61,8 +65,17 @@ Config <$> parseJSON (Object v) <*> v .:? "labels" .!= V.empty- <*> parseJSON (Object v)+ <*> (parseJSON =<< (v .: "font")) <*> v .: "image"++data SelectionEvent+ = Select Int+ deriving (Typeable)++instance EventType SelectionEvent where+ getEventType (Select _) = "select"++ getEventProperties (Select n) = HM.fromList [("index", Number $ fromIntegral n)] new :: Config -> MiniLight Selection new conf = do
src/Data/Config/Font.hs view
@@ -1,5 +1,6 @@ module Data.Config.Font where +import Control.Lens import Data.Aeson import Data.Word (Word8) import MiniLight@@ -12,20 +13,17 @@ color :: Vect.V4 Word8 } +makeLensesWith classyRules_ ''Config+ instance FromJSON Config where parseJSON = withObject "font" $ \v -> do- fontMaybe <- v .:? "font"-- case fontMaybe of- Nothing -> return $ Config (FontDescriptor "" (FontStyle False False)) 0 (Vect.V4 0 0 0 255)- Just w -> flip (withObject "font") w $ \v -> do- family <- v .: "family"- size <- v .: "size"- bold <- v .:? "bold" .!= False- italic <- v .:? "italic" .!= False- [r,g,b,a] <- v .:? "color" .!= [0, 0, 0, 255]+ family <- v .:? "family" .!= ""+ size <- v .:? "size" .!= 0+ bold <- v .:? "bold" .!= False+ italic <- v .:? "italic" .!= False+ [r,g,b,a] <- v .:? "color" .!= [0, 0, 0, 255] - return $ Config (FontDescriptor family (FontStyle bold italic)) size (Vect.V4 r g b a)+ return $ Config (FontDescriptor family (FontStyle bold italic)) size (Vect.V4 r g b a) -- | Load a system font from 'Config' type. loadFontFrom :: Config -> MiniLight SDL.Font.Font
src/Data/Vector/Mutable/PushBack.hs view
@@ -50,7 +50,7 @@ -> Int -> a -> IO ()-insert pvec@(IOVector _ uvec) i v = do+insert pvec i v = do len <- safeLength pvec read pvec (len - 1) >>= push pvec
src/MiniLight.hs view
@@ -6,6 +6,7 @@ module MiniLight.Event, module MiniLight.Figure, module MiniLight.Component,+ module MiniLight.Loader, runLightT, LoopState (..),@@ -29,12 +30,12 @@ import Data.IORef import qualified Data.Registry as R import qualified Data.Vector as V-import qualified Data.Text as T import Graphics.Text.TrueType import MiniLight.Component import MiniLight.Event import MiniLight.Figure import MiniLight.Light+import MiniLight.Loader import qualified System.FSNotify as Notify import qualified SDL import qualified SDL.Font@@ -62,7 +63,7 @@ -- | Use 'defConfig' for a default setting. data LoopConfig = LoopConfig { watchKeys :: Maybe [SDL.Scancode], -- ^ Set @Nothing@ if all keys should be watched. See also 'LoopState'.- appConfigFile :: Maybe FilePath, -- ^ Specify a yaml file which describes component settings. See 'MiniLight.Component' for the yaml syntax.+ appConfigFile :: Maybe FilePath, -- ^ Specify a yaml file which describes component settings. See 'MiniLight.Loader' for the yaml syntax. hotConfigReplacement :: Maybe FilePath, -- ^ The directory path to be watched. If set, the config file modification will replace the component dynamically. componentResolver :: Resolver, -- ^ Your custom mappings between a component name and its type. additionalComponents :: [Component] -- ^ The components here would be added during the initialization.@@ -102,19 +103,24 @@ loaderEnv = _loader . loaderEnv -instance HasLightEnv env' => HasLightEnv (env, env') where- lightEnv = _2 . lightEnv+data ComponentState a = ComponentState {+ stateL :: a,+ componentEnvL :: ComponentEnv+} -instance HasLoopEnv env' => HasLoopEnv (env, env') where- loopEnv = _2 . loopEnv+makeLensesWith classyRules_ ''ComponentState -instance HasLoaderEnv env' => HasLoaderEnv (env, env') where- loaderEnv = _2 . loaderEnv+instance HasLightEnv env => HasLightEnv (ComponentState env) where+ lightEnv = _stateL . lightEnv +instance HasLoopEnv env => HasLoopEnv (ComponentState env) where+ loopEnv = _stateL . loopEnv -instance HasComponentEnv (T.Text, env) where- uidL = _1+instance HasLoaderEnv env => HasLoaderEnv (ComponentState env) where+ loaderEnv = _stateL . loaderEnv +instance HasComponentEnv (ComponentState env) where+ componentEnv = _componentEnvL -- | Same as 'runMainloop' but fixing the type. runMiniloop :: LoopConfig -> s -> (s -> MiniLoop s) -> MiniLight ()@@ -179,7 +185,12 @@ R.modifyV_ (loader ^. _registry) $ return . propagate R.modifyV_ (loader ^. _registry) $ \comp ->- envLightT (\env -> (getUID comp, conv env loop loader)) $ update comp+ envLightT+ ( \env -> ComponentState+ (conv env loop loader)+ (ComponentEnv (getUID comp) (getHooks comp))+ )+ $ update comp s' <- envLightT (\env -> conv env loop loader) $ userloop s @@ -190,24 +201,21 @@ keys <- SDL.getKeyboardState envLightT (\env -> conv env loop loader) $ do- evref <- view _events- sigref <- view _signalQueue- signals <- liftIO $ readIORef sigref- liftIO- $ modifyMVar_ evref- $ return- . (map RawEvent events ++)- . (signals ++)- liftIO $ writeIORef sigref []-- envLightT (\env -> conv env loop loader) $ do evref <- view _events events <- liftIO $ modifyMVar evref (\a -> return ([], a)) R.modifyV_ (loader ^. _registry) $ \comp -> do- foldlM (\comp ev -> envLightT ((,) (getUID comp)) $ onSignal ev comp)- comp- events+ foldlM+ ( \comp ev ->+ envLightT+ ( \env -> ComponentState+ env+ (ComponentEnv (getUID comp) (getHooks comp))+ )+ $ onSignal ev comp+ )+ comp+ events forM_ ( catMaybes $ map@@ -220,6 +228,17 @@ $ \ev -> patchAppConfig (fromJust $ appConfigFile conf) (componentResolver conf)++ envLightT (\env -> conv env loop loader) $ do+ evref <- view _events+ sigref <- view _signalQueue+ signals <- liftIO $ readIORef sigref+ liftIO+ $ modifyMVar_ evref+ $ return+ . (map RawEvent events ++)+ . (signals ++)+ liftIO $ writeIORef sigref [] let loop' = loop
src/MiniLight/Component.hs view
@@ -1,47 +1,157 @@-{-| The package provides the configuration loader.+module MiniLight.Component (+ HookMap,+ HasComponentEnv(..),+ ComponentEnv(..),+ emit, -An configuration example:+ ComponentUnit(..),+ Component,+ newComponent,+ getComponentSize,+ getUID,+ getHooks,+ setHooks,+ propagate,+) where -@-_vars:- window:- width: 800- height: 600-app:- - name: message-layer- properties:- window:- image: resources/window-base.png- position:- x: 0- y: ${${var:window.height} - ${ref:..size.height}}- size:- width: ${var:window.width}- height: 150-@+import Control.Lens+import Control.Monad.Catch+import Control.Monad.IO.Class+import Data.Aeson+import qualified Data.HashMap.Strict as HM+import Data.IORef+import qualified Data.Text as T+import MiniLight.Light+import MiniLight.Event+import MiniLight.Figure+import qualified SDL -== Syntax+type HookMap = HM.HashMap T.Text (T.Text, Object -> Value) -=== @_vars@+-- | Environmental information, which are passed for each component+data ComponentEnv = ComponentEnv {+ uid :: T.Text, -- ^ The unique id+ callbacks :: Maybe HookMap -- ^ The hooks+} -You can define a new variable. Use object syntax under the @_vars@ field.+makeClassy_ ''ComponentEnv -The variables can be referenced in all siblings and under their siblings to the @_vars@, in the variable syntax @${var:_path_}@.+-- | Emit a signal, which will be catched at the next frame.+emit+ :: (HasLoopEnv env, HasComponentEnv env, MonadIO m, EventType et)+ => et+ -> LightT env m ()+emit et = do+ uid <- view _uid+ ref <- view _signalQueue+ liftIO $ modifyIORef' ref $ (signal uid et :) -=== Expr+ hs <- view _callbacks+ case HM.lookup (getEventType et) =<< hs of+ Just (name, param) -> liftIO+ $ modifyIORef' ref (GlobalSignal name (param $ getEventProperties et) :)+ Nothing -> return () -In each field, you can specify an expression defined in the loader.+-- | CompoonentUnit typeclass provides a way to define a new component.+-- Any 'ComponentUnit' instance can be embedded into 'Component' type.+class ComponentUnit c where+ -- | Updating a model.+ update :: (HasLightEnv env, HasLoopEnv env, HasComponentEnv env, MonadIO m, MonadMask m) => c -> LightT env m c+ update = return -- @${}@: enclose the expr by @${}@, to tell the parsers that the field is an expr not a plain string.-- @${ref:_path_}@: specify any path to refer any other value. The path resolution is performed once, not recursively resolved. @_path_@ consists of field names splitted by a period. Use double dots @..@ for a parent.-- @${var:_path_}@: specify any path to value defined at the field. @_path_@ consists of field names splitted by a period.-- arithmetic operator: addition, subtraction, multiplication and division (@+,-,*,/@) can also be used in @${}@.+ -- | Descirbes a view. The figures here would be cached. See also 'useCache' for the cache configuration.+ figures :: (HasLightEnv env, MonadIO m, MonadMask m) => c -> LightT env m [Figure] --}-module MiniLight.Component (- module MiniLight.Component.Types,- module MiniLight.Component.Loader,-) where+ -- | Drawing a figures.+ draw :: (HasLightEnv env, MonadIO m, MonadMask m) => c -> LightT env m ()+ draw comp = liftMiniLight . renders =<< figures comp+ {-# INLINE draw #-} -import MiniLight.Component.Types-import MiniLight.Component.Loader+ -- | Event handlers+ onSignal :: (HasLightEnv env, HasLoopEnv env, HasComponentEnv env, MonadIO m, MonadMask m) => Event -> c -> LightT env m c+ onSignal _ = return++ -- | Return @True@ if a cache stored in the previous frame should be used.+ useCache+ :: c -- ^ A model value in the previous frame+ -> c -- ^ A model value in the current frame+ -> Bool+ useCache _ _ = False++ -- | To be called just before clearing caches.+ -- If you want to destroy cached textures for memory efficiency, override this method.+ --+ -- __NB__: Freeing SDL textures and figures are not performed automatically. You must call 'freeFigure' at your own risk.+ beforeClearCache :: (HasLightEnv env, MonadIO m, MonadMask m) => c -> [Figure] -> LightT env m ()+ beforeClearCache _ _ = return ()++-- | A wrapper for 'ComponentUnit' instances.+data Component = forall c. ComponentUnit c => Component {+ uidOf :: T.Text,+ component :: c,+ prev :: c,+ cache :: IORef [Figure],+ callbackObject :: Maybe HookMap+}++-- | Create a new component.+newComponent+ :: (ComponentUnit c, HasLightEnv env, MonadIO m, MonadMask m)+ => T.Text+ -> c+ -> LightT env m Component+newComponent uid c = do+ figs <- figures c+ ref <- liftIO $ newIORef figs+ return $ Component+ { uidOf = uid+ , component = c+ , prev = c+ , cache = ref+ , callbackObject = Nothing+ }++-- | Get the size of a component.+getComponentSize+ :: (ComponentUnit c, HasLightEnv env, MonadIO m, MonadMask m)+ => c+ -> LightT env m (SDL.Rectangle Int)+getComponentSize comp = do+ figs <- figures comp+ return $ foldl union (SDL.Rectangle (SDL.P 0) 0) $ map targetArea figs++-- | Get its unique id.+getUID :: Component -> T.Text+getUID (Component uid _ _ _ _) = uid++-- | Get the hooks+getHooks :: Component -> Maybe HookMap+getHooks (Component _ _ _ _ h) = h++-- | Get the hooks+setHooks :: Component -> Maybe HookMap -> Component+setHooks (Component uid comp prev cache _) h = Component uid comp prev cache h++-- | Clear the previous model cache and reflect the current model.+propagate :: Component -> Component+propagate (Component uid comp _ cache h) = Component uid comp comp cache h++instance ComponentUnit Component where+ update (Component uid comp prev cache h) = do+ comp' <- update comp+ return $ Component uid comp' prev cache h++ figures (Component _ comp _ _ _) = figures comp++ draw (Component _ comp prev ref _) = do+ if useCache prev comp+ then liftMiniLight . renders =<< liftIO (readIORef ref)+ else do+ figs <- liftIO (readIORef ref)+ beforeClearCache comp figs++ figs <- figures comp+ liftMiniLight $ renders figs+ liftIO $ writeIORef ref figs++ onSignal ev (Component uid comp prev cache h) = fmap (\comp' -> Component uid comp' prev cache h) $ onSignal ev comp
− src/MiniLight/Component/Internal/Resolver.hs
@@ -1,129 +0,0 @@-{-# LANGUAGE PatternSynonyms #-}-{-# LANGUAGE ViewPatterns #-}-module MiniLight.Component.Internal.Resolver where--import Control.Applicative-import Data.Aeson hiding (Result)-import qualified Data.HashMap.Strict as HM-import qualified Data.Text as T-import qualified Data.Text.Encoding as TE-import Data.Scientific (Scientific, fromFloatDigits)-import qualified Data.Vector as V-import Text.Trifecta--data Expr- = None- | Ref T.Text -- ^ reference syntax: ${ref:...}- | Var T.Text -- ^ variable syntax: ${var:...}- | Op T.Text Expr Expr -- ^ expr operator: +, -, *, /- | Constant Value -- ^ constants (string or number or null)- deriving (Eq, Show)--parser :: Parser Expr-parser = try reference <|> try variable <|> (char '$' *> braces (number <|> expr))- where- expr = chainl expr1 op1 None- expr1 = chainl expr2 op2 None- expr2 = parens expr <|> try reference <|> try variable <|> try number-- -- low precedence infixl operator group- op1 = Op "+" <$ textSymbol "+" <|> Op "-" <$ textSymbol "-"-- -- high precedence infixl operator group- op2 = Op "*" <$ textSymbol "*" <|> Op "/" <$ textSymbol "/"-- reference = do- char '$'- braces $ text "ref:" *> (fmap (Ref . T.pack) (many (letter <|> oneOf ".")))- variable = do- char '$'- braces $ text "var:" *> (fmap (Var . T.pack) (many (letter <|> oneOf ".")))- number = fmap (Constant . Number . either fromIntegral fromFloatDigits)- integerOrDouble--data Context = Context {- path :: V.Vector (Either Int T.Text),- variables :: Object,- target :: Value-}--getAt :: Value -> [Either Int T.Text] -> Value-getAt = go- where- go value [] = value- go (Object obj) (Right key:ps) | key `HM.member` obj = go (obj HM.! key) ps- go (Array arr) (Left i :ps) | 0 <= i && i < V.length arr = go (arr V.! i) ps- go v (p:_) =- error- $ "TypeError: path `"- <> show p- <> "` is missing in `"- <> show v- <> "`"--normalize- :: V.Vector (Either Int T.Text) -> [Either Int T.Text] -> [Either Int T.Text]-normalize path1 ts = V.toList path1' ++ dropWhile (\v -> v == Right "") ts- where- depth = length $ takeWhile (\v -> v == Right "") ts- path1' = V.take (V.length path1 - depth - 1) path1--pattern Arithmetic :: T.Text -> Scientific -> Scientific -> Expr-pattern Arithmetic op n1 n2 =- Op op (Constant (Number n1)) (Constant (Number n2))--eval :: Context -> Expr -> Value-eval ctx = go- where- go None = ""- go (Ref path') =- getAt (target ctx) (normalize (path ctx) (convertPath path'))- go (Var path') =- getAt (Object (variables ctx)) (normalize V.empty (convertPath path'))- go (binds -> Arithmetic "+" n1 n2) = Number (n1+n2)- go (binds -> Arithmetic "-" n1 n2) = Number (n1-n2)- go (binds -> Arithmetic "*" n1 n2) = Number (n1*n2)- go (binds -> Arithmetic "/" n1 n2) = Number (n1/n2)- go expr = error $ "Illegal expression: " ++ show expr-- binds (Op op e1 e2) = Op op (Constant (eval ctx e1)) (Constant (eval ctx e2))- binds _ = undefined--convertPath :: T.Text -> [Either Int T.Text]-convertPath- = map- ( \t ->- foldResult (\_ -> Right t) (Left . fromIntegral) $ parseText index t- )- . T.splitOn "."- . (\t -> if T.length t > 0 && T.head t == '.' then T.tail t else t)- where index = char '[' *> natural <* char ']'--convert :: Context -> T.Text -> Value-convert ctx t = foldResult (\_ -> String t) (eval ctx) $ parseText parser t--parseText :: Parser a -> T.Text -> Result a-parseText parser = parseByteString parser mempty . TE.encodeUtf8--resolve :: Value -> Value-resolve = \value -> go (Context V.empty HM.empty value) value- where- go ctx (Object obj)- | "_vars" `HM.member` obj- = let vars = obj HM.! "_vars"- in go- ( ctx- { variables = HM.union ((\(Object o) -> o) $ vars) (variables ctx)- }- )- (Object (HM.delete "_vars" obj))- | otherwise- = Object $ HM.mapWithKey- (\key -> go (ctx { path = V.snoc (path ctx) (Right key) }))- obj- go ctx (Array arr) =- Array $ V.imap (\i -> go (ctx { path = V.snoc (path ctx) (Left i) })) arr- go ctx (String t) = convert ctx t- go _ (Number n) = Number n- go _ (Bool b) = Bool b- go _ Null = Null
− src/MiniLight/Component/Internal/Types.hs
@@ -1,50 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-module MiniLight.Component.Internal.Types where--import Data.Aeson-import qualified Data.Text as T-import qualified Data.Vector as V-import qualified Data.UUID-import qualified Data.UUID.V4-import GHC.Generics-import MiniLight.Component.Types-import MiniLight.Light--data ComponentConfig = ComponentConfig {- name :: T.Text,- properties :: Value-} deriving (Eq, Show, Generic)--instance ToJSON ComponentConfig-instance FromJSON ComponentConfig--data AppConfig = AppConfig {- app :: V.Vector ComponentConfig,- uuid :: V.Vector T.Text-} deriving (Eq, Show)--instance FromJSON AppConfig where- parseJSON = withObject "app" $ \v -> do- app <- v .: "app"-- return $ AppConfig app V.empty---- | The type for component resolver-type Resolver- = T.Text -- ^ Component Type- -> T.Text -- ^ UID- -> Value -- ^ Component Property- -> MiniLight (Either String Component)---- | Generate an unique id.-newUID :: MonadIO m => m T.Text-newUID = liftIO $ Data.UUID.toText <$> Data.UUID.V4.nextRandom---- | Create a component with given resolver.-createComponentBy- :: Resolver- -> Maybe T.Text- -> ComponentConfig- -> MiniLight (Either String Component)-createComponentBy resolver uid config =- maybe newUID return uid >>= \u -> resolver (name config) u (properties config)
− src/MiniLight/Component/Loader.hs
@@ -1,217 +0,0 @@-module MiniLight.Component.Loader (- module MiniLight.Component.Internal.Types,-- LoaderEnv (..),- HasLoaderEnv (..),-- resolveConfig,- loadAppConfig,- patchAppConfig,-) where--import Control.Lens-import Control.Monad-import qualified Control.Monad.Caster as Caster-import Control.Monad.Catch-import Control.Monad.Trans-import Control.Monad.Trans.Maybe-import Data.Aeson-import qualified Data.Aeson.Diff as Diff-import Data.Aeson.Patch-import Data.Aeson.Pointer-import Data.IORef-import qualified Data.Registry as R-import qualified Data.Vector as V-import Data.Yaml (decodeFileEither)-import MiniLight.Light-import MiniLight.Component.Types-import MiniLight.Component.Internal.Types-import MiniLight.Component.Internal.Resolver (resolve)---- | The environment for config loader-data LoaderEnv = LoaderEnv {- registry :: R.Registry Component,- appConfig :: IORef AppConfig-}--makeClassy_ ''LoaderEnv--toEither :: Result a -> Either String a-toEither (Error s) = Left s-toEither (Success a) = Right a---- | Load an config file and return the resolved @AppConfig@.-resolveConfig :: MonadIO m => FilePath -> m (Either String AppConfig)-resolveConfig path =- liftIO- $ (toEither . fromJSON <=< fmap resolve)- . either (Left . show) Right- <$> decodeFileEither path---- | Load an config file and set in the environment. Calling this function at once, this overrides all values in the environment.--- This will generate an error log and skip the component if the configuration is invalid.--- This function also assign unique IDs for each component, using 'assignUID'.-loadAppConfig- :: (HasLightEnv env, HasLoaderEnv env, MonadIO m, MonadCatch m)- => FilePath -- ^ Filepath to the yaml file.- -> Resolver -- ^ Specify any resolver.- -> LightT env m ()-loadAppConfig path mapper = fmap (maybe () id) $ runMaybeT $ do- conf <- resolveConfig path >>= \case- Left e -> do- lift $ Caster.err e- fail ""- Right r -> return r-- confs <- lift $ fmap (V.mapMaybe id) $ V.forM (app conf) $ \conf -> do- uid <- newUID- result <- liftMiniLight $ mapper (name conf) uid (properties conf)-- case result of- Left e -> do- Caster.err e- return Nothing- Right component -> do- reg <- view _registry- R.register reg uid component-- Caster.info- $ "Component loaded: {name: "- <> show (name conf)- <> ", uid: "- <> show uid- <> "}"-- return $ Just (uid, conf)-- ref <- view _appConfig- liftIO $ writeIORef ref $ AppConfig (V.map snd confs) (V.map fst confs)---- | Load the config file again and place the newly loaded components. This is used for HCR (hot component replacement).--- Call 'loadAppConfig' first.-patchAppConfig- :: (HasLightEnv env, HasLoaderEnv env, MonadIO m, MonadCatch m)- => FilePath -- ^ Filepath to the yaml file.- -> Resolver -- ^ Specify any resolver.- -> LightT env m ()-patchAppConfig path resolver = fmap (maybe () id) $ runMaybeT $ do- cref <- view _appConfig- appConfig <- liftIO $ readIORef cref-- conf' <- do- mconf <- lift $ resolveConfig path-- case mconf of- Left e -> do- lift $ Caster.err e- fail ""- Right r -> return r-- lift- $ forM_- ( Diff.patchOperations- $ Diff.diff (toJSON $ app appConfig) (toJSON $ app conf')- )- $ \op -> fmap (maybe () id) $ runMaybeT $ do- lift $ Caster.debug $ "CMR detected: " <> show op-- case op of- Add (Pointer [AKey n]) v -> create n v- Rem (Pointer [AKey n]) -> remove n- Rep (Pointer [AKey n ]) v -> modify n (Rep (Pointer []) v)- Rep (Pointer (AKey n:path)) v -> modify n (Rep (Pointer path) v)- Add (Pointer (AKey n:path)) v -> modify n (Add (Pointer path) v)- Rem (Pointer (AKey n:path)) -> modify n (Rem (Pointer path))- _ ->- lift- $ Caster.warn- $ "CMR does not support the operation yet: "- <> show op- where- create n v = do- cref <- view _appConfig- compConf <- case fromJSON v of- Success a -> return a- Error err -> do- lift $ Caster.err err- fail ""-- newID <- lift newUID- component <- do- result <- lift $ liftMiniLight $ createComponentBy resolver- (Just newID)- compConf-- case result of- Left err -> do- lift $ Caster.err $ "Failed to resolve: " <> err- fail ""- Right c -> return c-- reg <- view _registry- lift $ R.insert reg n (getUID component) component-- lift- $ Caster.info- $ "Component registered: {name: "- <> show (name compConf)- <> ", uid: "- <> show (getUID component)- <> "}"-- liftIO $ modifyIORef' cref $ \conf -> conf- { app = V.snoc (app conf) compConf- , uuid = V.snoc (uuid conf) newID- }-- remove n = do- cref <- view _appConfig- appConf <- liftIO $ readIORef cref-- let uid = uuid appConf V.! n-- reg <- view _registry- lift $ R.delete reg uid- lift $ Caster.info $ "Component deleted: {uid: " <> show uid <> "}"-- liftIO $ writeIORef cref $ appConf- { app = V.ifilter (\i _ -> i /= n) $ app appConf- , uuid = V.ifilter (\i _ -> i /= n) $ uuid appConf- }-- modify n op = do- cref <- view _appConfig- appConf <- liftIO $ readIORef cref-- compConf <-- case Diff.applyOperation op (toJSON (app appConf V.! n)) >>= fromJSON of- Success a -> return a- Error err -> do- lift $ Caster.err err- fail ""-- let uid = uuid appConf V.! n-- component <- do- result <- lift $ liftMiniLight $ createComponentBy resolver- (Just uid)- compConf-- case result of- Left err -> do- lift $ Caster.err $ "Failed to resolve: " <> err- fail ""- Right c -> return c-- reg <- view _registry- lift $ R.write reg uid component- lift- $ Caster.info- $ "Component replaced: {name: "- <> show (name compConf)- <> ", uid: "- <> show (getUID component)- <> "}"-- liftIO $ writeIORef cref $ appConf { app = app appConf V.// [(n, compConf)]- }
− src/MiniLight/Component/Types.hs
@@ -1,125 +0,0 @@-{-# LANGUAGE ExistentialQuantification #-}-module MiniLight.Component.Types (- HasComponentEnv(..),- emit,-- ComponentUnit(..),- Component,- newComponent,- getComponentSize,- getUID,- propagate,-) where--import Control.Lens-import Control.Monad.Catch-import Control.Monad.IO.Class-import Data.IORef-import qualified Data.Text as T-import MiniLight.Light-import MiniLight.Event-import MiniLight.Figure-import qualified SDL--class HasComponentEnv env where- -- | Lens to the unique id, which is provided for each component.- uidL :: Lens' env T.Text---- | Emit a signal, which will be catched at the next frame.-emit- :: (HasLoopEnv env, HasComponentEnv env, MonadIO m, EventType et)- => et- -> LightT env m ()-emit et = do- uid <- view uidL- ref <- view _signalQueue- liftIO $ modifyIORef' ref $ (signal uid et :)---- | CompoonentUnit typeclass provides a way to define a new component.--- Any 'ComponentUnit' instance can be embedded into 'Component' type.-class ComponentUnit c where- -- | Updating a model.- update :: (HasLightEnv env, HasLoopEnv env, HasComponentEnv env, MonadIO m, MonadMask m) => c -> LightT env m c- update = return-- -- | Descirbes a view. The figures here would be cached. See also 'useCache' for the cache configuration.- figures :: (HasLightEnv env, MonadIO m, MonadMask m) => c -> LightT env m [Figure]-- -- | Drawing a figures.- draw :: (HasLightEnv env, MonadIO m, MonadMask m) => c -> LightT env m ()- draw comp = liftMiniLight . renders =<< figures comp- {-# INLINE draw #-}-- -- | Event handlers- onSignal :: (HasLightEnv env, HasLoopEnv env, HasComponentEnv env, MonadIO m, MonadMask m) => Event -> c -> LightT env m c- onSignal _ = return-- -- | Return @True@ if a cache stored in the previous frame should be used.- useCache- :: c -- ^ A model value in the previous frame- -> c -- ^ A model value in the current frame- -> Bool- useCache _ _ = False-- -- | To be called just before clearing caches.- -- If you want to destroy cached textures for memory efficiency, override this method.- --- -- __NB__: Freeing SDL textures and figures are not performed automatically. You must call 'freeFigure' at your own risk.- beforeClearCache :: (HasLightEnv env, MonadIO m, MonadMask m) => c -> [Figure] -> LightT env m ()- beforeClearCache _ _ = return ()---- | A wrapper for 'ComponentUnit' instances.-data Component = forall c. ComponentUnit c => Component {- uid :: T.Text,- component :: c,- prev :: c,- cache :: IORef [Figure]-}---- | Create a new component.-newComponent- :: (ComponentUnit c, HasLightEnv env, MonadIO m, MonadMask m)- => T.Text- -> c- -> LightT env m Component-newComponent uid c = do- figs <- figures c- ref <- liftIO $ newIORef figs- return $ Component {uid = uid, component = c, prev = c, cache = ref}---- | Get the size of a component.-getComponentSize- :: (ComponentUnit c, HasLightEnv env, MonadIO m, MonadMask m)- => c- -> LightT env m (SDL.Rectangle Int)-getComponentSize comp = do- figs <- figures comp- return $ foldl union (SDL.Rectangle (SDL.P 0) 0) $ map targetArea figs---- | Get its unique id.-getUID :: Component -> T.Text-getUID (Component uid _ _ _) = uid---- | Clear the previous model cache and reflect the current model.-propagate :: Component -> Component-propagate (Component uid comp _ cache) = Component uid comp comp cache--instance ComponentUnit Component where- update (Component uid comp prev cache) = do- comp' <- update comp- return $ Component uid comp' prev cache-- figures (Component _ comp _ _) = figures comp-- draw (Component _ comp prev ref) = do- if useCache prev comp- then liftMiniLight . renders =<< liftIO (readIORef ref)- else do- figs <- liftIO (readIORef ref)- beforeClearCache comp figs-- figs <- figures comp- liftMiniLight $ renders figs- liftIO $ writeIORef ref figs-- onSignal ev (Component uid comp prev cache) = fmap (\comp' -> Component uid comp' prev cache) $ onSignal ev comp
src/MiniLight/Event.hs view
@@ -1,23 +1,32 @@+{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} module MiniLight.Event ( Event (..),- EventType,+ EventType (..), signal, asSignal, ) where import qualified SDL+import Data.Aeson+import qualified Data.HashMap.Strict as HM import qualified Data.Text as T import Data.Type.Equality import Type.Reflection import qualified System.FSNotify as Notify -- | EventType says some type can be used as an event type.-class Typeable e => EventType e+class Typeable e => EventType e where+ getEventType :: e -> T.Text+ default getEventType :: Show e => e -> T.Text+ getEventType = T.pack . show + getEventProperties :: e -> Object+ getEventProperties _ = HM.empty+ -- | This type is same as 'Dynamic' from @Data.Dynamic@, but it requires 'EventType' contraint. data Dynamic where Dynamic :: forall a. EventType a => TypeRep a -> a -> Dynamic@@ -32,8 +41,8 @@ -- | Event type representation data Event- = Never- | Signal T.Text Dynamic+ = Signal T.Text Dynamic+ | GlobalSignal T.Text Value | RawEvent SDL.Event | NotifyEvent Notify.Event
+ src/MiniLight/Loader.hs view
@@ -0,0 +1,279 @@+{-| The package provides the configuration loader.++An configuration example:++@+_vars:+ window:+ width: 800+ height: 600+app:+ - name: message-layer+ properties:+ window:+ image: resources/window-base.png+ position:+ x: 0+ y: ${${var:window.height} - ${ref:..size.height}}+ size:+ width: ${var:window.width}+ height: 150+@++== Syntax++=== @_vars@++You can define a new variable. Use object syntax under the @_vars@ field.++The variables can be referenced in all siblings and under their siblings to the @_vars@, in the variable syntax @${var:_path_}@.++=== Expr++In each field, you can specify an expression defined in the loader.++- @${}@: enclose the expr by @${}@, to tell the parsers that the field is an expr not a plain string.+- @${ref:_path_}@: specify any path to refer any other value. The path resolution is performed once, not recursively resolved. @_path_@ consists of field names splitted by a period. Use double dots @..@ for a parent.+- @${var:_path_}@: specify any path to value defined at the field. @_path_@ consists of field names splitted by a period.+- arithmetic operator: addition, subtraction, multiplication and division (@+,-,*,/@) can also be used in @${}@.++-}+module MiniLight.Loader (+ module MiniLight.Loader.Internal.Types,+ createComponentBy,++ LoaderEnv (..),+ HasLoaderEnv (..),++ resolveConfig,+ loadAppConfig,+ patchAppConfig,++ resolve,+ parseAppConfig,+) where++import Control.Lens+import Control.Monad+import qualified Control.Monad.Caster as Caster+import Control.Monad.Catch+import Control.Monad.Trans+import Control.Monad.Trans.Maybe+import Data.Aeson+import qualified Data.Aeson.Diff as Diff+import Data.Aeson.Patch+import Data.Aeson.Pointer+import Data.IORef+import qualified Data.Registry as R+import qualified Data.Text as T+import qualified Data.Vector as V+import Data.Yaml (decodeFileEither)+import MiniLight.Light+import MiniLight.Component+import MiniLight.Loader.Internal.Types+import MiniLight.Loader.Internal.Resolver (resolve, resolveWith, parseAppConfig, emptyContext, Context(..))++-- | Create a component with given resolver.+createComponentBy+ :: Resolver+ -> Maybe T.Text+ -> ComponentConfig+ -> MiniLight (Either String Component)+createComponentBy resolver uid config = do+ uuid <- maybe newUID return uid+ result <- resolver (name config) uuid (properties config)+ return $ fmap+ ( \c -> setHooks+ c+ ( fmap+ ( fmap $ \hk -> (,) (signalName hk) $ \evprops ->+ (\(Right r) -> r)+ $ resolveWith (emptyContext { values = evprops }) (parameter hk)+ )+ $ hooks config+ )+ )+ result++-- | The environment for config loader+data LoaderEnv = LoaderEnv {+ registry :: R.Registry Component,+ appConfig :: IORef AppConfig+}++makeClassy_ ''LoaderEnv++-- | Load an config file and return the resolved @AppConfig@.+resolveConfig :: MonadIO m => FilePath -> m (Either T.Text AppConfig)+resolveConfig path =+ liftIO+ $ (parseAppConfig <=< either (Left . T.pack . show) Right)+ <$> decodeFileEither path++-- | Load an config file and set in the environment. Calling this function at once, this overrides all values in the environment.+-- This will generate an error log and skip the component if the configuration is invalid.+-- This function also assign unique IDs for each component, using 'assignUID'.+loadAppConfig+ :: (HasLightEnv env, HasLoaderEnv env, MonadIO m, MonadCatch m)+ => FilePath -- ^ Filepath to the yaml file.+ -> Resolver -- ^ Specify any resolver.+ -> LightT env m ()+loadAppConfig path mapper = fmap (maybe () id) $ runMaybeT $ do+ conf <- resolveConfig path >>= \case+ Left e -> do+ lift $ Caster.err e+ fail ""+ Right r -> return r++ confs <- lift $ fmap (V.mapMaybe id) $ V.forM (app conf) $ \conf -> do+ uid <- newUID+ result <- liftMiniLight $ createComponentBy mapper (Just uid) conf++ case result of+ Left e -> do+ Caster.err e+ return Nothing+ Right component -> do+ reg <- view _registry+ R.register reg uid component++ Caster.info+ $ "Component loaded: {name: "+ <> show (name conf)+ <> ", uid: "+ <> show uid+ <> "}"++ return $ Just (uid, conf)++ ref <- view _appConfig+ liftIO $ writeIORef ref $ AppConfig (V.map snd confs) (V.map fst confs)++-- | Load the config file again and place the newly loaded components. This is used for HCR (hot component replacement).+-- Call 'loadAppConfig' first.+patchAppConfig+ :: (HasLightEnv env, HasLoaderEnv env, MonadIO m, MonadCatch m)+ => FilePath -- ^ Filepath to the yaml file.+ -> Resolver -- ^ Specify any resolver.+ -> LightT env m ()+patchAppConfig path resolver = fmap (maybe () id) $ runMaybeT $ do+ cref <- view _appConfig+ appConfig <- liftIO $ readIORef cref++ conf' <- do+ mconf <- lift $ resolveConfig path++ case mconf of+ Left e -> do+ lift $ Caster.err e+ fail ""+ Right r -> return r++ lift+ $ forM_+ ( Diff.patchOperations+ $ Diff.diff (toJSON $ app appConfig) (toJSON $ app conf')+ )+ $ \op -> fmap (maybe () id) $ runMaybeT $ do+ lift $ Caster.debug $ "CMR detected: " <> show op++ case op of+ Add (Pointer [AKey n]) v -> create n v+ Rem (Pointer [AKey n]) -> remove n+ Rep (Pointer [AKey n ]) v -> modify n (Rep (Pointer []) v)+ Rep (Pointer (AKey n:path)) v -> modify n (Rep (Pointer path) v)+ Add (Pointer (AKey n:path)) v -> modify n (Add (Pointer path) v)+ Rem (Pointer (AKey n:path)) -> modify n (Rem (Pointer path))+ _ ->+ lift+ $ Caster.warn+ $ "CMR does not support the operation yet: "+ <> show op+ where+ create n v = do+ cref <- view _appConfig+ compConf <- case fromJSON v of+ Success a -> return a+ Error err -> do+ lift $ Caster.err err+ fail ""++ newID <- lift newUID+ component <- do+ result <- lift $ liftMiniLight $ createComponentBy resolver+ (Just newID)+ compConf++ case result of+ Left err -> do+ lift $ Caster.err $ "Failed to resolve: " <> err+ fail ""+ Right c -> return c++ reg <- view _registry+ lift $ R.insert reg n (getUID component) component++ lift+ $ Caster.info+ $ "Component registered: {name: "+ <> show (name compConf)+ <> ", uid: "+ <> show (getUID component)+ <> "}"++ liftIO $ modifyIORef' cref $ \conf -> conf+ { app = V.snoc (app conf) compConf+ , uuid = V.snoc (uuid conf) newID+ }++ remove n = do+ cref <- view _appConfig+ appConf <- liftIO $ readIORef cref++ let uid = uuid appConf V.! n++ reg <- view _registry+ lift $ R.delete reg uid+ lift $ Caster.info $ "Component deleted: {uid: " <> show uid <> "}"++ liftIO $ writeIORef cref $ appConf+ { app = V.ifilter (\i _ -> i /= n) $ app appConf+ , uuid = V.ifilter (\i _ -> i /= n) $ uuid appConf+ }++ modify n op = do+ cref <- view _appConfig+ appConf <- liftIO $ readIORef cref++ compConf <-+ case Diff.applyOperation op (toJSON (app appConf V.! n)) >>= fromJSON of+ Success a -> return a+ Error err -> do+ lift $ Caster.err err+ fail ""++ let uid = uuid appConf V.! n++ component <- do+ result <- lift $ liftMiniLight $ createComponentBy resolver+ (Just uid)+ compConf++ case result of+ Left err -> do+ lift $ Caster.err $ "Failed to resolve: " <> err+ fail ""+ Right c -> return c++ reg <- view _registry+ lift $ R.write reg uid component+ lift+ $ Caster.info+ $ "Component replaced: {name: "+ <> show (name compConf)+ <> ", uid: "+ <> show (getUID component)+ <> "}"++ liftIO $ writeIORef cref $ appConf { app = app appConf V.// [(n, compConf)]+ }
+ src/MiniLight/Loader/Internal/Resolver.hs view
@@ -0,0 +1,223 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE ViewPatterns #-}+module MiniLight.Loader.Internal.Resolver where++import Control.Applicative+import Control.Monad+import Data.Aeson hiding (Result)+import qualified Data.HashMap.Strict as HM+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE+import Data.Scientific (fromFloatDigits)+import qualified Data.Vector as V+import GHC.Generics (Generic)+import MiniLight.Loader.Internal.Types+import Text.Trifecta++data Expr+ = None+ | Ref T.Text -- ^ reference syntax: ${ref:...}+ | Var T.Text -- ^ variable syntax: ${var:...}+ | Op T.Text Expr Expr -- ^ expr operator: +, -, *, /+ | Constant Value -- ^ constants (string or number or null)+ | Symbol T.Text -- ^ token symbol+ | App Expr [Expr] -- ^ function application ($func(a,b,c))+ deriving (Eq, Show, Generic)++instance ToJSON Expr+instance FromJSON Expr++parser :: Parser Expr+parser = try reference <|> try variable <|> try (char '$' *> braces expr)+ where+ expr = chainl expr1 op1 None+ expr1 = chainl expr2 op2 None+ expr2 =+ parens expr+ <|> try apply+ <|> try parameter+ <|> try reference+ <|> try variable+ <|> try number+ <|> try strlit++ -- low precedence infixl operator group+ op1 = Op "+" <$ textSymbol "+" <|> Op "-" <$ textSymbol "-"++ -- high precedence infixl operator group+ op2 = Op "*" <$ textSymbol "*" <|> Op "/" <$ textSymbol "/"++ reference = char '$' *> do+ braces $ text "ref:" *> (fmap (Ref . T.pack) (many (letter <|> oneOf ".")))+ variable = char '$' *> do+ braces $ text "var:" *> (fmap (Var . T.pack) (many (letter <|> oneOf ".")))+ number = fmap (Constant . Number . either fromIntegral fromFloatDigits)+ integerOrDouble+ strlit = fmap (Constant . String) $ stringLiteral++ parameter = char '$' *> do+ fmap (Symbol . T.pack) $ (:) <$> letter <*> many (letter <|> digit)+ apply = do+ func <- parameter+ exps <-+ parens+ $ option []+ $ fmap (filter (/= None))+ $ try+ $ (:)+ <$> expr+ <*> expr+ `sepBy` (char ',')+ return $ App func exps++data Context = Context {+ path :: V.Vector (Either Int T.Text),+ variables :: Object,+ values :: HM.HashMap T.Text Value+}++emptyContext :: Context+emptyContext = Context V.empty HM.empty HM.empty++getAt :: Value -> [Either Int T.Text] -> Either T.Text Value+getAt = go+ where+ go value [] = Right value+ go (Object obj) (Right key:ps) | key `HM.member` obj = go (obj HM.! key) ps+ go (Array arr) (Left i :ps) | 0 <= i && i < V.length arr = go (arr V.! i) ps+ go v (p:_) =+ Left+ $ "TypeError: path `"+ <> T.pack (show p)+ <> "` is missing in `"+ <> T.pack (show v)+ <> "`"++normalize+ :: V.Vector (Either Int T.Text) -> [Either Int T.Text] -> [Either Int T.Text]+normalize path1 ts = V.toList path1' ++ dropWhile (\v -> v == Right "") ts+ where+ depth = length $ takeWhile (\v -> v == Right "") ts+ path1' = V.take (V.length path1 - depth - 1) path1++eval :: Context -> Value -> Expr -> Either T.Text Value+eval ctx target = go+ where+ go None = Right ""+ go (Ref path') =+ either (Left . (("Error in `${ref:" <> path' <> "}`\n") <>)) Right+ $ getAt target (normalize (path ctx) (convertPath path'))+ go (Var path') =+ either (Left . (("Error in `${var:" <> path' <> "}`\n") <>)) Right+ $ getAt (Object (variables ctx)) (normalize V.empty (convertPath path'))+ go (Op "+" e1 e2) = runOp (+) e1 e2+ go (Op "-" e1 e2) = runOp (-) e1 e2+ go (Op "*" e1 e2) = runOp (*) e1 e2+ go (Op "/" e1 e2) = runOp (/) e1 e2+ go (Symbol t) =+ maybe (Left $ "Symbol not defined: `" <> t <> "`") Right+ $ HM.lookup t+ $ values ctx+ go expr = Left $ "Illegal expression: " <> T.pack (show expr)++ runOp op e1 e2 =+ fmap Number+ $ join+ $ (\x y -> op <$> asNumber x <*> asNumber y)+ <$> go e1+ <*> go e2++ asNumber (Number x) = Right x+ asNumber x = Left $ "Not a number: " <> T.pack (show x)++convertPath :: T.Text -> [Either Int T.Text]+convertPath =+ map (\t -> either (\_ -> Right t) (Left . fromIntegral) $ parseText index t)+ . T.splitOn "."+ . (\t -> if T.length t > 0 && T.head t == '.' then T.tail t else t)+ where index = char '[' *> natural <* char ']'++convert :: Context -> Value -> T.Text -> Either T.Text Value+convert ctx target t =+ either (\_ -> Right $ String t) (eval ctx target) $ parseText parser t++parseText :: Parser a -> T.Text -> Either T.Text a+parseText parser =+ foldResult (Left . T.pack . show) Right+ . parseByteString parser mempty+ . TE.encodeUtf8++resolveWith :: Context -> Value -> Either T.Text Value+resolveWith ctx target = go ctx target+ where+ go ctx (Object obj)+ | "_vars" `HM.member` obj+ = let vars = obj HM.! "_vars"+ in go+ ( ctx+ { variables = HM.union ((\(Object o) -> o) $ vars) (variables ctx)+ }+ )+ (Object (HM.delete "_vars" obj))+ | otherwise+ = fmap Object $ sequence $ HM.mapWithKey+ (\key -> go (ctx { path = V.snoc (path ctx) (Right key) }))+ obj+ go ctx (Array arr) = fmap Array $ sequence $ V.imap+ (\i -> go (ctx { path = V.snoc (path ctx) (Left i) }))+ arr+ go ctx (String t) = convert ctx target t+ go _ (Number n) = Right $ Number n+ go _ (Bool b) = Right $ Bool b+ go _ Null = Right Null++-- | Interpret a JSON value, and unsafely apply fromRight+resolve :: Value -> Value+resolve = (\(Right a) -> a) . resolveWith emptyContext++-- | Create 'AppConfig' value from JSON value+parseAppConfig :: Value -> Either T.Text AppConfig+parseAppConfig = conf (Context V.empty HM.empty HM.empty)+ where+ conf :: Context -> Value -> Either T.Text AppConfig+ conf ctx (Object obj) | "app" `HM.member` obj =+ let+ ctx' = maybe+ ctx+ ( \vars -> ctx+ { variables = HM.union ((\(Object o) -> o) vars) (variables ctx)+ }+ )+ (HM.lookup "_vars" obj)+ in+ fmap (\v -> AppConfig v V.empty) $ app ctx' (obj HM.! "app")+ conf _ (Object obj) =+ Left $ "path `app` is missing in " <> T.pack (show (Object obj))+ conf _ ast = Left $ "Invalid format: " <> T.pack (show ast)++ app :: Context -> Value -> Either T.Text (V.Vector ComponentConfig)+ app ctx (Array vec) = V.mapM (component ctx) vec+ app _ ast = Left $ "Invalid format: " <> T.pack (show ast)++ component :: Context -> Value -> Either T.Text ComponentConfig+ component ctx (Object obj) | all (`HM.member` obj) ["name", "properties"] = do+ let+ ctx' = maybe+ ctx+ ( \vars -> ctx+ { variables = HM.union ((\(Object o) -> o) vars) (variables ctx)+ }+ )+ (HM.lookup "_vars" obj)++ nameValue <- resolveWith ctx' (obj HM.! "name")+ case nameValue of+ String name -> do+ props <- resolveWith ctx' (obj HM.! "properties")+ hooks <-+ sequence+ $ (fmap (\(Object o) -> mapM toHook o) $ HM.lookup "hooks" obj)+ Right $ ComponentConfig name props hooks+ _ -> Left $ "Invalid format: " <> T.pack (show nameValue)+ component _ ast = Left $ "Invalid format: " <> T.pack (show ast)
+ src/MiniLight/Loader/Internal/Types.hs view
@@ -0,0 +1,71 @@+{-# LANGUAGE DeriveGeneric #-}+module MiniLight.Loader.Internal.Types where++import Data.Aeson+import qualified Data.HashMap.Strict as HM+import qualified Data.Text as T+import qualified Data.Vector as V+import qualified Data.UUID+import qualified Data.UUID.V4+import GHC.Generics (Generic)+import MiniLight.Component+import MiniLight.Light++data Hook = Hook {+ signalName :: T.Text,+ parameter :: Value+} deriving (Show, Generic)++instance ToJSON Hook++instance FromJSON Hook where+ parseJSON = withObject "hook" $ \v ->+ Hook <$> v .: "name" <*> v .: "parameter"++toHook :: Value -> Either T.Text Hook+toHook =+ ( \case+ Success a -> Right a+ Error err -> Left $ T.pack $ show err+ )+ . fromJSON++-- | A configuration for a component+data ComponentConfig = ComponentConfig {+ name :: T.Text,+ properties :: Value,+ hooks :: Maybe (HM.HashMap T.Text Hook)+}++instance ToJSON ComponentConfig where+ toJSON v = toJSON $ HM.fromList [("name" :: String, String (name v)), ("properties", properties v)]++instance FromJSON ComponentConfig where+ parseJSON = withObject "component" $ \v -> do+ name <- v .: "name"+ props <- v .: "properties"++ return $ ComponentConfig name props Nothing++-- | A configuration for the application itself+data AppConfig = AppConfig {+ app :: V.Vector ComponentConfig,+ uuid :: V.Vector T.Text+}++instance FromJSON AppConfig where+ parseJSON = withObject "app" $ \v -> do+ app <- v .: "app"++ return $ AppConfig app V.empty++-- | The type for component resolver+type Resolver+ = T.Text -- ^ Component Type+ -> T.Text -- ^ UID+ -> Value -- ^ Component Property+ -> MiniLight (Either String Component)++-- | Generate an unique id.+newUID :: MonadIO m => m T.Text+newUID = liftIO $ Data.UUID.toText <$> Data.UUID.V4.nextRandom