packages feed

minilight 0.1.1 → 0.2.0

raw patch · 22 files changed

+762/−458 lines, 22 filesdep +uuidPVP ok

version bump matches the API change (PVP)

Dependencies added: uuid

API changes (from Hackage documentation)

- MiniLight: data LoopState
- MiniLight.Component: defResolver :: Resolver
- MiniLight.Component.AnimationLayer: AnimationLayer :: Layer -> Int -> V2 Int -> Int -> Config -> AnimationLayer
- MiniLight.Component.AnimationLayer: Config :: Config -> V2 Int -> Config
- MiniLight.Component.AnimationLayer: [config] :: AnimationLayer -> Config
- MiniLight.Component.AnimationLayer: [counter] :: AnimationLayer -> Int
- MiniLight.Component.AnimationLayer: [division] :: Config -> V2 Int
- MiniLight.Component.AnimationLayer: [layerConf] :: Config -> Config
- MiniLight.Component.AnimationLayer: [layer] :: AnimationLayer -> Layer
- MiniLight.Component.AnimationLayer: [scaler] :: AnimationLayer -> Int
- MiniLight.Component.AnimationLayer: [tileSize] :: AnimationLayer -> V2 Int
- MiniLight.Component.AnimationLayer: data AnimationLayer
- MiniLight.Component.AnimationLayer: data Config
- MiniLight.Component.AnimationLayer: instance Data.Aeson.Types.FromJSON.FromJSON MiniLight.Component.AnimationLayer.Config
- MiniLight.Component.AnimationLayer: instance MiniLight.Component.Types.ComponentUnit MiniLight.Component.AnimationLayer.AnimationLayer
- MiniLight.Component.AnimationLayer: new :: Config -> MiniLight AnimationLayer
- MiniLight.Component.Button: Button :: Font -> Config -> Button
- MiniLight.Component.Button: Config :: V2 Int -> Text -> V4 Word8 -> FontDescriptor -> Int -> Config
- MiniLight.Component.Button: [color] :: Config -> V4 Word8
- MiniLight.Component.Button: [config] :: Button -> Config
- MiniLight.Component.Button: [fontConf] :: Config -> FontDescriptor
- MiniLight.Component.Button: [fontSize] :: Config -> Int
- MiniLight.Component.Button: [font] :: Button -> Font
- MiniLight.Component.Button: [label] :: Config -> Text
- MiniLight.Component.Button: [size] :: Config -> V2 Int
- MiniLight.Component.Button: data Button
- MiniLight.Component.Button: data Config
- MiniLight.Component.Button: instance Data.Aeson.Types.FromJSON.FromJSON MiniLight.Component.Button.Config
- MiniLight.Component.Button: instance MiniLight.Component.Types.ComponentUnit MiniLight.Component.Button.Button
- MiniLight.Component.Button: new :: Config -> MiniLight Button
- MiniLight.Component.Layer: Config :: FilePath -> V2 Int -> V2 Int -> Config
- MiniLight.Component.Layer: Layer :: Figure -> Layer
- MiniLight.Component.Layer: [image] :: Config -> FilePath
- MiniLight.Component.Layer: [layer] :: Layer -> Figure
- MiniLight.Component.Layer: [position] :: Config -> V2 Int
- MiniLight.Component.Layer: [size] :: Config -> V2 Int
- MiniLight.Component.Layer: data Config
- MiniLight.Component.Layer: data Layer
- MiniLight.Component.Layer: instance Data.Aeson.Types.FromJSON.FromJSON MiniLight.Component.Layer.Config
- MiniLight.Component.Layer: instance MiniLight.Component.Types.ComponentUnit MiniLight.Component.Layer.Layer
- MiniLight.Component.Layer: new :: Config -> MiniLight Layer
- MiniLight.Component.Layer: newNineTile :: Config -> MiniLight Layer
- MiniLight.Component.MessageEngine: Config :: Text -> Bool -> V4 Word8 -> FontDescriptor -> Int -> Config
- MiniLight.Component.MessageEngine: MessageEngine :: Font -> Int -> Int -> Figure -> Bool -> Config -> MessageEngine
- MiniLight.Component.MessageEngine: [color] :: Config -> V4 Word8
- MiniLight.Component.MessageEngine: [config] :: MessageEngine -> Config
- MiniLight.Component.MessageEngine: [counter] :: MessageEngine -> Int
- MiniLight.Component.MessageEngine: [finished] :: MessageEngine -> Bool
- MiniLight.Component.MessageEngine: [fontConf] :: Config -> FontDescriptor
- MiniLight.Component.MessageEngine: [fontSize] :: Config -> Int
- MiniLight.Component.MessageEngine: [font] :: MessageEngine -> Font
- MiniLight.Component.MessageEngine: [messages] :: Config -> Text
- MiniLight.Component.MessageEngine: [rendered] :: MessageEngine -> Int
- MiniLight.Component.MessageEngine: [static] :: Config -> Bool
- MiniLight.Component.MessageEngine: [textTexture] :: MessageEngine -> Figure
- MiniLight.Component.MessageEngine: data Config
- MiniLight.Component.MessageEngine: data MessageEngine
- MiniLight.Component.MessageEngine: instance Data.Aeson.Types.FromJSON.FromJSON MiniLight.Component.MessageEngine.Config
- MiniLight.Component.MessageEngine: instance MiniLight.Component.Types.ComponentUnit MiniLight.Component.MessageEngine.MessageEngine
- MiniLight.Component.MessageEngine: new :: Config -> MiniLight MessageEngine
- MiniLight.Component.MessageLayer: Config :: Config -> Config -> Config -> Config
- MiniLight.Component.MessageLayer: MessageLayer :: MessageEngine -> Layer -> AnimationLayer -> Config -> MessageLayer
- MiniLight.Component.MessageLayer: [config] :: MessageLayer -> Config
- MiniLight.Component.MessageLayer: [cursor] :: MessageLayer -> AnimationLayer
- MiniLight.Component.MessageLayer: [engine] :: Config -> Config
- MiniLight.Component.MessageLayer: [layer] :: MessageLayer -> Layer
- MiniLight.Component.MessageLayer: [messageEngine] :: MessageLayer -> MessageEngine
- MiniLight.Component.MessageLayer: [next] :: Config -> Config
- MiniLight.Component.MessageLayer: [window] :: Config -> Config
- MiniLight.Component.MessageLayer: cursorL :: Lens' MessageLayer AnimationLayer
- MiniLight.Component.MessageLayer: data Config
- MiniLight.Component.MessageLayer: data MessageLayer
- MiniLight.Component.MessageLayer: engineL :: Lens' MessageLayer MessageEngine
- MiniLight.Component.MessageLayer: instance Data.Aeson.Types.FromJSON.FromJSON MiniLight.Component.MessageLayer.Config
- MiniLight.Component.MessageLayer: instance MiniLight.Component.Types.ComponentUnit MiniLight.Component.MessageLayer.MessageLayer
- MiniLight.Component.MessageLayer: new :: Config -> MiniLight MessageLayer
- MiniLight.Event: ComponentEvent :: Name -> Text -> Event
+ Data.Component.AnimationLayer: AnimationLayer :: Layer -> Int -> V2 Int -> Int -> Config -> AnimationLayer
+ Data.Component.AnimationLayer: Config :: Config -> V2 Int -> Config
+ Data.Component.AnimationLayer: [config] :: AnimationLayer -> Config
+ Data.Component.AnimationLayer: [counter] :: AnimationLayer -> Int
+ Data.Component.AnimationLayer: [division] :: Config -> V2 Int
+ Data.Component.AnimationLayer: [layerConf] :: Config -> Config
+ Data.Component.AnimationLayer: [layer] :: AnimationLayer -> Layer
+ Data.Component.AnimationLayer: [scaler] :: AnimationLayer -> Int
+ Data.Component.AnimationLayer: [tileSize] :: AnimationLayer -> V2 Int
+ Data.Component.AnimationLayer: data AnimationLayer
+ Data.Component.AnimationLayer: data Config
+ Data.Component.AnimationLayer: instance Data.Aeson.Types.FromJSON.FromJSON Data.Component.AnimationLayer.Config
+ Data.Component.AnimationLayer: instance MiniLight.Component.Types.ComponentUnit Data.Component.AnimationLayer.AnimationLayer
+ Data.Component.AnimationLayer: new :: Config -> MiniLight AnimationLayer
+ Data.Component.Basic: Config :: V2 Int -> V2 Int -> V4 Word8 -> FontDescriptor -> Int -> Config
+ Data.Component.Basic: [MouseOver] :: V2 Int -> Signal
+ Data.Component.Basic: [MousePressed] :: V2 Int -> Signal
+ Data.Component.Basic: [MouseReleased] :: V2 Int -> Signal
+ Data.Component.Basic: [color] :: Config -> V4 Word8
+ Data.Component.Basic: [fontDesc] :: Config -> FontDescriptor
+ Data.Component.Basic: [fontSize] :: Config -> Int
+ Data.Component.Basic: [position] :: Config -> V2 Int
+ Data.Component.Basic: [size] :: Config -> V2 Int
+ Data.Component.Basic: areaRectangle :: Config -> Rectangle Int
+ Data.Component.Basic: contains :: (Ord a, Num a) => Rectangle a -> V2 a -> Bool
+ Data.Component.Basic: data Config
+ Data.Component.Basic: data Signal
+ Data.Component.Basic: emitBasicSignal :: (HasLightEnv env, HasLoopEnv env, HasComponentEnv env, MonadIO m) => Event -> Config -> LightT env m ()
+ Data.Component.Basic: instance Data.Aeson.Types.FromJSON.FromJSON Data.Component.Basic.Config
+ Data.Component.Basic: instance MiniLight.Event.EventType Data.Component.Basic.Signal
+ Data.Component.Basic: loadFontFrom :: Config -> MiniLight Font
+ Data.Component.Basic: wrapConfig :: (Config -> a -> Parser r) -> (Object -> Parser a) -> Value -> Parser r
+ 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.Button: Button :: Font -> Config -> Button
+ Data.Component.Button: Click :: ButtonEvent
+ Data.Component.Button: Config :: V2 Int -> Text -> V4 Word8 -> FontDescriptor -> Int -> Config
+ Data.Component.Button: [color] :: Config -> V4 Word8
+ Data.Component.Button: [config] :: Button -> Config
+ Data.Component.Button: [fontConf] :: Config -> FontDescriptor
+ Data.Component.Button: [fontSize] :: Config -> Int
+ Data.Component.Button: [font] :: Button -> Font
+ Data.Component.Button: [label] :: Config -> Text
+ Data.Component.Button: [size] :: Config -> V2 Int
+ Data.Component.Button: data Button
+ Data.Component.Button: data ButtonEvent
+ Data.Component.Button: data Config
+ Data.Component.Button: instance Data.Aeson.Types.FromJSON.FromJSON Data.Component.Button.Config
+ Data.Component.Button: instance MiniLight.Component.Types.ComponentUnit Data.Component.Button.Button
+ Data.Component.Button: instance MiniLight.Event.EventType Data.Component.Button.ButtonEvent
+ Data.Component.Button: new :: Config -> MiniLight Button
+ Data.Component.Layer: Config :: FilePath -> V2 Int -> V2 Int -> Config
+ Data.Component.Layer: Layer :: Figure -> Layer
+ Data.Component.Layer: [image] :: Config -> FilePath
+ Data.Component.Layer: [layer] :: Layer -> Figure
+ Data.Component.Layer: [position] :: Config -> V2 Int
+ Data.Component.Layer: [size] :: Config -> V2 Int
+ Data.Component.Layer: data Config
+ Data.Component.Layer: data Layer
+ Data.Component.Layer: instance Data.Aeson.Types.FromJSON.FromJSON Data.Component.Layer.Config
+ Data.Component.Layer: instance MiniLight.Component.Types.ComponentUnit Data.Component.Layer.Layer
+ Data.Component.Layer: new :: Config -> MiniLight Layer
+ Data.Component.Layer: newNineTile :: Config -> MiniLight Layer
+ Data.Component.MessageEngine: Config :: Text -> Bool -> V4 Word8 -> FontDescriptor -> Int -> Config
+ Data.Component.MessageEngine: MessageEngine :: Font -> Int -> Int -> Figure -> Bool -> Config -> MessageEngine
+ Data.Component.MessageEngine: [color] :: Config -> V4 Word8
+ Data.Component.MessageEngine: [config] :: MessageEngine -> Config
+ Data.Component.MessageEngine: [counter] :: MessageEngine -> Int
+ Data.Component.MessageEngine: [finished] :: MessageEngine -> Bool
+ Data.Component.MessageEngine: [fontConf] :: Config -> FontDescriptor
+ Data.Component.MessageEngine: [fontSize] :: Config -> Int
+ Data.Component.MessageEngine: [font] :: MessageEngine -> Font
+ Data.Component.MessageEngine: [messages] :: Config -> Text
+ Data.Component.MessageEngine: [rendered] :: MessageEngine -> Int
+ Data.Component.MessageEngine: [static] :: Config -> Bool
+ Data.Component.MessageEngine: [textTexture] :: MessageEngine -> Figure
+ Data.Component.MessageEngine: data Config
+ Data.Component.MessageEngine: data MessageEngine
+ Data.Component.MessageEngine: instance Data.Aeson.Types.FromJSON.FromJSON Data.Component.MessageEngine.Config
+ Data.Component.MessageEngine: instance MiniLight.Component.Types.ComponentUnit Data.Component.MessageEngine.MessageEngine
+ Data.Component.MessageEngine: new :: Config -> MiniLight MessageEngine
+ Data.Component.MessageLayer: Config :: Config -> Config -> Config -> Config
+ Data.Component.MessageLayer: MessageLayer :: MessageEngine -> Layer -> AnimationLayer -> Config -> MessageLayer
+ Data.Component.MessageLayer: [config] :: MessageLayer -> Config
+ Data.Component.MessageLayer: [cursor] :: MessageLayer -> AnimationLayer
+ Data.Component.MessageLayer: [engine] :: Config -> Config
+ Data.Component.MessageLayer: [layer] :: MessageLayer -> Layer
+ Data.Component.MessageLayer: [messageEngine] :: MessageLayer -> MessageEngine
+ Data.Component.MessageLayer: [next] :: Config -> Config
+ Data.Component.MessageLayer: [window] :: Config -> Config
+ Data.Component.MessageLayer: cursorL :: Lens' MessageLayer AnimationLayer
+ Data.Component.MessageLayer: data Config
+ Data.Component.MessageLayer: data MessageLayer
+ Data.Component.MessageLayer: engineL :: Lens' MessageLayer MessageEngine
+ Data.Component.MessageLayer: instance Data.Aeson.Types.FromJSON.FromJSON Data.Component.MessageLayer.Config
+ Data.Component.MessageLayer: instance MiniLight.Component.Types.ComponentUnit Data.Component.MessageLayer.MessageLayer
+ Data.Component.MessageLayer: new :: Config -> MiniLight MessageLayer
+ Data.Component.Resolver: foldResult :: (String -> b) -> (a -> b) -> Result a -> b
+ Data.Component.Resolver: resolver :: Resolver
+ MiniLight: [env] :: LoopEnv env -> env
+ MiniLight: [signalQueue] :: LoopEnv env -> IORef [Event]
+ MiniLight: data LoopEnv env
+ MiniLight: instance MiniLight.Component.Types.HasComponentEnv (Data.Text.Internal.Text, env)
+ MiniLight: instance MiniLight.Light.HasLightEnv env => MiniLight.Light.HasLightEnv (Data.Text.Internal.Text, env)
+ MiniLight: instance MiniLight.Light.HasLightEnv env => MiniLight.Light.HasLightEnv (MiniLight.LoopEnv env)
+ MiniLight: instance MiniLight.Light.HasLoopEnv (MiniLight.LoopEnv env)
+ MiniLight: instance MiniLight.Light.HasLoopEnv env => MiniLight.Light.HasLoopEnv (Data.Text.Internal.Text, env)
+ MiniLight: type MiniLoop = LightT (LoopEnv LightEnv) IO
+ MiniLight.Component.Types: class HasComponentEnv env
+ MiniLight.Component.Types: emit :: (HasLoopEnv env, HasComponentEnv env, MonadIO m, EventType et) => et -> LightT env m ()
+ MiniLight.Component.Types: getUID :: Component -> Text
+ MiniLight.Component.Types: uidL :: HasComponentEnv env => Lens' env Text
+ MiniLight.Event: Signal :: Text -> Dynamic -> Event
+ MiniLight.Event: asSignal :: EventType a => Event -> Text -> Maybe a
+ MiniLight.Event: class Typeable e => EventType e
+ MiniLight.Event: signal :: EventType a => Text -> a -> Event
+ MiniLight.Light: class HasLoopEnv env
+ MiniLight.Light: eventsL :: HasLoopEnv env => Lens' env (IORef [Event])
+ MiniLight.Light: keyStatesL :: HasLoopEnv env => Lens' env (HashMap Scancode Int)
+ MiniLight.Light: signalQueueL :: HasLoopEnv env => Lens' env (IORef [Event])
- MiniLight: LoopState :: HashMap Scancode Int -> [Event] -> IOVector Component -> LoopState
+ MiniLight: LoopState :: env -> HashMap Scancode Int -> IORef [Event] -> IORef [Event] -> IOVector Component -> LoopEnv env
- MiniLight: [components] :: LoopState -> IOVector Component
+ MiniLight: [components] :: LoopEnv env -> IOVector Component
- MiniLight: [events] :: LoopState -> [Event]
+ MiniLight: [events] :: LoopEnv env -> IORef [Event]
- MiniLight: [keyStates] :: LoopState -> HashMap Scancode Int
+ MiniLight: [keyStates] :: LoopEnv env -> HashMap Scancode Int
- MiniLight: runLightT :: (HasLightEnv env, MonadIO m, MonadMask m) => (LightEnv -> env) -> LightT env m a -> m a
+ MiniLight: runLightT :: (MonadIO m, MonadMask m) => LightT LightEnv m a -> m a
- MiniLight: runMainloop :: (HasLightEnv env, MonadIO m, MonadMask m) => LoopConfig -> s -> (LoopState -> s -> LightT env m s) -> LightT env m ()
+ MiniLight: runMainloop :: (HasLightEnv env, HasLightEnv loop, HasLoopEnv loop, MonadIO m, MonadMask m) => (LoopEnv env -> loop) -> LoopConfig -> s -> (s -> LightT loop m s) -> LightT env m ()
- MiniLight.Component.Types: onSignal :: (ComponentUnit c, HasLightEnv env, MonadIO m, MonadMask m) => Event -> c -> LightT env m c
+ 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: update :: (ComponentUnit c, HasLightEnv env, MonadIO m, MonadMask m) => c -> LightT env m c
+ MiniLight.Component.Types: update :: (ComponentUnit c, HasLightEnv env, HasLoopEnv env, HasComponentEnv env, MonadIO m, MonadMask m) => c -> LightT env m c

Files

CHANGELOG.md view
@@ -1,5 +1,11 @@ # Revision history for minilight +## 0.2 -- 2019-04-23++* New features:+    * Re-structured modules+    * Emit signals and signal handlers+ ## 0.1.1 -- 2019-04-14  * Fixes:
+ README.md view
@@ -0,0 +1,19 @@+# minilight++[![CircleCI](https://circleci.com/gh/myuon/minilight.svg?style=svg)](https://circleci.com/gh/myuon/minilight) [![Hackage](http://img.shields.io/hackage/v/minilight.svg)](https://hackage.haskell.org/package/minilight) [![MIT license](http://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)++A simple but powerful graphics library.++## Build++You first need to install [sdl2](https://www.libsdl.org/index.php) packages.++For Ubuntu 18.04:++```sh+~$ sudo apt install -y libsdl2-dev libsdl2-image-dev libsdl2-ttf-dev libsdl2-gfx-dev+```++## Examples++See [examples](https://github.com/myuon/minilight/tree/master/examples)
examples/boids.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}+import Data.Component.Resolver import MiniLight import Control.Monad.State import qualified Data.Vector as V@@ -32,7 +33,7 @@ height :: Int height = 600 -mainloop :: StateT Game MiniLight ()+mainloop :: StateT Game MiniLoop () mainloop = do   Game { objects = objects, pic = pic } <- get @@ -109,10 +110,15 @@     )     vec -  runLightT id $ do+  runLightT $ do     pic <- triangleOutline (Vect.V4 100 100 100 255) (Vect.V2 10 20)      runMainloop-      (defConfig { appConfigFile = Nothing, additionalComponents = [] })+      id+      ( defConfig { appConfigFile        = Nothing+                  , additionalComponents = []+                  , componentResolver    = resolver+                  }+      )       (Game {objects = objs, pic = pic})-      (\_ -> execStateT mainloop)+      (execStateT mainloop)
examples/button-counter.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}+import Data.Component.Resolver import MiniLight import qualified Data.Text as T import qualified SDL@@ -38,10 +39,15 @@  main :: IO () main = do-  runLightT id $ do+  runLightT $ do     button <- newComponent =<< new      runMainloop-      (defConfig { appConfigFile = Nothing, additionalComponents = [button] })+      id+      ( defConfig { appConfigFile        = Nothing+                  , additionalComponents = [button]+                  , componentResolver    = resolver+                  }+      )       ()-      (\_ -> return)+      return
minilight.cabal view
@@ -3,7 +3,7 @@ --   For further documentation, see http://haskell.org/cabal/users-guide/  name:                minilight-version:             0.1.1+version:             0.2.0 synopsis:            A SDL2-based graphics library, batteries-included. description:   This package provides the wheel for a graphical application or a game.@@ -22,7 +22,9 @@ maintainer:          ioi.joi.koi.loi@gmail.com -- copyright: category:            Graphics-extra-source-files:  CHANGELOG.md+extra-source-files:+  CHANGELOG.md+  README.md  source-repository head   type:     git@@ -37,11 +39,13 @@     MiniLight.Component     MiniLight.Component.Types     MiniLight.Component.Loader-    MiniLight.Component.Layer-    MiniLight.Component.AnimationLayer-    MiniLight.Component.MessageEngine-    MiniLight.Component.MessageLayer-    MiniLight.Component.Button+    Data.Component.Basic+    Data.Component.Layer+    Data.Component.AnimationLayer+    Data.Component.MessageEngine+    Data.Component.MessageLayer+    Data.Component.Button+    Data.Component.Resolver   -- other-modules:   -- other-extensions:   build-depends:@@ -65,6 +69,7 @@     sdl2-image           >= 2.0.0 && < 2.1,     sdl2-ttf             >= 2.1.0 && < 2.2,     trifecta             >= 2 && < 2.1,+    uuid                 >= 1.3.12 && < 1.4,     yaml                 >= 0.11.0 && < 0.12,     mwc-random           >= 0.14.0 && < 0.15,   hs-source-dirs:      src
+ src/Data/Component/AnimationLayer.hs view
@@ -0,0 +1,54 @@+module Data.Component.AnimationLayer where++import Control.Monad.State+import Data.Aeson+import Linear+import Lens.Micro+import MiniLight+import qualified Data.Component.Layer as Layer+import qualified SDL+import qualified SDL.Vect as Vect++data AnimationLayer = AnimationLayer {+  layer :: Layer.Layer,+  counter :: Int,+  tileSize :: Vect.V2 Int,+  scaler :: Int,+  config :: Config+}++instance ComponentUnit AnimationLayer where+  update = execStateT $ do+    modify $ \c -> c { counter = (counter c + 1) }+    modify $ \c -> c { counter = if counter c >= (division (config c) ^._x * division (config c) ^. _y) * scaler c then 0 else counter c }++  figures comp = do+    let iv = V2 ((counter comp `div` scaler comp) `mod` division (config comp) ^. _x) ((counter comp `div` scaler comp) `div` division (config comp) ^. _x)+    return [+      clip (SDL.Rectangle (SDL.P (tileSize comp * iv)) (tileSize comp)) $ Layer.layer $ layer comp+      ]++data Config = Config {+  layerConf :: Layer.Config,+  division :: Vect.V2 Int+}++instance FromJSON Config where+  parseJSON = withObject "config" $ \v -> do+    conf <- parseJSON (Object v)+    division <- (\v -> Vect.V2 <$> v .: "x" <*> v .: "y") =<< v .: "division"++    return $ Config conf division++new :: Config -> MiniLight AnimationLayer+new conf = do+  layer <- Layer.new (layerConf conf)+  let size = getFigureSize (Layer.layer layer)++  return $ AnimationLayer+    { layer    = layer+    , counter  = 0+    , tileSize = div <$> size <*> division conf+    , scaler   = 25+    , config   = conf+    }
+ src/Data/Component/Basic.hs view
@@ -0,0 +1,122 @@+{-| The package provides the basics for all components in the library.++A component should have the followings (those can be omitted):++- position: @{x: int, y: int}@+- size: @{width: int, height: int}@+- color: @int[4]@+- font: @{family: string, bold: bool, italic: bool, size: int}@++-}+module Data.Component.Basic where++import Data.Aeson+import Data.Aeson.Types+import Data.Typeable+import Data.Word (Word8)+import qualified SDL+import qualified SDL.Vect as Vect+import qualified SDL.Font+import MiniLight++-- | Basic config type+data Config = Config {+  size :: Vect.V2 Int,+  position :: Vect.V2 Int,+  color :: Vect.V4 Word8,+  fontDesc :: FontDescriptor,+  fontSize :: Int+}++instance FromJSON Config where+  parseJSON = withObject "config" $ \v -> do+    sizeMaybe <- v .:? "size"+    size <- (\w -> maybe (return 0) w sizeMaybe) $ withObject "size" $ \v ->+      Vect.V2 <$> v .: "width" <*> v .: "height"++    positionMaybe <- v .:? "position"+    position <- (\w -> maybe (return 0) w positionMaybe) $ withObject "position" $ \v ->+      Vect.V2 <$> v .: "x" <*> v .: "y"++    color <- fmap (\[r,g,b,a] -> Vect.V4 r g b a) $ v .:? "color" .!= [255, 255, 255, 255]++    fontMaybe <- v .:? "font"+    (fontDesc, fontSize) <- (\w -> maybe (return (FontDescriptor "" (FontStyle False False), 0)) w fontMaybe) $ withObject "font" $ \v -> do+      family <- v .: "family"+      size <- v .: "size"+      bold <- v .:? "bold" .!= False+      italic <- v .:? "italic" .!= False++      return $ (FontDescriptor family (FontStyle bold italic), size)++    return $ Config size position color fontDesc fontSize++-- | Load a system font from 'Config' type.+loadFontFrom :: Config -> MiniLight SDL.Font.Font+loadFontFrom conf = loadFont (fontDesc conf) (fontSize conf)++-- | This wrapper function is useful when you write your component config parser.+wrapConfig+  :: (Config -> a -> Parser r) -> (Object -> Parser a) -> Value -> Parser r+wrapConfig f p = withObject "wrapConfig" $ \v -> do+  other <- p v+  conf  <- parseJSON (Object v)+  f conf other++-- | The rectangle region of the component.+areaRectangle :: Config -> SDL.Rectangle Int+areaRectangle conf = SDL.Rectangle (SDL.P (position conf)) (size conf)++-- | Basic signal type.+data Signal where+  MousePressed+    :: Vect.V2 Int  -- ^ The relative position of the mouse pointer+    -> Signal+  MouseReleased+    :: Vect.V2 Int  -- ^ The relative position of the mouse pointer+    -> Signal+  MouseOver+    :: Vect.V2 Int  -- ^ The relative position of the mouse pointer+    -> Signal+  deriving Typeable++instance EventType Signal++-- | This wrapper function is useful when you write your own 'onSignal' component.+wrapSignal+  :: ( HasLightEnv env+     , HasLoopEnv env+     , HasComponentEnv env+     , MonadIO m+     , ComponentUnit c+     )+  => (c -> Config)  -- ^ 'Config' getter+  -> (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++-- | Basic signaling function.+emitBasicSignal+  :: (HasLightEnv env, HasLoopEnv env, HasComponentEnv env, MonadIO m)+  => Event+  -> Config+  -> 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+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+emitBasicSignal _ _ = return ()++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) =+  x <= px && px <= x + w && y <= py && py <= y + h
+ src/Data/Component/Button.hs view
@@ -0,0 +1,69 @@+module Data.Component.Button where++import Data.Aeson+import qualified Data.Text as T+import Data.Typeable+import Data.Word (Word8)+import MiniLight+import qualified SDL+import qualified SDL.Font+import qualified SDL.Vect as Vect++data Button = Button {+  font :: SDL.Font.Font,+  config :: Config+}++data ButtonEvent = Click+  deriving Typeable++instance EventType ButtonEvent++instance ComponentUnit Button where+  update = return++  figures comp = do+    textTexture <- liftMiniLight $ text (font comp) (color (config comp)) $ label (config comp)+    base <- liftMiniLight $ rectangleFilled (Vect.V4 200 200 200 255) (getFigureSize textTexture)++    return [+      base,+      textTexture+      ]++  useCache _ _ = True++  onSignal (RawEvent (SDL.Event _ (SDL.MouseButtonEvent (SDL.MouseButtonEventData _ SDL.Released _ _ _ _)))) comp = do+    emit Click+    return comp+  onSignal _ comp = return comp++  beforeClearCache _ figs = mapM_ freeFigure figs++data Config = Config {+  size :: Vect.V2 Int,+  label :: T.Text,+  color :: Vect.V4 Word8,+  fontConf :: FontDescriptor,+  fontSize :: Int+}++instance FromJSON Config where+  parseJSON = withObject "config" $ \v -> do+    size <- withObject "font" (\v -> Vect.V2 <$> v .: "width" <*> v .: "height") =<< v .: "size"+    label <- v .: "label"+    [r,g,b,a] <- v .:? "color" .!= [255, 255, 255, 255]+    (fontConf, fontSize) <- (v .: "font" >>=) $ withObject "font" $ \v -> do+      family <- v .: "family"+      size <- v .: "size"+      bold <- v .:? "bold" .!= False+      italic <- v .:? "italic" .!= False++      return $ (FontDescriptor family (FontStyle bold italic), size)++    return $ Config size label (Vect.V4 r g b a) fontConf fontSize++new :: Config -> MiniLight Button+new conf = do+  font <- loadFont (fontConf conf) (fontSize conf)+  return $ Button {font = font, config = conf}
+ src/Data/Component/Layer.hs view
@@ -0,0 +1,83 @@+module Data.Component.Layer where++import Control.Monad+import Data.Aeson+import Lens.Micro+import Lens.Micro.Mtl+import Linear+import MiniLight+import qualified SDL+import qualified SDL.Vect as Vect++data Layer = Layer {+  layer :: Figure+}++instance ComponentUnit Layer where+  figures comp = return [layer comp]++data Config = Config {+  image :: FilePath,+  size :: Vect.V2 Int,+  position :: Vect.V2 Int+}++instance FromJSON Config where+  parseJSON = withObject "config" $ \v -> do+    image <- v .: "image"+    size <- withObject "size" (\v -> Vect.V2 <$> v .: "width" <*> v .: "height") =<< v .: "size"++    positionMaybe <- v .:? "position"+    position <- maybe (return 0) (withObject "position" (\v -> Vect.V2 <$> v .: "x" <*> v .: "y")) positionMaybe++    return $ Config image size position++new :: Config -> MiniLight Layer+new conf = do+  pic <- picture (image conf)+  return $ Layer {layer = pic}++newNineTile :: Config -> MiniLight Layer+newNineTile conf = do+  pic <- picture $ image conf+  let siz     = fmap toEnum $ size conf+  let tex     = texture pic+  let texSize = fmap toEnum $ getFigureSize pic++  tinfo    <- SDL.queryTexture tex+  renderer <- view rendererL++  target   <- SDL.createTexture renderer+                                (SDL.texturePixelFormat tinfo)+                                SDL.TextureAccessTarget+                                siz+  SDL.rendererRenderTarget renderer SDL.$= Just target+  SDL.textureBlendMode target SDL.$= SDL.BlendAlphaBlend++  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+          )++    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++  tex <- fromTexture target+  return $ Layer {layer = tex}+
+ src/Data/Component/MessageEngine.hs view
@@ -0,0 +1,78 @@+module Data.Component.MessageEngine where++import Control.Monad.State+import Data.Aeson+import qualified Data.Text as T+import Data.Word (Word8)+import Lens.Micro.Mtl+import MiniLight+import qualified SDL+import qualified SDL.Font+import qualified SDL.Vect as Vect++data MessageEngine = MessageEngine {+  font :: SDL.Font.Font,+  counter :: Int,+  rendered :: Int,+  textTexture :: Figure,+  finished :: Bool,+  config :: Config+}++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 })++        comp <- get+        when (rendered comp == T.length (messages (config comp))) $ do+          id %= (\c -> c { finished = True })++      id %= (\c -> c { counter = counter c + 1 })++  figures comp = do+    (w, h) <- SDL.Font.size (font comp) (T.take (rendered comp) $ messages (config comp))++    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+}++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++      return $ (FontDescriptor family (FontStyle bold italic), size)++    return $ Config messages static (Vect.V4 r g b a) fontConf size++new :: Config -> MiniLight MessageEngine+new conf = do+  font        <- loadFont (fontConf conf) (fontSize conf)+  textTexture <- text font (color conf) $ messages conf++  return $ MessageEngine+    { font        = font+    , counter     = 0+    , rendered    = if static conf then T.length (messages conf) else 0+    , textTexture = textTexture+    , finished    = static conf+    , config      = conf+    }
+ src/Data/Component/MessageLayer.hs view
@@ -0,0 +1,75 @@+module Data.Component.MessageLayer where++import Control.Monad.State+import Data.Aeson+import Lens.Micro+import Lens.Micro.Mtl+import Linear+import MiniLight+import qualified Data.Component.Layer as CLayer+import qualified Data.Component.AnimationLayer as CAnim+import qualified Data.Component.MessageEngine as CME+import qualified SDL.Vect as Vect++data MessageLayer = MessageLayer {+  messageEngine :: CME.MessageEngine,+  layer :: CLayer.Layer,+  cursor :: CAnim.AnimationLayer,+  config :: Config+}++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 })++instance ComponentUnit MessageLayer where+  update = execStateT $ do+    zoom engineL $ do+      c <- use id+      id <~ lift (update c)++    zoom cursorL $ do+      c <- use id+      id <~ lift (update c)++  figures comp = do+    baseLayer <- figures $ layer comp+    cursorLayer <- figures $ cursor comp+    textLayer <- figures $ messageEngine comp++    let cursorSize = div <$> CAnim.tileSize (cursor comp) <*> CAnim.division (CAnim.config (cursor comp))+    let windowSize = CLayer.size $ window $ config comp++    return+      $ map (translate (CLayer.position (window (config comp)))) $ baseLayer+      ++ map (translate (Vect.V2 20 10)) textLayer+      ++ map (translate (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"++    return $ Config messageEngineConf layerConf nextConf++new :: Config -> MiniLight MessageLayer+new conf = do+  engine <- CME.new (engine conf)+  layer  <- CLayer.newNineTile (window conf)+  cursor <- CAnim.new (next conf)++  return $ MessageLayer+    { messageEngine = engine+    , layer         = layer+    , cursor        = cursor+    , config        = conf+    }
+ src/Data/Component/Resolver.hs view
@@ -0,0 +1,38 @@+{-| This module provides the default resolver for pre-defined components.+-}+module Data.Component.Resolver (+  resolver,+  foldResult,+) where++import qualified Data.Aeson as Aeson+import qualified Data.Text as T+import MiniLight+import qualified Data.Component.AnimationLayer as AnimationLayer+import qualified Data.Component.Button as Button+import qualified Data.Component.Layer as Layer+import qualified Data.Component.MessageEngine as MessageEngine+import qualified Data.Component.MessageLayer as MessageLayer++foldResult :: (String -> b) -> (a -> b) -> Aeson.Result a -> b+foldResult f g r = case r of+  Aeson.Error   err -> f err+  Aeson.Success a   -> g a++-- | Pre-defined resolver supports all components in this library.+resolver :: Resolver+resolver name props = case name of+  "animation-layer" -> newComponent+    =<< AnimationLayer.new (foldResult error id $ Aeson.fromJSON props)+  "button" ->+    newComponent =<< Button.new (foldResult error id $ Aeson.fromJSON props)+  "layer" ->+    newComponent =<< Layer.new (foldResult error id $ Aeson.fromJSON props)+  "message-engine" -> newComponent+    =<< MessageEngine.new (foldResult error id $ Aeson.fromJSON props)+  "message-layer" -> newComponent+    =<< MessageLayer.new (foldResult error id $ Aeson.fromJSON props)+  "tiled-layer" -> newComponent+    =<< Layer.newNineTile (foldResult error id $ Aeson.fromJSON props)+  _ -> error $ T.unpack $ "Component not defined: `" <> name <> "`"+
src/MiniLight.hs view
@@ -9,7 +9,8 @@   runLightT,   LoopConfig (..),   defConfig,-  LoopState (..),+  LoopEnv (..),+  MiniLoop,   runMainloop, ) where @@ -17,12 +18,14 @@ import Control.Monad.Catch import Control.Monad.Reader import qualified Data.Aeson as Aeson-import Data.Hashable (Hashable(..)) import Data.Foldable (foldlM)+import Data.Hashable (Hashable(..)) import qualified Data.HashMap.Strict as HM+import Data.IORef import qualified Data.Text as T import qualified Data.Vector.Mutable as VM import Graphics.Text.TrueType+import Lens.Micro import Lens.Micro.Mtl import MiniLight.Component import MiniLight.Event@@ -35,18 +38,11 @@   hashWithSalt n sc = hashWithSalt n (SDL.unwrapScancode sc)  -- | Run a Light monad.-runLightT-  :: (HasLightEnv env, MonadIO m, MonadMask m)-  => (LightEnv -> env)  -- ^ construct @env@ value with initial 'LightEnv'-  -> LightT env m a-  -> m a-runLightT init prog = withSDL $ withWindow $ \window -> do+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-  runReaderT (runLightT' prog) $ init $ LightEnv-    { renderer  = renderer-    , fontCache = fc-    }+  runReaderT (runLightT' prog) $ LightEnv {renderer = renderer, fontCache = fc}  -- | Use 'defConfig' for a default setting. data LoopConfig = LoopConfig {@@ -56,47 +52,94 @@   additionalComponents :: [Component]  -- ^ The components here would be added during the initialization. } --- | Default configurations for the mainloop.+-- | Default configurations for the mainloop. You need to set @componentResolver@ if you use a component. defConfig :: LoopConfig defConfig = LoopConfig   { watchKeys            = Nothing   , appConfigFile        = Nothing-  , componentResolver    = defResolver+  , componentResolver    = \_ _ -> undefined   , additionalComponents = []   } --- | LoopState value would be passed to user side in a mainloop.-data LoopState = LoopState {-  keyStates :: HM.HashMap SDL.Scancode Int,  -- ^ Contains the number of frames that a specific keys are continuously pressing.-  events :: [SDL.Event],  -- ^ Occurred events since the last frame.-  components :: VM.IOVector Component  -- ^ Current components managed in a mainloop. Be careful to modify a component destructively.-}- fromList :: MonadIO m => [a] -> m (VM.IOVector a) fromList xs = liftIO $ do   vec <- VM.new $ length xs   forM_ (zip [0 ..] xs) $ uncurry (VM.write vec)   return vec +-- | LoopEnv value would be passed to user side in a mainloop.+data LoopEnv env = LoopState {+  env :: env,+  keyStates :: HM.HashMap SDL.Scancode Int,+  events :: IORef [Event],+  signalQueue :: IORef [Event],+  components :: VM.IOVector Component+}++-- | Lens to the env inside 'LoopState'+envL :: Lens' (LoopEnv env) env+envL = lens env (\e r -> e { env = r })++instance HasLightEnv env => HasLightEnv (LoopEnv env) where+  rendererL = envL . rendererL+  fontCacheL = envL . fontCacheL++instance HasLoopEnv (LoopEnv env) where+  keyStatesL = lens keyStates (\env r -> env { keyStates = r })+  eventsL = lens events (\env r -> env { events = r })+  signalQueueL = lens signalQueue (\env r -> env { signalQueue = r })++instance HasLightEnv env => HasLightEnv (T.Text, env) where+  rendererL = _2 . rendererL+  fontCacheL = _2 . fontCacheL++instance HasLoopEnv env => HasLoopEnv (T.Text, env) where+  keyStatesL = _2 . keyStatesL+  eventsL = _2 . eventsL+  signalQueueL = _2 . signalQueueL++instance HasComponentEnv (T.Text, env) where+  uidL = _1++-- | Type synonym to the minimal type of the mainloop+type MiniLoop = LightT (LoopEnv LightEnv) IO+ -- | Run a mainloop. -- In a mainloop, components and events are managed. -- -- Components in a mainloop: draw ~ update ~ (user-defined function) ~ event handling runMainloop-  :: (HasLightEnv env, MonadIO m, MonadMask m)-  => LoopConfig  -- ^ loop config+  :: ( HasLightEnv env+     , HasLightEnv loop+     , HasLoopEnv loop+     , MonadIO m+     , MonadMask m+     )+  => (LoopEnv env -> loop)  -- ^ LoopState conversion function (you can pass @id@, fixing @loop@ as @'LoopState' 'LightEnv'@)+  -> LoopConfig  -- ^ loop config   -> s  -- ^ initial state-  -> (LoopState -> s -> LightT env m s)  -- ^ a function called in every loop+  -> (s -> LightT loop m s)  -- ^ a function called in every loop   -> LightT env m ()-runMainloop conf initial loop = do+runMainloop conv conf initial loop = do   components <-     liftMiniLight $ fromList . (++ additionalComponents conf) =<< maybe       (return [])       (flip loadAppConfig (componentResolver conf))       (appConfigFile conf)+  events      <- liftIO $ newIORef []+  signalQueue <- liftIO $ newIORef [] -  go (LoopState {keyStates = HM.empty, events = [], components = components})-     initial+  env         <- view id+  go+    ( LoopState+      { keyStates   = HM.empty+      , events      = events+      , signalQueue = signalQueue+      , env         = env+      , components  = components+      }+    )+    initial  where   go loopState s = do     renderer <- view rendererL@@ -114,10 +157,11 @@      forM_ [0 .. VM.length (components loopState) - 1] $ \i -> do       comp  <- liftIO $ VM.read (components loopState) i-      comp' <- update comp+      comp' <- envLightT (\env -> (getUID comp, conv $ loopState { env = env }))+        $ update comp       liftIO $ VM.write (components loopState) i comp' -    s' <- loop loopState s+    s' <- envLightT (\env -> conv $ loopState { env = env }) $ loop s      liftIO $ SDL.present renderer @@ -125,11 +169,27 @@     events <- SDL.pollEvents     keys   <- SDL.getKeyboardState -    forM_ [0 .. VM.length (components loopState) - 1] $ \i -> do-      comp  <- liftIO $ VM.read (components loopState) i-      comp' <- foldlM (\comp ev -> onSignal (RawEvent ev) comp) comp events-      liftIO $ VM.write (components loopState) i comp'+    envLightT (\env -> conv $ loopState { env = env }) $ do+      evref <- view eventsL+      liftIO $ writeIORef evref $ map RawEvent events +      sigref  <- view signalQueueL+      signals <- liftIO $ readIORef sigref+      liftIO $ modifyIORef evref $ (++ signals)+      liftIO $ writeIORef sigref []++    envLightT (\env -> conv $ loopState { env = env }) $ do+      evref  <- view eventsL+      events <- liftIO $ readIORef evref++      forM_ [0 .. VM.length (components loopState) - 1] $ \i -> do+        comp  <- liftIO $ VM.read (components loopState) i+        comp' <- foldlM+          (\comp ev -> envLightT ((,) (getUID comp)) $ onSignal ev comp)+          comp+          events+        liftIO $ VM.write (components loopState) i comp'+     let       specifiedKeys = HM.mapWithKey         (\k v -> if keys k then v + 1 else 0)@@ -139,7 +199,7 @@             (watchKeys conf)         $ keyStates loopState         )-    let loopState' = loopState { keyStates = specifiedKeys, events = events }+    let loopState' = loopState { keyStates = specifiedKeys }     let quit = any           ( \event -> case SDL.eventPayload event of             SDL.WindowClosedEvent _ -> True
src/MiniLight/Component.hs view
@@ -1,4 +1,4 @@-{-| The package provides configuration loader and pre-defined resolver.+{-| The package provides the configuration loader.  An configuration example: @@ -43,7 +43,6 @@   loadAppConfig,    Resolver,-  defResolver ) where  import qualified Data.Aeson as Aeson@@ -51,32 +50,6 @@ import MiniLight.Light import MiniLight.Component.Types import MiniLight.Component.Loader-import qualified MiniLight.Component.AnimationLayer as AnimationLayer-import qualified MiniLight.Component.Button as Button-import qualified MiniLight.Component.Layer as Layer-import qualified MiniLight.Component.MessageEngine as MessageEngine-import qualified MiniLight.Component.MessageLayer as MessageLayer -foldResult :: (String -> b) -> (a -> b) -> Aeson.Result a -> b-foldResult f g r = case r of-  Aeson.Error   err -> f err-  Aeson.Success a   -> g a- type Resolver = T.Text -> Aeson.Value -> MiniLight Component --- | Pre-defined resolver supports all components in this library.-defResolver :: Resolver-defResolver name props = case name of-  "animation-layer" -> newComponent-    =<< AnimationLayer.new (foldResult error id $ Aeson.fromJSON props)-  "button" ->-    newComponent =<< Button.new (foldResult error id $ Aeson.fromJSON props)-  "layer" ->-    newComponent =<< Layer.new (foldResult error id $ Aeson.fromJSON props)-  "message-engine" -> newComponent-    =<< MessageEngine.new (foldResult error id $ Aeson.fromJSON props)-  "message-layer" -> newComponent-    =<< MessageLayer.new (foldResult error id $ Aeson.fromJSON props)-  "tiled-layer" -> newComponent-    =<< Layer.newNineTile (foldResult error id $ Aeson.fromJSON props)-  _ -> error $ T.unpack $ "Component not defined: `" <> name <> "`"
− src/MiniLight/Component/AnimationLayer.hs
@@ -1,56 +0,0 @@-module MiniLight.Component.AnimationLayer where--import Control.Monad.State-import Data.Aeson-import Linear-import Lens.Micro-import MiniLight.Figure-import MiniLight.Light-import qualified MiniLight.Component.Layer as Layer-import MiniLight.Component.Types-import qualified SDL-import qualified SDL.Vect as Vect--data AnimationLayer = AnimationLayer {-  layer :: Layer.Layer,-  counter :: Int,-  tileSize :: Vect.V2 Int,-  scaler :: Int,-  config :: Config-}--instance ComponentUnit AnimationLayer where-  update = execStateT $ do-    modify $ \c -> c { counter = (counter c + 1) }-    modify $ \c -> c { counter = if counter c >= (division (config c) ^._x * division (config c) ^. _y) * scaler c then 0 else counter c }--  figures comp = do-    let iv = V2 ((counter comp `div` scaler comp) `mod` division (config comp) ^. _x) ((counter comp `div` scaler comp) `div` division (config comp) ^. _x)-    return [-      clip (SDL.Rectangle (SDL.P (tileSize comp * iv)) (tileSize comp)) $ Layer.layer $ layer comp-      ]--data Config = Config {-  layerConf :: Layer.Config,-  division :: Vect.V2 Int-}--instance FromJSON Config where-  parseJSON = withObject "config" $ \v -> do-    conf <- parseJSON (Object v)-    division <- (\v -> Vect.V2 <$> v .: "x" <*> v .: "y") =<< v .: "division"--    return $ Config conf division--new :: Config -> MiniLight AnimationLayer-new conf = do-  layer <- Layer.new (layerConf conf)-  let size = getFigureSize (Layer.layer layer)--  return $ AnimationLayer-    { layer    = layer-    , counter  = 0-    , tileSize = div <$> size <*> division conf-    , scaler   = 25-    , config   = conf-    }
− src/MiniLight/Component/Button.hs
@@ -1,67 +0,0 @@-module MiniLight.Component.Button where--import Control.Monad.IO.Class-import Data.Aeson-import qualified Data.Text as T-import Data.Word (Word8)-import MiniLight.Component.Types-import MiniLight.Event-import MiniLight.Figure-import MiniLight.Light-import qualified SDL-import qualified SDL.Font-import qualified SDL.Vect as Vect--data Button = Button {-  font :: SDL.Font.Font,-  config :: Config-}--instance ComponentUnit Button where-  update = return--  figures comp = do-    textTexture <- liftMiniLight $ text (font comp) (color (config comp)) $ label (config comp)-    base <- liftMiniLight $ rectangleFilled (Vect.V4 200 200 200 255) (getFigureSize textTexture)--    return [-      base,-      textTexture-      ]--  useCache _ _ = True--  onSignal (RawEvent (SDL.Event _ (SDL.MouseButtonEvent (SDL.MouseButtonEventData _ SDL.Released _ _ _ _)))) comp = do-    liftIO $ print "hey!"-    return comp-  onSignal _ comp = return comp--  beforeClearCache _ figs = mapM_ freeFigure figs--data Config = Config {-  size :: Vect.V2 Int,-  label :: T.Text,-  color :: Vect.V4 Word8,-  fontConf :: FontDescriptor,-  fontSize :: Int-}--instance FromJSON Config where-  parseJSON = withObject "config" $ \v -> do-    size <- withObject "font" (\v -> Vect.V2 <$> v .: "width" <*> v .: "height") =<< v .: "size"-    label <- v .: "label"-    [r,g,b,a] <- v .:? "color" .!= [255, 255, 255, 255]-    (fontConf, fontSize) <- (v .: "font" >>=) $ withObject "font" $ \v -> do-      family <- v .: "family"-      size <- v .: "size"-      bold <- v .:? "bold" .!= False-      italic <- v .:? "italic" .!= False--      return $ (FontDescriptor family (FontStyle bold italic), size)--    return $ Config size label (Vect.V4 r g b a) fontConf fontSize--new :: Config -> MiniLight Button-new conf = do-  font <- loadFont (fontConf conf) (fontSize conf)-  return $ Button {font = font, config = conf}
− src/MiniLight/Component/Layer.hs
@@ -1,85 +0,0 @@-module MiniLight.Component.Layer where--import Control.Monad-import Data.Aeson-import Lens.Micro-import Lens.Micro.Mtl-import Linear-import MiniLight.Component.Types-import MiniLight.Light-import MiniLight.Figure-import qualified SDL-import qualified SDL.Vect as Vect--data Layer = Layer {-  layer :: Figure-}--instance ComponentUnit Layer where-  figures comp = return [layer comp]--data Config = Config {-  image :: FilePath,-  size :: Vect.V2 Int,-  position :: Vect.V2 Int-}--instance FromJSON Config where-  parseJSON = withObject "config" $ \v -> do-    image <- v .: "image"-    size <- withObject "size" (\v -> Vect.V2 <$> v .: "width" <*> v .: "height") =<< v .: "size"--    positionMaybe <- v .:? "position"-    position <- maybe (return 0) (withObject "position" (\v -> Vect.V2 <$> v .: "x" <*> v .: "y")) positionMaybe--    return $ Config image size position--new :: Config -> MiniLight Layer-new conf = do-  pic <- picture (image conf)-  return $ Layer {layer = pic}--newNineTile :: Config -> MiniLight Layer-newNineTile conf = do-  pic <- picture $ image conf-  let siz     = fmap toEnum $ size conf-  let tex     = texture pic-  let texSize = fmap toEnum $ getFigureSize pic--  tinfo    <- SDL.queryTexture tex-  renderer <- view rendererL--  target   <- SDL.createTexture renderer-                                (SDL.texturePixelFormat tinfo)-                                SDL.TextureAccessTarget-                                siz-  SDL.rendererRenderTarget renderer SDL.$= Just target-  SDL.textureBlendMode target SDL.$= SDL.BlendAlphaBlend--  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-          )--    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--  tex <- fromTexture target-  return $ Layer {layer = tex}-
− src/MiniLight/Component/MessageEngine.hs
@@ -1,80 +0,0 @@-module MiniLight.Component.MessageEngine where--import Control.Monad.State-import Data.Aeson-import qualified Data.Text as T-import Data.Word (Word8)-import Lens.Micro.Mtl-import MiniLight.Component.Types-import MiniLight.Figure-import MiniLight.Light-import qualified SDL-import qualified SDL.Font-import qualified SDL.Vect as Vect--data MessageEngine = MessageEngine {-  font :: SDL.Font.Font,-  counter :: Int,-  rendered :: Int,-  textTexture :: Figure,-  finished :: Bool,-  config :: Config-}--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 })--        comp <- get-        when (rendered comp == T.length (messages (config comp))) $ do-          id %= (\c -> c { finished = True })--      id %= (\c -> c { counter = counter c + 1 })--  figures comp = do-    (w, h) <- SDL.Font.size (font comp) (T.take (rendered comp) $ messages (config comp))--    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-}--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--      return $ (FontDescriptor family (FontStyle bold italic), size)--    return $ Config messages static (Vect.V4 r g b a) fontConf size--new :: Config -> MiniLight MessageEngine-new conf = do-  font        <- loadFont (fontConf conf) (fontSize conf)-  textTexture <- text font (color conf) $ messages conf--  return $ MessageEngine-    { font        = font-    , counter     = 0-    , rendered    = if static conf then T.length (messages conf) else 0-    , textTexture = textTexture-    , finished    = static conf-    , config      = conf-    }
− src/MiniLight/Component/MessageLayer.hs
@@ -1,77 +0,0 @@-module MiniLight.Component.MessageLayer where--import Control.Monad.State-import Data.Aeson-import Lens.Micro-import Lens.Micro.Mtl-import Linear-import MiniLight.Component.Types-import qualified MiniLight.Component.Layer as CLayer-import qualified MiniLight.Component.AnimationLayer as CAnim-import qualified MiniLight.Component.MessageEngine as CME-import MiniLight.Figure-import MiniLight.Light-import qualified SDL.Vect as Vect--data MessageLayer = MessageLayer {-  messageEngine :: CME.MessageEngine,-  layer :: CLayer.Layer,-  cursor :: CAnim.AnimationLayer,-  config :: Config-}--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 })--instance ComponentUnit MessageLayer where-  update = execStateT $ do-    zoom engineL $ do-      c <- use id-      id <~ lift (update c)--    zoom cursorL $ do-      c <- use id-      id <~ lift (update c)--  figures comp = do-    baseLayer <- figures $ layer comp-    cursorLayer <- figures $ cursor comp-    textLayer <- figures $ messageEngine comp--    let cursorSize = div <$> CAnim.tileSize (cursor comp) <*> CAnim.division (CAnim.config (cursor comp))-    let windowSize = CLayer.size $ window $ config comp--    return-      $ map (translate (CLayer.position (window (config comp)))) $ baseLayer-      ++ map (translate (Vect.V2 20 10)) textLayer-      ++ map (translate (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"--    return $ Config messageEngineConf layerConf nextConf--new :: Config -> MiniLight MessageLayer-new conf = do-  engine <- CME.new (engine conf)-  layer  <- CLayer.newNineTile (window conf)-  cursor <- CAnim.new (next conf)--  return $ MessageLayer-    { messageEngine = engine-    , layer         = layer-    , cursor        = cursor-    , config        = conf-    }
src/MiniLight/Component/Types.hs view
@@ -1,25 +1,48 @@ {-# LANGUAGE ExistentialQuantification #-} module MiniLight.Component.Types (+  HasComponentEnv(..),+  emit,+   ComponentUnit(..),   Component,   newComponent,   getComponentSize,+  getUID,   propagate, ) where  import Control.Monad.Catch import Control.Monad.IO.Class import Data.IORef+import qualified Data.UUID+import qualified Data.UUID.V4+import qualified Data.Text as T+import Lens.Micro+import Lens.Micro.Mtl 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 signalQueueL+  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, MonadIO m, MonadMask m) => c -> LightT env m c+  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.@@ -31,7 +54,7 @@   {-# INLINE draw #-}    -- | Event handlers-  onSignal :: (HasLightEnv env, MonadIO m, MonadMask m) => Event -> c -> LightT env m c+  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.@@ -50,6 +73,7 @@  -- | A wrapper for 'ComponentUnit' instances. data Component = forall c. ComponentUnit c => Component {+  uid :: T.Text,   component :: c,   prev :: c,   cache :: IORef [Figure]@@ -61,9 +85,10 @@   => c   -> LightT env m Component newComponent c = do+  uid  <- liftIO $ Data.UUID.toText <$> Data.UUID.V4.nextRandom   figs <- figures c   ref  <- liftIO $ newIORef figs-  return $ Component {component = c, prev = c, cache = ref}+  return $ Component {uid = uid, component = c, prev = c, cache = ref}  -- | Get the size of a component. getComponentSize@@ -74,26 +99,30 @@   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 comp _ cache) = Component comp comp cache+propagate (Component uid comp _ cache) = Component uid comp comp cache  instance ComponentUnit Component where-  update (Component comp prev cache) = do+  update (Component uid comp prev cache) = do     comp' <- update comp-    return $ Component comp' prev cache+    return $ Component uid comp' prev cache -  figures (Component comp _ _) = figures comp+  figures (Component _ comp _ _) = figures comp -  draw (Component comp prev ref) = liftMiniLight $ do+  draw (Component _ comp prev ref) = do     if useCache prev comp-      then renders =<< liftIO (readIORef ref)+      then liftMiniLight . renders =<< liftIO (readIORef ref)       else do         figs <- liftIO (readIORef ref)         beforeClearCache comp figs          figs <- figures comp-        renders figs+        liftMiniLight $ renders figs         liftIO $ writeIORef ref figs -  onSignal ev (Component comp prev cache) = fmap (\comp' -> Component comp' prev cache) $ onSignal ev comp+  onSignal ev (Component uid comp prev cache) = fmap (\comp' -> Component uid comp' prev cache) $ onSignal ev comp
src/MiniLight/Event.hs view
@@ -1,12 +1,43 @@+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-} module MiniLight.Event (-  Event(..),+  Event (..),+  EventType,+  signal,+  asSignal, ) where  import qualified SDL import qualified Data.Text as T-import Language.Haskell.TH (Name)+import Data.Type.Equality+import Type.Reflection +-- | EventType says some type can be used as an event type.+class Typeable e => EventType e++-- | 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++toDyn :: EventType a => a -> Dynamic+toDyn v = Dynamic typeRep v++fromDynamic :: forall a . (EventType a) => Dynamic -> Maybe a+fromDynamic (Dynamic t v) | Just HRefl <- t `eqTypeRep` rep = Just v+                          | otherwise                       = Nothing+  where rep = typeRep :: TypeRep a++-- | Event type representation data Event   = Never-  | ComponentEvent Name T.Text+  | Signal T.Text Dynamic   | RawEvent SDL.Event++signal :: EventType a => T.Text -> a -> Event+signal t v = Signal t (toDyn v)++asSignal :: EventType a => Event -> T.Text -> Maybe a+asSignal (Signal t1 v) t2 | t1 == t2 = fromDynamic v+asSignal _             _             = Nothing
src/MiniLight/Light.hs view
@@ -1,8 +1,6 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE RoleAnnotations #-} module MiniLight.Light (-  MonadIO(..),-   HasLightEnv (..),   LightT (..),   LightEnv (..),@@ -11,11 +9,16 @@   envLightT,   mapLightT, +  HasLoopEnv (..),+   FontDescriptor(..),   FontStyle(..),   loadFontCache,   loadFont,-  withFont+  withFont,++  -- * Re-exports+  MonadIO(..), ) where  import Control.Monad.IO.Class@@ -23,9 +26,11 @@ import Control.Monad.Reader import Data.Hashable (Hashable(..)) import qualified Data.HashMap.Strict as HM+import Data.IORef import Graphics.Text.TrueType import Lens.Micro import Lens.Micro.Mtl+import MiniLight.Event import qualified SDL import qualified SDL.Font @@ -73,6 +78,16 @@ mapLightT :: (m a -> n a) -> LightT env m a -> LightT env n a mapLightT f m = LightT $ ReaderT $ f . runReaderT (runLightT' m) {-# INLINE mapLightT #-}++class HasLoopEnv env where+  -- | Contains the number of frames that a specific keys are continuously pressing.+  keyStatesL :: Lens' env (HM.HashMap SDL.Scancode Int)++  -- | Occurred events since the last frame.+  eventsL :: Lens' env (IORef [Event])++  -- | A queue storing the events occurred in this frame.+  signalQueueL :: Lens' env (IORef [Event])  loadFontCache :: MonadIO m => m FontMap loadFontCache = do