packages feed

happlets-0.1.0.0: src/Happlets/Initialize.hs

-- | To begin programming your 'Happlet', import the back-end 'Happlets.Provider.Provider' you want
-- to use, for example @import Happlets.Lib.Gtk@. The functions in this module provide a convenient
-- interface for evaluating the functions provided by the 'Happlets.Provider.Provider', making it
-- easy to setup your application from within the "main" function of your executable program.
--
-- If your Happlet is just a simple, single-window happlet, then use 'simpleHapplet' to initialize
-- your Happlet. The 'simpleHapplet' function takes a 'Happlets.Provider.Provider', a function for
-- setting the 'Happlets.Config.Config' parameters, an initial value for the document model data,
-- and an initializing 'Happlets.GUI.GUI' function for installing event handlers. See the
-- documentation for 'simpleHapplet' for an example.
--
-- It is also possible to define a Happlets where multiple Happlet windows are placed on screen when
-- the program is executed. To do this, the task of initializing the 'Happlets.GUI.Happlet'
-- container, creating a new window, and attaching a 'Happlets.GUI.Happlet' container to the window,
-- are all separate initialization steps which are evaluated in the 'Initialize' monad.
--
-- The 'happlet' function takes a back-end 'Happlets.Provider.Provider' and then evaluates an
-- 'Initialize' function, which behaves a lot like an imparative program in which you set parameter
-- variables and then call functions which make inspect the parameter variables.
--
-- In the 'Initialize' function, you can use the various
-- @'Control.Lens.Lens'' 'Happlets.Config.Config'@ lenses defined in the "Happlets.Config" module
-- with the @'(Control.Lens..=')@ operator to set various configuration settings in a stateful way,
-- much like setting environment variables.
--
-- After setting the 'Happlets.Config.Config' paramters, you must:
--
-- 1. call 'newWindow' to create a new window,
-- 2. call 'newHapplet' to create a new 'Happlets.GUI.Happlet' container which contains your
--    document object model.
-- 3. call 'attachWindow' to attach the 'Happlets.GUI.Happlet' to the window, passing an
--    initializing 'Happlets.GUI.GUI' function to setup the event handlers.
-- 4. perform the above 3 steps for as many windows as necessary.
-- 5. call 'launchGUIEventLoop' to start the application
-- 6. if necessary, call 'deleteWindow'.
--
-- @
-- main :: IO ()
-- main = 'happlet' gtkHapplet $ do
--     ---
--     --- setting the 'Happlets.Config.Config' paramters ---
--     ---
--     'Happlets.Config.registeredAppName' 'Control.Lens..=' "My First Happlet!"
--     'Happlets.Config.windowTitleBar' .= "Hello World Happlet"
--     'Happlets.Config.recommendWindowSize' 'Control.Lens..=' (640, 480)
--     ---
--     --- creating the new window ---
--     ---
--     win <- newWindow
--     ---
--     --- create a new 'Happlets.GUI.Happlet' to contain the document object model ---
--     ---
--     happ <- 'newHapplet' MyDocumentModel
--         { myFoo = emptyFoo
--         , myBar = emptyBar
--         }
--     ---
--     --- attaching the happlet container to the window and initializing the event handlers ---
--     ---
--     'attachWindow' win happ initMyHapplet
--     ---
--     'launchGUIEventLoop' -- Will not return until an exit condition halts the event loop.
--     ---
--     --- not always necessary, but delete the windows after the event loop halts ---
--     ---
--     'deleteWindow' win
-- @
--
-- The initializing function passed to 'attachWindow' takes a 'Happlets.Draw.SampCoord.PixSize', so
-- if your document model contains information sensitive to the geometry of the window, you can
-- initialize your document model using the given window size.
--
-- The document model stored in the 'Happlets.GUI.Happlet' container can be any data type at
-- all. Haskell's static type checker ensures that the 'GUI' function you use to initialize the
-- event handlers must match the content of the 'Happlets.GUI.Happlet' container. As your
-- 'Happlets.GUI.GUI' program begins receiving events from the operating system's window manager,
-- the document model is updated using the ordinary 'Control.Monad.State.Class.MonadState' API
-- functions like 'Control.Monad.State.Class.get', 'Control.Monad.State.Class.put',
-- 'Control.Monad.State.Class.modify', as well as the 'Control.Lens.Lens'' functions
-- 'Control.Lens.use', 'Control.Lens.assign' and it's infix form ('Control.Lens..='), and
-- 'Control.Lens.modifying' and it's infix form ('Control.Lens.%=').
--
-- Please also refer to the "Happlets.GUI" documentation on the 'Happlets.GUI.GUI' function type
-- which is the Happlets equivalent of the @IO@ function type. There you will find instructions on
-- how to install event handlers.
module Happlets.Initialize where

import           Happlets.Config
import           Happlets.Draw.SampCoord
import           Happlets.GUI
import           Happlets.Provider

import           Control.Monad.Reader
import           Control.Monad.State

----------------------------------------------------------------------------------------------------

-- | This function carries around with it a reference to a 'Happlets.Provider.Provider' and a
-- 'Happlets.Config.Config' data structure.
-- 
-- The real use of this function type is provided by the various classes that can be instantiated by
-- the back-end provider.
newtype Initialize window a
  = Initialize { unwrapGUI :: ReaderT (Provider window) (StateT Config IO) a }
  deriving (Functor, Applicative, Monad, MonadIO)

instance MonadReader (Provider window) (Initialize window) where
  ask = Initialize ask
  local f = Initialize . local f . unwrapGUI

instance MonadState Config (Initialize window) where
  state = Initialize . state

-- | This function is an all-in-one setup function that lets you create a 'Happlets.GUI.Happlet'
-- with a single function call. Pass the back-end 'Happlets.Provider.Provider', a stateful function
-- for updating the default 'Happlets.Config.Config', an initial @model@, and an initial
-- 'Happlets.GUI.GUI' function for installing the 'Happlets.GUI.Happlet' event handlers. This
-- function will automatically call 'newHapplet', 'newWindow', 'attachWindow', 'happlet', and then
-- return when the GUI event loop halts. Here is an example of how you might use it:
--
-- @
-- import Happlets
--
-- -- Here you define your document model data type, which will be updated by the 'Happlets.GUI.GUI'
-- -- function whenever an event occurs.
-- data MyDocModel = MyDocModel
--      { someFoo      :: FooType
--        someBar      :: BarType
--        myDocWinSize :: 'Happlets.Draw.SampCoord.PixSize'
--      }
-- 
-- -- Here you define your Haskell "main" function.
-- main :: IO ()
-- main = 'simpleHapplet' gtkHapplet <-- let's say we are using the Gtk+ 'Happlets.Provider.Provider'
--     (do registeredAppName   'Control.Lens..=' "My Happlet"
--         windowTitleBar      'Control.Lens..=' "New Document"
--         recommendWindowSize 'Control.Lens..=' (640, 480)
--         -- The type of this "do" block is ('Control.Monad.State.StateT' 'Happlets.Config.Config' IO ()).
--         -- You use the ('Control.Lens..=') function to set the various "Happlets.Config" parameters.
--         -- The configurable parameters are of type ('Control.Lens.Lens'' 'Happlets.Config.Config').
--     )
--     MyDocModel
--       { someFoo = initNewFoo
--       , someBar = emptyBar
--       , winSize = 'Linear.V2.V2' (-1) (-1)
--         -- Initialize your document model. Notice that your data type can contain a field
--         -- storing the size of the window. This is helpful for rendering graphics where the
--         -- geometry depends on the size of the window, for example, for graphics that you
--         -- like to center in the window.
--       }
--     (\\ winSiz -> do
--         -- This is the first function that runs before any event handlers are called.
--         -- Use this function to update your document model with the window size (if necessary),
--         -- and install the event handler functions.
--
--         'Happlets.GUI.modifyModel' $ \ mydoc ->
--             mydoc{ myDocWinSize = winSiz } --  <-- here you set the window size
--
--         'Happlets.GUI.mouseEvents' 'Happlets.GUI.MouseButton' $ \ mousEvt -> do
--             ---
--             --- some code for handling mouse events ...
--             ---
--
--         'Happlets.GUI.keyboardEvents' $ \ keyEvt -> do
--             ---
--             --- some code for handling key events ...
--             ---
--     )
-- @
simpleHapplet
  :: Provider window
  -> StateT Config IO ()
  -> model
  -> (PixSize -> GUI window model ())
  -> IO ()
simpleHapplet provider updcfg model init = happlet provider $ do
  get >>= liftIO . execStateT updcfg >>= put
  join $ attachWindow True <$> newWindow <*> newHapplet model <*> pure init

-- | Run the 'Initialize' function with the given back-end 'Happlets.Happlet.Provider'.
happlet :: Provider window -> Initialize window a -> IO a
happlet provider (Initialize f) = do
  doInitializeGUI provider
  (a, config) <- runStateT (runReaderT f provider) (defaultConfig provider)
  doGUIEventLoopLaunch provider config
  return a

-- | Create a new @window@. The @window@ itself only contains stateful information relevant to the
-- 'Happlets.GUI.GUI' back-end 'Happlets.Provider.Provider', it does not do anything on it's
-- own. This function does not place a window on the screen, only the 'attachWindow' function can do
-- that.
newWindow :: Initialize window window
newWindow = liftIO =<< asks doWindowNew <*> get

-- | Create a new 'Happlets.GUI.Happlet', which contains a document object model of any type. When
-- you call 'attachWindow' you must supply this 'Happlets.GUI.Happlet' container along with a
-- 'Happlets.GUI.GUI' function that installs all the necessary event handlers into the @window@. As
-- the @window@ begins reciving events from the operating system's window manager, these event
-- handlers are evaluated, and the content of this 'Happlets.GUI.Happlet' can be updated using the
-- 'Control.Monad.State.Class.MonadState' functions like 'Control.Monad.State.Class.get',
-- 'Control.Monad.State.Class.put', 'Control.Monad.State.Class.modify', as well as the
-- 'Control.Lens.Lens'' functions 'Control.Lens.use', 'Control.Lens.assign' and it's infix form
-- ('Control.Lens..='), and 'Control.Lens.modifying' and it's infix form ('Control.Lens.%=').
newHapplet :: model -> Initialize window (Happlet model)
newHapplet = liftIO . makeHapplet

-- | This function evaluates the given 'Happlets.GUI.GUI' function to install 'Happlets.GUI.GUI'
-- event handlers into the @window@ that can modify the content of the 'Happlets.GUI.Happlet'
-- container in response to events received from the operating system's window manager.
--
-- Note that it is possible to detach a 'Happlets.GUI.Happlet' container from a @window@ and
-- re-attach the @window@ to an entirely different type of 'Happlets.GUI.Happlet' container using
-- the 'Happlets.GUI.windowChangeHapplet' function as the program is running and receiving
-- events. This allows a single @window@ to switch between multiple 'Happlets.GUI.Happlet's during
-- program execution. Please refer to the 'Happlets.GUI.windowChangeHapplet' function documentation
-- for information about how to do this.
attachWindow
  :: Bool
  -> window
  -> Happlet model
  -> (PixSize -> GUI window model ())
  -> Initialize window ()
attachWindow vis win happ init = liftIO =<<
  asks doWindowAttach <*> pure vis <*> pure win <*> pure happ <*> pure init

-- | This function launches the GUI event loop. You must call this function once after all of your
-- Happlet setup has been performed. This function will not return until the GUI event loop has
-- halted, so it must be the last function called in the 'Initialize' "do" block, except perhaps for
-- the 'deleteWindow' function which can be evaluated after it.
launchGUIEventLoop :: Initialize window ()
launchGUIEventLoop = get >>= \ config -> asks doGUIEventLoopLaunch >>= liftIO . ($ config)

-- | Delete a window. It often is not necessary to delete a Happlet window because usually once the
-- 'Initialize' function has completed evaluating, there ought not be anything after the 'happlet'
-- function in the @main@ function, and so the executable program will quit, signaling to the
-- operating system window manager to delete all windows associated with this program. None the
-- less, this function exists for the purpose of explicitly deleting windows.
deleteWindow :: window -> Initialize window ()
deleteWindow win = liftIO =<< asks doWindowDelete <*> pure win