diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -9,7 +9,9 @@
 
 <br>
 
-Currently a Work In Progress, but technically functional. Documentation is currently minimal and that is something I'm working on, as well as everything else
+Currently a Work In Progress, but technically barely functional. The example in the app folder works, it displays an 800x800 window with the Pine logo.
+
+Documentation is currently minimal and that is something I'm working on, as well as everything else.
 
 ### TODO:
 
diff --git a/app/FPS.hs b/app/FPS.hs
new file mode 100644
--- /dev/null
+++ b/app/FPS.hs
@@ -0,0 +1,40 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main where
+
+import Pine
+import qualified SDL
+import Control.Monad.State
+
+-- define the state of the game
+data App = App
+  { logo :: Image
+  , frames :: (Int, Double)
+  }
+
+gameInit =
+  App
+    (newImage "src/Media/logo.png" Nothing (Just $ rect 200 200 400 400))
+    (0,0)
+
+inc :: DeltaTime -> PineState App
+inc dt =
+  with $ \state ->
+    let (count,accum) = frames state
+    in (Cont, state {frames = (count+1, accum+dt)})
+
+instance Stateful App where
+  update _ WindowClose               = quit
+  update _ (KeyPressed SDL.KeycodeQ) = do
+    appState <- get
+    let (count,accum) = frames appState
+    quitLog $ "Average FPS: " <> show (1 / (accum / fromIntegral count))
+  update dt Step                     = inc dt
+  update _ _                         = cont
+
+instance Drawable App where
+  draw = fromImage . logo
+
+-- | This simply opens a window with the Pine logo displayed
+main :: IO ()
+main = pine "Pine" withDefaultConfig gameInit
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -1,6 +1,24 @@
+{-# LANGUAGE OverloadedStrings #-}
+
 module Main where
 
 import Pine
+import qualified SDL
 
+-- define the state of the game
+data DefaultState = Logo Image
+
+defaultInitial = Logo $ newImage "src/Media/logo.png" Nothing (Just $ rect 200 200 400 400)
+
+instance Stateful DefaultState where
+  update _ Load                      = return $ Log "Hello, Pine!"
+  update _ WindowClose               = return Quit
+  update _ (KeyPressed SDL.KeycodeQ) = return $ QuitWithLog "Goodbye, Pine!"
+  update _ _                         = return Cont
+
+instance Drawable DefaultState where
+  draw (Logo img) = fromImage img
+
+-- | This simply opens a window with the Pine logo displayed
 main :: IO ()
-main = runDefault
+main = pine "Pine" withDefaultConfig defaultInitial
diff --git a/pine.cabal b/pine.cabal
--- a/pine.cabal
+++ b/pine.cabal
@@ -1,13 +1,13 @@
 cabal-version: 2.2
 
--- This file has been generated from package.yaml by hpack version 0.31.2.
+-- This file has been generated from package.yaml by hpack version 0.33.0.
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: c6906bc45dcc27ae37ed3ac5ae2e0ab85f7cbd22e891ba597dca6603e515ea25
+-- hash: 599ef45b2d5a0dd0ba895eee72f9fd8f6e8f6fa7a4bfab1b8bb0f7dc90696ef4
 
 name:           pine
-version:        0.1.0.2
+version:        0.1.0.3
 synopsis:       Functional 2D Game Framework
 description:    Please see the README on GitHub at <https://github.com/grinshpon/pine#readme>
 category:       Game
@@ -32,14 +32,16 @@
       Pine
       Pine.Internal
       Pine.Internal.Keyboard
-      Pine.Internal.Renderer
+      Pine.Internal.Pine
       Pine.Internal.Types
   hs-source-dirs:
       src
   build-depends:
       base >=4.7 && <5
     , containers >=0.6 && <0.7
-    , sdl2 >=2.4 && <2.5
+    , linear
+    , mtl >=2.2
+    , sdl2 >=2.5
     , sdl2-image >=0.1 && <2.1
     , stm >=2.4 && <2.6
     , text >=1.2.3 && <1.3
@@ -53,13 +55,32 @@
   build-depends:
       base >=4.7 && <5
     , containers >=0.6 && <0.7
+    , linear
+    , mtl >=2.2
     , pine
-    , sdl2 >=2.4 && <2.5
+    , sdl2 >=2.5
     , sdl2-image >=0.1 && <2.1
     , stm >=2.4 && <2.6
     , text >=1.2.3 && <1.3
   default-language: Haskell2010
 
+executable pine-fps-exe
+  main-is: FPS.hs
+  hs-source-dirs:
+      app
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.7 && <5
+    , containers >=0.6 && <0.7
+    , linear
+    , mtl >=2.2
+    , pine
+    , sdl2 >=2.5
+    , sdl2-image >=0.1 && <2.1
+    , stm >=2.4 && <2.6
+    , text >=1.2.3 && <1.3
+  default-language: Haskell2010
+
 test-suite pine-test
   type: exitcode-stdio-1.0
   main-is: Spec.hs
@@ -69,8 +90,10 @@
   build-depends:
       base >=4.7 && <5
     , containers >=0.6 && <0.7
+    , linear
+    , mtl >=2.2
     , pine
-    , sdl2 >=2.4 && <2.5
+    , sdl2 >=2.5
     , sdl2-image >=0.1 && <2.1
     , stm >=2.4 && <2.6
     , text >=1.2.3 && <1.3
diff --git a/src/Pine.hs b/src/Pine.hs
--- a/src/Pine.hs
+++ b/src/Pine.hs
@@ -1,20 +1,20 @@
-
 -- | Everything you need is reixported by Pine, so you do not need to import any of the internal modules.
 module Pine
-  ( runDefault
-  , pine
+  ( pine
+  , withDefaultConfig
+  , with
   , Drawable(..)
   , Stateful(..)
+  , PineState
+  , Return(..)
+  , cont,quit,contLog,quitLog
   , Image(..)
   , newImage
+  , rect
   , Scene
   , fromImage
   , Event(..)
+  , DeltaTime
   ) where
 
 import Pine.Internal
-
-
--- | Run the default program, which displays the Pine logo
-runDefault :: IO ()
-runDefault = defaultApp
diff --git a/src/Pine/Internal.hs b/src/Pine/Internal.hs
--- a/src/Pine/Internal.hs
+++ b/src/Pine/Internal.hs
@@ -1,8 +1,7 @@
 module Pine.Internal
-  ( Pine.Internal.Renderer.defaultApp
-  , Pine.Internal.Renderer.pine
+  ( module Pine.Internal.Pine
   , module Pine.Internal.Types
   ) where
 
-import Pine.Internal.Renderer
+import Pine.Internal.Pine
 import Pine.Internal.Types
diff --git a/src/Pine/Internal/Pine.hs b/src/Pine/Internal/Pine.hs
new file mode 100644
--- /dev/null
+++ b/src/Pine/Internal/Pine.hs
@@ -0,0 +1,140 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE LambdaCase #-}
+
+module Pine.Internal.Pine
+  ( pine
+  , withDefaultConfig
+  , with
+  ) where
+
+import Pine.Internal.Types
+
+import qualified SDL
+import qualified SDL.Image as SDLI
+import Control.Concurrent.STM
+
+import Data.Text (Text)
+import Data.Map (Map)
+import qualified Data.Map as M
+import Control.Monad
+import Control.Monad.State
+import Data.Word (Word32)
+
+type TextureCache = Map FilePath SDL.Texture
+
+-- | This function initializes the window and takes an initial `Stateful` object that will be updated.
+pine :: (Stateful s, Drawable s)
+     => Text -- ^ Title
+     -> SDL.WindowConfig -- ^ Window Configuration
+     -> s -- ^ Initial state
+     -> IO ()
+pine title windowConfig state_ = do
+  SDL.initializeAll
+  window <- SDL.createWindow title windowConfig
+  renderer <- SDL.createRenderer window (-1) $ SDL.RendererConfig
+    { SDL.rendererType = SDL.AcceleratedRenderer
+    , SDL.rendererTargetTexture = False
+    }
+  let
+    appLoop :: TextureCache -> IO ()
+    appLoop cache = do
+      updateQueue <- newTChanIO
+      timer <- SDL.addTimer 16 (fpsTimer updateQueue)
+      time <- SDL.ticks
+      dcache <- updateEvents (fromIntegral time / 1000) [Load] state_ >>= \case
+        Just loadState -> SDL.pollEvents >>= appStep time updateQueue cache loadState
+        Nothing        -> pure cache
+      _ <- SDL.removeTimer timer
+      foldMap SDL.destroyTexture dcache
+      SDL.destroyRenderer renderer
+      SDL.destroyWindow window
+
+    appStep :: (Stateful s, Drawable s) => Word32 -> TChan () -> TextureCache -> s -> [SDL.Event] -> IO TextureCache
+    appStep time updateQueue cache state sdlEvents = do
+      atomically $ readTChan updateQueue -- not ideal, events could get backed up
+      time' <- SDL.ticks
+      let dt = (fromIntegral (time' - time) :: Double) / 1000
+      --print $ 1/dt
+      cache' <- drawScene cache $ draw state
+--      mousePos <- SDL.getModalMouseLocation >>= \case  --waiting for PR to be merged on sdl2
+--        SDL.AbsoluteModalLocation (SDL.P pos) -> pos
+--        SDL.RelativeModalLocation pos -> pos
+      let pineEvents = eventState sdlEvents
+      updateEvents dt (Step:pineEvents) state >>= \case
+        Just newState -> SDL.pollEvents >>= appStep time' updateQueue cache' newState
+        Nothing       -> pure cache'
+      where
+        eventState events =
+          case events of
+            [] -> []
+            (ev:evs) ->
+              [SDLEvent ev] <> case SDL.eventPayload ev of
+                SDL.WindowClosedEvent _ -> [WindowClose] <> (eventState evs)
+                SDL.KeyboardEvent keyboardEvent ->
+                  case SDL.keyboardEventKeyMotion keyboardEvent of
+                    SDL.Pressed  -> [KeyPressed  (SDL.keysymKeycode (SDL.keyboardEventKeysym keyboardEvent))] <> (eventState evs)
+                    SDL.Released -> [KeyReleased (SDL.keysymKeycode (SDL.keyboardEventKeysym keyboardEvent))] <> (eventState evs)
+                SDL.MouseMotionEvent mmeData -> [MouseMoved (SDL.mouseMotionEventRelMotion mmeData)] <> (eventState evs) -- maybe include new position as well
+                _ -> (eventState evs)
+
+    updateEvents :: (Stateful s, Drawable s) => DeltaTime -> [Event] -> s -> IO (Maybe s)
+    updateEvents _  []     state = pure $ Just state
+    updateEvents dt (e:es) state = do
+      let (r, nState) = runState (update dt e) state
+      case r of
+        Cont          -> updateEvents dt es nState
+        Log s         -> putStrLn s *> (updateEvents dt es nState)
+        QuitWithLog s -> putStrLn s *> (pure $ Nothing)
+        Quit          -> pure $ Nothing
+
+    fpsTimer :: TChan () -> Word32 -> IO SDL.RetriggerTimer
+    fpsTimer updateQueue _ = do
+      atomically $ writeTChan updateQueue ()
+      pure $ SDL.Reschedule 16
+
+    drawScene :: TextureCache -> Scene -> IO TextureCache
+    drawScene cache canvas = do
+      SDL.clear renderer
+      cache' <- drawScene' cache canvas
+      SDL.present renderer
+      pure cache'
+
+    drawScene' :: TextureCache -> Scene -> IO TextureCache
+    drawScene' cache canvas = do
+      case canvas of
+        SingleScene m -> loadMedia cache [m]
+        MultiScene ms -> loadMedia cache ms
+        EmptyScene    -> pure cache
+
+    loadMedia :: TextureCache -> [Media] -> IO TextureCache
+    loadMedia cache [] = pure cache
+    loadMedia cache (m:imgs) =
+      case m of
+        MImage img ->
+          case cache M.!? (imageSrc img) of
+            Nothing -> do
+              tex <- SDLI.loadTexture renderer (imageSrc img)
+              SDL.copy renderer tex Nothing Nothing
+              loadMedia (M.insert (imageSrc img) tex cache) imgs
+            Just tex -> do
+              SDL.copy renderer tex (imageQuad img) (imageRect img)
+              loadMedia cache imgs
+        _ -> loadMedia cache imgs
+
+   in appLoop mempty
+
+withDefaultConfig = SDL.WindowConfig
+  { SDL.windowBorder        = True
+  , SDL.windowHighDPI       = False
+  , SDL.windowInputGrabbed  = False
+  , SDL.windowMode          = SDL.Windowed
+  , SDL.windowGraphicsContext = SDL.NoGraphicsContext
+  , SDL.windowPosition      = SDL.Wherever
+  , SDL.windowResizable     = True
+  , SDL.windowInitialSize   = SDL.V2 800 800
+  , SDL.windowVisible       = True
+  }
+
+with :: (MonadState s m) => (s -> (a,s)) -> m a
+with = state
diff --git a/src/Pine/Internal/Renderer.hs b/src/Pine/Internal/Renderer.hs
deleted file mode 100644
--- a/src/Pine/Internal/Renderer.hs
+++ /dev/null
@@ -1,142 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE LambdaCase #-}
-
-module Pine.Internal.Renderer
-  ( defaultApp
-  , pine
-  ) where
-
-import Pine.Internal.Types
-
-import qualified SDL
-import qualified SDL.Image as SDLI
-import Control.Concurrent.STM
-
-import Data.Text (Text)
-import Data.Map (Map)
-import qualified Data.Map as M
-import Control.Monad
-import Data.Word (Word32)
-
-type TextureCache = Map FilePath SDL.Texture
-
--- | This function initializes the window and takes an initial `Stateful` object that will be updated.
-pine :: (Stateful s, Drawable s)
-     => Text
-     -> SDL.WindowConfig
-     -> s
-     -> IO ()
-pine title windowConfig state_ = do
-  SDL.initializeAll
-  window <- SDL.createWindow title windowConfig
-  renderer <- SDL.createRenderer window (-1) $ SDL.RendererConfig
-    { SDL.rendererType = SDL.AcceleratedRenderer
-    , SDL.rendererTargetTexture = False
-    }
-  let
-    appLoop :: TextureCache -> IO ()
-    appLoop cache = do
-      updateQueue <- newTChanIO
-      timer <- SDL.addTimer 16 (fpsTimer updateQueue)
-      time <- SDL.ticks
-      SDL.pollEvent >>= go time updateQueue cache state_
-      _ <- SDL.removeTimer timer
-      pure ()
-
-    go :: (Stateful s, Drawable s) => Word32 -> TChan () -> TextureCache -> s -> Maybe SDL.Event -> IO ()
-    go time updateQueue cache state mevent = do
-      atomically $ readTChan updateQueue -- not ideal, events could get backed up
-      time' <- SDL.ticks
-      let dt = (fromIntegral (time' - time) :: Double) / 1000
-      --print $ 1/dt
-      cache' <- drawScene cache $ draw state
-      case mevent of
-        Nothing -> SDL.pollEvent >>= go time' updateQueue cache' (update (DeltaTime dt) state)
-        Just ev -> case SDL.eventPayload ev of
-          SDL.WindowClosedEvent _ -> pure ()
-          _ -> SDL.pollEvent >>= go time' updateQueue cache' (update (SDLEvent ev) $ update (DeltaTime dt) state)
-
-    fpsTimer :: TChan () -> Word32 -> IO SDL.RetriggerTimer
-    fpsTimer updateQueue _ = do
-      atomically $ writeTChan updateQueue ()
-      pure $ SDL.Reschedule 16
-
-    drawScene :: TextureCache -> Scene -> IO TextureCache
-    drawScene cache canvas = do
-      SDL.clear renderer
-      cache' <- drawScene' cache canvas
-      SDL.present renderer
-      pure cache'
-
-    drawScene' :: TextureCache -> Scene -> IO TextureCache
-    drawScene' cache canvas = do
-      case canvas of
-        SingleImage img -> drawImages cache [img]
-        Images imgs -> drawImages cache imgs
-        EmptyScene -> pure cache
-
-    drawImages :: TextureCache -> [Image] -> IO TextureCache
-    drawImages cache [] = pure cache
-    drawImages cache (img:imgs) =
-      case cache M.!? (imageSrc img) of
-        Nothing -> do
-          tex <- SDLI.loadTexture renderer (imageSrc img)
-          SDL.copy renderer tex Nothing Nothing
-          drawImages (M.insert (imageSrc img) tex cache) imgs
-        Just tex -> do
-          SDL.copy renderer tex Nothing Nothing
-          drawImages cache imgs
-
-   in appLoop mempty
-
-data DefaultState = Logo Image
-
-instance Stateful DefaultState where
-  initial = Logo $ newImage "src/Media/logo.png"
-  update = const id
-
-instance Drawable DefaultState where
-  draw (Logo img) = fromImage img
-
--- | This simply opens a window with the Pine logo displayed
-defaultApp :: IO ()
-defaultApp = pine "Pine" defaultConfig (initial :: DefaultState)
-  where
-    defaultConfig = SDL.WindowConfig
-      { SDL.windowBorder        = True
-      , SDL.windowHighDPI       = False
-      , SDL.windowInputGrabbed  = False
-      , SDL.windowMode          = SDL.Windowed
-      , SDL.windowOpenGL        = Nothing
-      , SDL.windowPosition      = SDL.Wherever
-      , SDL.windowResizable     = True
-      , SDL.windowInitialSize   = SDL.V2 800 800
-      , SDL.windowVisible       = True
-      }
-
-{-
-  addEventWatch $ \ev ->
-    case eventPayload ev of
-      WindowSizeChangedEvent sizeChangeData ->
-        putStrLn $ "eventWatch windowSizeChanged: " ++ show sizeChangeData
-      KeyboardEvent kev ->
-        putStrLn "key event"
-      _ -> return ()
-  appLoop mempty
-    where
-      appLoop :: TextureCache -> IO ()
-      appLoop tc = pollEvent >>= go tc
-
-      go :: TextureCache -> Maybe Event -> IO ()
-      go tc = \case
-        Nothing -> pollEvent >>= go tc
-        Just ev -> case eventPayload ev of
-          KeyboardEvent keyboardEvent
-            |  keyboardEventKeyMotion keyboardEvent == Pressed &&
-               keysymKeycode (keyboardEventKeysym keyboardEvent) == KeycodeQ
-            -> return ()
-          _ -> pollEvent >>= go tc
-
-
-
--}
diff --git a/src/Pine/Internal/Types.hs b/src/Pine/Internal/Types.hs
--- a/src/Pine/Internal/Types.hs
+++ b/src/Pine/Internal/Types.hs
@@ -1,44 +1,101 @@
 module Pine.Internal.Types where
 
-import qualified SDL (Event)
+import qualified SDL -- (Event, Rectangle(..), WindowConfig, Keycode(..))
+import qualified SDL.Vect as SDLV
+import Foreign.C.Types (CInt)
+import Data.Int (Int32)
+import Linear (V2)
 
 import Data.Semigroup
+import Control.Monad.State -- see below
 
--- DeltaTime Double | KeyPress | KeyRelease | KeyState deriving (Eq, Show) --ideas
--- | placeholder
-data Event = DeltaTime Double | SDLEvent SDL.Event deriving (Eq, Show)
+data Event
+  = Load -- ^ very first event to be called
+  | Step -- ^ Other events may be called multiple times per frame, but the Step event occurs once per frame
+  | KeyPressed  SDL.Keycode
+  | KeyReleased SDL.Keycode
+  | KeyState --(Key -> Bool)
+  | MousePosition (V2 Int32)
+  | MouseMoved (V2 Int32)
+  | MouseClick MouseButton
+  | MouseScroll -- WIP
+  | WindowPosition (Double, Double)
+  | WindowResized (Double,Double)
+  | WindowMinimized
+--  | AudioState AudioState
+  | WindowClose
+  | SDLEvent SDL.Event--(raw SDL data, shouldn't be used typically)
+  deriving (Eq, Show)
 
+data MouseButton = MouseLeft | MouseRight | MouseMiddle deriving (Eq, Show)
 
+data Return = Cont | Log String | Quit | QuitWithLog String -- change config settings (like resize window), show/hide mouse
+
+-- | Most apps or games do not run forever, and sometimes people like to log things while building up a project.
+-- In order for the user of this framework to do that, they must be able to send values back to the main control loop.
+type PineState s = State s Return
+
+cont,quit :: PineState s
+cont = pure Cont
+quit = pure Quit
+
+contLog,quitLog :: String -> PineState s
+contLog s = pure $ Log s
+quitLog s = pure $ QuitWithLog s
+
+
 -- | Drawable class contains the draw function, which takes a type and converts it into a `Scene`
 class Drawable d where
   draw :: d -> Scene
 
+type DeltaTime = Double
+
 -- | Stateful class contains initial and update functions. Any objects in your game, including the overrall world, will update according to events that occur
 class Stateful s where
-  initial :: s
-  update  :: Event -> s -> s
+  update :: DeltaTime -> Event -> PineState s
 
 -- | An Image which is converted into a `Texture`
-newtype Image = Image
-  { imageSrc :: FilePath
+data Image = Image
+  { imageSrc  :: FilePath -- ^ source file
+  , imageQuad :: Maybe (SDL.Rectangle CInt) -- ^ quad, or Nothing for entire image
+  , imageRect :: Maybe (SDL.Rectangle CInt) -- ^ location and dimensions, or Nothing to fit entire window
   } deriving (Eq, Show) -- put in other info later (like dimensions, quads, etc)
 
+-- | Construct a rectangle
+rect :: ()
+     => CInt -- ^ x
+     -> CInt -- ^ y
+     -> CInt -- ^ w
+     -> CInt -- ^ h
+     -> SDL.Rectangle CInt
+rect x y w h = SDL.Rectangle (SDLV.P $ SDLV.V2 x y) (SDLV.V2 w h)
 
+
 -- | Create an image from a file
-newImage :: FilePath -> Image
+newImage :: ()
+         => FilePath -- ^ The source of the image file
+         -> Maybe (SDL.Rectangle CInt) -- ^ A quad or Nothing for the whole image
+         -> Maybe (SDL.Rectangle CInt) -> Image -- ^ The rendering target: The location of the image on the window and its size, or Nothing to take up the whole window.
 newImage = Image
 
+newtype Audio = Audio -- WIP
+  { audiosrc :: FilePath
+  } deriving (Eq, Show)
 
+data AudioState = Playing Audio | Stopped Audio deriving (Eq, Show) --WIP
+
+data Playback = Playback Audio | Continue Audio | Stop Audio deriving (Eq, Show) -- WIP
+
 data Media = MImage Image | MAudio | MText deriving (Eq, Show) -- WIP (TODO: replace instances of Image in Scene with Media
 
 -- | A Scene can be empty, a single `Image`, or a group of `Image`s. (WIP: Later text and other stuff will be added)
-data Scene = EmptyScene | SingleImage Image | Images [Image] deriving (Eq, Show)
+data Scene = EmptyScene | SingleScene Media | MultiScene [Media] deriving (Eq, Show)
 
 instance Semigroup Scene where
-  (<>) (SingleImage img1) (SingleImage img2) = Images [img1,img2]
-  (<>) (Images imgs1) (Images imgs2) = Images $ imgs1 <> imgs2
-  (<>) (SingleImage img) (Images imgs) = Images $ img:imgs
-  (<>) (Images imgs) (SingleImage img) = Images $ imgs <> [img]
+  (<>) (SingleScene img1) (SingleScene img2) = MultiScene [img1,img2]
+  (<>) (MultiScene imgs1) (MultiScene imgs2) = MultiScene $ imgs1 <> imgs2
+  (<>) (SingleScene img) (MultiScene imgs) = MultiScene $ img:imgs
+  (<>) (MultiScene imgs) (SingleScene img) = MultiScene $ imgs <> [img]
   (<>) EmptyScene c = c
   (<>) c EmptyScene = c
 
@@ -48,4 +105,4 @@
 
 -- | Convert a single `Image` into a `Scene`
 fromImage :: Image -> Scene
-fromImage = SingleImage
+fromImage = SingleScene . MImage
