minilight 0.4.0 → 0.4.1
raw patch · 18 files changed
+574/−261 lines, 18 filesdep ~hashabledep ~sdl2dep ~template-haskellPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: hashable, sdl2, template-haskell, trifecta
API changes (from Hackage documentation)
- Data.Component.MessageEngine: NextPage :: EngineEvent
- Data.Component.Resolver: foldResult :: (String -> b) -> (a -> b) -> Result a -> b
- Data.Component.Selection: [conf] :: Selection -> Config
- Data.Component.Selection: hoverL :: Lens' Selection (Maybe Int)
- MiniLight.Event: GlobalSignal :: Text -> Value -> Event
- MiniLight.Loader.Internal.Types: [name] :: ComponentConfig -> Text
+ Control.Monad.Caster: iohandleLogger :: Handle -> LogLevel -> IO LogQueue
+ Data.Component.Basic: [SetVisibility] :: Bool -> Signal
+ Data.Component.Basic: [visible] :: Config -> Bool
+ Data.Component.Basic: _position :: HasConfig c_a1m7T => Lens' c_a1m7T (V2 Int)
+ Data.Component.Basic: _size :: HasConfig c_a1m7T => Lens' c_a1m7T (V2 Int)
+ Data.Component.Basic: _visible :: HasConfig c_a1m7T => Lens' c_a1m7T Bool
+ Data.Component.Basic: class HasConfig c_a1m7T
+ Data.Component.Basic: config :: HasConfig c_a1m7T => Lens' c_a1m7T Config
+ Data.Component.Basic: defConfig :: Config
+ Data.Component.Basic: handleBasicSignal :: (HasLightEnv env, HasLoopEnv env, HasComponentEnv env, MonadIO m) => Event -> Config -> LightT env m Config
+ Data.Component.Basic: instance Data.Component.Basic.HasConfig Data.Component.Basic.Config
+ Data.Component.Basic: instance GHC.Show.Show Data.Component.Basic.Config
+ Data.Component.Layer: _basic :: Lens' Config Config
+ Data.Component.Layer: _config :: Lens' Layer Config
+ Data.Component.Layer: _image :: Lens' Config FilePath
+ Data.Component.Layer: _layer :: Lens' Layer Figure
+ Data.Component.Layer: instance Data.Component.Basic.HasConfig Data.Component.Layer.Config
+ Data.Component.MessageEngine: [NextPage] :: EngineEvent
+ Data.Component.MessageEngine: [SetMessage] :: [Text] -> EngineEvent
+ Data.Component.MessageEngine: [currentMessages] :: MessageEngine -> Vector Text
+ Data.Component.MessageEngine: _currentMessages :: Lens' MessageEngine (Vector Text)
+ Data.Component.MessageLayer: [Finish] :: MessageLayerEvent
+ Data.Component.MessageLayer: [basic] :: Config -> Config
+ Data.Component.MessageLayer: _basic :: Lens' Config Config
+ Data.Component.MessageLayer: _engine :: Lens' Config Config
+ Data.Component.MessageLayer: _next :: Lens' Config Config
+ Data.Component.MessageLayer: _window :: Lens' Config Config
+ Data.Component.MessageLayer: data MessageLayerEvent
+ Data.Component.MessageLayer: instance Data.Component.Basic.HasConfig Data.Component.MessageLayer.Config
+ Data.Component.MessageLayer: instance MiniLight.Event.EventType Data.Component.MessageLayer.MessageLayerEvent
+ Data.Component.Resolver: extendResolver :: (FromJSON a, ComponentUnit c) => Text -> (a -> MiniLight c) -> Resolver -> Resolver
+ Data.Component.Selection: SetOptions :: [Text] -> SelectionEvent
+ Data.Component.Selection: [config] :: Selection -> Config
+ Data.Component.Selection: [currentLabels] :: Selection -> Vector Text
+ Data.Component.Selection: _basic :: Lens' Config Config
+ Data.Component.Selection: _config :: Lens' Selection Config
+ Data.Component.Selection: _currentLabels :: Lens' Selection (Vector Text)
+ Data.Component.Selection: _font :: Lens' Selection Font
+ Data.Component.Selection: _fontConfig :: Lens' Config Config
+ Data.Component.Selection: _hover :: Lens' Selection (Maybe Int)
+ Data.Component.Selection: _image :: Lens' Config FilePath
+ Data.Component.Selection: _labels :: Lens' Config (Vector Text)
+ Data.Component.Selection: _layer :: Lens' Selection Layer
+ Data.Component.Selection: instance Data.Component.Basic.HasConfig Data.Component.Selection.Config
+ Data.Config.Font: textFrom :: Config -> Text -> MiniLight Figure
+ MiniLight: (@@!) :: (EventType et, HasLoaderEnv env, HasLoopEnv env, HasLightEnv env, MonadIO m) => Text -> et -> LightT env m ()
+ MiniLight: LightConfig :: Bool -> (LogLevel -> IO LogQueue) -> LogLevel -> LightConfig
+ MiniLight: [headless] :: LightConfig -> Bool
+ MiniLight: [logLevel] :: LightConfig -> LogLevel
+ MiniLight: [logQueue] :: LightConfig -> LogLevel -> IO LogQueue
+ MiniLight: data LightConfig
+ MiniLight: defLightConfig :: LightConfig
+ MiniLight: quit :: (MonadIO m, HasLoopEnv env) => LightT env m ()
+ MiniLight: runComponentEnv :: (HasLightEnv env, HasLoopEnv env) => Component -> (forall env'. (HasComponentEnv env', HasLoopEnv env', HasLightEnv env') => LightT env' m ()) -> LightT env m ()
+ MiniLight: runLightTWith :: (MonadIO m, MonadMask m) => LightConfig -> LightT LightEnv m a -> m a
+ MiniLight.Component: _unsafeAs :: ComponentUnit c => Lens' Component c
+ MiniLight.Component: emitGlobally :: (HasLoopEnv env, HasComponentEnv env, MonadIO m, EventType et) => et -> LightT env m ()
+ MiniLight.Event: EventData :: Text -> Value -> EventData
+ MiniLight.Event: asEventData :: Event -> Maybe EventData
+ MiniLight.Event: asNotifyEvent :: Event -> Maybe Event
+ MiniLight.Event: asRawEvent :: Event -> Maybe Event
+ MiniLight.Event: data EventData
+ MiniLight.Event: instance GHC.Show.Show MiniLight.Event.EventData
+ MiniLight.Event: instance MiniLight.Event.EventType MiniLight.Event.EventData
+ MiniLight.Figure: emptyFigure :: Figure
+ MiniLight.Loader: [tagRegistry] :: LoaderEnv -> IORef (HashMap Text Text)
+ MiniLight.Loader: _tagRegistry :: HasLoaderEnv c_a16iS => Lens' c_a16iS (IORef (HashMap Text Text))
+ MiniLight.Loader: lookupByTagID :: (HasLightEnv env, HasLoaderEnv env, MonadIO m) => Text -> LightT env m (Maybe Text)
+ MiniLight.Loader.Internal.Types: [componentType] :: ComponentConfig -> Text
+ MiniLight.Loader.Internal.Types: [tagID] :: ComponentConfig -> Maybe Text
- Data.Component.Basic: Config :: V2 Int -> V2 Int -> Config
+ Data.Component.Basic: Config :: V2 Int -> V2 Int -> Bool -> Config
- Data.Component.Basic: wrapSignal :: (HasLightEnv env, HasLoopEnv env, HasComponentEnv env, MonadIO m, ComponentUnit c) => (c -> Config) -> (Event -> c -> LightT env m c) -> Event -> c -> LightT env m c
+ Data.Component.Basic: wrapSignal :: (HasLightEnv env, HasLoopEnv env, HasComponentEnv env, MonadIO m, ComponentUnit c) => Lens' c Config -> (Event -> c -> LightT env m c) -> Event -> c -> LightT env m c
- Data.Component.MessageEngine: MessageEngine :: Font -> Int -> Int -> Int -> Figure -> Bool -> Config -> MessageEngine
+ Data.Component.MessageEngine: MessageEngine :: Font -> Int -> Int -> Int -> Figure -> Bool -> Vector Text -> Config -> MessageEngine
- Data.Component.MessageLayer: Config :: Config -> Config -> Config -> Config
+ Data.Component.MessageLayer: Config :: Config -> Config -> Config -> Config -> Config
- Data.Component.Selection: Selection :: Layer -> Font -> Maybe Int -> Config -> Selection
+ Data.Component.Selection: Selection :: Layer -> Font -> Maybe Int -> Config -> Vector Text -> Selection
- Data.Config.Font: _color :: HasConfig c_a1cqt => Lens' c_a1cqt (V4 Word8)
+ Data.Config.Font: _color :: HasConfig c_a1fQz => Lens' c_a1fQz (V4 Word8)
- Data.Config.Font: _descriptor :: HasConfig c_a1cqt => Lens' c_a1cqt FontDescriptor
+ Data.Config.Font: _descriptor :: HasConfig c_a1fQz => Lens' c_a1fQz FontDescriptor
- Data.Config.Font: _size :: HasConfig c_a1cqt => Lens' c_a1cqt Int
+ Data.Config.Font: _size :: HasConfig c_a1fQz => Lens' c_a1fQz Int
- Data.Config.Font: class HasConfig c_a1cqt
+ Data.Config.Font: class HasConfig c_a1fQz
- Data.Config.Font: config :: HasConfig c_a1cqt => Lens' c_a1cqt Config
+ Data.Config.Font: config :: HasConfig c_a1fQz => Lens' c_a1fQz Config
- MiniLight.Component: _callbacks :: HasComponentEnv c_aNwC => Lens' c_aNwC (Maybe HookMap)
+ MiniLight.Component: _callbacks :: HasComponentEnv c_aP76 => Lens' c_aP76 (Maybe HookMap)
- MiniLight.Component: _uid :: HasComponentEnv c_aNwC => Lens' c_aNwC Text
+ MiniLight.Component: _uid :: HasComponentEnv c_aP76 => Lens' c_aP76 Text
- MiniLight.Component: class HasComponentEnv c_aNwC
+ MiniLight.Component: class HasComponentEnv c_aP76
- MiniLight.Component: componentEnv :: HasComponentEnv c_aNwC => Lens' c_aNwC ComponentEnv
+ MiniLight.Component: componentEnv :: HasComponentEnv c_aP76 => Lens' c_aP76 ComponentEnv
- MiniLight.Component: emit :: (HasLoopEnv env, HasComponentEnv env, MonadIO m, EventType et) => et -> LightT env m ()
+ MiniLight.Component: emit :: (HasLoopEnv env, HasComponentEnv env, MonadIO m, EventType et) => Maybe Text -> et -> LightT env m ()
- MiniLight.Event: Signal :: Text -> Dynamic -> Event
+ MiniLight.Event: Signal :: Text -> Maybe Text -> Dynamic -> Event
- MiniLight.Event: asSignal :: EventType a => Event -> Text -> Maybe a
+ MiniLight.Event: asSignal :: EventType a => Event -> Maybe a
- MiniLight.Event: signal :: EventType a => Text -> a -> Event
+ MiniLight.Event: signal :: EventType a => Text -> Maybe Text -> a -> Event
- MiniLight.Figure: Figure :: Texture -> Rectangle Int -> Rectangle Int -> Double -> Figure
+ MiniLight.Figure: Figure :: Maybe Texture -> Rectangle Int -> Rectangle Int -> Double -> Figure
- MiniLight.Figure: [texture] :: Figure -> Texture
+ MiniLight.Figure: [texture] :: Figure -> Maybe Texture
- MiniLight.Light: LightEnv :: Renderer -> FontMap -> LogQueue -> LightEnv
+ MiniLight.Light: LightEnv :: Maybe Renderer -> FontMap -> LogQueue -> LightEnv
- MiniLight.Light: [renderer] :: LightEnv -> Renderer
+ MiniLight.Light: [renderer] :: LightEnv -> Maybe Renderer
- MiniLight.Light: _events :: HasLoopEnv c_aFkk => Lens' c_aFkk (MVar [Event])
+ MiniLight.Light: _events :: HasLoopEnv c_aGoo => Lens' c_aGoo (MVar [Event])
- MiniLight.Light: _fontCache :: HasLightEnv c_aFfk => Lens' c_aFfk FontMap
+ MiniLight.Light: _fontCache :: HasLightEnv c_aGjo => Lens' c_aGjo FontMap
- MiniLight.Light: _keyStates :: HasLoopEnv c_aFkk => Lens' c_aFkk (HashMap Scancode Int)
+ MiniLight.Light: _keyStates :: HasLoopEnv c_aGoo => Lens' c_aGoo (HashMap Scancode Int)
- MiniLight.Light: _logger :: HasLightEnv c_aFfk => Lens' c_aFfk LogQueue
+ MiniLight.Light: _logger :: HasLightEnv c_aGjo => Lens' c_aGjo LogQueue
- MiniLight.Light: _renderer :: HasLightEnv c_aFfk => Lens' c_aFfk Renderer
+ MiniLight.Light: _renderer :: HasLightEnv c_aGjo => Lens' c_aGjo (Maybe Renderer)
- MiniLight.Light: _signalQueue :: HasLoopEnv c_aFkk => Lens' c_aFkk (IORef [Event])
+ MiniLight.Light: _signalQueue :: HasLoopEnv c_aGoo => Lens' c_aGoo (IORef [Event])
- MiniLight.Light: class HasLightEnv c_aFfk
+ MiniLight.Light: class HasLightEnv c_aGjo
- MiniLight.Light: class HasLoopEnv c_aFkk
+ MiniLight.Light: class HasLoopEnv c_aGoo
- MiniLight.Light: lightEnv :: HasLightEnv c_aFfk => Lens' c_aFfk LightEnv
+ MiniLight.Light: lightEnv :: HasLightEnv c_aGjo => Lens' c_aGjo LightEnv
- MiniLight.Light: loopEnv :: HasLoopEnv c_aFkk => Lens' c_aFkk LoopEnv
+ MiniLight.Light: loopEnv :: HasLoopEnv c_aGoo => Lens' c_aGoo LoopEnv
- MiniLight.Loader: LoaderEnv :: Registry Component -> IORef AppConfig -> LoaderEnv
+ MiniLight.Loader: LoaderEnv :: Registry Component -> IORef (HashMap Text Text) -> IORef AppConfig -> LoaderEnv
- MiniLight.Loader: _appConfig :: HasLoaderEnv c_a14jy => Lens' c_a14jy (IORef AppConfig)
+ MiniLight.Loader: _appConfig :: HasLoaderEnv c_a16iS => Lens' c_a16iS (IORef AppConfig)
- MiniLight.Loader: _registry :: HasLoaderEnv c_a14jy => Lens' c_a14jy (Registry Component)
+ MiniLight.Loader: _registry :: HasLoaderEnv c_a16iS => Lens' c_a16iS (Registry Component)
- MiniLight.Loader: class HasLoaderEnv c_a14jy
+ MiniLight.Loader: class HasLoaderEnv c_a16iS
- MiniLight.Loader: createComponentBy :: Resolver -> Maybe Text -> ComponentConfig -> MiniLight (Either String Component)
+ MiniLight.Loader: createComponentBy :: (HasLoaderEnv env, HasLightEnv env, MonadIO m) => Resolver -> Maybe Text -> ComponentConfig -> LightT env m (Either String Component)
- MiniLight.Loader: loaderEnv :: HasLoaderEnv c_a14jy => Lens' c_a14jy LoaderEnv
+ MiniLight.Loader: loaderEnv :: HasLoaderEnv c_a16iS => Lens' c_a16iS LoaderEnv
- MiniLight.Loader.Internal.Types: ComponentConfig :: Text -> Value -> Maybe (HashMap Text Hook) -> ComponentConfig
+ MiniLight.Loader.Internal.Types: ComponentConfig :: Text -> Maybe Text -> Value -> Maybe (HashMap Text Hook) -> ComponentConfig
Files
- minilight.cabal +5/−5
- src/Control/Monad/Caster.hs +13/−0
- src/Data/Component/Basic.hs +50/−18
- src/Data/Component/Button.hs +1/−1
- src/Data/Component/Layer.hs +54/−41
- src/Data/Component/MessageEngine.hs +43/−23
- src/Data/Component/MessageLayer.hs +22/−7
- src/Data/Component/Resolver.hs +11/−1
- src/Data/Component/Selection.hs +55/−32
- src/Data/Config/Font.hs +8/−0
- src/MiniLight.hs +129/−41
- src/MiniLight/Component.hs +23/−5
- src/MiniLight/Event.hs +41/−7
- src/MiniLight/Figure.hs +65/−51
- src/MiniLight/Light.hs +1/−1
- src/MiniLight/Loader.hs +41/−21
- src/MiniLight/Loader/Internal/Resolver.hs +6/−3
- src/MiniLight/Loader/Internal/Types.hs +6/−4
minilight.cabal view
@@ -3,7 +3,7 @@ -- For further documentation, see http://haskell.org/cabal/users-guide/ name: minilight-version: 0.4.0+version: 0.4.1 synopsis: A SDL2-based graphics library, batteries-included. description: This package provides the wheel for a graphical application or a game.@@ -67,20 +67,20 @@ vector >= 0.12.0 && < 0.13, aeson >= 1.4.2 && < 1.5, scientific >= 0.3.6 && < 0.4,- hashable >= 1.2.7 && < 1.3,+ hashable >= 1.3.0 && < 1.4, hashtables >= 1.2.3 && < 1.3,- template-haskell >= 2.14.0 && < 2.15,+ template-haskell >= 2.14.0 && < 2.16, unordered-containers >= 0.2.10 && < 0.3, exceptions >= 0.10.1 && < 0.11, mtl >= 2.2.2 && < 2.3, lens >= 4.17 && < 4.18, linear >= 1.20.8 && < 1.21,- sdl2 >= 2.4.1 && < 2.5,+ sdl2 >= 2.5.0 && < 2.6, sdl2-gfx >= 0.2 && < 0.3, sdl2-image >= 2.0.0 && < 2.1, sdl2-ttf >= 2.1.0 && < 2.2, transformers >= 0.5.6 && < 0.6,- trifecta >= 2 && < 2.1,+ trifecta >= 2.1 && < 2.2, uuid >= 1.3.12 && < 1.4, yaml >= 0.11.0 && < 0.12, mwc-random >= 0.14.0 && < 0.15,
src/Control/Monad/Caster.hs view
@@ -5,10 +5,12 @@ LogLevel (..), LogQueue, stdoutLogger,+ iohandleLogger, ) where import Control.Monad.IO.Class import Control.Concurrent+import GHC.IO.Handle (Handle) import System.Log.Caster as Caster class MonadLogger m where@@ -33,5 +35,16 @@ _ <- forkIO $ relayLog chan level terminalListener _ <- forkIO $ broadcastLog q chan++ return q++iohandleLogger :: Handle -> LogLevel -> IO LogQueue+iohandleLogger handle level = do+ chan <- newLogChan+ q <- newLogQueue++ _ <- forkIO+ $ relayLog chan level (handleListenerFlush terminalFormatter handle)+ _ <- forkIO $ broadcastLog q chan return q
src/Data/Component/Basic.hs view
@@ -10,8 +10,11 @@ -} module Data.Component.Basic where +import Control.Lens hiding (contains)+import Control.Monad import Data.Aeson import Data.Aeson.Types+import qualified Data.HashMap.Strict as HM import Data.Typeable import qualified SDL import qualified SDL.Vect as Vect@@ -20,9 +23,15 @@ -- | Basic config type data Config = Config { size :: Vect.V2 Int,- position :: Vect.V2 Int-}+ position :: Vect.V2 Int,+ visible :: Bool+} deriving (Show) +makeClassy_ ''Config++defConfig :: Config+defConfig = Config {size = 0, position = 0, visible = True}+ instance FromJSON Config where parseJSON = withObject "config" $ \v -> do sizeMaybe <- v .:? "size"@@ -33,8 +42,11 @@ position <- (\w -> maybe (return 0) w positionMaybe) $ withObject "position" $ \v -> Vect.V2 <$> v .: "x" <*> v .: "y" - return $ Config size position+ visibleMaybe <- v .:? "visible"+ let visible = maybe True id visibleMaybe + return $ Config size position visible+ -- | This wrapper function is useful when you write your component config parser. wrapConfig :: (Config -> a -> Parser r) -> (Object -> Parser a) -> Value -> Parser r@@ -58,16 +70,22 @@ MouseOver :: Vect.V2 Int -- ^ The relative position of the mouse pointer -> Signal+ SetVisibility :: Bool -> Signal deriving Typeable instance EventType Signal where getEventType (MousePressed _) = "mouse-pressed" getEventType (MouseReleased _) = "mouse-released" getEventType (MouseOver _) = "mouse-over"+ getEventType (SetVisibility _) = "set-visibility" + getEventProperties (SetVisibility o) = HM.fromList [("visibility", Bool o)]+ getEventProperties _ = error "not implemeneted yet"+ -- | This automatically applies basic configuration such as: position. wrapFigures :: Config -> [Figure] -> [Figure]-wrapFigures conf = map (translate (position conf))+wrapFigures conf fs =+ if not (conf ^. _visible) then [] else map (translate (position conf)) fs -- | This wrapper function is useful when you write your own 'onSignal' component. wrapSignal@@ -77,14 +95,16 @@ , MonadIO m , ComponentUnit c )- => (c -> Config) -- ^ 'Config' getter- -> (Event -> c -> LightT env m c) -- ^ Custom @onSignal@ function+ => Lens' c Config -- ^ lens to 'Config'+ -> (Event -> c -> LightT env m c) -- ^ custom @onSignal@ function -> (Event -> c -> LightT env m c)-wrapSignal getter f ev comp = do- emitBasicSignal ev (getter comp)- f ev comp+wrapSignal lens f ev comp = do+ conf' <- handleBasicSignal ev (comp ^. lens) --- | Basic signaling function.+ when (comp ^. lens ^. _visible) $ emitBasicSignal ev conf'+ f ev (comp & lens .~ conf')++-- | Basic signaling function. Signals are emitted towards the source component. emitBasicSignal :: (HasLightEnv env, HasLoopEnv env, HasComponentEnv env, MonadIO m) => Event@@ -92,17 +112,29 @@ -> LightT env m () emitBasicSignal (RawEvent (SDL.Event _ (SDL.MouseMotionEvent (SDL.MouseMotionEventData _ _ _ (SDL.P pos) _)))) conf | contains (areaRectangle conf) (fmap fromEnum pos)- = emit $ MouseOver $ fmap fromEnum pos - position conf+ = view _uid+ >>= \t -> emit (Just t) $ MouseOver $ fmap fromEnum pos - position conf emitBasicSignal (RawEvent (SDL.Event _ (SDL.MouseButtonEvent (SDL.MouseButtonEventData _ state _ _ _ (SDL.P pos))))) conf | contains (areaRectangle conf) (fmap fromEnum pos)- = emit- $ ( case state of- SDL.Pressed -> MousePressed- SDL.Released -> MouseReleased- )- $ fmap fromEnum pos- - position conf+ = view _uid >>= \t ->+ emit (Just t)+ $ ( case state of+ SDL.Pressed -> MousePressed+ SDL.Released -> MouseReleased+ )+ $ fmap fromEnum pos+ - position conf emitBasicSignal _ _ = return ()++-- | handle basic signals+handleBasicSignal+ :: (HasLightEnv env, HasLoopEnv env, HasComponentEnv env, MonadIO m)+ => Event+ -> Config+ -> LightT env m Config+handleBasicSignal ev conf = case asSignal ev of+ Just (SetVisibility b) -> return $ conf { visible = b }+ _ -> return conf contains :: (Ord a, Num a) => SDL.Rectangle a -> Vect.V2 a -> Bool contains (SDL.Rectangle (Vect.P (Vect.V2 x y)) (Vect.V2 w h)) (Vect.V2 px py) =
src/Data/Component/Button.hs view
@@ -35,7 +35,7 @@ useCache _ _ = True onSignal (RawEvent (SDL.Event _ (SDL.MouseButtonEvent (SDL.MouseButtonEventData _ SDL.Released _ _ _ _)))) comp = do- emit Click+ emitGlobally Click return comp onSignal _ comp = return comp
src/Data/Component/Layer.hs view
@@ -1,6 +1,7 @@ module Data.Component.Layer where import Control.Lens+import Control.Lens.TH.Rules import Control.Monad import Data.Aeson import Linear@@ -9,27 +10,33 @@ import qualified SDL.Vect as Vect import qualified Data.Component.Basic as Basic +data Config = Config {+ basic :: Basic.Config,+ image :: FilePath+}++instance FromJSON Config where+ parseJSON = Basic.wrapConfig (\b l -> return $ Config b l) $ \v ->+ v .: "image"+ data Layer = Layer { layer :: Figure, config :: Config } +makeLensesWith lensRules_ ''Config+makeLensesWith lensRules_ ''Layer++instance Basic.HasConfig Config where+ config = _basic+ instance ComponentUnit Layer where figures comp = return $ Basic.wrapFigures (basic $ config comp) [layer comp] - onSignal = Basic.wrapSignal (basic . config) (\_ -> return)+ onSignal = Basic.wrapSignal (_config . Basic.config) (\_ -> return) useCache _ _ = True -data Config = Config {- basic :: Basic.Config,- image :: FilePath-}--instance FromJSON Config where- parseJSON = Basic.wrapConfig (\b l -> return $ Config b l) $ \v ->- v .: "image"- new :: Config -> MiniLight Layer new conf = do pic <- picture (image conf)@@ -41,45 +48,51 @@ newNineTile :: Config -> MiniLight Layer newNineTile conf = do- pic <- picture $ image conf- let siz = fmap toEnum $ Basic.size $ basic conf- let tex = texture pic- let texSize = fmap toEnum $ getFigureSize pic+ mrenderer <- view _renderer+ target <- flip mapM mrenderer $ \renderer -> do+ pic <- picture $ image conf+ let siz = fmap toEnum $ Basic.size $ basic conf+ let Just tex = texture pic+ let texSize = fmap toEnum $ getFigureSize pic - tinfo <- SDL.queryTexture tex- renderer <- view _renderer+ tinfo <- SDL.queryTexture tex - target <- SDL.createTexture renderer+ target <- SDL.createTexture renderer (SDL.texturePixelFormat tinfo) SDL.TextureAccessTarget siz- SDL.rendererRenderTarget renderer SDL.$= Just target- SDL.textureBlendMode target SDL.$= SDL.BlendAlphaBlend+ SDL.rendererRenderTarget renderer SDL.$= Just target+ SDL.textureBlendMode target SDL.$= SDL.BlendAlphaBlend - let tileSize = fmap (`div` 3) texSize+ let tileSize = fmap (`div` 3) texSize - forM_ [0 .. 2] $ \ix -> forM_ [0 .. 2] $ \iy -> do- let targetSize = V2- (if ix == 1 then siz ^. _x - 2 * tileSize ^. _x else tileSize ^. _x)- (if iy == 1 then siz ^. _y - 2 * tileSize ^. _y else tileSize ^. _y)- let targetLoc = V2- ( if ix == 0- then 0- else if ix == 1 then tileSize ^. _x else siz ^. _x - tileSize ^. _x- )- ( if iy == 0- then 0- else if iy == 1 then tileSize ^. _y else siz ^. _y - tileSize ^. _y- )+ forM_ [0 .. 2] $ \ix -> forM_ [0 .. 2] $ \iy -> do+ let targetSize = V2+ (if ix == 1 then siz ^. _x - 2 * tileSize ^. _x else tileSize ^. _x)+ (if iy == 1 then siz ^. _y - 2 * tileSize ^. _y else tileSize ^. _y)+ let targetLoc = V2+ ( if ix == 0+ then 0+ else if ix == 1+ then tileSize ^. _x+ else siz ^. _x - tileSize ^. _x+ )+ ( if iy == 0+ then 0+ else if iy == 1+ then tileSize ^. _y+ else siz ^. _y - tileSize ^. _y+ ) - SDL.copy- renderer- tex- (Just $ SDL.Rectangle (SDL.P (tileSize * Vect.V2 ix iy)) tileSize)- (Just $ SDL.Rectangle (SDL.P targetLoc) targetSize)+ SDL.copy+ renderer+ tex+ (Just $ SDL.Rectangle (SDL.P (tileSize * Vect.V2 ix iy)) tileSize)+ (Just $ SDL.Rectangle (SDL.P targetLoc) targetSize) - SDL.rendererRenderTarget renderer SDL.$= Nothing+ SDL.rendererRenderTarget renderer SDL.$= Nothing - tex <- fromTexture target- return $ Layer {layer = tex, config = conf}+ return target + tex <- maybe (return emptyFigure) fromTexture target+ return $ Layer {layer = tex, config = conf}
src/Data/Component/MessageEngine.hs view
@@ -38,16 +38,22 @@ textCounter :: Int, textTexture :: Figure, finished :: Bool,+ currentMessages :: V.Vector T.Text, config :: Config } makeLensesWith lensRules_ ''MessageEngine -data EngineEvent = NextPage+data EngineEvent where+ NextPage :: EngineEvent+ SetMessage+ :: [T.Text] -- ^ pages messages+ -> EngineEvent deriving Typeable instance EventType EngineEvent where getEventType NextPage = "next-page"+ getEventType (SetMessage _) = "set-message" instance ComponentUnit MessageEngine where update = execStateT $ do@@ -59,14 +65,15 @@ tc <- use _textCounter p <- use _page- messages <- use $ _config . _messages+ messages <- use _currentMessages when (p == V.length messages - 1 && tc == T.length (messages V.! p)) $ do _finished .= True _counter %= (+1) figures comp = do- (w, h) <- SDL.Font.size (comp ^. _fontData) (T.take (comp ^. _textCounter) $ (config comp ^. _messages) V.! (comp ^. _page))+ let messages = comp ^. _currentMessages+ (w, h) <- SDL.Font.size (comp ^. _fontData) (T.take (comp ^. _textCounter) $ messages V.! (comp ^. _page)) return [ clip (SDL.Rectangle 0 (Vect.V2 w h)) $ textTexture comp@@ -74,36 +81,49 @@ useCache c1 c2 = page c1 == page c2 && textCounter c1 == textCounter c2 - 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+ onSignal ev = execStateT $ case asSignal ev of+ Just NextPage -> do+ fin <- use _finished+ unless fin $ do+ _page %= (+1)+ _textCounter .= 0 - 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+ font <- use _fontData+ fontColor <- use $ _config . _font . Font._color+ p <- use _page+ messages <- use _currentMessages+ tex <- lift $ liftMiniLight $ text font fontColor (messages V.! p)+ _textTexture .= tex+ Just (SetMessage ms) -> do+ let vs = V.fromList ms+ _counter .= 0+ _page .= 0 - go _ = return+ st <- use $ _config . _static+ _textCounter .= if st then T.length (vs V.! 0) else 0 + font <- use _fontData+ fontColor <- use $ _config . _font . Font._color+ tex <- lift $ liftMiniLight $ text font fontColor $ vs V.! 0+ _textTexture .= tex+ _finished .= st+ _currentMessages .= vs+ _ -> return ()+ new :: Config -> MiniLight MessageEngine new conf = do font <- Font.loadFontFrom (font conf) textTexture <- text font (conf ^. _font ^. Font._color) $ messages conf V.! 0 return $ MessageEngine- { fontData = font- , counter = 0- , page = 0+ { fontData = font+ , counter = 0+ , page = 0 , textCounter = if static conf then T.length (messages conf V.! 0) else 0- , textTexture = textTexture- , finished = static conf- , config = conf+ , textTexture = textTexture+ , finished = static conf+ , currentMessages = messages conf+ , config = conf } wrapSignal
src/Data/Component/MessageLayer.hs view
@@ -4,6 +4,7 @@ import Control.Lens.TH.Rules import Control.Monad.State import Data.Aeson+import Data.Typeable (Typeable) import Linear import MiniLight import qualified Data.Component.Basic as Basic@@ -13,6 +14,7 @@ import qualified SDL.Vect as Vect data Config = Config {+ basic :: Basic.Config, engine :: CME.Config, window :: CLayer.Config, next :: CAnim.Config@@ -24,7 +26,7 @@ nextConf <- parseJSON =<< v .: "next" messageEngineConf <- parseJSON =<< v .: "engine" - return $ Config messageEngineConf layerConf nextConf+ return $ Config (layerConf ^. Basic.config) messageEngineConf layerConf nextConf data MessageLayer = MessageLayer { messageEngine :: CME.MessageEngine,@@ -33,14 +35,25 @@ config :: Config } +makeLensesWith lensRules_ ''Config makeLensesWith lensRules_ ''MessageLayer +instance Basic.HasConfig Config where+ config = _basic+ engineL :: Lens' MessageLayer CME.MessageEngine engineL = lens messageEngine (\s a -> s { messageEngine = a }) cursorL :: Lens' MessageLayer CAnim.AnimationLayer cursorL = lens cursor (\s a -> s { cursor = a }) +data MessageLayerEvent where+ Finish :: MessageLayerEvent+ deriving Typeable++instance EventType MessageLayerEvent where+ getEventType Finish = "finish"+ instance ComponentUnit MessageLayer where update = execStateT $ do zoom engineL $ do@@ -66,14 +79,16 @@ ++ map (translate (position + Vect.V2 ((windowSize ^. _x - cursorSize ^. _x) `div` 2) (windowSize ^. _y - cursorSize ^. _y))) cursorLayer onSignal- = Basic.wrapSignal (CLayer.basic . CLayer.config . layer)+ = Basic.wrapSignal (_config . Basic.config) $ 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+ $ \ev -> execStateT $ case asSignal ev of+ Just (Basic.MouseReleased _) -> do+ me <- use _messageEngine - go _ = return+ if me ^. CME._finished+ then lift $ emitGlobally Finish+ else lift $ emitGlobally CME.NextPage+ _ -> return () new :: Config -> MiniLight MessageLayer new conf = do
src/Data/Component/Resolver.hs view
@@ -2,7 +2,7 @@ -} module Data.Component.Resolver ( resolver,- foldResult,+ extendResolver, ) where import Control.Monad@@ -42,3 +42,13 @@ resultM (fromJSON props) $ newComponent uid <=< Layer.newNineTile "selection" -> resultM (fromJSON props) $ newComponent uid <=< Selection.new _ -> return $ Left $ "Unsupported component: " ++ T.unpack name++extendResolver+ :: (FromJSON a, ComponentUnit c)+ => T.Text -- ^ Name+ -> (a -> MiniLight c) -- ^ Constructor of the component+ -> Resolver -- ^ Old resolver+ -> Resolver+extendResolver newName func resolver name uid props = if name == newName+ then resultM (fromJSON props) $ newComponent uid <=< func+ else resolver name uid props
src/Data/Component/Selection.hs view
@@ -1,6 +1,7 @@ module Data.Component.Selection where import Control.Lens+import Control.Lens.TH.Rules import Control.Monad.State import Data.Aeson hiding ((.=)) import qualified Data.Config.Font as Font@@ -15,70 +16,92 @@ import qualified Data.Component.Basic as Basic import qualified Data.Component.Layer as Layer +data Config = Config {+ basic :: Basic.Config,+ labels :: V.Vector T.Text,+ fontConfig :: Font.Config,+ image :: FilePath+}++instance FromJSON Config where+ parseJSON = withObject "selection" $ \v ->+ Config+ <$> parseJSON (Object v)+ <*> v .:? "labels" .!= V.empty+ <*> (parseJSON =<< (v .: "font"))+ <*> v .: "image"+ data Selection = Selection { layer :: Layer.Layer, font :: SDL.Font.Font, hover :: Maybe Int,- conf :: Config+ config :: Config,+ currentLabels :: V.Vector T.Text } -hoverL :: Lens' Selection (Maybe Int)-hoverL = lens hover (\env r -> env { hover = r })+makeLensesWith lensRules_ ''Config+makeLensesWith lensRules_ ''Selection +instance Basic.HasConfig Config where+ config = _basic+ instance ComponentUnit Selection where update = return figures comp = do let p = Vect.V2 15 10- textTextures <- V.forM (V.indexed $ labels $ conf comp) $ \(i,label) -> liftMiniLight $ fmap (translate (p + Vect.V2 0 (i * 30))) $ text (font comp) (Font.color $ fontConfig $ conf comp) label+ textTextures <- V.forM (V.indexed $ comp ^. _currentLabels) $ \(i,label) -> liftMiniLight $ fmap (translate (p + Vect.V2 0 (i * 30))) $ text (font comp) (Font.color $ fontConfig $ comp ^. _config) label base <- figures (layer comp)- highlight <- liftMiniLight $ rectangleFilled (Vect.V4 240 240 240 40) $ _y .~ 30 $ Basic.size (basic (conf comp))+ highlight <- liftMiniLight $ rectangleFilled (Vect.V4 240 240 240 40) $ _y .~ 30 $ comp ^. _config . Basic._size - return $ base- ++ map (translate (Basic.position $ basic $ conf comp)) (translate (Vect.V2 0 (maybe 0 id (hover comp) * 30 + p ^. _y)) highlight+ return $ Basic.wrapFigures (comp ^. _config . Basic.config) $ base+ ++ (translate (Vect.V2 0 (maybe 0 id (hover comp) * 30 + p ^. _y)) highlight : V.toList textTextures) - useCache c1 c2 = hover c1 == hover c2+ useCache c1 c2+ = c1 ^. _currentLabels == c2 ^. _currentLabels+ && c1 ^. _config . Basic._visible == c2 ^. _config . Basic._visible && c1 ^. _hover == c2 ^. _hover - onSignal = Basic.wrapSignal (basic . conf) $ \ev sel -> flip execStateT sel $ do- uid <- view _uid+ onSignal = Basic.wrapSignal (_config . Basic.config) $ \ev -> execStateT $ do+ labels <- use _currentLabels - 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)+ case asSignal ev of+ Just (Basic.MouseOver pos) | (pos ^. _y) `div` 30 <= V.length labels - 1 -> do+ _hover .= Just ((pos ^. _y) `div` 30)+ Just (Basic.MouseReleased pos) | (pos ^. _y) `div` 30 <= V.length labels - 1 -> do+ lift $ emitGlobally $ Select ((pos ^. _y) `div` 30) _ -> return () + case asSignal ev of+ Just (SetOptions xs) -> _currentLabels .= V.fromList xs+ _ -> return ()+ -- OMG+ beforeClearCache _ [] = return () beforeClearCache _ figs = mapM_ freeFigure $ tail figs -data Config = Config {- basic :: Basic.Config,- labels :: V.Vector T.Text,- fontConfig :: Font.Config,- image :: FilePath-}--instance FromJSON Config where- parseJSON = withObject "selection" $ \v ->- Config- <$> parseJSON (Object v)- <*> v .:? "labels" .!= V.empty- <*> (parseJSON =<< (v .: "font"))- <*> v .: "image"- data SelectionEvent = Select Int+ | SetOptions [T.Text] deriving (Typeable) instance EventType SelectionEvent where getEventType (Select _) = "select"+ getEventType (SetOptions _) = "set-options" getEventProperties (Select n) = HM.fromList [("index", Number $ fromIntegral n)]+ getEventProperties (SetOptions ts) = HM.fromList [("options", Array $ fmap String $ V.fromList ts)] new :: Config -> MiniLight Selection new conf = do font <- Font.loadFontFrom (fontConfig conf)- layer <- Layer.newNineTile $ Layer.Config (basic conf) (image conf)- return $ Selection {font = font, conf = conf, hover = Nothing, layer = layer}+ layer <- Layer.newNineTile $ Layer.Config+ (Basic.defConfig { Basic.size = Basic.size $ basic conf })+ (image conf)+ return $ Selection+ { font = font+ , config = conf+ , hover = Nothing+ , layer = layer+ , currentLabels = conf ^. _labels+ }
src/Data/Config/Font.hs view
@@ -2,6 +2,7 @@ import Control.Lens import Data.Aeson+import qualified Data.Text as T import Data.Word (Word8) import MiniLight import qualified SDL.Font@@ -28,3 +29,10 @@ -- | Load a system font from 'Config' type. loadFontFrom :: Config -> MiniLight SDL.Font.Font loadFontFrom conf = loadFont (descriptor conf) (size conf)++-- | Create a text texture from the config.+-- **NB** This function is a slow operation since it loads the font data every time.+textFrom :: Config -> T.Text -> MiniLight Figure+textFrom conf t = do+ font <- loadFontFrom conf+ text font (conf ^. _color) t
src/MiniLight.hs view
@@ -9,12 +9,18 @@ module MiniLight.Loader, runLightT,+ runLightTWith,+ LightConfig (..),+ defLightConfig, LoopState (..), LoopConfig (..), defConfig, runMainloop, MiniLoop, runMiniloop,+ runComponentEnv,+ (@@!),+ quit, ) where import Control.Concurrent (threadDelay, forkIO)@@ -23,12 +29,13 @@ import qualified Control.Monad.Caster as Caster import Control.Monad.Catch import Control.Monad.Reader-import Data.Foldable (foldlM)+import Data.Foldable import Data.Hashable (Hashable(..)) import qualified Data.HashMap.Strict as HM import Data.Maybe import Data.IORef import qualified Data.Registry as R+import qualified Data.Text as T import qualified Data.Vector as V import Graphics.Text.TrueType import MiniLight.Component@@ -43,19 +50,47 @@ instance Hashable SDL.Scancode where hashWithSalt n sc = hashWithSalt n (SDL.unwrapScancode sc) --- | Run a Light monad.+-- | Run a light monad with default configuration.+-- @+-- runLightT = runLightTWith defLightConfig+-- @ runLightT :: (MonadIO m, MonadMask m) => LightT LightEnv m a -> m a-runLightT prog = withSDL $ withWindow $ \window -> do- renderer <- SDL.createRenderer window (-1) SDL.defaultRenderer- fc <- loadFontCache- logger <- liftIO $ Caster.stdoutLogger Caster.LogDebug- runReaderT (runLightT' prog)- $ LightEnv {renderer = renderer, fontCache = fc, logger = logger}+runLightT = runLightTWith defLightConfig++-- | Custom configuration for LightT+data LightConfig = LightConfig {+ headless :: Bool, -- Set False if you don't need graphical user interface (mostly for testing)+ logQueue :: Caster.LogLevel -> IO Caster.LogQueue, -- ^ LogQueue for logger+ logLevel :: Caster.LogLevel -- ^ LogLevel for logger+}++-- | Default configuration for 'runLightT'+defLightConfig :: LightConfig+defLightConfig = LightConfig+ { headless = False+ , logQueue = Caster.stdoutLogger+ , logLevel = Caster.LogWarn+ }++-- | Run a Light monad.+runLightTWith+ :: (MonadIO m, MonadMask m) => LightConfig -> LightT LightEnv m a -> m a+runLightTWith conf prog =+ withSDLFont+ $ ( if headless conf+ then (\f -> f Nothing)+ else withSDL . withWindow . (\f w -> f (Just w))+ )+ $ \mwindow -> do+ renderer <- flip mapM mwindow $ \window -> do+ SDL.createRenderer window (-1) SDL.defaultRenderer+ fc <- loadFontCache+ logger <- liftIO $ logQueue conf (logLevel conf)+ runReaderT (runLightT' prog)+ $ LightEnv {renderer = renderer, fontCache = fc, logger = logger} where- withSDL =- bracket (SDL.initializeAll >> SDL.Font.initialize)- (\_ -> SDL.Font.quit >> SDL.quit)- . const+ withSDL = bracket SDL.initializeAll (\_ -> SDL.quit) . const+ withSDLFont = bracket SDL.Font.initialize (\_ -> SDL.Font.quit) . const withWindow = bracket (SDL.createWindow "window" SDL.defaultWindow) SDL.destroyWindow@@ -122,6 +157,39 @@ instance HasComponentEnv (ComponentState env) where componentEnv = _componentEnvL +-- | Run an action over a component.+runComponentEnv+ :: (HasLightEnv env, HasLoopEnv env)+ => Component+ -> ( forall env'+ . (HasComponentEnv env', HasLoopEnv env', HasLightEnv env')+ => LightT env' m ()+ )+ -> LightT env m ()+runComponentEnv c =+ envLightT (\env -> ComponentState env (ComponentEnv (getUID c) (getHooks c)))+++-- | Emit a signal with a loader-defined target name+-- @+-- (@@!) :: EventType et => T.Text -> et -> MiniLoop ()+-- @+(@@!)+ :: ( EventType et+ , HasLoaderEnv env+ , HasLoopEnv env+ , HasLightEnv env+ , MonadIO m+ )+ => T.Text+ -> et+ -> LightT env m ()+t @@! ev = do+ key <- fmap (\(Just x) -> x) $ lookupByTagID t+ reg <- view _registry+ v <- reg R.! key+ runComponentEnv v $ emit (Just key) $ ev+ -- | Same as 'runMainloop' but fixing the type. runMiniloop :: LoopConfig -> s -> (s -> MiniLoop s) -> MiniLight () runMiniloop = runMainloop LoopState@@ -147,11 +215,12 @@ events <- liftIO $ newMVar [] signalQueue <- liftIO $ newIORef [] reg <- R.new+ tag <- liftIO $ newIORef $ HM.empty conf <- liftIO $ newIORef $ AppConfig V.empty V.empty run (LoopEnv {keyStates = HM.empty, events = events, signalQueue = signalQueue})- (LoaderEnv {registry = reg, appConfig = conf})+ (LoaderEnv {registry = reg, tagRegistry = tag, appConfig = conf}) initial where run loop loader s = do@@ -175,9 +244,10 @@ R.register reg (getUID component) component go loop loader s = do- renderer <- view _renderer- liftIO $ SDL.rendererDrawColor renderer SDL.$= 255- liftIO $ SDL.clear renderer+ mrenderer <- view _renderer+ forM_ mrenderer $ \renderer -> do+ liftIO $ SDL.rendererDrawColor renderer SDL.$= 255+ liftIO $ SDL.clear renderer R.forV_ (loader ^. _registry) $ \comp -> draw comp @@ -192,18 +262,28 @@ ) $ update comp - s' <- envLightT (\env -> conv env loop loader) $ userloop s-- liftIO $ SDL.present renderer-- liftIO $ threadDelay (100000 `div` 60)- events <- SDL.pollEvents- keys <- SDL.getKeyboardState+ s' <- envLightT (\env -> conv env loop loader) $ userloop s - envLightT (\env -> conv env loop loader) $ do+ -- event handling+ globalEvents <- envLightT (\env -> conv env loop loader) $ do evref <- view _events events <- liftIO $ modifyMVar evref (\a -> return ([], a))+ let (componentEvent, globalEvent, notifyEvent) = foldl'+ ( \(a, b, c) -> \case+ NotifyEvent n -> (a, b, n : c)+ ev@(Signal _ (Just t) _) -> ((t, ev) : a, b, c)+ ev -> (a, ev : b, c)+ )+ ([], [], [])+ events + -- send an event to the target+ forM_ componentEvent $ \(target, ev) -> do+ R.update (loader ^. _registry) target $ \v -> envLightT+ (\env -> ComponentState env (ComponentEnv (getUID v) (getHooks v)))+ (onSignal ev v)++ -- send a global event to all components R.modifyV_ (loader ^. _registry) $ \comp -> do foldlM ( \comp ev ->@@ -215,20 +295,22 @@ $ onSignal ev comp ) comp- events+ globalEvent - forM_- ( catMaybes $ map- ( \e -> case e of- NotifyEvent ev -> Just ev- _ -> Nothing- )- events- )- $ \ev -> patchAppConfig- (fromJust $ appConfigFile conf)- (componentResolver conf)+ -- process notification events+ forM_ notifyEvent+ $ \ev -> patchAppConfig (fromJust $ appConfigFile conf)+ (componentResolver conf) + return globalEvent++ forM_ mrenderer $ \renderer -> do+ liftIO $ SDL.present renderer++ liftIO $ threadDelay (100000 `div` 60)+ events <- SDL.pollEvents+ keys <- SDL.getKeyboardState+ envLightT (\env -> conv env loop loader) $ do evref <- view _events sigref <- view _signalQueue@@ -253,11 +335,17 @@ (watchKeys conf) let quit = any- ( \event -> case SDL.eventPayload event of- SDL.WindowClosedEvent _ -> True- SDL.QuitEvent -> True- _ -> False+ ( \case+ RawEvent (SDL.Event _ (SDL.WindowClosedEvent _)) -> True+ RawEvent (SDL.Event _ SDL.QuitEvent ) -> True+ _ -> False )- events+ globalEvents unless quit $ go loop' loader s'++-- | Quit the mainloop and terminate the application.+quit :: (MonadIO m, HasLoopEnv env) => LightT env m ()+quit = do+ evref <- view _events+ liftIO $ modifyMVar_ evref $ return . (RawEvent (SDL.Event 0 SDL.QuitEvent) :)
src/MiniLight/Component.hs view
@@ -3,9 +3,11 @@ HasComponentEnv(..), ComponentEnv(..), emit,+ emitGlobally, ComponentUnit(..), Component,+ _unsafeAs, newComponent, getComponentSize, getUID,@@ -25,6 +27,7 @@ import MiniLight.Event import MiniLight.Figure import qualified SDL+import Unsafe.Coerce type HookMap = HM.HashMap T.Text (T.Text, Object -> Value) @@ -39,19 +42,28 @@ -- | Emit a signal, which will be catched at the next frame. emit :: (HasLoopEnv env, HasComponentEnv env, MonadIO m, EventType et)- => et+ => Maybe T.Text -- ^ target component ID+ -> et -> LightT env m ()-emit et = do+emit target et = do uid <- view _uid ref <- view _signalQueue- liftIO $ modifyIORef' ref $ (signal uid et :)+ liftIO $ modifyIORef' ref $ (signal uid target et :) hs <- view _callbacks case HM.lookup (getEventType et) =<< hs of- Just (name, param) -> liftIO- $ modifyIORef' ref (GlobalSignal name (param $ getEventProperties et) :)+ Just (name, param) -> liftIO $ modifyIORef'+ ref+ (signal uid Nothing (EventData name (param $ getEventProperties et)) :) Nothing -> return () +-- | Emit a signal globally.+emitGlobally+ :: (HasLoopEnv env, HasComponentEnv env, MonadIO m, EventType et)+ => et+ -> LightT env m ()+emitGlobally = emit Nothing+ -- | CompoonentUnit typeclass provides a way to define a new component. -- Any 'ComponentUnit' instance can be embedded into 'Component' type. class ComponentUnit c where@@ -93,6 +105,12 @@ cache :: IORef [Figure], callbackObject :: Maybe HookMap }++-- | Unsafe coercing the component+_unsafeAs :: (ComponentUnit c) => Lens' Component c+_unsafeAs = lens+ (\(Component _ c _ _ _) -> unsafeCoerce c)+ (\(Component a _ c d e) b -> Component a (unsafeCoerce b) c d e) -- | Create a new component. newComponent
src/MiniLight/Event.hs view
@@ -6,8 +6,12 @@ module MiniLight.Event ( Event (..), EventType (..),+ EventData (..), signal, asSignal,+ asRawEvent,+ asNotifyEvent,+ asEventData, ) where import qualified SDL@@ -41,14 +45,44 @@ -- | Event type representation data Event- = Signal T.Text Dynamic- | GlobalSignal T.Text Value+ = Signal T.Text (Maybe T.Text) Dynamic | RawEvent SDL.Event | NotifyEvent Notify.Event -signal :: EventType a => T.Text -> a -> Event-signal t v = Signal t (toDyn v)+-- | Create a signal event.+signal+ :: EventType a+ => T.Text -- ^ source component ID+ -> Maybe T.Text -- ^ target component ID, leave Nothing if this is a global event+ -> a+ -> Event+signal s t v = Signal s t (toDyn v) -asSignal :: EventType a => Event -> T.Text -> Maybe a-asSignal (Signal t1 v) t2 | t1 == t2 = fromDynamic v-asSignal _ _ = Nothing+-- | Cast a signal event to some 'EventType'.+asSignal :: EventType a => Event -> Maybe a+asSignal (Signal _ _ v) = fromDynamic v+asSignal _ = Nothing++-- | Cast an event to some 'SDL.Event'+asRawEvent :: Event -> Maybe SDL.Event+asRawEvent (RawEvent e) = Just e+asRawEvent _ = Nothing++-- | Cast an event to some 'Notify.Event'+asNotifyEvent :: Event -> Maybe Notify.Event+asNotifyEvent (NotifyEvent e) = Just e+asNotifyEvent _ = Nothing++-- | Canonical datatype of 'Event'. It consists of event name and event data itself.+-- This type is usually used for global events.+data EventData = EventData T.Text Value+ deriving (Show, Typeable)++instance EventType EventData where+ getEventType (EventData t _) = t+ getEventProperties (EventData _ o) = HM.singleton "data" o++-- | Cast a signal event to 'EventData'+asEventData :: Event -> Maybe EventData+asEventData (Signal _ Nothing v) = fromDynamic v+asEventData _ = Nothing
src/MiniLight/Figure.hs view
@@ -8,6 +8,7 @@ import Control.Lens import Control.Monad.Catch import Control.Monad.IO.Class+import Data.Foldable import qualified Data.Text as T import Data.Word (Word8) import Linear (_x, _y)@@ -31,18 +32,22 @@ -- | Figure type carries a texture, sizing information and rotation information. data Figure = Figure {- texture :: SDL.Texture,+ texture :: Maybe SDL.Texture, -- ^ Texture can be Nothing in headless mode sourceArea :: SDL.Rectangle Int, targetArea :: SDL.Rectangle Int, rotation :: Double } +-- | A figure which has no texture. You can render it but do nothing.+emptyFigure :: Figure+emptyFigure = Figure Nothing (SDL.Rectangle 0 0) (SDL.Rectangle 0 0) 0+ getFigureSize :: Figure -> Vect.V2 Int getFigureSize fig = (\(SDL.Rectangle _ size) -> size) $ targetArea fig {-# INLINE getFigureSize #-} freeFigure :: MonadIO m => Figure -> m ()-freeFigure = SDL.destroyTexture . texture+freeFigure = maybe (return ()) SDL.destroyTexture . texture {-# INLINE freeFigure #-} union :: SDL.Rectangle Int -> SDL.Rectangle Int -> SDL.Rectangle Int@@ -54,15 +59,15 @@ -- | Render a figure. render :: (HasLightEnv env, MonadIO m, MonadMask m) => Figure -> LightT env m () render fig = do- renderer <- view _renderer-- SDL.copyEx renderer- (texture fig)- (Just (fmap toEnum $ sourceArea fig))- (Just (fmap toEnum $ targetArea fig))- (realToFrac (rotation fig) * 180 / pi)- Nothing- (Vect.V2 False False)+ mrend <- view _renderer+ forM_ mrend $ \rend -> do+ SDL.copyEx rend+ ((\(Just t) -> t) $ texture fig)+ (Just (fmap toEnum $ sourceArea fig))+ (Just (fmap toEnum $ targetArea fig))+ (realToFrac (rotation fig) * 180 / pi)+ Nothing+ (Vect.V2 False False) {-# INLINE render #-} -- | Render figures.@@ -139,75 +144,84 @@ {-# INLINE rotate #-} text font color txt = do- renderer <- view _renderer+ mrend <- view _renderer+ tex <- flip mapM mrend $ \rend -> do+ withBlendedText font txt color $ \surf -> do+ texture <- SDL.createTextureFromSurface rend surf+ tinfo <- SDL.queryTexture texture+ let rect = fmap fromEnum $ SDL.Rectangle (SDL.P 0) (Vect.V2 (SDL.textureWidth tinfo) (SDL.textureHeight tinfo)) - withBlendedText font txt color $ \surf -> do- texture <- SDL.createTextureFromSurface renderer surf- tinfo <- SDL.queryTexture texture- let rect = fmap fromEnum $ SDL.Rectangle (SDL.P 0) (Vect.V2 (SDL.textureWidth tinfo) (SDL.textureHeight tinfo))+ return $ Figure (Just texture) rect rect 0 - return $ Figure texture rect rect 0+ return $ maybe emptyFigure id tex {-# INLINE text #-} picture filepath = do- renderer <- view _renderer-- texture <- SDL.Image.loadTexture renderer filepath- tinfo <- SDL.queryTexture texture- let rect = fmap fromEnum $ SDL.Rectangle (SDL.P 0) (Vect.V2 (SDL.textureWidth tinfo) (SDL.textureHeight tinfo))+ mrend <- view _renderer+ tex <- flip mapM mrend $ \rend -> do+ texture <- SDL.Image.loadTexture rend filepath+ tinfo <- SDL.queryTexture texture+ let rect = fmap fromEnum $ SDL.Rectangle (SDL.P 0) (Vect.V2 (SDL.textureWidth tinfo) (SDL.textureHeight tinfo)) - return $ Figure texture rect rect 0+ return $ Figure (Just texture) rect rect 0+ return $ maybe emptyFigure id tex {-# INLINE picture #-} fromTexture tex = do tinfo <- SDL.queryTexture tex let size = fmap fromEnum $ Vect.V2 (SDL.textureWidth tinfo) (SDL.textureHeight tinfo) - return $ Figure tex (SDL.Rectangle 0 size) (SDL.Rectangle 0 size) 0+ return $ Figure (Just tex) (SDL.Rectangle 0 size) (SDL.Rectangle 0 size) 0 {-# INLINE fromTexture #-} rectangleOutline color size = do- rend <- view _renderer- tex <- SDL.createTexture rend SDL.RGBA8888 SDL.TextureAccessTarget (fmap toEnum size)- SDL.textureBlendMode tex SDL.$= SDL.BlendAlphaBlend+ mrend <- view _renderer+ tex <- flip mapM mrend $ \rend -> do+ tex <- SDL.createTexture rend SDL.RGBA8888 SDL.TextureAccessTarget (fmap toEnum size)+ SDL.textureBlendMode tex SDL.$= SDL.BlendAlphaBlend - bracket (SDL.get (SDL.rendererRenderTarget rend)) (\target -> SDL.rendererRenderTarget rend SDL.$= target) $ \_ -> do- SDL.rendererRenderTarget rend SDL.$= Just tex- SDL.rendererDrawColor rend SDL.$= color- SDL.drawRect rend (Just $ SDL.Rectangle 0 (fmap toEnum size))+ bracket (SDL.get (SDL.rendererRenderTarget rend)) (\target -> SDL.rendererRenderTarget rend SDL.$= target) $ \_ -> do+ SDL.rendererRenderTarget rend SDL.$= Just tex+ SDL.rendererDrawColor rend SDL.$= color+ SDL.drawRect rend (Just $ SDL.Rectangle 0 (fmap toEnum size)) + return tex return $ Figure tex (SDL.Rectangle 0 size) (SDL.Rectangle 0 size) 0 {-# INLINE rectangleOutline #-} rectangleFilled color size = do- rend <- view _renderer- tex <- SDL.createTexture rend SDL.RGBA8888 SDL.TextureAccessTarget (fmap toEnum size)- SDL.textureBlendMode tex SDL.$= SDL.BlendAlphaBlend+ mrend <- view _renderer+ tex <- flip mapM mrend $ \rend -> do+ tex <- SDL.createTexture rend SDL.RGBA8888 SDL.TextureAccessTarget (fmap toEnum size)+ SDL.textureBlendMode tex SDL.$= SDL.BlendAlphaBlend - bracket (SDL.get (SDL.rendererRenderTarget rend)) (\target -> SDL.rendererRenderTarget rend SDL.$= target) $ \_ -> do- SDL.rendererRenderTarget rend SDL.$= Just tex- SDL.rendererDrawColor rend SDL.$= color- SDL.fillRect rend (Just $ SDL.Rectangle 0 (fmap toEnum size))+ bracket (SDL.get (SDL.rendererRenderTarget rend)) (\target -> SDL.rendererRenderTarget rend SDL.$= target) $ \_ -> do+ SDL.rendererRenderTarget rend SDL.$= Just tex+ SDL.rendererDrawColor rend SDL.$= color+ SDL.fillRect rend (Just $ SDL.Rectangle 0 (fmap toEnum size)) + return tex return $ Figure tex (SDL.Rectangle 0 size) (SDL.Rectangle 0 size) 0 {-# INLINE rectangleFilled #-} triangleOutline color size = do- rend <- view _renderer- tex <- SDL.createTexture rend SDL.RGBA8888 SDL.TextureAccessTarget (fmap toEnum size)- SDL.textureBlendMode tex SDL.$= SDL.BlendAlphaBlend+ mrend <- view _renderer+ tex <- flip mapM mrend $ \rend -> do+ tex <- SDL.createTexture rend SDL.RGBA8888 SDL.TextureAccessTarget (fmap toEnum size)+ SDL.textureBlendMode tex SDL.$= SDL.BlendAlphaBlend - bracket (SDL.get (SDL.rendererRenderTarget rend)) (\target -> SDL.rendererRenderTarget rend SDL.$= target) $ \_ -> do- SDL.rendererRenderTarget rend SDL.$= Just tex- SDL.rendererDrawColor rend SDL.$= color+ bracket (SDL.get (SDL.rendererRenderTarget rend)) (\target -> SDL.rendererRenderTarget rend SDL.$= target) $ \_ -> do+ SDL.rendererRenderTarget rend SDL.$= Just tex+ SDL.rendererDrawColor rend SDL.$= color - let size' = fmap toEnum size- Gfx.smoothTriangle- rend- (Vect.V2 (size' ^. _x `div` 2) 0)- (Vect.V2 (size' ^. _x - 1) (size' ^. _y - 1))- (Vect.V2 0 (size' ^. _y - 1))- color+ let size' = fmap toEnum size+ Gfx.smoothTriangle+ rend+ (Vect.V2 (size' ^. _x `div` 2) 0)+ (Vect.V2 (size' ^. _x - 1) (size' ^. _y - 1))+ (Vect.V2 0 (size' ^. _y - 1))+ color + return tex return $ Figure tex (SDL.Rectangle 0 size) (SDL.Rectangle 0 size) 0 {-# INLINE triangleOutline #-}
src/MiniLight/Light.hs view
@@ -46,7 +46,7 @@ -- | The environment for LightT monad. data LightEnv = LightEnv- { renderer :: SDL.Renderer -- ^ Renderer for SDL2+ { renderer :: Maybe SDL.Renderer -- ^ Renderer for SDL2 , fontCache :: FontMap -- ^ System font information , logger :: Caster.LogQueue -- ^ Logger connected stdout }
src/MiniLight/Loader.hs view
@@ -41,6 +41,7 @@ module MiniLight.Loader ( module MiniLight.Loader.Internal.Types, createComponentBy,+ lookupByTagID, LoaderEnv (..), HasLoaderEnv (..),@@ -64,6 +65,7 @@ import Data.Aeson.Patch import Data.Aeson.Pointer import Data.IORef+import qualified Data.HashMap.Strict as HM import qualified Data.Registry as R import qualified Data.Text as T import qualified Data.Vector as V@@ -73,15 +75,35 @@ import MiniLight.Loader.Internal.Types import MiniLight.Loader.Internal.Resolver (resolve, resolveWith, parseAppConfig, emptyContext, Context(..)) +-- | The environment for config loader+data LoaderEnv = LoaderEnv {+ registry :: R.Registry Component,+ tagRegistry :: IORef (HM.HashMap T.Text T.Text),+ appConfig :: IORef AppConfig+}++makeClassy_ ''LoaderEnv+ -- | Create a component with given resolver. createComponentBy- :: Resolver+ :: (HasLoaderEnv env, HasLightEnv env, MonadIO m)+ => Resolver -> Maybe T.Text -> ComponentConfig- -> MiniLight (Either String Component)+ -> LightT env m (Either String Component) createComponentBy resolver uid config = do uuid <- maybe newUID return uid- result <- resolver (name config) uuid (properties config)+ result <- liftMiniLight+ $ resolver (componentType config) uuid (properties config)++ -- register tag+ case tagID config of+ Just tag -> do+ regRef <- view _tagRegistry+ liftIO $ modifyIORef' regRef $ \reg -> HM.insert tag uuid reg+ Caster.debug $ "TagID registered: " <> show tag <> " = " <> show uuid+ _ -> return ()+ return $ fmap ( \c -> setHooks c@@ -95,13 +117,15 @@ ) result --- | The environment for config loader-data LoaderEnv = LoaderEnv {- registry :: R.Registry Component,- appConfig :: IORef AppConfig-}+lookupByTagID+ :: (HasLightEnv env, HasLoaderEnv env, MonadIO m)+ => T.Text+ -> LightT env m (Maybe T.Text)+lookupByTagID tag = do+ regRef <- view _tagRegistry+ reg <- liftIO $ readIORef regRef -makeClassy_ ''LoaderEnv+ return $ HM.lookup tag reg -- | Load an config file and return the resolved @AppConfig@. resolveConfig :: MonadIO m => FilePath -> m (Either T.Text AppConfig)@@ -127,7 +151,7 @@ confs <- lift $ fmap (V.mapMaybe id) $ V.forM (app conf) $ \conf -> do uid <- newUID- result <- liftMiniLight $ createComponentBy mapper (Just uid) conf+ result <- createComponentBy mapper (Just uid) conf case result of Left e -> do@@ -138,8 +162,8 @@ R.register reg uid component Caster.info- $ "Component loaded: {name: "- <> show (name conf)+ $ "Component loaded: {type: "+ <> show (componentType conf) <> ", uid: " <> show uid <> "}"@@ -200,9 +224,7 @@ newID <- lift newUID component <- do- result <- lift $ liftMiniLight $ createComponentBy resolver- (Just newID)- compConf+ result <- lift $ createComponentBy resolver (Just newID) compConf case result of Left err -> do@@ -215,8 +237,8 @@ lift $ Caster.info- $ "Component registered: {name: "- <> show (name compConf)+ $ "Component registered: {type: "+ <> show (componentType compConf) <> ", uid: " <> show (getUID component) <> "}"@@ -255,9 +277,7 @@ let uid = uuid appConf V.! n component <- do- result <- lift $ liftMiniLight $ createComponentBy resolver- (Just uid)- compConf+ result <- lift $ createComponentBy resolver (Just uid) compConf case result of Left err -> do@@ -270,7 +290,7 @@ lift $ Caster.info $ "Component replaced: {name: "- <> show (name compConf)+ <> show (componentType compConf) <> ", uid: " <> show (getUID component) <> "}"
src/MiniLight/Loader/Internal/Resolver.hs view
@@ -201,7 +201,7 @@ 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+ component ctx (Object obj) | all (`HM.member` obj) ["type", "properties"] = do let ctx' = maybe ctx@@ -211,13 +211,16 @@ ) (HM.lookup "_vars" obj) - nameValue <- resolveWith ctx' (obj HM.! "name")+ nameValue <- resolveWith ctx' (obj HM.! "type") 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+ Right $ ComponentConfig name+ (fmap (\(String s) -> s) $ HM.lookup "id" obj)+ 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
@@ -32,20 +32,22 @@ -- | A configuration for a component data ComponentConfig = ComponentConfig {- name :: T.Text,+ componentType :: T.Text,+ tagID :: Maybe 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)]+ toJSON v = toJSON $ HM.fromList $ [("type" :: String, String (componentType v)), ("properties", properties v)] ++ maybe [] (\x -> [("id", String x)]) (tagID v) instance FromJSON ComponentConfig where parseJSON = withObject "component" $ \v -> do- name <- v .: "name"+ componentType <- v .: "type"+ tagID <- v .:? "id" props <- v .: "properties" - return $ ComponentConfig name props Nothing+ return $ ComponentConfig componentType tagID props Nothing -- | A configuration for the application itself data AppConfig = AppConfig {