packages feed

phooey-0.3: src/Graphics/UI/Phooey/MonadUI.hs

{-# OPTIONS -fglasgow-exts #-}

----------------------------------------------------------------------
-- |
-- Module      :  Graphics.UI.Phooey.MonadUI
-- Copyright   :  (c) Conal Elliott 2006
-- License     :  LGPL
-- 
-- Maintainer  :  conal@conal.net
-- Stability   :  provisional
-- Portability :  portable
-- 
-- A functional UI monad.
----------------------------------------------------------------------

module Graphics.UI.Phooey.MonadUI
  (
  -- * The UI monad
   UI, runUI, runNamedUI
  -- * High-level widgets
  , stringDisplay, showDisplay, textEntry, islider
  , checkBoxDisplay, checkBoxEntry, title
  -- * Tools for defining high-level widgets
  , mkWidget
  -- * Layout
  , onLayoutT, mapLayout
  ) where

import Control.Applicative
import Control.Monad.Reader
import Control.Monad.Trans
import Graphics.UI.WX hiding (textEntry)
import qualified Graphics.UI.WX as WX

import Graphics.UI.Phooey.Imperative
import Graphics.UI.Phooey.CallBackT
import Graphics.UI.Phooey.TagT (Unop,mkTag,mapTag)
import Graphics.UI.Phooey.LayoutT


{----------------------------------------------------------
    The UI monad
----------------------------------------------------------}

-- | The UI monad
type UI = CallBackT (ReaderT Win (LayoutT IO))

instance Functor UI where { fmap = (<$>) }
instance Applicative UI where { pure = return; (<*>) = ap }

-- | Run a 'UI' with window title \"Monadic Phooey GUI\".
runUI :: UI (Source ()) -> IO ()
runUI = runNamedUI "Monadic Phooey GUI"

-- | Run a 'UI' with given window title.
runNamedUI :: String -> UI (Source ()) -> IO ()
runNamedUI name = runL name . runReaderT . runCB (lift.lift)


{----------------------------------------------------------
    High-level widgets
----------------------------------------------------------}

-- | String display widget
stringDisplay :: Source String -> UI (Source ())
stringDisplay src = mkHWidget $ \ win ->
  do  ctl <- mkText win
      return  (  Just (hwidget ctl)
              ,  (skip, munch, src >>= set1 ctl text))

-- | Showable display widget
showDisplay :: Show a => Source a -> UI (Source ())
showDisplay src = stringDisplay (fmap show src)

-- | Simple text input widget
textEntry :: UI (Source String)
textEntry = mkHWidget $ \ win ->
  do  ctl <- mkText win
      return  (  Just (hwidget ctl)
              ,  (get ctl text, onCommand ctl, skip))

mkText :: WX.Window a -> IO (WX.TextCtrl ())
mkText win = WX.textEntry win []


-- | Slider widget with static initial value and dynamic bounds (min,max)
islider :: Int -> Source (Int,Int) -> UI (Source Int)
islider initial bounds = mkHWidget $ \ win ->
  do  (ctl,snk) <- hsliderDyn win True [ selection := initial ]
      return  (  Just (hwidget ctl)
              ,  (getSel ctl, onCommand ctl, bounds>>=snk) )

-- | String display widget
checkBoxDisplay :: Source Bool -> UI (Source ())
checkBoxDisplay src = mkWidget id $ \ win ->
  do  ctl <- WX.checkBox win []
      return  (  Just (hwidget ctl)
              ,  (skip, munch, src >>= set1 ctl checked) )

-- | Simple checkbox input widget
checkBoxEntry :: Bool -> UI (Source Bool)
checkBoxEntry initial = mkWidget id $ \ win ->
  do  ctl <- WX.checkBox win [ checked := initial ]
      return  (  Just (hwidget ctl)
              ,  (get ctl checked, onCommand ctl, skip) )

-- TODO: factor out commonalities between textEntry & checkBoxEntry and
-- between stringDisplay & checkBoxDisplay.


-- | Wrap a title around a 'UI'
title :: String -> Unop (UI a)
title str = mapLayout (boxed str)


{----------------------------------------------------------
    Tools for defining high-level widgets
----------------------------------------------------------}

-- | Make a widget
mkWidget :: Unop Layout                 -- ^ id, hfill, vfill, or fill
         -> (Win -> IO  (  Maybe Layout
                        ,  (a, Sink Updater, Updater)))
         -> UI a
mkWidget layf f = CB (mkRead (mkTag . panWrap layf f))

-- | Make a widget with horizontal filling
mkHWidget :: (Win -> IO  (  Maybe Layout
                         ,  (a, Sink Updater, Updater)))
          -> UI a
mkHWidget = mkWidget hfill


-- Wrap another panel and layout unop (probably id, hfill, vfill, or fill)
-- to get desired stretchiness.
panWrap :: Unop Layout -> Unop (Win -> IO (Maybe Layout, b))
panWrap layf f w = do  pan     <- panel w []
                       (mbl,b) <- f pan
                       -- if we get a layout, use it for the panel.
                       maybe skip (set1 pan layout) mbl
                       return (Just (layf (widget pan)), b)

-- | Tweak the 'LayoutT'
onLayoutT :: LayoutTOp IO -> Unop (UI a)
onLayoutT f (CB reader) = CB (mkRead (f . runReaderT reader))

-- | Tweak the layout
mapLayout :: Unop Layout -> Unop (UI a)
mapLayout f = onLayoutT (mapTag f)


---- Misc unexported

mkRead :: Monad m => (r -> m a) -> ReaderT r m a
mkRead f = do  r <- ask
               lift (f r)